novel_style_check
baselineScope 的文案随选择变化,判定与容差规则不变;完整语义、小样本容差与回退披露见下文「3 · 基线范围与「排除本章」口径」。风格自检。把目标章节与基线(默认是「全书除本章外的其余章」,也可在面板「风格基线」里改成按部分、只用你勾选的章)当作两个样本,先算句式指纹的余弦相似度并给出 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 | 基线说明,内容随面板「基线范围」变化(v6.5.5):按全书 时仅 1 个其他章为该章文件名,否则为「全书除本章外的 N 章」(沿用旧措辞,按旧格式解析的读取方不受影响);按部分 时为「自定义基线 N 章(选中 M 章)」(有取不到的章时括注为「(选中 M 章,其中 K 章未纳入基线(已删除、或正文过短无法测量))」。N = 真正进 μ/σ 的可测章数——本路两种档位下都会用可测章池再解析一次范围(只覆盖回报口径),故与改稿台报的「可测 N 章」同口径(v6.5.5 统一:此前按全书时本路报"章池章数"、改稿台报"可测章数",加上风格画像的 baselineChapterCount 会出现同一事实三个数字;已修,按全书 的 N 同样是可测章数);M = 配置里选中的总数(含取不到的)。一章都取不到而回退时按原因给两种文案:「(自定义选择已失效,已回退全书)」或「(选中的章只有本章自身,不能作为自己的基线,已回退全书)」 |
similarity | number | 0~1,千分位取整(Math.round(x*1000)/1000) |
verdict | string | 枚举 high / medium / low |
diffs | array | 偏差清单,元素为 {dimension, diff, note};diff 单位为百分点(句长项为字) |
chapterFingerprint | string | 本章指纹串(九类占比 + 句长 + 主观性 + 主导情绪 + 首选句式模板) |
baselineFingerprint | string | 基线指纹串,同格式,可直接肉眼对照 |
baselineMode | string | 基线范围档位(v6.5.5):all = 按全书(默认) / custom = 按面板选中的部分章。回退发生后取值是 all——要区分「本来就是按全书」与「回退」,读 baselineScope 文案里的后缀(按原因两种:「(自定义选择已失效,已回退全书)」/「(选中的章只有本章自身,不能作为自己的基线,已回退全书)」) |
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 条;锚包构建失败时缺省 |
explicitness | object | v6.9.0,仅非净化模式(features.rawWriting)开启时出现:{ chapter:{chars,total,per1000,dialogueRatio}, baseline:{…}, ratio, verdict, items:[{id,label,count,samples[]}], note }。per1000 = 「把场面绕过去」的写法密度(处/千字:省略号整段 / 明示跳过 / 时间跳 / 代称替代 / 跳出场景的评论 / 英文淡化),纯规则、零 token、启发式;verdict ∈ clean(无淡化写法)/ new(基线为 0,本章新增)/ similar / softer / much-softer / more-explicit;ratio 在基线为 0 时为 null(不编造比值)。它衡量的是「有没有被绕过去」,不是质量评分;开关关闭时该键不存在 |
slop | object | v7.0.0,仅当命中数 > 0 时出现(total = 0 时整键省略,与其它「有就返回」的键同一约定):{ total, densityPer1000, topCategories, byCategory }。total = 同一行内重叠命中合并后的处数;densityPer1000 = 处数 / 千字(分母为本章 text.length,保留 1 位小数);topCategories = 命中最多的前 3 个类别英文键(取值见 SLOP_CATEGORIES,如 stat_numeric;同频按类别展示顺序,不含命中原文);byCategory = 8 个类别的计数(值为 0 的类别也在)。它只是一句摘要:不参与 verdict / similarity / diffs,也不产出待办;读设定表或扫描抛错时整键省略,主流程照常返回 |
advice | string | 面向生成端的建议文本,已与六维判定统一口径。v5.1.0 起不再要求「逐句对比修正」:改为「先判断这是不是有意为之(章节类型不同本来就会偏离全书均值);确实跑偏时才对照 fixAnchors 只调整那几处,不要整章重写,也不要为对齐数字改文」 |
渲染层(render)依次输出:检查章节、对比基线、风格相似度百分比与中文分级、偏差清单(无偏差时写「偏差: 无明显偏差」)、语义对比备注、原著锚段(带「逐句对比本章对应写法,只修跑偏部分,不得整章重写」的约束语)、本章指纹与基线指纹、文笔六维逐维「本章 v vs 基线 μ(±%,marks)」、advice。六维行的状态符号为 ⚠ 出带 / △ 提醒 / ✓ 在带;metric.enabled === false 时该段只打印 note。
slop 与改稿台的「AI套话」同源、不同级。两处用的是同一张表(lib/lexicons/slop.js 的 SLOP_RULES / SLOP_CATEGORIES,8 类结构片段)与同一个豁免入口 buildSlopExemptions,参照集也同为基线章文本(按面板「基线范围」筛出、已排除目标章的那些章),故同一章在两处的类别口径一致。级别不同:本工具只在渲染文本里多一行(位置在六维对照 / 直白度对照之后、【建议】之前)「陈词套话扫描:命中 N 处(D 处/千字,主要为:<类别中文名>)——如需逐行定位可运行 novel_fix_plan」,不改判定、不进 verdict、不产出待办;要行号区间与改写方向,用 novel_fix_plan。
豁免口径与数字可比性。题材静音取设定表
worldview 条目的 name / description / basis / notes,命中 SYSTEM_GENRE_RE(系统|无限|网游|数据面板|属性面板|游戏异界|数据流|面板流)时整类静音 stat_numeric;参照集里「≥ 0.5 处/千字且 ≥ 2 次」的 pattern 视为本书习惯写法并逐条豁免(豁免细节与边界修复见改稿台页的对应说明)。豁免只会让 total 变小:与豁免前(或旧版本)的数字不可直接比,也不要把「数字变小」读成「套话变少了」——要比较就用同一版本重扫两章。
计算原理
本工具做两类判定,共用一次逐章读盘:句式指纹相似度(整章 vs 基线语料聚合文本——v6.5.5 起语料按面板「基线范围」选取并排除目标章,因此 baselineFingerprint 也随范围变化)与六维文笔基线对照(逐章测量后再汇总)。
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 取得全书每章的六维测量值(带缓存),再按基线范围筛出语料(目标章无条件排除),最后算判定基线与其容差:
buildTolerance 是全插件唯一实现(本工具与 novel_fix_plan、novel_chapter_brief 共用)——若两条路径口径不同,同一章会出现「改稿台说有问题、开写包说没问题」这种互相矛盾的结论,比单条结论错更伤信任。用户自定义容差只覆盖显式给出的维度,未设置的维度继续沿用该基线自己的推荐值;recTol 缺失或非有限值时回退 15。文档更正:本页旧版此处写的是「容差取含目标章的全书推荐值」,而实现自 v6.2.0 起已统一走 buildTolerance(排除本章后的基线)(随后再由基线范围决定候选语料),旧描述不再成立。六维的定义、μ±σ 公式与 out / warn / ok 判据与 novel_style_report 完全同源,见该页「六个维度的定义与公式」与「基线带 μ±σ 的计算」两节。
语义要点:①
按部分 是「只用」选中的章,不是「优先 + 不够再补」——补进来的章不可控,很可能正是你想排除的 AI 续写章;② 目标章无条件排除,即使它出现在选中列表里(拿自己跟自己比必然「完美符合」,比不检查更糟,会让作者以为「没问题」);③ 选中列表里取不到的章(已改名 / 已删除,或正文过短无法测量)被跳过并如实计数——文案写作「…,其中 K 章未纳入基线(已删除、或正文过短无法测量)」,两种原因都列出来,不把「章太短」说成「章没了」。小样本与回退:实际参与基线的章(已剔除目标章)少于 3 章时,推荐容差被
buildTolerance 抬到至少 ±25%——所以单章也能当基线,不会因 σ=0 把一切都报成偏差;若一章都取不到导致基线为空,则回退 按全书 并在 baselineScope 里按原因写明:选择全都失效 → 「(自定义选择已失效,已回退全书)」;只选中了本章自身 → 「(选中的章只有本章自身,不能作为自己的基线,已回退全书)」(后者不是「失效」,而是它在设计上就不能当自己的基线)——绝不返回空基线:空基线算出的 μ 是假的,会把「没有基线」伪装成「全都在带内」。按书存储:
<root>/.novel-writer/baseline/<书名>.json({ mode, chapters, updatedAt },mode 只认 all / custom,其余一律回落 all;单条文件名截 220 字、上限 2000 章);文件不存在即等于 按全书。它与全局的容差设置(styleTolerance)不同层:容差所有书共用一份,范围一书本一份。
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解析不到时抛「找不到章节」。调用方应把这三类视为参数/环境错误,而不是降级结果。 - 「基线范围」只缩小候选语料,不改变判定规则(v6.5.5):
按部分时基线只由选中章构成(目标章仍被排除),容差、分级阈值、偏差判据全部不变;因此「出带」与否会随范围变化——这是同一章相对不同标杆的两种答案,不是矛盾。面板里的容差是全局设置、范围是按书设置,两者互不覆盖。插件不判断「哪些章是原作」(novel_new_chapter建章时不落标记,存量书更没有,猜错比不猜更糟),标杆选错(例如把 AI 续写的章一并勾进去)结论就会偏——这是设计取舍,不是缺陷。 - 取不到的章与回退都是「可披露」的,不是静默的:选中章里取不到的(已改名 / 已删除,或正文过短无法测量)被跳过并在
baselineScope里如实计数(文案两种原因都列,不把「章太短」谎报成「章没了」);一章都取不到时回退按全书,并在同一字段按原因写明「(自定义选择已失效,已回退全书)」或「(选中的章只有本章自身,不能作为自己的基线,已回退全书)」——读取方不应把回退后的结果当作自定义范围的结果展现给用户。仅有 1 章可用基线时 μ 由单章得出、σ 不可估计,容差被抬到 ±25% 后分辨力很低,只适合看「有没有明显跑偏」。 - 基线缓存每书一份、按内容哈希失效。基线分析写入
<root>/.novel-writer/analysis/<book>__base-full.json,失效键是sha1(基线全文)前 16 位加算法版本号。bookAnalysisCached命中时要求th与ver同时相等;任一章节改动都会改变基线全文,从而整份重建——改「基线范围」(v6.5.5)同样会改变基线全文,首次检查会重建一次,属预期行为。文件不含目标章名,因此检查不同章不会堆积近似全书大小的缓存文件。 - 六维逐章测量缓存单独一份:
<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 行)、resolveBaselineScope(v6.5.5 新增,基线范围的唯一裁决点,配套readBaselineScope/writeBaselineScope/normalizeBaselineScope与buildTolerance;行号随版本变动,请按函数名检索) - lib/embedding.js ——
chunkText(第 520 行)、buildIndexIncremental(第 463 行)、loadIndexMeta(第 440 行)、saveIndex(第 408 行)、fingerprint(第 338 行)、cosine(第 179 行) - mcp/server.mjs —— MCP 形态下的
--root越界拒绝与回退(resolveLibraryRoot第 130 行、参数补全与校验第 259~281 行)