novel_style_check
风格自检。把目标章节与「全书除本章外的其余章」当作两个样本,先算句式指纹的余弦相似度并给出 high / medium / low 分级与偏差清单,再用同一套六维文笔指标做「本章值 vs 其他章基线 μ±σ」的带外判定,最后附上原著锚段供对照校准语感。基线永远不含目标章:目标章只参与判定,不参与统计,否则风格漂移会把自己的基线一起拉走。
参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
book | string | 是 | 书名,即 novels/ 下的子目录名。经 requiredString 判空后交 sanitizeSegment 过滤。 |
chapter | string | 是 | 目标章节标识:章号(1 / 01)、文件名(第01章.md)或标题子串,由 findChapter 解析;找不到则抛错。 |
root | string | 否 | 章节库根目录。省略时取插件 config.root,再省略取会话工作目录。MCP 形态下必须落在启动参数 --root 指定的书库根内,越界会被静默回退到该书库根。 |
本工具没有 threshold、top 一类可调参数:偏差阈值与判定分级都是常量,语义增强是否参与由侧边栏功能开关决定。
输出结构
契约字段(output.schema.required):book、chapter、baselineScope、similarity、verdict、diffs、chapterFingerprint、baselineFingerprint、advice、semantic。其余字段按需出现。
| 字段 | 类型 | 内容 |
|---|---|---|
chapter | string | 解析后的目标章文件名(不是调用方传入的原始值) |
baselineScope | string | 仅 1 个其他章时是该章文件名,否则为「全书除本章外的 N 章」 |
similarity | number | 0~1,千分位取整(Math.round(x*1000)/1000) |
verdict | string | 枚举 high / medium / low |
diffs | array | 偏差清单,元素为 {dimension, diff, note};diff 单位为百分点(句长项为字) |
chapterFingerprint | string | 本章指纹串(九类占比 + 句长 + 主观性 + 主导情绪 + 首选句式模板) |
baselineFingerprint | string | 基线指纹串,同格式,可直接肉眼对照 |
semantic | object | 必返对象。enabled:true 时含 similarity 与 note;不可用时仅 enabled:false 与说明文案 |
metric | object | 六维对照。enabled:true 时含 baseline / metrics / verdicts / outCount / skippedDims / summary;不可用时仅 enabled:false 与 note |
fixAnchors | array | 原著锚段,元素为 {label, text},最多 3 条;锚包构建失败时缺省 |
advice | string | 面向生成端的建议文本,已与六维判定统一口径。v5.1.0 起不再要求「逐句对比修正」:改为「先判断这是不是有意为之(章节类型不同本来就会偏离全书均值);确实跑偏时才对照 fixAnchors 只调整那几处,不要整章重写,也不要为对齐数字改文」 |
渲染层(render)依次输出:检查章节、对比基线、风格相似度百分比与中文分级、偏差清单(无偏差时写「偏差: 无明显偏差」)、语义对比备注、原著锚段(带「逐句对比本章对应写法,只修跑偏部分,不得整章重写」的约束语)、本章指纹与基线指纹、文笔六维逐维「本章 v vs 基线 μ(±%,marks)」、advice。六维行的状态符号为 ⚠ 出带 / △ 提醒 / ✓ 在带;metric.enabled === false 时该段只打印 note。
计算原理
本工具做两类判定,共用一次逐章读盘:句式指纹相似度(整章 vs 全书其他章的文本聚合)与六维文笔基线对照(逐章测量后再汇总)。
1 · 风格指纹与向量化
指纹串由 buildFingerprint 拼装,向量由 fingerprintSimilarity 内部按固定维度顺序构造,共 13 维:
句长维度先按 100 字截顶再归一,其余各维本身已在 0~1 量级,因此余弦不会被句长一项主导。九类短码为 S / ENV / PSY / DLG / Q / RQ / EX / IMP / …,与 novel_sentence_analysis 的 TYPE_CODE 同源。
2 · 分级与偏差清单
偏差清单由 styleDiffs(baseline, chapter) 产生,方向为「本章相对基线」,四类判据与阈值固定:
清单不做数量上限:九类 + 句长 + 短句 + 情绪最多 12 条。但 advice 文案只取前 4 条拼入自然语言建议,其余条目仍完整保留在 diffs 里由渲染层逐条打印。方向词映射为「偏高→略多、偏低→略少、偏长→略长、偏短→略短」。
3 · 六维基线的「排除本章」口径
先用 metricChaptersCached 取得全书每章的六维测量值(带缓存),再分两步算两组基线:
容差取「含目标章的全书推荐值」而非「排除目标章后的推荐值」,是因为排除后样本更少、σ 更不稳定;用户自定义容差只覆盖显式给出的维度,未设置的维度继续沿用全书推荐值。recTol 缺失或非有限值时回退 15。六维的定义、μ±σ 公式与 out / warn / ok 判据与 novel_style_report 完全同源,见该页「六个维度的定义与公式」与「基线带 μ±σ 的计算」两节。
metric.outCount > 0,advice 会被覆写:指纹无偏差时改写为「句式指纹与全书高度一致;六维层面:<summary>——单一维度出带属正常波动(带宽 = 作者自身波动的 1.5σ),只有偏离达到 2 倍容差以上、或那一处读起来确实别扭时才需要调整,不要为对齐数字改写文本」,有偏差时在原建议后追加「另(六维):…——同上,轻微出带属正常波动,不必逐项消除」。此举避免了同一份输出既说「可以放心续写」又标「⚠ 需修正」的自相矛盾。v5.1.0 另修两处会诱导"反复打磨"的措辞:① 六维汇总行(
style-metrics.js 的 judge.summary)由「N 个维度超出容差带,需修正」改为「N 个维度超出容差带(先判断是否为有意为之,偏离明显或读起来别扭才改)」;② 渲染层删掉了句尾那句「——请对照基线修正后再续写」——它与紧随其后的建议段自相矛盾,而模型会听更早、更硬的那半句。
fixAnchors 只调整那几处,不要整章重写,也不要为对齐数字改文」构成。原来的「逐句对比修正」被执行成「把整章按锚段重写一遍」,而六维容差带本身是 1.5σ(约作者自身章节波动)——在这个带宽下,六维里至少有一维出带是常见现象,把它当成缺陷反复打磨只会把作者的写法磨掉。因此「出带」不是「必改」:只有偏离达到 2 倍容差以上、或那一处读起来确实别扭时才动笔。fixAnchors 的用途也随之收敛为「拿去对味、校准语感」,而不是「照着逐句仿写」。
4 · 原著锚段 fixAnchors
锚段由 buildStyleAnchorPackage 从**基线章文本**(已读入内存的逐章文本,不再二次读盘)中挑选,段落需 trim().length ≥ 40:
等距抽样而非顺序取前 n 条,是为了避免锚段全部来自序章与开头章。跨桶去重(对话×2 → 心理×1 → 描写×2 中先各取一条)修正了此前「slice(0,2) 恒取到两条对话锚、心理与描写锚永远进不了输出」的缺陷。
5 · 语义风格相似度(可选增强)
仅当 semanticStyle(含 semanticEmbedding 总开关)开启且本地引擎可用时执行。目标章与基线文本各自切块后合并为一份临时索引,块 id 加 "0|" / "1|" 前缀以区分两侧:
基准侧的 20 条等距抽样是必要的:早先的 slice(0,20) 只覆盖全书开头约 20 段,大书结果系统性偏置。索引缓存键为 <book>__style_<章名清洗后>,写盘失败不阻塞本次结果,只在 semantic.note 末尾追加「(语义索引缓存写入失败,下次会重建)」。
约束与边界
- 三处前置断言会直接抛错:写作助手总开关关闭时抛「写作助手功能(句式模式分析)当前已关闭…」;作品章数 ≤ 1 时抛「只有 N 章,无法做风格对比(至少需要 2 章)」;
chapter解析不到时抛「找不到章节」。调用方应把这三类视为参数/环境错误,而不是降级结果。 - 基线缓存每书一份、按内容哈希失效。基线分析写入
<root>/.novel-writer/analysis/<book>__base-full.json,失效键是sha1(基线全文)前 16 位加算法版本号。bookAnalysisCached命中时要求th与ver同时相等;任一章节改动都会改变基线全文,从而整份重建。文件不含目标章名,因此检查不同章不会堆积近似全书大小的缓存文件。 - 六维逐章测量缓存单独一份:
<root>/.novel-writer/analysis/<book>-chapters-metrics.json,指纹按文件名排序后拼file + "\0" + text再哈希,与传入章序无关——本工具传「其他章 + 目标章」,novel_style_report传全书序,两者共用同一缓存不会互相击穿。 - 六维对照可能整体不可用:排除目标章后没有可用基线章时返回
{enabled:false, note:"基线章节不足(至少需要 1 个其他章节)"};测量过程抛异常时返回{enabled:false, note:"六维测量失败:…"}(异常文本截断到 80 字)。两种情形下diffs/similarity仍是有效结果,调用方不应把metric.enabled === false读成「六维合格」。 - 六维可能静默跳过维度:某维基线 μ=0 且 σ=0 时无法做相对判定,该维登记进
metric.skippedDims,summary会写明「另有 N 个维度基线均值为 0、未做相对判定(维度名)」。μ=0 但 σ>0 时改用绝对尺度(|v| > 1.5σ判 out),该项devPct为null、basis为"absolute",渲染层打印「基线为 0,按绝对尺度判定」而不是null%。 - 语义增强有三条降级路径,文案各异:功能未开启时为
{enabled:false, note:"语义增强未启用(纯规则模式)"};目标侧或基准侧无段落时为{enabled:false, note:"语义对比样本不足(目标章或基准章为空)"};计算抛异常时为{enabled:false, note:"语义对比失败:…"}(异常文本截断到 80 字)。semantic在 schema 中是必返 object,enabled:false属正常返回而非错误。 - 被静默跳过的情形:锚包构建抛异常时
fixAnchors缺省,判定与建议不受影响;语义索引写盘失败只追加一行备注;cleanOutput会统一把-0归一为0、把NaN归一为null、剔除undefined值的键,以满足宿主无损 JSON 校验。 - 跨版本不可横比:六维口径在 v4.3.0 有多处行为变化(句末符统一为
。!?…!?且省略号单独成句、动作词表去重清洗到 625 条、抽象度改为最长匹配、修饰密度新增停用词表、留白指数量纲统一为千字密度)。同一本书在旧版本下算出的similarity/diffs/metric数值不可与新版本直接比较;缓存另设算法版本键,升级后自动失效重建。 - 语义索引指纹算法变更属一次性代价:v4.3.0 为内容指纹字段加了长度前缀定界(消除字段含分隔符导致的边界歧义),既存索引与风格缓存的指纹一次性失效并重建,之后恢复增量命中。
相关工具
- novel_style_report —— 提供六维指标的定义、词表规模与 μ±σ 完整公式;本工具的
metric段是其判定路径的「单章版」。 - novel_sentence_analysis —— 九类句式分布、句长分布、情感曲线与风格指纹的原始实现,本工具的
diffs全部来自该分析结果。 - novel_semantic_search —— 与本节「语义风格相似度」共用同一个本地向量引擎与同一份索引缓存文件。
源码位置
- lib/index.js —— 工具注册与执行(
registerNovelStyleCheck,第 899 行起;execute自第 989 行)、渲染(第 947 行)、语义门控semanticGate(第 109 行) - lib/analysis.js ——
buildFingerprint(第 1600 行)、fingerprintSimilarity(第 1607 行)、styleDiffs(第 1634 行)、analyzeText(第 1174 行) - lib/style-metrics.js ——
measureStyleMetrics(第 242 行)、computeBaselineFromPerChapter(第 399 行)、judgeAgainstBaseline(第 460 行)、METRIC_ORDER(第 394 行) - lib/core.js ——
metricChaptersCached(第 1864 行)、bookAnalysisCached(第 1881 行)、buildStyleAnchorPackage(第 2071 行)、cleanOutput(第 2308 行) - lib/embedding.js ——
chunkText(第 520 行)、buildIndexIncremental(第 463 行)、loadIndexMeta(第 440 行)、saveIndex(第 408 行)、fingerprint(第 338 行)、cosine(第 179 行) - mcp/server.mjs —— MCP 形态下的
--root越界拒绝与回退(resolveLibraryRoot第 130 行、参数补全与校验第 259~281 行)