← dsh-novel-writer 技术文档 / novel_style_report

novel_style_report

v6.5.5:六维基线带的语料改为按面板「基线范围」选取(按全书 默认,=旧行为;按部分 只用勾选的章,按书存储),baseline / baselineChapterCount / baselineScope / baselineMode 随之变化;勾选的章一章都取不到时回退按全书并在 baselineScope 里按原因写明(选择已失效 / 只选中了本章自身两种文案)。范围只影响「基线」这一项——文风指纹 / 高频词 / 题材流派 / 情感量化 / 氛围 12 维 / 语义风格距离,以及渲染层附的「原著段落 / 句式骨架」,仍全部是全书口径(它们回答的是「这本书是什么样」,不是「拿什么当基准」)。
v6.1.0:完整报告新增 semanticDiagnostics: { reason, detail, count, at },表示本次语义风格计算的原因、细节、结果数与时间;功能未启用为 not-run,引擎不可用为 no-engine,精简报告省略明细。

风格画像报告。对基线范围内的章节(默认全书)逐章测量六个文笔维度,汇总为基线带 μ±σ,并聚合文风指纹、高频词、题材流派、情感量化、氛围 12 维与语义风格距离,作为「测量数据」交给调用方模型做风格判断。插件只报数、不下结论:模型的判断可通过 aiJudgment 回传存盘,供后续续写时复用。

参数

参数类型必填说明
bookstring是书名,即 novels/ 下的子目录名。经 sanitizeSegment 过滤,拒绝路径分隔符与上跳。
rootstring否章节库根目录。MCP 形态下必须落在启动参数 --root 指定范围内,越界会被静默回退。
actionstring否report(默认)生成测量报告;get 只读取已保存的 AI 判断,不重算。
briefboolean否true 返回一句话精简结论,用于省 token 的场景。
aiJudgmentstring否模型给出的风格气质判断。传入后与测量数据一同写入 style-reports/<book>.json。

输出结构

契约字段(output.schema):book 与 report 为必返;其余按需出现。渲染层会在正文之后追加「风格锚·原著段落」与「句式骨架」两段,供生成端照样例写,而不是只盯数字。

字段类型内容
reportstring人类可读的完整报告文本
baselineobject六维基线与每章测量值,见下文「基线带」
baselineChapterCountinteger实际参与基线统计的章数(已按面板「基线范围」筛选、并剔除正文 < 40 字的章节)
baselineScopestring基线说明(v6.5.5,仅完整报告返回;brief: true 时该键不出现,范围文案并进 brief 文本,形如「六维基线(自定义基线 N 章…)」且只在自定义时才有):写明本次基线取自哪些章——按全书 时为「全书 N 章」(语料只有 1 章时直接给章名)——注意本路没有「本章」概念(它不判定某一章,不排除任何章),故不写「除本章外」,这是本版新增字段,无历史读取方、不涉及兼容;按部分 时为「自定义基线 N 章(选中 M 章)」(有取不到的章时括注为「(选中 M 章,其中 K 章未纳入基线(已删除、或正文过短无法测量))」;两种档位下 N 都是真正进 μ/σ 的可测章数——本路会用可测章池再解析一次范围(只覆盖回报口径),故 N 与同一返回体里的 baselineChapterCount 恒等(按部分时若选中的章全过短而回退,则保留章池口径,由报告正文的"基线不足"另行说明,避免写出「回退全书 0 章」这种自相矛盾的文案);M = 配置里选中的总数(含取不到的)),一章都取不到而回退时按原因给两种文案:「(自定义选择已失效,已回退全书)」或「(选中的章只有本章自身,不能作为自己的基线,已回退全书)」
baselineModestring范围档位(v6.5.5):all = 按全书(默认) / custom = 按选中的部分章。回退后取值是 all,要区分「本来就是按全书」与「回退」,读 baselineScope 文案里的后缀(按原因两种:「(自定义选择已失效,已回退全书)」/「(选中的章只有本章自身,不能作为自己的基线,已回退全书)」)
dimensionsobject指纹 / 词汇 / 题材 / 情感 / 氛围 12 维 / 语义距离(全书口径,不受基线范围影响)
anchors / skeletonsarray原著代表性段落与句式骨架样例(全书口径,与 dimensions 一样不受基线范围影响——本工具里只有 baseline 系列字段跟着范围走)
briefstring精简模式下的单句结论
savedJudgmentstringaction:"get" 时返回的历史判断

六个维度的定义与公式

全部维度零依赖、纯规则统计,不做模型推理。所有计数先按千字归一,量纲统一:

per1000(n) = n / totalChars × 1000

