novel_new_chapter
plan(动笔前针对本章各事件子句的具象执行计划,不写入正文文件,只用于写盘后核对兑现)与返回字段 postWriteAudit(写盘即时体检,最多 5 条,按严重度降序:未兑现的 plan 子句 / 章末升华 / 连续站桩对话 / 高频套话)。体检只告警、不阻断保存——章已经写好了,它只告诉你哪几处值得回头改。baselineScope(文案)与 baselineMode(all / custom),渲染文本也带上取样范围。此前锚段仍取全书、与基线口径不一致(书越长 AI 续写占比越高,锚段就越像 AI 自己写的——正是 issue #1 描述的漂移),本版一并修正。
新建章节。为指定作品创建一个章节文件,章号可显式指定或自动取下一个,可同时写入 Markdown 一级标题与初始正文。同号章节一律拒绝创建,绝不覆盖既有稿件;创建成功后返回体自带按面板「基线范围」取样的六维风格基线 μ 摘要(附 baselineScope / baselineMode 范围披露)与「风格锚包」(代表性段落 + 句式骨架,与基线同范围),使调用方模型在动笔前即可拿到测量数据与用于校准语感的样例,无需先调用风格报告工具。v7.0.0 起还可传入动笔前写下的 plan,写盘后立刻得到一份只告警、不阻断保存的体检结论(postWriteAudit)。
参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
book | string | 是 | 书名,即 novels/ 下的子目录名。经 sanitizeSegment 清洗:剥离 \ / : * ? " < > |、首尾点与空白,Windows 保留名(CON / NUL 等)加下划线前缀;清洗后为空则报错。目录不存在时自动创建。 |
chapter | integer | 否 | 显式章号,须为 1–99999 的整数。省略时取现有最大可解析章号 + 1。 |
title | string | 否 | 章节标题,写为正文首行 # 标题,并(v5.0.0 起)一并进文件名:第NN章 标题.md。空串或纯空白视为未提供。 |
plan | string | 否 | v7.0.0:动笔前针对本章各事件子句的具象执行计划(每行对应一条子句,写明具体器物名词、肢体动作与这一幕停在哪)。绝不写入正文文件——它只用于写盘后核对「写没写出来」(结果进 postWriteAudit)。参数声明里它排在 content 之前,因为多数模型按 schema 顺序生成参数,计划先落笔会形成一层「自我承诺」;这只是经验做法、不是协议保证——模型把它生成在 content 之后时兑现核对照常工作,只是承诺效应弱一些,所以不要依赖顺序做逻辑判断。 |
content | string | 否 | 初始正文。空串或纯空白视为未提供。 |
root | string | 否 | 章节库根目录。缺省回退顺序:args.root → config.root → 会话工作目录。MCP 形态下必须落在启动参数 --root 之内。 |
参数对象声明 additionalProperties: false,多传的键会被宿主拒绝。
输出结构
契约字段(output.schema.required):book / number / file / path / chars 五项必返;其余字段按条件出现。返回前经 cleanOutput 处理,值为 undefined 的键会被整体剔除。
| 字段 | 类型 | 内容 |
|---|---|---|
book | string | 必返。清洗后的书名 |
number | integer | 必返。本次实际使用的章号 |
file | string | 必返。文件名:无 title 时为 第NN章.md(章号左补零到至少 2 位);给了 title 时为 第NN章 标题.md(v5.0.0 修正:标题经 sanitizeSegment 剥掉 \ / : * ? " < > | 等非法字符并截断到 24 字;标题全是非法字符则退回不带标题的文件名,不因此拒绝创建) |
path | string | 必返。落盘绝对路径 |
chars | integer | 必返。写入文本的字符数(合成文本整体长度,含标题行与结尾换行) |
baseline | string | 六维基线 μ 摘要,形如「标签 值 / 标签 值 …」,无法计算时为缺失。语料按面板「基线范围」取样(v6.5.5;按部分 只用勾选的章),并排除刚创建的新章——范围文案见同级的 baselineScope |
baselineScope | string | 取样范围披露(v6.5.5,baseline / anchors 的共同范围):按全书 时为「全书除本章外的 N 章」,按部分 时为「自定义基线 N 章(选中 M 章)」(有取不到的章时括注为「(选中 M 章,其中 K 章未纳入基线(已删除、或正文过短无法测量))」;N = 本路语料池里实际入选的章数——本路递的是逐章指标,故 N 就是真正进 μ/σ 的可测章数);一章都取不到而回退时按原因给两种文案:「(自定义选择已失效,已回退全书)」或「(选中的章只有本章自身,不能作为自己的基线,已回退全书)」。渲染层把它写进模型可见的文本:【风格基线 μ】…(取样范围:<文案>) |
baselineMode | string | 范围档位(v6.5.5):all = 按全书(默认) / custom = 按面板选中的部分章。回退发生后取值是 all,要区分「本来就是按全书」与「回退」,读 baselineScope 文案里的后缀——两种原因两种文案:「(自定义选择已失效,已回退全书)」/「(选中的章只有本章自身,不能作为自己的基线,已回退全书)」。渲染层据它切换锚段标签:custom 时「【风格锚·原著段落】」改为「【风格锚·取样章节】动笔照此味道写(取样范围:…;数字基线只做事后校验):」——按部分时那些章未必是「原著」 |
reminder | string | 「基线怎么用」提示(v5.1.0 改口径:此前这段标题里带「强制」二字,现改为「基线怎么用」):① 锚段读一遍校准语感(不要照 skeletons 造句)② 写完建议跑一次 novel_style_check 自检,偏离明显时才逐句修正。当前实现下恒有值 |
anchors | array | 风格锚·代表性段落,元素 { label, text },最多 5 条。v6.5.5 起与基线同范围:语料先按面板「基线范围」筛选既有章节(新章本就不参与——它是刚创建的稿子,不能当「原著样例」) |
skeletons | array | 句式骨架,元素 { type, text },最多 4 条 |
rawMode | string | v6.9.0,仅非净化模式(features.rawWriting)开启时出现:与开写包【非净化模式·目标带】同源的一段写作约束(留在场景里 / 细节密度对齐原著 / 不用省略号或跳场带过 / 不用总结代称代替场面 / 不插评论与免责)。开关关闭时该键不存在(不是空串) |
postWriteAudit | array | v7.0.0:写盘即时体检,最多 5 条、按严重度降序,元素 { key, severity, title, detail }。key 取值 plan_unfulfilled(plan 里写了、正文里找不到的物象 / 动作,severity 5)、plan_unqualified(整份 plan 没有一行含具体器物或动作 → 跳过兑现核对,severity 1 提示)、duplicate_h1(v7.0.0:title 与 content 开头都带一级标题 → 已自动合并,severity 2;正文里还剩多处一级标题时为 severity 3 的「正文含多个一级标题」且不做合并)、以及单章症状 ending_preach / bare_dialogue / slop。只告警、不阻断保存;本章体检为空(或体检本身失败)时该键不存在(体检失败会记 silent_skip 事件日志,章照常保存) |
行为与实现
本工具不涉及统计模型,全部为确定性文件操作与规则抽取。执行顺序:校验 → 建目录 → 扫描现有章节 → 定章号 → 查重 → 合成文本 → 独占写入 → 计算基线 → 构建锚包 → 记录书库根。
1 · 章号解析与文件名
现有章节由 scanChapters 收集:只认书目录顶层的 .md / .markdown / .txt(扩展名小写比较),子目录中的文件不参与,排序为「章号升序(不可解析者排最后)+ 文件名 localeCompare」。章号解析入口先做全角宽度归一(第01章 → 第01章),因此下列写法都解析为同一章号:第1章.md、第01章.md、第一章.md、书名-01-标题.md、书名 01.md。中文数字支持到「千」位(如 第一千零一章 解析为 1001)。
2 · 防覆盖与独占写入
查重(按解析出的章号)与写入(按文件名是否存在)是两套口径,两者之间存在竞态窗口;wx 标志把该窗口收敛为一次可上报的失败,而不是静默互相覆盖。已知取舍:建目录发生在查重之前,因此同号创建失败时书目录仍可能已被创建。
3 · 文本合成与字符数口径
标题行与正文之间由数组 join 产生的空行是格式的一部分,会一并计入 chars。该字段用于核对「是否真的写入了内容」,不代表正文字数。
title 与 content 开头的重复 # 标题 自动合并成一条 H1。只在同时传了 title 时才做:写盘时先落 # <title>;若 content 以一级标题行开头,先剥掉第一行标题以及紧随其后的一个换行(或一个空行),其余正文原样写入。
splitBlocks 与 extractChapterTitle 会把标题行当成正文段落、或把章标题取到第二条,进而影响站桩对话、章末升华这类按段落判定的体检症状口径。两条告警互斥,且都只是格式提示:保存早已完成,没有任何一项能否决这次写入;它们同属 postWriteAudit(最多 5 条、按 severity 降序),可能被更高优先级的项(如 plan 未兑现的 severity 5)挤出。
4 · 风格基线 μ 摘要
all 并按实披露两种原因。
excluding = scoped.items
if ( excluding.length > 0 )
baseline = METRIC_ORDER.map( k => 标签[k] + " " + ( 有 mu ? mu : "-" ) ).join(" / ")
baselineScope = scoped.scope ← 渲染文本里的「(取样范围:…)」
baselineMode = scoped.mode ← all / custom,渲染层据此切换锚段标签基线测量集合包含新章,以与风格报告、风格自检的「全章集合」保持同一缓存键(否则三者交替调用会全部缓存 miss);但摘要本身排除新章,因为新章不能作为自己的基线。无其它章节时不输出 μ=0 的假基线。novel_style_report 基线计算中「正文不足 40 字的章节不参与统计」的规则由测量层统一执行。整段被 try/catch 包裹:失败静默,不影响章节创建。
baselineScope(文案)与 baselineMode(档位),渲染文本也带上:【风格基线 μ】…(取样范围:全书除本章外的 N 章) 或 …(取样范围:自定义基线 2 章(选中 2 章))。范围按书存于 <root>/.novel-writer/baseline/<书名>.json({ mode, chapters, updatedAt },文件不存在即等于「按全书」),与全局的容差设置(styleTolerance)不同层。选中章一章都取不到时回退「按全书」,并在文案里按原因注明:「(自定义选择已失效,已回退全书)」或「(选中的章只有本章自身,不能作为自己的基线,已回退全书)」——绝不返回空基线(空基线算出的 μ 是假的,会把「没有基线」伪装成「全都在带内」)。
5 · 风格锚包
锚包是「校准语感」的载体,不是造句模板:anchors 读一遍对上味道即可,skeletons 只在写不出该类型句子时参考,不要照它造句、不要套句式模板;数字基线只用于事后校验。整段同样被 try/catch 包裹,失败时返回空锚包。渲染标签与语料同受「基线范围」影响(见 §6)。
6 · 渲染文本
7 · 写盘即时体检 postWriteAudit(v7.0.0)
为什么放在建章返回里:事后审计(novel_style_check / novel_fix_plan)在默认工作流里很少被主动调用,而这一段是零额外调用的触点——章刚写完、材料还在模型手里。它只告警:写入早已完成,清单里没有任何一项能否决这次保存。
plan 格式不合格不是错误:整份计划里连一行具体器物或动作都没有(例如全是「按大纲推进」这类元指令),就没有可核对的东西,此时只给一条 severity 1 的提示并跳过兑现核对,不产「未兑现」告警。要它真起作用,每行至少写一个具体器物或动作,例如「柜台上的铜钱:他数了三枚推过去」。
约束与边界
- 同号即失败,不覆盖:
第1章.md/第01章.md/第一章.md都按章号 1 拦截。需要改写既有章节时应另选章号或先由调用方删除旧文件——本工具不提供任何覆盖或更新通道。 - chapter 的取值范围是硬断言:非整数(浮点、数字字符串)、0、负数、大于 99999 一律被拒绝并报错,而不是截断到边界。
- 并发只报错不重试:独占创建失败时返回「刚被并发创建」,需调用方自行重试或改用其它章号。
- 建目录是前置副作用:即使后续因同号失败,书目录也已被创建。
- 基线/锚包均为可选增强:任一环节抛错都被吞掉,返回体中相应字段缺失或为空数组。调用方不应假定
baseline恒有值——单章作品、基线计算失败、锚包抽取失败都会导致缺失。本工具没有degraded通道,所以这些失败对调用方是沉默的;v6.6.0 起它们会在事件日志里留痕(silent_skip:如「建章时的原著锚段/句式骨架不可用(返回空 anchors/skeletons)」),可用novel_diagnose区分「这本书抽不出锚段」与「生成过程失败了」——前者不是故障,后者要查日志。 plan不落盘、不进正文(v7.0.0):它只参与写盘后的兑现核对,正文文件里没有一个字来自它;核对结果同样不落盘,只出现在本次返回体的postWriteAudit里。postWriteAudit同属沉默路径:体检失败时章照常保存、该键不出现,只在事件日志留一条silent_skip。- 取样范围与回退(v6.5.5):
baseline与anchors的语料由面板「基线范围」决定(按全书 默认 / 按部分 只用勾选的章,按书存储),并始终排除刚创建的新章;勾选的章若取不到(已删除 / 正文过短)会被跳过并如实计数(文案「…,其中 K 章未纳入基线(已删除、或正文过短无法测量)」),一章都取不到时回退「按全书」并按原因写进baselineScope(「(自定义选择已失效,已回退全书)」或「(选中的章只有本章自身,不能作为自己的基线,已回退全书)」),不返回空基线。范围文案恒在渲染文本里(【风格基线 μ】…(取样范围:…)),读取方不要把失去范围说明的 μ 当作全书基线——按部分时它只代表那几章。 - 只统计顶层文件:
scanChapters不递归,放在子目录里的章节文件对本书「不可见」,既不计入章号也不参与基线。 - 自动章号依赖可解析章号:目录内若存在无法解析章号的文件(命名完全不含数字或中文数字),它们被排到末尾且不参与
max;此时「下一个章号」由可解析的最大值决定,可能与文件名视觉顺序不一致。 - 章号解析的已知缺口:中文数字解析器识别
十/百/千,但不识别「万」——第一万章.md解析不出章号,会被当作不可解析文件排到列表末尾,既不参与自动章号计算,也不阻止同号冲突检测(该文件不占用任何章号)。四位以内的阿拉伯章号不受此影响。 - root 与 MCP 边界:MCP 形态下服务器为未显式提供
root的调用注入--root书库根;显式传入越界路径会被静默回退书库根。 - 工具开关:
execute首行执行assertToolEnabled,若该工具在「写作助手功能」UI 中被关闭,调用直接报错(可用novel_sentence_config重新开启)。 - 写盘编码固定为 UTF-8,不写 BOM,换行为
\n;不生成备份文件与临时文件。
相关工具
- novel_chapters —— 创建前查看现有章号,避免显式
chapter撞号。 - novel_read —— 按章号或标题读回刚创建的正文。
- novel_style_check ——
reminder建议的写后自检,用同一套六维基线与目标章比对;偏离明显时才据其修正。 - novel_import —— 已有整批原稿件时的入库入口,替代逐章创建。
- novel_chapter_brief —— 动笔前的材料装配:它的
clauseFrames就是本工具plan要逐条兑现的子句框架,prevChapterIssues与本工具的postWriteAudit同源。
源码位置
- lib/index.js —— 工具注册与
execute:参数 schema、输出契约、基线摘要、提醒文案(registerNovelNewChapter,行号随版本变动,请按函数名检索;plan参数与postWriteAudit的装配也在同一函数内) - lib/chapter-diagnose.js —— v7.0.0 写盘体检的实现:
diagnoseChapterSymptoms(结构性症状,与开写包共用)与checkPlanFulfillment(plan 兑现核对与qualified格式门);套话词表在 lib/lexicons/slop.js - lib/core.js ——
sanitizeSegment路径段清洗、resolveRoot根目录回退、bookDir/novelsDir路径拼接、scanChapters章节扫描、parseChapterNumber章号解析、cleanOutput输出清理 - lib/core.js ——
buildStyleAnchorPackage风格锚包抽样与分类(约 2071 行)、metricChaptersCached六维测量缓存(约 1864 行)、resolveBaselineScope(v6.5.5 基线范围的唯一裁决点,配套readBaselineScope/writeBaselineScope,行号随版本变动,请按函数名检索) - lib/style-metrics.js ——
computeBaselineFromPerChapter/METRIC_ORDER/METRIC_LABELS,基线 μ 的计算与维度顺序