novel_continuity_check
连贯性审计。四种模式由参数选择且互斥:chapter 做单章衔接检查(时间跳变 / 语义距离 / 人物延续 / 钩子承接),outline 做创作资料大纲方向行与正文开头的关键词重合度对照,ooc 做登记角色的情绪基线偏离哨兵;三者都不传时对照设定表扫描全书,输出矛盾候选(同章号多文件 / 数字口径 / 人物缺场 / 别名 / 设定重复 / 用语与语用冲突)。chapter 模式除衔接候选外,还会对本章独立跑同一套用语/语用扫描,结果放在 styleCandidates(v6.4.0 起)。全部判定由本地规则与本地向量引擎完成,不调用在线模型;返回值是候选而非结论,逐条需人工确认;checklist 字段列出本次覆盖与未覆盖的维度。v6.5.0 起,用语/语用的每条候选带 tier("hard" / "review" / "info")、word 与 locations(该文件内真实行号区间),2 字词进「待判」档交由模型按行复核。
isGuardedArtifact,而 ① detectCulture 扫标记词、② 第 ⑤ 路「文化基准存疑」提示仍是裸子串匹配——「他知道士兵们很累,身上香气扑鼻」会被同时算成「道士」与「上香」两个中式信号,进而误导文化基准判定。现在两路都接上守卫(core.js 的 scanWordHits 与 index.js 的 ⑤ 路)。同时:东方默认语用规范从禁用称谓里移除了「先生」(教书先生 / 账房先生 / 说书先生 属正常用法),并把「点烛 / 蜡烛 / 礼拜」三条硬规则收窄为「祭坛点烛 / 祭坛蜡烛·祷告蜡烛·献祭蜡烛 / 做礼拜·礼拜日·主日礼拜」;新增「失敬 / 银子 / 银两 / 铜钱 / 包袱」五个词的守卫与实证反例。
text.includes(word) 裸子串匹配,而禁用词 93% 是 2 字词(默认表 69 条中 64 条)——中文没有词边界,2 字词当子串的概率天然很高。实测:19 句无害句子报出 15 条候选,其中 14 条是误报(「这里正是」「在下面」「大大人物」);同一批语料现在只剩 1 条(那 1 条是真命中)。做法是四条:① 逐条候选新增 tier / word / locations 三个字段(type 字符串保持不变,档位只由 tier 表达);② 2 字词降到「复核级」,给行号区间让模型自己读那一行判断;③ 有实证的子串碰撞守卫(lib/lexicons/guards.js);④ 用户登记的仪式正则先过静态安全校验,扫描改为逐句,被拒绝的模式显式报出而不是静默跳过。细节见下文「5 · 用语/语用扫描的定位、分档与安全」。
chapter 时只做「与上一章的衔接审计」就返回,函数尾部的世界观禁词与语用规范扫描根本执行不到——用户写完一章自检看到的「候选 0 条」只代表衔接没问题,与「有没有写出禁词」毫无关系。现在两种模式共用同一套四路扫描(禁词 / 客套 / 仪式 / 称谓)。同时,全书模式新增 checklist:未登记 speechStyle、未登记人物、本次未启用大纲对照等盲区都会以「跳过 / 未启用」显式列出,避免把「没查」读成「没事」。
type: "同章号多文件" 的候选(chapters 为该组全部文件名)。这类书用章号定位的工具会直接报错(见 novel_read),所以把它放进审计里,可以在报错之前先看到是哪几章撞了。
参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
book | string | 是 | 书名,即 novels/ 下的子目录名。经 sanitizeSegment 过滤(剥离 \ / : * ? " < > | 与首尾点、空白),清洗后为空即报错。 |
chapter | string | 否 | 衔接检查模式。章号 / 文件名 / 标题子串,由 findChapter 解析。同时对该章跑用语/语用扫描(世界观禁词 + speechStyle),结果放在 styleCandidates。与 outline、ooc 互斥。 |
outline | boolean | 否 | 大纲对照模式。仅 true 计入模式判定;false 等同未传。与 chapter、ooc 互斥。 |
ooc | boolean | 否 | OOC 哨兵模式。仅 true 计入模式判定。与 chapter、outline 互斥。 |
root | string | 否 | 章节库根目录。省略时取 config.root,再退到会话工作目录。MCP 形态下必须落在启动参数 --root 之内(含根本身),越界会被静默回退该根。 |
chapter 已定义且字符串化后非空」「outline === true」「ooc === true」三项计数,计数 > 1 直接抛错,不做优先级择一。因此 chapter: "" 不算衔接模式,会落回全书扫描。
输出结构
契约必返字段(output.schema.required):book、candidates、advice。其余字段按模式出现。
| 字段 | 类型 | 内容 |
|---|---|---|
book | string | 净化后的书名 |
candidates | array | 候选列表。每项为 { type, detail, chapters? },type 与 detail 必返;chapters 为章节文件名数组。v6.5.0 起,用语/语用扫描产出的一切候选(含「文化基准存疑」与被拒绝的模式)还带 tier / word / locations;衔接 / OOC / 大纲三路不带这三个字段(它们的定位口径是 chapters 与 detail 内的原文引用) |
advice | string | 按模式与候选条数生成的处理建议 |
action | string | 哨兵模式返回 "衔接" / "大纲对照" / "OOC";无参数扫描模式不返回该字段 |
chapter | string | 衔接模式返回目标章的文件名(含「第一章无上一章可比」的提前返回) |
styleCandidates | array | 仅 chapter 模式返回(v6.4.0):本章的用语/语用命中。v6.5.0 起元素为 { type, tier, word, detail, locations, chapters? }——tier 取 "hard" / "review" / "info";word 为命中的词(仪式一路为模式串,称谓一路的 type 没有 word);locations 为 [{ chapter, ranges: [{ from, to }] }],ranges 是该章文件内的真实行号区间。type 仍是 用语冲突 / 语用冲突·客套 / 语用冲突·仪式 / 语用冲突·称谓 / 文化基准存疑,被拒绝执行的仪式模式另有两个诚实命名 语用冲突·仪式(模式被拒绝执行) 与 语用冲突·仪式(模式无法编译)(均 tier: "info"、locations: [])。chapters 恒为本章文件名。不与衔接候选合并——两类问题的判定依据与修法不同,混在一个数组里会让「衔接 0 条」掩盖「禁词命中 3 条」。 |
checklist | array | v6.4.0:本次审计的必查项清单,元素为 { item, state, note }。state 取 checked(本次已扫)/ skipped(本次应扫但缺登记或数据)/ off(本模式不含,需另调参数)。全书模式列 11 项,章节模式列 5 项。 |
reportFile | string | 仅无参数扫描模式返回审计报告路径;三种哨兵模式不落盘、不返回该字段 |
additionalProperties: false,不含 brief;渲染层也没有精简分支。输出体积由内部的候选条数与章节列表上限控制(见「约束与边界」),调用方无法用参数进一步压缩。渲染文本为 <path> / <type>novel-continuity-check</type> / <content> 包裹的清单:候选为空时衔接模式输出「未发现明显衔接问题。」、其余模式输出「未发现明显矛盾候选。」;章节模式随后单列 【本章用语/语用】 一节;有 checklist 时再列 【本次审计覆盖范围】(每项前缀 [已查] / [跳过] / [未启用])。
计算原理
1 · 衔接检查(chapter)
先取上一章(scanChapters 按章号排序后的前一个文件)的末 600 字与本章开头 800 字;两段文本任一去空白后为空则直接返回空数组。四路检查依次执行,任一路的失败都只影响该路。
dePunct 指剔除以 ,。!?、 与空白后的字符集合;钩子比对是单字级集合命中率而非 2-gram,语义接续但字面弱的情况不误报。时间词表 hard 为 28 项(第二天 / 三天后 / 半年后 / 多年后 / 不久后 …),soft 为 12 项(次日 / 翌日 / 隔天 / 深夜 / 黎明 / 清晨 / 黄昏 / 午夜 / 当晚 …)。
并行的用语/语用扫描(v6.4.0)。衔接四路之外,本模式还会读一次本章全文,把 [{ file: 本章文件, text }] 交给 resolveStyleRule 求出生效规则,再跑与全书模式完全同一个函数的四路扫描,结果放进 styleCandidates。因此:给定本章之外的章节内容不会影响结果(文化基准只在未登记 worldview 时用正文检测,此时检测样本也只是本章);chapters 恒为本章文件名;单章模式下禁词/客套的措辞由「×N 章」改为「本章出现」。本书只有一章时同样执行——第一章的衔接是空结果,但用语/语用扫描照跑。
2 · OOC 哨兵(ooc)
需要设定表已登记角色(name.length ≥ 2),否则返回空候选并提示先登记。对每个角色逐章取上下文窗口,按章求情绪均值,再与该角色的全书均值比较。
meanValence 来自 valenceStats:词表按最长优先消费区间,命中位置先过 negatedAt 否定过滤,再以 100 字窗口统计。零命中窗口不进入均值序列(避免 0 稀释),因此无情感词的章不产生效价值——该角色在该章不进样本。
3 · 大纲对照(outline)
两个提前返回:仅 ENOENT(大纲文件真的不存在)时返回 advice: "该书无创作资料…";文件存在但无方向行时返回 advice: "创作资料大纲没有方向行…"。两种都是空候选。skipped > 0 时 advice 追加「(跳过 N 章读取失败)」。本模式不读取全书正文,只按需读命中的章节开头。
v6.6.0 起「读不出来」不再说成「无创作资料」:EACCES / 编码识别失败等情形返回 advice: "剧情大纲读取失败:该文件存在,但读不出来(<原因>)——不必 novel_outline init,请先检查该文件的权限/编码后重试:<路径>",并记一条 config 日志事件;取消(AbortError)先放行上抛,不落进这条 advice(旧版会把取消也讲成"无创作资料")。
4 · 全书设定表扫描(无参数)
先登记 readSettings 的五张表(characters / locations / items / timeline / worldview),再逐章读全文,按下列固定顺序生成候选。
同章号多文件(v4.3.1 起,排在全部检查之前)。对 scanChapters 的结果按解析出的章号分组,凡一组内文件数 > 1 就生成一条候选,detail 列出该组全部文件名(不套用 5 章截断——撞号分组必须完整可见)。章号解析不出(number 缺省)的文件不参与分组。
数字口径。扫描 \d+\s*(?:万亿|千万|百万|十万|[万亿千百]) 或 [零一二两三四五六七八九十]{1,2}(?:万亿|千万|百万|十万|[万亿千百]),去掉空白后要求写法以数量单位字(万亿 / 千万 / 百万 / 十万 / 万 / 亿 / 千 / 百 / 十)结尾,并剔除「十」「百」「千」「万」「亿」这类纯单位字写法。把写法解析回数值(万亿=1e12、千万=1e7、百万=1e6、十万=1e5、亿=1e8、万=1e4、千=1e3、百=1e2、十=1e1),只对数值相同而写法不同的成对条目生成候选,总数上限 20 条。
人物与别名。对每个登记人物统计「全文不含其 name 的章节」:全部章节都不含 → 「人物缺失」;部分不含且全书章节数 > 1 → 「人物缺场」;两者 chapters 都只取前 5 章。若登记了 alias 却全书无任何别名出现 → 「别名未用」(chapters 为空数组)。
设定重复。对 characters / locations / items 三张表,取 normalizeSettingList 后的 name 序列,出现次数大于 1 的名字 → 「设定重复」。
用语与语用。词源由 lib/core.js 的 resolveStyleRule(settings, chapterTexts) 单点裁决(v6.4.0 P0-4;开写包与本工具共用同一处,结构上不可能再分叉):文化基准取 worldview 登记(cultureSource: "worldview"),未登记时对传入正文跑 hintCulture 检测("detected",判不出则 "default" 落欧式);bannedWords = 去重(用户登记 ∪ 该文化基准对应的默认表)——western → DEFAULT_BANNED_WORDS(欧式默认表:老夫 / 上香 / 掌柜 / 客栈 / 银两 / 衙门 / 郎中…)、eastern → SPEECH_STYLE_RULES.eastern.honorBad(禁西式词:Miss / 先生 / 教堂 / 神甫…)、modern → 空;显式登记 bannedWords: [] 会使 defaultEnabled: false(只用用户表 = 「我不要默认表」),此前的实现是「用户登记非空就整份丢弃默认表」——越精心配置、防护越弱。语用三路扫的是 styleRule.speechStyle(合并全部 worldview 条目的结果):honorBad(客套,建议词取 honorGood)、ritualBadPatterns(逐条 RegExp,v6.5.0 起先过 validatePattern 静态校验、再按句匹配;被拒绝的模式显式报出而非跳过,见下节)与称谓规范——称谓仅在 speechStyle.title 文本中含「不用…小姐 / 小姐…禁用 / 禁用…小姐 / 不称…小姐」这类否定式时才扫描 XX小姐。四路的 chapters 一律经 capCandidateChapters 截断到 5 章;type 取 用语冲突 / 语用冲突·客套 / 语用冲突·仪式 / 语用冲突·称谓,章节模式复用同一实现产出(仅措辞改「本章」)。
审计报告落盘。扫描模式把候选写入 <root>/.novel-writer/audits/<book>-<sha1(book|章节文件名逗号串)[:16]>.json,内容为 { book, generatedAt, checklist, candidates }(v6.4.0 起含 checklist,报告自身也说明本次覆盖了哪些维度);文件名中的哈希只由书名与章节文件名列表决定,正文改动不换文件,增删章节才换。
5 · 用语/语用扫描的定位、分档与安全(v6.5.0)
用语/语用四路(禁词 / 客套 / 仪式 / 称谓)由 collectStyleCandidates(styleRule, chapterTexts, singleChapter) 一处实现,全书模式与章节模式共用。本节回答四个口径问题:报成什么档、报在哪里、什么不算命中、什么样的模式不许跑。
① 三档(tier)。档位的由来是词长:中文没有词边界,text.includes(word) 无法区分「里正=官名」与「这里+正是」——而这恰恰与词长强相关。
tier | 判据 | 含义与由来 | 调用方该怎么办 |
|---|---|---|---|
hard | 命中词 ≥ 3 字;仪式正则命中;称谓命中 | 语料实测 0 误报——3 字以上的中文词当子串撞车的概率可以忽略 | 直接按 detail 的定位改 |
review | 命中词恰好 2 字 | 插件在字面上无法判断这是不是那个词,只能确定「这两个字在这里相邻出现」。detail 会写明这一点并举反例(这[里正]是 / [在下]面 / 大[大人]物) | 读 locations 指的那几行自行判断:确为该词则改;只是巧合则不要改,并说明判断依据。detail 里已显式允许判「不是问题」 |
info | 纯提示:文化基准存疑、被拒绝执行的仪式模式 | 不指向正文里的「错词」,而是告诉调用方本轮有什么没被保护到(例如某个模式根本没生效) | 不需要改正文;按提示处置(显式登记 basis / 按建议改写模式后重新登记) |
review 档必须把判断权交出去。这与插件里既有的「情感净化预警」是同一套做法:检测到污染时不替作者删句子,而是提示「这里可能不自然,请抽查原文复核」——判据在字面上不成立时,插件给证据与位置,判断留给读得懂上下文的一方。反过来,若 review 档也写成肯定句,模型会退化成「一律照改」,把本来正确的官名、地名、人名改坏。
② 行号定位(locations)。候选不再贴原文片段,而是给该文件内的真实行号区间:
为什么是行号而不是原文:省 token(不复制正文)、可扩展(一条区间覆盖几十处命中,贴原文只能贴首处)、可回读(模型需要上下文时用 novel_read 按 offset 读回,行号就是它要的那个坐标系)。这也与词表审计已有的「带行号定位」口径一致——同一本书里,两套审计给出的位置说法不再有两套坐标系。
Lx 既不是任何文件的真实行号、也不带章名——模型拿着它读不回原文,等于给了一个无法使用的位置。因此现在的口径固定为「先逐章取号、再带章名合并」,绝不在拼接文本上取偏移。
③ 子串守卫:什么不算命中。lib/lexicons/guards.js 的 COLLISION_GUARDS 是一张有实证的子串碰撞表。判据极简且可审计:词紧邻的前一个字(notBefore)或紧邻的后一个字(notAfter)落在表里,即判为巧合。
notAfterSeq / notBeforeSeq 是多字序列通道,用于单字判不到的场合:老夫 的匹配后紧跟的字恒为「老」,而「老夫老矣」是真用法,所以不能把「老」放进 notAfter,只能判完整的「老妻」序列。
① 构词——
notBefore 收 X ⟺ X + 词首字是常用词(这+里=「这里」,于是「这[里正]是」);notAfter 收 Y ⟺ 词尾字 + Y 是常用词(下+面=「下面」,于是「[在下]面」)。② 实证——该碰撞要有真实原句写进
evidenceBy;举不出实证就删掉。③ 两侧都成立就不填——多报一条走
review 档交给模型判断,比静默漏报好。④ 每条守卫另带
trueUse(「该词作真词用、且紧邻字恰好就是该字」的反例句),测试据此断言真用法不被吃掉。不要凭「看起来像」推断:「秀才 ← 优秀人才」「和尚 ← 和平尚且」这类推断经实测并不成立(字不相邻)。
在下前(下前不是词)、大人会(人会不是词)这类零收益的纯损失大量存在:验证代理构造 49 句真用法,31 句完全静默(连「被守卫忽略」的痕迹都没有)。重建后边界字/序列由 235 收到 120,同一套件复测静默 31 → 1(余 1 条是套件夹具假象)。教训:一个"宁可少填"的纪律如果不可执行,就等于没有——现在每个边界字都必须有实证、且必须给出不被吃掉的 trueUse,两者都成了测试断言。
实测对比。同一批 19 句无害句子(欧式基准、正文全部是合法中文):旧实现报出 15 条候选(老夫 / 伙计 / 门房 / 有劳 / 道士 / 油条 / 江湖 / 小二 / 里正 / 保长 / 见教 / 大人 / 官人 / 在下 …),其中 14 条是误报;现在只剩 1 条——用语冲突「铜板」,那是真命中(正文确实用了这个词)。
已知残留(如实记录)。在下 的 着 两侧都成立(「还[在下]着」是巧合、「[在下]着人送信」是真用法),按频次不对称保留,代价是后者仍静默;圣器 / 科举 因整条碰撞与真用法冲突而整条不填,其碰撞(「神圣器皿」「各科举行」)退化为 review 档候选,交给模型读那一行判断。
④ 去重:中式基准下两路扫的是同一份表。中式基准的 bannedWords 与 speechStyle.honorBad 是同一份表:登记时 lib/index.js 把 SPEECH_STYLE_RULES.eastern.honorBad 写进 speechStyle.honorBad,解析时 lib/core.js 又把同一批词当默认禁词表取出。旧版于是把每个词报两遍(一遍 用语冲突、一遍 语用冲突·客套)。现在客套一路跳过 ① 已报过的词,并在 ① 的 detail 末尾注明「(该词同属语用规范·客套,此处合并为一条)」。实测:同一批语料里 9 个命中词,旧版报 18 条、现在报 9 条。
⑤ 正则安全:不让一条登记把宿主卡死。用户登记的 ritualBadPatterns 会被 new RegExp(p, "g") 原样编译并执行,而 Node 的同步正则无法中断——不能在运行期「跑 50 毫秒就掐掉」。实测(29 字符输入):
也就是说,一条写法有毒的模式能让宿主长时间无响应,而它在配置里只是「世界观里的一条仪式禁式」。因此 validatePattern 在入口做静态校验,三档处置:
| 档位 | 拒绝 / 提示的对象 | 给的建议 |
|---|---|---|
reject | 嵌套量词:(x+)+ / (x*)* / (x+)* / (x{1,})* 一类写法(指数回溯) | 把内层重复改成有界量词,例如 (上+)+ → 上{0,20};或去掉外层的重复 |
reject | 无界通配:.* / [\s\S]* / [\s\S]+ / .{n,}(会跨越句号与换行,把不相干的上下文连起来误报) | 改成句内且有界的间隔,例如 .* → [^。!?\n]{0,20} |
reject | 模式为空、超过 200 字符、正则语法错误 | 拆分 / 检查括号与转义是否配对 |
warn | 无上界的重复次数 {n,}(线性,但长文本上代价不可控)、整串锚定 ^ / $(本审计是「在正文里找可疑片段」,锚定整串的模式通常匹配不到) | 给出上界({1,} → {1,20});去掉 ^ / $ |
三道防线叠在一起:① 登记时静态拒绝(validatePattern 给出可执行改法)+ ② 执行时再校验一次(历史配置、导入的配置不经过登记路径,但它们照样会让扫描卡死)+ ③ 逐句扫描。逐句(splitSentences 按 。!?!?;; 与换行切句,无标点的超长段落按 1000 字硬切)把单次匹配的输入规模限住,也让跨句匹配在结构上不可能——实测 /上.*香/ 曾在 121 字文本上跨 3 句命中 115 字(它把「托盘上摆放…油墨香味」连成了一处并不存在的「仪式表达」)。
被拒绝的模式会显式报出(tier: "info",type 为 语用冲突·仪式(模式被拒绝执行) 或 (模式无法编译),locations: []),detail 写明未生效的原因与改法,并明说「该模式目前不提供任何保护作用」。为什么不静默跳过:静默跳过会让用户以为自己在被保护,而实际上一条都没在查——报出来才有修的机会。
⑥ 逐词静音(本书豁免)。resolveStyleRule 支持按书静音:worldview 条目上的 allowWords(与调用方传入的 opts.allowWords 取并集)。被静音的词从 bannedWords 与 speechStyle.honorBad 里过滤掉,因此 用语冲突 与 语用冲突·客套 两路不再报它;静音结果在返回体的 allowedWords 与 sources.muted 中(改稿台已据此在 summary.notes 里披露「静音了几个词、豁免表是什么」)。边界两处:静音只作用于本书(不提供全局静音);它按词匹配,因此不覆盖仪式禁式——那是正则,需要改模式或删条目。review 档候选的 detail 末尾也附了一行可执行的静音指引(同一本书里本来就该这么用的词,登记一次即可,不必每次重新判断)。字段语义与登记方式见 novel_settings 的 allowWords 一节。
约束与边界
- 三种哨兵模式互斥。同传 ≥ 2 种直接抛错,不做优先级择一;调用方必须拆分调用。要同时得到衔接与全书设定表结论时,先跑无参数扫描,再单独跑
chapter;只关心某章的用语/语用时单独跑chapter即可(v6.4.0 起该模式已含四路用语扫描,不必再为禁词跑一次全书)。 - 「0 条候选」不等于「没问题」。
checklist是专门为此存在的字段:每项带checked/skipped/off与原因(如「未登记 speechStyle,客套/仪式/称谓三路全部跳过」「大纲对照本次未启用」「必用词覆盖率不在本工具职责内」)。渲染成【本次审计覆盖范围】;其中「跳过」只说明本次没扫,既不是通过也不是失败。 - 第一章的衔接检查是空结果,但用语/语用扫描照跑。目标章在排序后位于第 0 位时
candidates: []、advice写明「第一章无上一章可比对」;chapter(v6.4.0 起补上)、styleCandidates、checklist三个字段仍然返回。 - 候选条数与章节列表都有硬上限。每个候选的
chapters最多 5 个文件名(CANDIDATE_CHAPTER_LIMIT),超出时detail追加「(共 N 章,仅列前 5)」。数字口径候选总数上限 20。例外:人物缺失 / 人物缺场两路同样是前 5 章,但不追加该提示文案——不能靠有没有该文案判断是否被截断。 - 档位只在
tier,不在type。type的取值集合是既有契约(用语冲突/语用冲突·客套/语用冲突·仪式/语用冲突·称谓/文化基准存疑),v6.5.0 没有把档位拼进type;调用方按tier分支即可。衔接 / OOC / 大纲三路的候选没有tier(它们不是按词长分档的判定),读取时要用默认值兜底。 review档是「请你判断」,不是「请你改」。它的存在前提是插件在字面上判不出来;调用方若把review当hard处理(一律照改),会把本来正确的官名 / 地名 / 人名改坏。正确用法是读locations指的那几行后再决定,并允许给出「不是问题」的结论——这与「情感净化预警:污染时提示 AI 抽查原文复核」是同一套做法。detail里的位置是摘要,locations是完整数据。渲染进detail的位置有两级截断:每章最多 8 段行号区间(超出写「等 N 段」)、最多列 4 章(超出写「等 N 章」)。locations字段本身不截断——它是给调用方按需回读用的原数据;只有chapters仍按CANDIDATE_CHAPTER_LIMIT截到 5 个文件名。要穷举全部位置请读locations,不要从detail文案反推。- 子串守卫的错填代价不对称。漏填只是多报一条(走
review档,模型能自己否掉);错填会静默吃掉真命中——候选少一条通常没人察觉。因此守卫表只收有实证的边界字(每条带evidence原句),并明确拒绝「看起来像」的推断。 - 逐词静音只覆盖词,不覆盖模式;且只作用于本书。
allowWords过滤的是bannedWords与speechStyle.honorBad;仪式禁式是正则,逐词静音对它无效(需改模式或删条目)。静音也不提供全局范围:全局静音会静默削弱所有书的覆盖,而人很难记得自己什么时候加过什么。 - 三路衔接检查会被静默跳过。语义距离一路需要
semanticStyle功能开启且本地模型文件在位(加载失败有 30 秒冷却后重试);人物延续一路需要设定表登记了长度 ≥ 2 的角色名;钩子承接一路需要novels/创作资料/<书>/钩子记录.md里有与上一章章号相同的方向行。任一前提不成立时该路不产生任何候选——「没报语义距离」不等于「语义衔接良好」。 - 唯一不静默的降级是整章读不出来。章节文本读取抛错时反向透出「衔接·检查跳过」候选,并在
detail内附原因前 120 字,说明时间 / 人物 / 钩子三路本次全部跳过。 - OOC 是统计哨兵而非判定。采样窗口固定为角色名前后各 120 字、每章最多 5 处,短场景与对白密集章的代表性有限;出现章数 ≤ 4 时改用固定阈值 0.3(小样本的 σ 会被离群值撑大)。
detail会写明本次用的是固定阈值还是 2σ,并在文案中提示「若有重大剧情触发情绪变化可忽略」。 - 角色过滤口径两种模式不一致。OOC 模式要求
name.length ≥ 2;全书扫描的人物缺场检查只要求name !== ""。单字角色名只在后者参与检测。 - 大纲对照是字面重合度,不是语义对照。只看方向行的相邻二字组在该章前 600 字中的命中比例,阈值 0.12;方向文本长度 < 4 的方向行、章号对不上的方向行都被跳过且不计入「跳过」计数。返回值只是「可能偏离」提示,偏离本身不一定是错。
- 章号对齐用解析结果,不取文件名首段数字。大纲与钩子两处都走
parseChapterNumber,因此第1卷03章.md按章号 3 匹配;全角章号(第01章)先做宽度归一。 - 扫描模式只读正文与设定表,唯一写入是审计报告。报告写盘失败不阻塞执行;但
reportFile字段在写盘前就已算出,写入失败时返回值里仍会出现这个路径,该文件此时并不存在。 - 未登记 worldview 的影响是双向的。「查不到禁用词表」不代表「不扫描」:
resolveStyleRule会先用hintCulture对传入正文判文化基准再选词表(eastern → 中式禁词、modern → 空表、其余 → 默认欧式基准),并把来源写进cultureSource(worldview/detected/default)与checklist的备注。显式登记bannedWords: [](「不要任何禁用词」)与「未登记」语义不同:前者defaultEnabled: false,默认表整份不生效。 - 数值与条数不可跨版本横比。v4.3.0 把候选章节列表统一截断到 5(此前用语 / 客套 / 仪式 / 称谓四路会把全书章节名全部塞进候选)、数字口径改为按数值分组去重(此前任意两两配对会产生 N(N−1)/2 条候选)、衔接模式的读取失败不再吞成空数组、书名在
detectChapterBridge入口统一净化。v6.4.0 又有两处口径变化:chapter模式开始返回styleCandidates(此前该模式完全不扫禁词/语用,同样的正文本次会多出候选),且词源改由resolveStyleRule合并「用户登记 ∪ 默认表」(此前用户登记非空即丢弃默认表,同样的正文也可能多出候选)。同一本书在不同版本下的候选条数不可直接比较。v6.5.0 的变化更大:候选条数因「子串守卫 + 中式基准同表去重」而减少(实测 19 句无害语料 15 条 → 1 条;中式基准下 9 个命中词 18 条 → 9 条),同时每条候选多了tier/word/locations三个字段、位置口径由「首处命中」改为「全部命中行的区间」,且跨句的模式不再可能命中——因此 v6.4.0 与 v6.5.0 的输出也不能直接对照。 - 语义距离的引擎状态与
novel_semantic_search同源。该路复用embedding.isAvailable/embed/cosine,与索引检索无关,是两次单段嵌入;仅对 600 字与 800 字文本做一次余弦,不落盘、不进索引缓存。
相关工具
- novel_settings —— 五张设定表的维护入口;本工具的人物缺场 / 别名 / 设定重复 / 用语与语用五路全部读它。v6.5.0 起
worldview条目还承载逐词静音(allowWords,仅本书生效),字段语义见该页。 - novel_outline —— 创作资料(含
剧情大纲.md与钩子记录.md)的初始化与回填;大纲对照与钩子承接两路依赖它写入的- N方向行。 - novel_summary —— 章节摘要;长书审计后按摘要复核候选比逐章重读省 token。
- novel_lexicon —— 必用词表。其
action: "audit"只管「必须写什么」(覆盖率 + 通用替身词),不含本工具的禁词/语用扫描;两者分工见该页「与世界观禁用词的分工」。本工具的checklist也会把这条边界写成一项。 - novel_chapters —— 章节清单;
chapter参数取值先用它确认。
源码位置
- lib/index.js —— 工具注册与四种模式的实现:
registerNovelContinuityCheck、collectStyleCandidates(v6.4.0 起用语/语用四路扫描的单一实现,全书与章节模式共用;v6.5.0 重写为「逐行定位 + 三档 + 子串守卫 + 去重 + 逐句正则」)、buildAuditChecklist(v6.4.0:必查项清单)、capCandidateChapters、CANDIDATE_CHAPTER_LIMIT - lib/core.js ——
detectChapterBridge(衔接四路)、resolveStyleRule/hintCulture(v6.4.0 词源与语用规范的唯一裁决点;v6.5.0 增加逐词静音opts.allowWords与allowedWords/sources.muted回传)、linesContaining/mergeLineRanges/formatLineRanges(行号定位)、isGuardedArtifact(子串守卫判定)、validatePattern(正则静态安全校验)、splitSentences(逐句切分)、TIME_JUMP_WORDS、readSettings、findChapter、parseChapterNumber、detectCulture、normalizeSettingList - lib/lexicons/guards.js ——
COLLISION_GUARDS/guardOf(v6.5.0 新增):有实证的子串碰撞守卫表,notBefore/notAfter单字邻接与notBeforeSeq/notAfterSeq多字序列;文件头写明填写纪律与代价不对称 - lib/analysis.js ——
valenceStats/valenceSeries(OOC 的情绪效价与否定过滤) - lib/embedding.js ——
isAvailable/embed/cosine(语义距离一路) - lib/lexicons/markers.js ——
DEFAULT_BANNED_WORDS/SPEECH_STYLE_RULES/CULTURE_MARKERS - mcp/server.mjs —— MCP 侧的
root注入与越界回退