测量前先剥离 Markdown 标题行(^#{1,6}[ \t]+[^\n]*),章节标题不参与任何统计。

1 · 句法复杂度 complexity

complexity = Σ max(小句数ᵢ, 1) / 句数

切句用与 analysis.js 共用的句末符集合(。!?…!?),正则为「句末符后紧跟非句末符处切分,或遇换行切分」;句末符后的闭合引号会被吞掉(她说:「走吧。」 不产生孤儿引号句),纯标点残片(!!!)丢弃。小句按 [,,;;::] 切分后计非空段。该维度是「平均每句由几个小句构成」,近似嵌套深度。

2 · 修饰密度 modifierDensity

modifierDensity = per1000(adjMods + advMods) adjMods:/[\u4e00-\u9fff]{1,3}(?<!目)(?<!的)的/g → 再按 2 字窗口排除词尾/词首假阳性 advMods:/[\u4e00-\u9fff]{1,4}地/g → 再排除「地」结尾与「地X」名词

「的」作为词尾(真的 / 似的 / 目的 / 有的 / 是的)或词首(的确 / 的士)时都不是修饰助词,用两张停用词表按命中位置的 2 字窗口排除;刻意不收录「中的 / 的话」——它们在「梦中的场景」「他的话很多」里是真修饰语。X地 侧按「地」的位置分两张名词表:以「地」结尾的(土地 / 原地 / 目的地…)用后缀判定,以「地」开头的(地上 / 地下 / 地方…)用命中之后的 2 字窗口判定。

3 · 抽象度 abstractDensity

abstractDensity = per1000(abstractCount) 逐位扫描:对每个未消费位置,按 4 → 3 → 2 字尝试最长匹配 命中条件:整词为纯汉字 且 末字 ∈ 抽象后缀表(57 项:感/性/度/情/绪/念/命/运/缘/罪/魂/魄…) 排除:假阳性停用词 12 项(知道/味道/街道/心里/哪里/事情/睡觉/地道/门道/过道/生意/注意) 命中后按命中长度做区间消费,避免同一段文字被重复计数

采用「最长匹配 + 区间消费」而非正则前瞻:旧写法要求抽象词后必须是非汉字,导致「她的感情很深。」计 0 而「她的感情,很深。」计 1——同词只因后随标点不同而结果相反。已知取舍:相邻两个抽象词若被一个 4 字窗口跨越(如「感情和理性」),只计 1 个。

4 · 动作密度 actionDensity

actionDensity = per1000(actionCount) 单字动作词表 625 条(去重 29 条 + 清洗 11 条名词/形容词后) 2 字搭配黑名单 28 项(十分/高兴/不行/行李/方法/瓶子/太阳/平静/安静/墙壁…) 对每个命中位置依次尝试: ① 后接动态助词「了着过起住上下进出开完掉得」→ actionCount++; 若其后仍是动作词,视为动补合并(走出去 / 跑回来)整体跳 3 字 ② 后接名词性汉字(排除 的了着过在是把被给跟从向对于和与以及就都也很又再)→ actionCount++,跳 2 字 ③ 位于句末(后接标点/空白/文本结尾)→ actionCount++,跳 1 字

词表本身是文档:单字表无法区分「分(分开)」与「十分」、「行(行走)」与「行李」,因此用 2 字黑名单在命中处兜底,窗口取命中字的前一字与后一字两种组合。第 ③ 条修的是越过文本末位与句末动词的漏检。

5 · 不确定性 hedgeDensity

hedgeDensity = per1000(hedgeCount) 模糊限制语表 20 项:似乎 仿佛 好像 大概 也许 或许 可能 像是 隐约 依稀 差不多 八成 兴许 恍若 貌似 如同 好似 疑似 感觉 像是要 匹配策略:按词长降序,命中后用占位符消费该区间

最长优先 + 占位符是为了避免重叠计数:像是要 不再被算成 像是 两次加 像是要 两次。

6 · 留白指数 gapIndex

gapIndex = per1000(省略号数 × 2 + 破折号数 × 2) + per1000(未完句数) 省略号 = 「……」+「...」出现次数;破折号 = 「——」出现次数 未完句判定:以省略号/破折号结尾 → 是;以 。!?!? 结尾 → 否; 否则其后不是换行/结尾 → 是(行末无标点按排版分行处理,不计)

两项都按千字归一是硬性要求:早期版本把千字密度与「未完句百分比」(0–100 量级)直接相加,15 字文本能算出 222.22,量纲不可比。未完句定位用顺序游标推进,重复文本不会误判。

基线带 μ±σ 的计算

先按基线范围筛出语料章,再逐章测量后汇总,跳过正文长度不足 40 字的章节(不足则不参与统计,也不计入 baselineChapterCount):

scoped = resolveBaselineScope( perChapter, 目标章 = 无 ) ← v6.5.5:按全书 / 按部分 mode = all → 全部章(默认,=旧行为,存量书零迁移) mode = custom → 只用面板选中的章(「只用」,不是「优先 + 不够再补」) 选中章里取不到的(已删除 / 正文过短)不纳入基线并如实计数(文案「…,其中 K 章未纳入基线(已删除、或正文过短无法测量)」) 一章都取不到 → 回退 all 并按原因披露(「已回退全书」/「选中的章只有本章自身…」);绝不返回空基线 custom 时另用可测章池再解析一次范围 → baselineScope 的 N 与 baselineChapterCount 一致 样本 < 3 章 → recTol 下限抬到 25(小样本;单章也能当基线) μ = Σ vᵢ / n σ_measured = sqrt( Σ (vᵢ − μ)² / n ) ← 总体标准差 minSigma = max( μ × 0.15 , μ = 0 ? 0 : 1e-4 ) σ_used = max( σ_measured , minSigma ) ← 小书容差带过窄的保护 low / high = μ ∓ σ_used recTol = clamp( round(1.5 × σ_used / μ × 100 / 5) × 5 , 10 , 100 )

minSigmaRatio 默认 0.15,即 σ 下限为均值的 15%。推荐容差取「作者自身章节波动的 1.5 倍 σ」,四舍五入到 5 的倍数,下限 10%、上限 100%;μ=0 时回退 15 以避免除零。

v6.5.5:基线范围(按全书 / 按部分)——「以哪些章为基准」由作者定,按书存储。缘起是一个真实诉求(issue #1「风格漂移」):模型续写每章只参考上一章,从第 2 章续写起参考的就是自己的产物;而报告原本对全书逐章测量(含 AI 写的章),锚点自己也在漂——书越长 AI 占比越高,报告给出的「作者风格」逐代变成 AI 的风格。本工具不判定「你是否漂移了」,只负责回答「相对你指定的标杆,这本书的写法处在什么位置」。
与容差不同层:范围写在 <root>/.novel-writer/baseline/<书名>.json({ mode, chapters, updatedAt };mode 只认 all / custom,其余回落 all;单条文件名截 220 字、上限 2000 章),文件不存在即等于「按全书」;面板里的容差(styleTolerance)是全局一份、所有书共用。
小样本:实际参与统计的章少于 3 章时推荐容差抬到至少 ±25%——所以单章也能当基线,不会因 σ=0 把一切都报成偏差(此时 baselineChapterCount 会等于 1,读取方应据此降低结论强度)。回退:勾选的章一章都取不到时回退「按全书」,并在 baselineScope 里按原因写明「(自定义选择已失效,已回退全书)」或「(选中的章只有本章自身,不能作为自己的基线,已回退全书)」;取不到的章(已删除 / 正文过短)会被跳过并如实计数(文案「…,其中 K 章未纳入基线(已删除、或正文过短无法测量)」)——绝不返回空基线:空基线算出的 μ 是假的,会把「没有基线」伪装成「全都在带内」。
范围只动「基线」,不动「这本书是什么样」:指纹 / 高频词 / 题材流派 / 情感量化 / 氛围 12 维 / 语义距离(dimensions)以及渲染层附的「原著段落 / 句式骨架」仍按全书统计;跟着范围走的只有 baseline / baselineChapterCount / baselineScope / baselineMode。brief: true 时这一点最明显——文案形如「全书 1329 字:六维基线(自定义基线 12 章(选中 12 章))μ=…」:字数是全书,基线才是那几章,别读成「整份报告只统计了那几章」。
σ 为什么要分两个字段。返回体同时给出 sigmaMeasured(原始样本标准差)、sigmaUsed(参与 low/high/recTol 的钳制值)与 sigmaClamped(是否发生钳制)。早期版本只返回钳制后的值,展示层把 0.15μ 的下限当成「作者自身波动」念给用户——实测真实 σ=0.0084 被报成 0.15,差 18 倍。

偏离判定

给新章指标 v 与基线 μ:

devPct = round( (v − μ) / μ × 1000 ) / 10 ← 相对均值的百分比 μ ≠ 0:status = devPct < tol.low ‖ devPct > tol.high → "out" devPct < tol.low/2 ‖ devPct > tol.high/2 → "warn" 否则 → "ok" μ = 0:σ > 0 且 |v| > 1.5σ → "out"(basis: "absolute",devPct 记 null) 否则整维登记进 skippedDims,由 summary 披露

tol 默认取 { low: −recTol, high: +recTol },也接受调用方逐维自定义。容差语义有明确区分:显式 0 表示零容差(任何偏差都判 out),只有缺失 / null / 空串 / 非有限值才回退推荐值——早期用 Number(x) || -recT 把 null 也当 0,单侧缺失时那一侧会退化成零容差。

约束与边界(对调用方模型)

  • book 必填,且经 sanitizeSegment 过滤;root 在 MCP 形态下必须落在启动时的 --root 之内——越界时由 MCP 层静默回退到 --root 并只写 stderr 日志,调用方不会收到错误,因此不要把 root 当作可靠的越权探测手段。
  • 测量与判断分离:本工具不返回风格结论。需要「这本书的文风是什么气质」这类判断时,由模型阅读报告后给出,并通过 aiJudgment 回传,插件负责存盘与复用。
  • 过短章节被排除:正文 < 40 字的章节不参与基线;baselineChapterCount 会小于实际章数,这是预期行为。v6.5.5 起它还受面板「基线范围」影响:按部分 时只统计勾选的章(实际参与数 = 勾选章中正文 ≥ 40 字者),取不到的章(已删除 / 正文过短)被跳过并如实计数;一章都取不到则回退 按全书,并在 baselineScope 按原因写明「(自定义选择已失效,已回退全书)」或「(选中的章只有本章自身,不能作为自己的基线,已回退全书)」。
  • 基线范围不改变维度定义与阈值:换范围只会换语料,μ / σ / recTol 的公式与偏离判据(out / warn / ok)一字未改;因此同一本书在不同范围下的基线带不可直接互比(这是两种标杆,不是两次采样)。范围按书存于 <root>/.novel-writer/baseline/<书名>.json,与全局的容差设置不同层;action:"get" 只读已保存的 AI 判断,不会因范围变化而重算。
  • 只有 1 章基线时分辨力很低:μ 由单章得出、σ 不可估计(会被 minSigma 或 25% 下限接管),此时 baselineChapterCount = 1——读取方应据此降低结论强度,不要把它当作「作者风格」的代表样本。
  • 维度可能被跳过:当某维基线 μ=0 且 σ=0 时无法做相对判定,该维进入 skippedDims,summary 会写明「另有 N 个维度基线均值为 0、未做相对判定」。调用方不应把「无维度超出容差带」读成「全部维度合格」。
  • 数值不可跨版本横比:v4.3.0 统一了句末符口径(省略号单独成句)、清洗了动作词表(去重 29 + 剔除 11 个非动词)、抽象度改为最长匹配、修饰密度增加停用词表——同一本书在新旧版本下的报告数值不可直接比较。v6.9.5 又改动了两维的口径:动作密度清出了 70 余个混在动词表里的非动词(于/属/象/头/绪/脉络/框架/程序/环节/焦/热/痛/弱/短/难 等),并把「十分 / 非常 / 格外 / 无比 / 极其 / 特别 / 相当」等双字程度副词纳入性质形容词豁免(旧版只判前一个字,「十分香」会被计成动作);抽象度增加词首排除(代词 / 副词 / 介词 / 动词不得作为抽象词词首)并大幅扩充停用表(天空 / 办法 / 整理 / 写信 / 认真 / 醒来 / 做梦 / 救命 等)。因此动作密度、抽象度、修饰密度在多数书上会下降。v7.0.0 的细节密度去噪与本页无关:它只作用于 novel_sentence_analysis 的 density(口径标识 METRIC_CALIBER = "v7.0.0"),六维基线的口径本次未变,本页六维数值不会因它而下降。
  • 落盘位置:报告写入 <root>/.novel-writer/style-reports/<book>.json;action:"get" 只读取该文件,不重算。

相关工具

  • novel_style_check —— 用同一套维度做「新章 vs 基线」的对照判定;基线会先剔除目标章再计算,范围同样取自面板「基线范围」。
  • novel_sentence_analysis —— 九类句式分布、情感曲线与风格指纹,是本报告「维度」部分的原始来源。
  • novel_semantic_search —— 语义风格距离所依赖的本地向量引擎。

源码位置

  • lib/style-metrics.js —— 六维测量、基线计算、偏离判定(measureStyleMetrics / computeBaselineFromPerChapter / judgeAgainstBaseline)
  • lib/core.js —— resolveBaselineScope(v6.5.5 基线范围的唯一裁决点,配套 readBaselineScope / writeBaselineScope / normalizeBaselineScope 与 buildTolerance)
  • lib/analysis.js —— TERMINATORS 句末符常量、句式与情感分析
  • lib/index.js —— 工具注册:参数、输出契约与 execute 实现
  • lib/vibe.js —— 氛围光谱 12 维