dsh-novel-writer 6.5.0 → 6.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -10,13 +10,13 @@ English | [中文](./README.md)
10
10
  [![GitHub stars](https://img.shields.io/github/stars/siweina/dsh-novel-writer.svg?style=flat-square&color=orange)](https://github.com/siweina/dsh-novel-writer/stargazers)
11
11
  [![siweina/dsh-novel-writer MCP server](https://glama.ai/mcp/servers/siweina/dsh-novel-writer/badges/score.svg)](https://glama.ai/mcp/servers/siweina/dsh-novel-writer)
12
12
 
13
- **19 tools covering the whole loop: gather inputs before you write → quantify & self-check → work a prioritised fix list → audit the cross-chapter structure.**
13
+ **20 tools covering the whole loop: gather inputs before you write → quantify & self-check → work a prioritised fix list → audit the cross-chapter structure.**
14
14
  One call before writing gathers **16 inputs** (missing one is how style drift starts); after you write, you don't just get numbers — you get a **priority-ordered to-do list** (line-anchored original text, current vs. target values, reference passages, **direction only, never ghost-written prose**); across chapters you can finally see "the thread snapped, a character vanished, a hook was forgotten".
15
15
  Sentence, emotion and semantic analysis all run **on your machine**: a 24MB Chinese model ships with the package,
16
16
  **zero API cost, your manuscript never leaves the device**. Built for DeepSeek Harness (DSH); the same engine is also
17
17
  exposed as a **stdio MCP server** for Claude Desktop / Cursor.
18
18
 
19
- [Website](https://siweina.github.io/dsh-novel-writer/) · [Technical docs (parameters & examples for all 19 tools)](https://siweina.github.io/dsh-novel-writer/tools/) · [Install](#install) · [The writing loop](#the-writing-loop-end-to-end) · [60-second start](#60-second-start) · [See the output](#see-the-output) · [The 19 tools](#provided-tools-19) · [MCP server](#mcp-server-usable-outside-dsh)
19
+ [Website](https://siweina.github.io/dsh-novel-writer/) · [Technical docs (parameters & examples for all 20 tools)](https://siweina.github.io/dsh-novel-writer/tools/) · [Install](#install) · [The writing loop](#the-writing-loop-end-to-end) · [60-second start](#60-second-start) · [See the output](#see-the-output) · [The 20 tools](#provided-tools-20) · [MCP server](#mcp-server-usable-outside-dsh)
20
20
 
21
21
  ---
22
22
 
@@ -39,6 +39,7 @@ Full version history: see [CHANGELOG.md](./CHANGELOG.md).
39
39
  | Your pain | What you get here |
40
40
  |---|---|
41
41
  | "This passage I just wrote doesn't sound like me" | **Six-metric style baseline**: μ±σ of the original measured per chapter across syntactic complexity / modifier density / abstraction / action density / hedging / gap index; a new chapter is compared dimension by dimension and flagged ⚠ when out of band |
42
+ | "The AI keeps writing, and a few chapters in it sounds like the AI, not like my novel" | **Baseline scope (whole book / selected chapters)**: the baseline no longer has to be sampled from the whole book — tick the chapters you treat as the yardstick (say, the first 30 you wrote yourself) and the style check, chapter brief, fix plan, style report and new-chapter baseline will use **only** those chapters as the baseline; the chapter being checked is always excluded. **The plugin never decides "you have drifted" — it tells you "how far this chapter sits from the yardstick you chose"** (answers issue #1, "style drift") |
42
43
  | "The AI says my style changed but can't say where" | **Style check**: similarity score + a deviation list (which sentence types increased, how far sentence length drifted, whether the dominant emotion changed) |
43
44
  | "I have to poke six or seven tools before writing, and I still miss one" | **Chapter brief**: 16 inputs in one call; whatever can't be found is explained in `degraded` — **missing an input is the number one source of style drift and setting contradictions** |
44
45
  | "I can see the chapter has problems but not which one to fix first" | **Fix plan**: to-dos sorted by severity → effort → line, each with line-anchored text, current vs. target values, a reference passage and a rewrite direction; re-checkable in three states after editing |
@@ -47,6 +48,7 @@ Full version history: see [CHANGELOG.md](./CHANGELOG.md).
47
48
  | "I planted a plot thread and forgot to pay it off" | **Plot registry**: add / list / scan / done, automatically recording which chapters mention each thread |
48
49
  | "Character settings contradict each other" | **Five settings tables + continuity audit** (bridge / OOC / outline drift) |
49
50
  | "I can't read the report" | Everything is **tabulated numbers + quoted anchors from your own text** — screenshot-ready |
51
+ | "An error gives me one line, and never says why it keeps happening" | **Diagnostic report** (`novel_diagnose`): reads the local event log and runs **five health checks** (library root / library contents / data-directory corruption / semantic engine / switch snapshot), aggregating repeated **tool × book** trouble spots, top failure codes and actionable suggestions; the report goes through the **share layer** (free text stripped wholesale, every error classified into a fixed code), so it is **safe to paste into an issue** — use it (or the panel's "copy diagnostic bundle") rather than attaching the local log file. The local log is **metadata-first** (tool / book / chapter / status / duration / counts / paths / error code), and free-text fields (error messages) are clamped to **at most one sentence (≤160 characters)**: that local file may therefore echo a fragment of what you passed in, but it **never leaves the machine** |
50
52
 
51
53
  ## Install
52
54
 
@@ -83,6 +85,8 @@ Then ask, in order (this is one complete chapter):
83
85
  3. **"check chapter 7"** → `novel_style_check` returns similarity, a deviation list and the six-metric comparison
84
86
  4. **"turn chapter 7 into a fix list"** → `novel_fix_plan` returns the prioritised to-dos; run `verify` afterwards to re-check the three states
85
87
 
88
+ > Want the baseline to follow **the chapters you wrote yourself** instead of the whole book? In the panel's **Style baseline** view, set **Baseline scope** to **selected chapters** and tick them: the check, the chapter brief, the fix plan and the style report all switch to those chapters as the baseline (**per book**; the default **whole book** is the old behaviour — leave it alone and nothing changes). Creating a chapter (`novel_new_chapter`) uses the same scope for the μ summary and the anchor pack it attaches.
89
+
86
90
  To just look at the baseline first, ask **"run novel_style_report on my novel"** and you get:
87
91
 
88
92
  ```text
@@ -104,7 +108,7 @@ To just look at the baseline first, ask **"run novel_style_report on my novel"**
104
108
  - [mu5kjla…] 琥珀色齿轮怀表的来历与停摆的指针(high|第 1 章埋下,已过 6 章)
105
109
  【相关人物】- 林昭:守码头的女人,父亲失踪后回到旧宅
106
110
  【世界观用语】欧式中世纪沿海城邦;点烛不烧香|禁用:上香、烧香、时辰、老夫
107
- 【风格基线】complexity μ=2.42 modifierDensity μ=22.2 abstractDensity μ=10.06 …
111
+ 【风格基线】complexity μ=2.42 modifierDensity μ=22.2 abstractDensity μ=10.06 …(取样范围:全书除本章外的 7 章)
108
112
  【原著锚段·照这个味道写】[对话] … [心理] …
109
113
  【上一章自检】第 7 章六维对照:全部维度在容差带内 ✓
110
114
  【本章禁用清单】- 禁词:上香(建议改用:点烛)
@@ -152,7 +156,7 @@ fixAnchors:3 条原著锚段(对话 / 心理 / 描写各一条,供逐句
152
156
  | Cost | **0 (local inference)** | Billed per token | Self-hosted |
153
157
  | Purpose-built for Chinese fiction | **Yes** | Generic | No |
154
158
  | Style baseline (μ±σ) | **Yes** | Rare | No |
155
- | DSH integration | **19 tools + official seats & website-style UI** | None | None |
159
+ | DSH integration | **20 tools + official seats & website-style UI** | None | None |
156
160
  | Usable without DSH | **Yes (MCP)** | Yes | You wrap it yourself |
157
161
 
158
162
  > **A note for non-Chinese users**: this plugin is designed for analysing and writing Chinese fiction — the sentence-pattern,
@@ -173,14 +177,14 @@ fixAnchors:3 条原著锚段(对话 / 心理 / 描写各一条,供逐句
173
177
  7. **Worldview & pragmatics detection**: auto cultural-baseline detection with confidence; speechStyle title/honorifics/rituals/tone norms; genre & theme + webnovel signals.
174
178
  8. **Writing toolkit**: plot tracking / five settings tables (characters·locations·items·timeline·worldview) / chapter summaries / continuity audit / batch import / style check / continuation writing.
175
179
  9. **Per-tool UI toggles**: a 「写作助手功能」 ("Writing Assistant") entry in the left sidebar plus a main-column panel (**official DSH 0.2.0 seats**; hosts older than 0.2.0 fall back automatically) — master switch + grouped tool toggles + feature toggles + prompt mode/scene/**lean workflow**, plain-language labels, data-dir usage & semantic-engine status. All colors go through `--dsw-alias-*` semantic tokens (three themes in sync with the host), controls use the official UI primitives, DeepSeek-blue accents, a glow hero header with section kickers, and equal-width pages across every level.<br>**The UI goes further (Plan A)**: source split + build chain (assert-failed builds never write, `--check` verifies source==artifact), 6 unified components with a single row pattern (`PreferenceRow`), group cards across every switch page, 19+5 line icons, a slider as auxiliary input on the tolerance rows, and page-level push-pop transition animations (zero state / zero hook).
176
- 10. **Style Baseline**: Six writing metrics (syntactic complexity / modifier density / abstraction / action density / hedging / gap index) + per-chapter μ±σ baseline band; `novel_style_report` outputs the band, `novel_style_check` compares new chapters (in-band ✓ / out-of-band ⚠); per-metric ±% tolerance configurable in the panel's **Six-metric baseline** view (**recommended = 1.5× σ of the book's chapter variance**, rounded, clamped to ±10%~100%; leave blank to use recommended) — free theme, writing style kept inside the band.
180
+ 10. **Style Baseline**: Six writing metrics (syntactic complexity / modifier density / abstraction / action density / hedging / gap index) + per-chapter μ±σ baseline band; `novel_style_report` outputs the band, `novel_style_check` compares new chapters (in-band ✓ / out-of-band ⚠); per-metric ±% tolerance configurable in the panel's **Six-metric baseline** view (**recommended = 1.5× σ of the book's chapter variance**, rounded, clamped to ±10%~100%; leave blank to use recommended) — free theme, writing style kept inside the band.<br>**Baseline scope (v6.5.5, configured per book)**: the baseline does not have to be the whole book — in the panel's **Style baseline** view choose **whole book** (default, = the old behaviour) or **selected chapters**, then tick the chapters you treat as the yardstick; the baseline then uses **only** those (it means "only these", not "prefer these and top up when short"). This exists for "the AI keeps writing and ends up sounding like the AI": from chapter 2 onward the model is really reading its own previous chapter for reference, and **a baseline sampled from the whole book drifts along with it** (the longer the book, the higher the AI share). Nail the yardstick to the chapters you wrote yourself and the plugin can answer "**relative to those chapters**, how far has this chapter moved" — it never judges whether you have drifted, which is not decidable in the first place. The chapter being checked is **always excluded** (otherwise it would be comparing itself with itself); when fewer than 3 chapters are ticked the recommended tolerance widens to **at least ±25%**, so even a single chapter can serve as the baseline; chapters that cannot be used (renamed / deleted, or **too short to measure**) are skipped and reported honestly, and if not one of them can be used it falls back to **whole book** and states the reason in the returned baseline description. The scope is stored per book (`<library root>/.novel-writer/baseline/<book>.json`), so books do not affect each other.
177
181
  11. **Writing sentinels**: `novel_continuity_check` extended — ①**bridge check** (`chapter`: time jumps / semantic distance / character continuity / hook handoff, with quoted evidence) ②**OOC check** (`ooc`: per-character emotion baseline deviation) ③**outline drift** (`outline`: direction vs body keyword overlap); **brief mode** for report tools.
178
182
  12. **Original mode & creation files**: fill in creation settings in the panel's **Original mode** view (worldview/characters/forbidden/main conflict/genre/extras, blank = model decides, per-book profile library); novel_outline maintains creation files (bible/characters/outline/hooks/status), enforcing the bible → outline → hook chain with dynamic batches (10→20→30 chapters) to prevent plot jumps and OOC.
179
- 13. **Experience & stats**: main panel **library stats** (per-book chapters/total chars/7-day active chars, 🔥 green), **🎬 demo** (built-in sample, no files, runs the 6-dim baseline), **📊 report history** (analysis/style-reports browsing); actionable error hints; slimmer tool descriptions.
183
+ 13. **Experience & stats**: main panel **library stats** (per-book chapters/total chars/7-day active chars, 🔥 green), **🎬 demo** (built-in sample, no files, runs the 6-dim baseline), **📊 report history** (analysis/style-reports browsing), **🩺 diagnostics** (diagnostic report + statistics window + **one-click copy of the diagnostic bundle** for pasting into an issue); actionable error hints; slimmer tool descriptions.
180
184
 
181
185
  ---
182
186
 
183
- ## Provided Tools (19)
187
+ ## Provided Tools (20)
184
188
 
185
189
  > Parameters, return shapes and worked examples for every tool live in the [online tool manual](https://siweina.github.io/dsh-novel-writer/tools/).
186
190
 
@@ -205,12 +209,13 @@ fixAnchors:3 条原著锚段(对话 / 心理 / 描写各一条,供逐句
205
209
  | `novel_chapter_brief` | **Chapter brief** — one call before writing gathers everything (previous-chapter hand-off / chapter direction / characters / open threads / wording rules / style baseline / anchors & skeletons / avoid-list / checklist) |
206
210
  | `novel_fix_plan` | **Fix plan** — turns style diagnostics into a priority-ordered to-do list (line anchors + reference passages), re-checkable after edits; **direction only, never generates prose** |
207
211
  | `novel_lexicon` | **Required-lexicon table** — per-book + global two-layer registry of proper nouns and author-favoured wording, batch import, hard-checklist injection into the chapter brief, and a wording audit for finished drafts |
212
+ | `novel_diagnose` | **Diagnostic report** (v6.6.0) — reads the local event log and runs five health checks (library root / library contents / data-directory corruption / semantic engine / switch snapshot), aggregating recent calls / degradations / failures, repeated **tool × book** trouble spots, top failure codes and actionable suggestions; three actions: `report` / `events` / `clear`. **The report carries metadata only** (tool / book / chapter / status / duration / counts) — free text is stripped wholesale at the share layer and every error is classified into a fixed code, so it is **safe to paste into an issue** (use this or the panel's "copy diagnostic bundle", not the local log file) |
208
213
 
209
214
  ---
210
215
 
211
216
  ## MCP server (usable outside DSH)
212
217
 
213
- The package ships a **stdio MCP server** (`mcp/server.mjs`) that exposes all 19 tools to any MCP client,
218
+ The package ships a **stdio MCP server** (`mcp/server.mjs`) that exposes all 20 tools to any MCP client,
214
219
  e.g. Claude Desktop or Cursor. **It runs as a local process on your own machine — no server, no network, no daemon.**
215
220
 
216
221
  ```bash
@@ -254,6 +259,8 @@ start the server with `--allow-external-src`. Details: [mcp/README.md](./mcp/REA
254
259
 
255
260
  Under `<library-root>/.novel-writer/`: `plots` / `settings` / `summaries` / `analysis` / `audits` (continuity audits + fix-plan lists) / `embedding` / `style-reports`.
256
261
 
262
+ **The event log is deliberately not here**: it lives next to the state file — `~/.dsh/dsh-novel-writer/logs/events.jsonl` (overridable via `DSH_NOVEL_WRITER_STATE`), rotating at 512 KB and keeping the 3 most recent files. Reason: the most common failure is precisely "the library root is wrong", and at that moment `.novel-writer/` does not exist or is not writable — exactly when the evidence matters most. The log is **metadata-first** (time / tool / book / chapter / status / duration / counts / paths / error code), **but it cannot claim "no prose at all"**: free-text fields (error messages) are clamped to **at most one sentence (≤160 characters)**, so a fragment echoed back by an error message can land in the log **on this machine** (measured: when `novel_lexicon` cannot find a term it echoes the term you passed into `detail`). The log file **never leaves the machine**; to share evidence (issue / AI) use the `novel_diagnose` report or the panel's "copy diagnostic bundle" — that path goes through the **share layer**, which strips free text wholesale.
263
+
257
264
  ---
258
265
 
259
266
  ## Dependencies, permissions and failure bounds
@@ -277,8 +284,12 @@ Under `<library-root>/.novel-writer/`: `plots` / `settings` / `summaries` / `ana
277
284
  - Built-in skill: registers its own `novel-writing` skill through `ctx.skills`; it only reads the packaged
278
285
  `skills/novel-writing/SKILL.md`, **writes to no skill directory** and needs no host configuration; hosts without a `skills`
279
286
  service are skipped silently.
280
- - Local HTTP: registers 5 routes inside the DSH Web GUI (state / reveal / reports / demo / update-check), loopback-only;
281
- `allowLanState` defaults to off, so LAN access is denied by default.
287
+ - Local HTTP: registers 8 routes inside the DSH Web GUI (state / reveal / reports / demo / update-check / lexicon / baseline / **diagnose**), loopback-only;
288
+ `allowLanState` defaults to off, so LAN access is denied by default. Logging and diagnosis stay on the machine: the event log lives at
289
+ `~/.dsh/dsh-novel-writer/logs/events.jsonl` (512 KB rotation, 3 kept) is **metadata-first** (time / tool / book / chapter / status / duration / counts / paths / error code) — **free-text fields (error messages) are clamped to at most one sentence (≤160 characters)**, so a fragment echoed back by an error message can appear in that file **on this machine**; the file itself **never leaves the machine**. `novel_diagnose`
290
+ and the panel's "copy diagnostic bundle" go through the **share layer**, which strips free text before anything is emitted (**nothing that leaves the machine carries free text**);
291
+ **the share layer is the default** (`readLogEvents` returns the free-text local layer only when `forShare:false` is passed
292
+ explicitly — a privacy boundary defaults to giving less, never more).
282
293
  - MCP server (`mcp/server.mjs`): the `root` argument of every tool must fall inside the library root given to `--root`
283
294
  (out-of-root values are rejected and fall back); `novel_import`'s `src` is likewise root-limited unless you explicitly
284
295
  opt out with `--allow-external-src`.
@@ -288,7 +299,12 @@ Under `<library-root>/.novel-writer/`: `plots` / `settings` / `summaries` / `ana
288
299
  - Lifecycle scripts: none (no preinstall / postinstall / prepare).
289
300
 
290
301
  **Failure bounds**: semantic engine failure falls back to pure rule mode; cache/disk failures never block
291
- tool results; a plugin load failure cannot affect the DSH host process.
302
+ tool results; **"can't read it" is never reported as "there's nothing there"** — only a genuinely missing file
303
+ (`ENOENT`) is described as "not initialised / not registered / not saved / no creation files"; when the file
304
+ exists but cannot be read (corrupt / no permission / undecodable encoding) the plugin states that fact with the
305
+ path and reason, says an `init` is *not* needed, and records an event you can inspect with `novel_diagnose`;
306
+ - **Known leftover (deferred to the next release, recorded as-is)**: if a chapter file is replaced by a **directory with the same name**, `scanChapters`'s `if (!entry.isFile()) continue` skips it **silently** — the chapter disappears from the chapter table (`novel_chapters` loses one entry), `novel_diagnose` reports **zero** unreadable files, and `novel_chapter_brief`'s `degraded` contains **no** "could not read chapter" entry. On that one path, **"can't read it" is still reported as "there's nothing there"**; when a chapter seems to have vanished, first check whether a **directory named like the chapter** exists in the book folder;
307
+ a plugin load failure cannot affect the DSH host process.
292
308
 
293
309
  **Compatibility**: Node.js >= 22.3 (`engines.node`); DSH >= 0.1.1-rc.2 (`dsh.engines.dsh`).
294
310
 
package/README.md CHANGED
@@ -10,12 +10,12 @@
10
10
  [![GitHub stars](https://img.shields.io/github/stars/siweina/dsh-novel-writer.svg?style=flat-square&color=orange)](https://github.com/siweina/dsh-novel-writer/stargazers)
11
11
  [![siweina/dsh-novel-writer MCP server](https://glama.ai/mcp/servers/siweina/dsh-novel-writer/badges/score.svg)](https://glama.ai/mcp/servers/siweina/dsh-novel-writer)
12
12
 
13
- **19 个工具,覆盖「动笔前取材料 → 写完量化自检 → 改稿按优先级复测 → 跨章结构体检」整条流程。**
13
+ **20 个工具,覆盖「动笔前取材料 → 写完量化自检 → 改稿按优先级复测 → 跨章结构体检」整条流程。**
14
14
  动笔前**一次调用取齐 16 项材料**(漏读一样就漂移);改稿不再只丢给你一堆数字,而是**按严重度排好的待办**(带原句行号、当前值与目标值、原著锚段,**只给方向不代写**);跨章层面能查「线断了、人丢了、伏笔忘了」。
15
15
  句式 / 情感 / 语义全部**在本地算**:24MB 中文模型随包分发,**零 API 花费、正文不出本机**。
16
16
  为 DeepSeek Harness(DSH)打造;同一套能力也可作为 **MCP 服务器**给 Claude Desktop / Cursor 使用。
17
17
 
18
- [官网](https://siweina.github.io/dsh-novel-writer/) · [技术文档(19 个工具的参数与示例)](https://siweina.github.io/dsh-novel-writer/tools/) · [安装](#安装) · [写作流程](#写作流程一条能走完的闭环) · [60 秒上手](#60-秒上手) · [看看输出](#看看输出长什么样) · [19 个工具](#提供的工具19-个) · [MCP 服务器](#mcp-服务器非-dsh-用户也能用)
18
+ [官网](https://siweina.github.io/dsh-novel-writer/) · [技术文档(20 个工具的参数与示例)](https://siweina.github.io/dsh-novel-writer/tools/) · [安装](#安装) · [写作流程](#写作流程一条能走完的闭环) · [60 秒上手](#60-秒上手) · [看看输出](#看看输出长什么样) · [20 个工具](#提供的工具20-个) · [MCP 服务器](#mcp-服务器非-dsh-用户也能用)
19
19
 
20
20
  ---
21
21
 
@@ -38,6 +38,7 @@
38
38
  | 你的困扰 | 这里给的答案 |
39
39
  |---|---|
40
40
  | "我续写的这段,读起来不像我自己写的" | **文笔六维基线**:从句法复杂度 / 修饰密度 / 抽象度 / 动作密度 / 不确定性 / 留白指数六个维度算出原著的 μ±σ,新章逐维对照,出带标 ⚠ |
41
+ | "AI 接着往下写,几章之后文风越来越像 AI 自己,不像原著了" | **基线范围(按全书 / 按部分)**:基线不必再从全书取样——勾出你认作标杆的那几章(比如你亲手写的前 30 章),自检 / 开写包 / 改稿台 / 风格画像 / 建章都**只用**这几章当基准,正在检查的那一章永远排除在外。**插件不判定「你漂移了」,只告诉你「相对你选的标杆,这章偏了多少」**(回应 issue #1「风格漂移」) |
41
42
  | "AI 说文风变了,但说不清哪儿变了" | **风格自检**:相似度 + 偏差清单(哪类句式多了、句长偏了多少、主导情绪有没有换) |
42
43
  | "动笔前要翻六七个工具,还老漏读一样" | **开写包**:一次调用取齐 16 项材料;取不到的在 `degraded` 里说明缺什么、怎么补——**漏读正是文风漂移与设定矛盾的头号来源** |
43
44
  | "知道这章有问题,但不知道先改哪句" | **改稿台**:按「严重度 → 难度 → 行号」排好待办,每条给原句行号 / 当前值与目标值 / 原著锚段 / 改写方向;改完可三态复测 |
@@ -46,6 +47,7 @@
46
47
  | "伏笔埋了忘了收" | **伏笔登记表**:add / list / scan / done,自动记录每条伏笔在哪些章被提到 |
47
48
  | "人物设定前后打架" | **设定五张表 + 连贯性审计**(衔接 / OOC / 大纲走偏三件套) |
48
49
  | "报告看不懂" | 全是**表格化数字 + 原文锚段**,可以直接截图分享 |
50
+ | "出错了只给一句话,说不清为什么**反复**出错" | **诊断报告**(`novel_diagnose`):读本地事件日志 + **五处体检**(书库根 / 书库内容 / 数据目录损坏 / 语义引擎 / 开关快照),聚合「工具 × 书名」反复出问题的组合、高频失败类型与可执行建议;报告走**分享层**(自由文本整体剔除、错误一律归类成固定 code),**可安全贴进 issue**——报 bug 请用面板「复制诊断包」或本工具的 `report`,**不要**直接附本机日志文件。本地日志**以元数据为主**(工具名 / 书名 / 章号 / 状态 / 耗时 / 计数 / 路径 / 错误 code),**但自由文本字段(错误消息等)经收敛后至多保留一句(≤160 字)**——所以**本机**日志里可能出现被错误消息回显的输入片段;**日志文件不出本机**,只有**离开本机**的内容才整体剔除自由文本 |
49
51
 
50
52
  ## 安装
51
53
 
@@ -80,6 +82,8 @@ mkdir -p novels/我的小说 # 把章节文件放进去(第01章.md、第0
80
82
  3. **「自检第 7 章」** → `novel_style_check` 给出相似度、偏差清单与六维对照
81
83
  4. **「把第 7 章排成改稿清单」** → `novel_fix_plan` 给出按优先级排好的待办;改完再 `verify` 复测三态
82
84
 
85
+ > 想让基线对着**你亲手写的那些章**、而不是全书?面板「风格基线」→ **基线范围** 选 **按部分** 并勾章即可:上面这条链上的自检、开写包、改稿台与风格画像都会改用这几章当基线(**按书生效**;默认「按全书」=旧行为,不动它就跟以前完全一样)。建章(`novel_new_chapter`)时附的 μ 摘要与锚段用的是同一份范围。
86
+
83
87
  只想先看看家底,就说 **「用 novel_style_report 给我的小说做一次风格画像」**:
84
88
 
85
89
  ```text
@@ -100,7 +104,7 @@ mkdir -p novels/我的小说 # 把章节文件放进去(第01章.md、第0
100
104
  - [mu5kjlb…] 海图上红铅笔圈的礁区坐标(high|第 3 章埋下,已过 4 章)
101
105
  【相关人物】- 林昭:守码头的女人,父亲失踪后回到旧宅 - 沈砚:随船出海的人
102
106
  【世界观用语】欧式中世纪沿海城邦;点烛不烧香|禁用:上香、烧香、时辰、老夫
103
- 【风格基线】complexity μ=2.42 modifierDensity μ=22.2 abstractDensity μ=10.06 actionDensity μ=122.45 …
107
+ 【风格基线】complexity μ=2.42 modifierDensity μ=22.2 abstractDensity μ=10.06 actionDensity μ=122.45 …(取样范围:全书除本章外的 7 章)
104
108
  【原著锚段·照这个味道写】[对话] … [心理] …
105
109
  【上一章自检】第 7 章六维对照:全部维度在容差带内 ✓
106
110
  【本章禁用清单】- 禁词:上香(建议改用:点烛) - 禁词:时辰(建议改用:钟点)
@@ -151,7 +155,7 @@ fixAnchors:3 条原著锚段(对话 / 心理 / 描写各一条,供逐句
151
155
  | 花费 | **0(本地推理)** | 按 token 计费 | 自建 |
152
156
  | 中文小说专用 | **是** | 通用 | 否 |
153
157
  | 风格基线(μ±σ) | **有** | 少见 | 无 |
154
- | 与 DSH 集成 | **19 工具 + 官方席位与官网式界面** | 无 | 无 |
158
+ | 与 DSH 集成 | **20 工具 + 官方席位与官网式界面** | 无 | 无 |
155
159
  | 非 DSH 用户可用 | **可以(MCP)** | 可以 | 需自己封装 |
156
160
 
157
161
  > **致非中文用户**:本插件为中文小说分析写作而设计——句式、情感、意象等核心能力以及内置的语义模型,全部针对中文语料构建与调优。在深耕中文的同时兼顾英文等其他语言,确实超出了我目前的能力范围。若因此给您带来不便,我深感抱歉,恳请谅解。
@@ -169,14 +173,14 @@ fixAnchors:3 条原著锚段(对话 / 心理 / 描写各一条,供逐句
169
173
  7. **世界观与语用检测**:文化基准自动判断(西/东/混合)+ 置信度;speechStyle 称谓/客套/仪式/语气规范;题材流派 + 网文信号。
170
174
  8. **写作辅助全家桶**:伏笔登记表 / 设定五张表(人物·地点·道具·时间线·世界观)/ 章节摘要 / 连贯性审计 / 批量导入 / 风格自检 / 续写辅助。
171
175
  9. **全工具 UI 开关**:左栏「写作助手功能」入口 + 主栏面板(**DSH 0.2.0 官方席位**;老宿主 < 0.2.0 自动回退旧挂载)——总开关 + 工具开关分组 + 功能开关 + 提示词档位/场景/**精简工作流**,大白话文案,显示数据目录占用与语义引擎状态。颜色全走 `--dsw-alias-*` 语义 token(三主题与官方同源)、控件用官方 UI primitives、品牌蓝强调、光晕头部与分区 kicker、各级页面等宽。<br>**界面再进一步(方案 A)**:源码分块 + 构建链装配(构建失败不写盘、`--check` 校验源与产物一致)、6 个统一组件与单一行模式(`PreferenceRow`)、分组卡片贯穿全部开关页、19+5 枚线图标、阈值 slider 辅助输入、页面级 push-pop 过渡动画(零 state / 零 hook)。
172
- 10. **风格基线**:文笔六维测量(句法复杂度/修饰密度/抽象度/动作密度/不确定性/留白指数)+ 按章节 μ±σ 基线带;`novel_style_report` 输出基线带,`novel_style_check` 对照新章偏差(带内 ✓ / 出带 ⚠);面板「六维基线」视图可自定义每维 ±% 容差(**推荐值 = 原著章节波动的 1.5 倍 σ**,自动取整、限 ±10%~100%;输入框留空即用推荐)——原创/续写时主题自由、写法保持在基线带内。
176
+ 10. **风格基线**:文笔六维测量(句法复杂度/修饰密度/抽象度/动作密度/不确定性/留白指数)+ 按章节 μ±σ 基线带;`novel_style_report` 输出基线带,`novel_style_check` 对照新章偏差(带内 ✓ / 出带 ⚠);面板「六维基线」视图可自定义每维 ±% 容差(**推荐值 = 原著章节波动的 1.5 倍 σ**,自动取整、限 ±10%~100%;输入框留空即用推荐)——原创/续写时主题自由、写法保持在基线带内。<br>**基线范围(v6.5.5,按书选择)**:基线不必永远是全书——面板「风格基线」里选**按全书**(默认,等于旧行为)/ **按部分**,再用章节选择器勾出你认作标杆的章,基线就**只用**这几章(是「只用」,不是「不够再补」)。这是给「AI 续写越写越像 AI 自己」准备的:从第 2 章续写起,模型参考的其实是它自己写的上一章,而**从全书取样的基线自己也在漂**(书越长 AI 占比越高)。把标杆钉死在你亲手写的那些章上,插件就能回答「**相对这几章**,新章偏了多少」——它不判断你有没有漂移,那一项本就不可判定。正在检查的那一章**永远排除**(否则等于拿自己跟自己比);勾中的章少于 3 章时推荐容差自动放宽到**至少 ±25%**,所以单章也能当基线;取不到的章(已改名 / 已删除,或**正文过短无法测量**)会被跳过并如实计数,**一章都取不到**时自动回退「按全书」并在返回体里写明原因。范围按书保存(`<书库根>/.novel-writer/baseline/<书名>.json`),换书互不影响。
173
177
  11. **写作哨兵三件套**:`novel_continuity_check` 扩展——①**衔接检查**(chapter 参数:时间硬跳/语义距离/人物延续/钩子承接四路检测,带原文引用)②**OOC 检测**(ooc 参数:角色情绪基线偏离)③**大纲走偏**(outline 参数:方向行 vs 正文关键词重合);报告工具支持 **brief 精简模式**。
174
178
  12. **原创模式与创作资料**:面板「原创模式」填写创作设定(世界观/角色/禁忌/主线/题材/额外要求,留空=模型自定,多书独立设定库);novel_outline 维护创作资料(创作设定/人物/剧情大纲/钩子记录/创作状态卡),原创强制「设定书→大纲→钩子」链,动态批次(10→20→30 章)防剧情跳跃与角色 OOC。
175
- 13. **体验与统计**:主面板**书库统计卡**(每本章数/总字数/近 7 天活跃字数,活跃🔥标绿)、**🎬 体验演示**(内置示例不落盘跑六维基线)、**📊 报告历史**(analysis/style-reports 列表浏览);错误提示带解决步骤;工具说明压缩省 token。
179
+ 13. **体验与统计**:主面板**书库统计卡**(每本章数/总字数/近 7 天活跃字数,活跃🔥标绿)、**🎬 体验演示**(内置示例不落盘跑六维基线)、**📊 报告历史**(analysis/style-reports 列表浏览)、**🩺 诊断**(诊断报告 + 统计窗口 + **一键复制诊断包**,用于贴进 issue);错误提示带解决步骤;工具说明压缩省 token。
176
180
 
177
181
  ---
178
182
 
179
- ## 提供的工具(19 个)
183
+ ## 提供的工具(20 个)
180
184
 
181
185
  > 每个工具的参数、返回结构与示例,见[在线工具手册](https://siweina.github.io/dsh-novel-writer/tools/)。
182
186
 
@@ -201,12 +205,13 @@ fixAnchors:3 条原著锚段(对话 / 心理 / 描写各一条,供逐句
201
205
  | `novel_chapter_brief` | **开写包**——动笔前一次调用取齐材料(上一章承接口/本章方向/相关人物/待回收伏笔/用语规范/风格基线/锚段与骨架/禁用清单/开写清单) |
202
206
  | `novel_fix_plan` | **改稿台**——把风格诊断变成按优先级排好的待办(带行号定位与锚段),改完可复测;**只给方向不生成正文** |
203
207
  | `novel_lexicon` | **必用词表**——专有词/作者惯用词的书级+全局两层登记、批量导入、开写包硬清单注入、成稿用词审计 |
208
+ | `novel_diagnose` | **诊断报告**(v6.6.0)——读本地事件日志 + 五处体检(书库根 / 书库内容 / 数据目录损坏 / 语义引擎 / 开关快照),聚合近期调用/降级/失败、**「工具 × 书名」反复出问题的组合**、高频失败类型与可执行建议;`report` / `events` / `clear` 三态。**报告只含元数据**(工具名/书名/章号/状态/耗时/计数),自由文本在分享层已被整体剔除、错误一律归类成固定 code,**可安全贴进 issue**(报 bug 请用它或面板「复制诊断包」,**不要**直接附本机日志文件) |
204
209
 
205
210
  ---
206
211
 
207
212
  ## MCP 服务器(非 DSH 用户也能用)
208
213
 
209
- 包里自带一个 **stdio MCP 服务器**(`mcp/server.mjs`),把 19 个工具原样暴露给任何 MCP 客户端,
214
+ 包里自带一个 **stdio MCP 服务器**(`mcp/server.mjs`),把 20 个工具原样暴露给任何 MCP 客户端,
210
215
  例如 Claude Desktop、Cursor。**它是跑在你自己电脑上的本地进程,不需要服务器、不需要联网、不需要常驻。**
211
216
 
212
217
  ```bash
@@ -246,6 +251,8 @@ npx -y -p dsh-novel-writer dsh-novel-writer-mcp --root /你的小说库路径
246
251
 
247
252
  `<书库根>/.novel-writer/`:`plots`(伏笔)/ `settings`(设定)/ `summaries`(摘要)/ `analysis`(分析报告)/ `audits`(连贯性审计 + 改稿清单 `fix-plan-*.json`)/ `embedding`(语义索引)/ `style-reports`(风格画像)。
248
253
 
254
+ **事件日志不在这里**:它写在状态文件旁——`~/.dsh/dsh-novel-writer/logs/events.jsonl`(受 `DSH_NOVEL_WRITER_STATE` 覆盖),512 KB 轮转、保留最近 3 份。之所以不放书库根:最常见的故障恰恰是"书库根不对",那时 `.novel-writer/` 根本不存在或不可写,偏偏最需要留下证据。日志**以元数据为主**(时间 / 工具名 / 书名 / 章号 / 状态 / 耗时 / 计数 / 路径 / 错误 code),**但自由文本字段(错误消息等)经收敛后至多保留一句(≤160 字)**——**本机**日志里因此**可能**出现被错误消息回显的输入片段(实测:`novel_lexicon` 找不到词条时会把传入的词条名回显进 `detail`)。**日志文件不出本机**;要贴给别人(issue / AI)请用 `novel_diagnose` 的报告或面板「复制诊断包」——那条路走**分享层**,自由文本整体剔除。
255
+
249
256
  ---
250
257
 
251
258
  ## 依赖、权限与失败边界
@@ -268,8 +275,11 @@ npx -y -p dsh-novel-writer dsh-novel-writer-mcp --root /你的小说库路径
268
275
  也限制在书库根内(见下方 MCP 一节)。
269
276
  - 内置技能:通过 `ctx.skills` 注册自带 `novel-writing` 技能,只读取包内
270
277
  `skills/novel-writing/SKILL.md`,**不写入任何技能目录**、不需要改宿主配置;宿主没有 `skills` 服务时静默跳过。
271
- - 本地 HTTP:在 DSH Web GUI 内注册 5 条路由(state / reveal / reports / demo / update-check),
272
- 仅回环地址可访问;`allowLanState` 默认关闭,局域网访问默认拒绝。
278
+ - 本地 HTTP:在 DSH Web GUI 内注册 8 条路由(state / reveal / reports / demo / update-check / lexicon / baseline / **diagnose**),
279
+ 仅回环地址可访问;`allowLanState` 默认关闭,局域网访问默认拒绝。日志与诊断都在本机:事件日志写在
280
+ `~/.dsh/dsh-novel-writer/logs/events.jsonl`(512 KB 轮转、留 3 份),**以元数据为主**(时间 / 工具名 / 书名 / 章号 / 状态 / 耗时 / 计数 / 路径 / 错误 code),**但自由文本字段(错误消息等)经收敛后至多保留一句(≤160 字)**——所以**本机**日志里可能出现被错误消息回显的输入片段,**该文件不出本机**;
281
+ `novel_diagnose` 与面板「复制诊断包」走**分享层**,自由文本整体剔除后才输出(**任何离开本机的内容都不含自由文本**);**分享层是默认行为**
282
+ (`readLogEvents` 只有显式 `forShare:false` 才返回带自由文本的本地层,其余一律脱敏——隐私边界的默认值偏向"少给")。
273
283
  - MCP 服务器(`mcp/server.mjs`):工具参数里的 `root` 必须落在启动时 `--root` 指定的书库根之内,
274
284
  越界会被拒绝并回退;`novel_import` 的 `src` 默认也限根内,确需导入外部目录时用
275
285
  `--allow-external-src` 显式放开。单行报文上限 4 MiB,超限整行丢弃;stderr 默认只记一行错误摘要,
@@ -285,6 +295,8 @@ npx -y -p dsh-novel-writer dsh-novel-writer-mcp --root /你的小说库路径
285
295
 
286
296
  - 语义引擎不可用(模型缺失或 WASM 初始化失败)时自动回退纯规则模式,其余功能不受影响;
287
297
  - 分析结果落盘失败不阻塞工具返回;缓存损坏按"无缓存"处理并重建;
298
+ - **「读不到」不等于「没有」**:只有文件真的不存在(`ENOENT`)才说「尚未初始化 / 未登记 / 尚未保存 / 无创作资料」;文件存在却读不出来(损坏 / 无权限 / 编码识别不了)时只说事实并给出路径与原因,并说明不必去 `init`——同时记一条事件日志,可用 `novel_diagnose` 查真因;
299
+ - **已知残留(下版处理,如实记下)**:把某章文件换成**同名目录**时,`scanChapters` 里那句 `if (!entry.isFile()) continue` 会**静默跳过**它——该章从章表消失(`novel_chapters` 少一章)、`novel_diagnose` 的「读不出来的文件」计数为 0、`novel_chapter_brief` 的 `degraded` 里也**没有**「读取章节失败」。也就是**这一条路径上「读不到」仍被讲成「没有」**;遇到「章数莫名少了一章」时,先确认该书目录下是不是有一个**叫章节名的目录**;
288
300
  - 插件加载失败不影响 DSH 主进程:工具注册与提示词注入相互独立。
289
301
 
290
302
  **兼容范围**:Node.js >= 22.3(`engines.node`);DSH >= 0.1.1-rc.2(`dsh.engines.dsh`)。
package/cordis.patch.yml CHANGED
@@ -1,4 +1,4 @@
1
- # dsh-novel-writer bundle patch: 19 tools (incl. novel_semantic_search, novel_lexicon) + local embedding engine.
1
+ # dsh-novel-writer bundle patch: 20 tools (incl. novel_semantic_search, novel_lexicon, novel_diagnose) + local embedding engine.
2
2
  # dsh-novel-writer bundle patch: mounts the novel-writing assistant plugin
3
3
  # into the profile's loader tree. The entry's name is the plugin package
4
4
  # itself, resolved from the profile's node_modules.
package/lib/analysis.js CHANGED
@@ -1836,6 +1836,8 @@ export function densityOf(text) {
1836
1836
  // 懒加载 + 内存缓存;七类 → 五情感映射,供 sentimentCounts 扩展计数(词表未覆盖的词也能计分)
1837
1837
  import { createRequire } from "node:module";
1838
1838
  import { gunzipSync } from "node:zlib";
1839
+ // v6.6.0:事件日志(log.js 无依赖,可安全被本模块导入)
1840
+ import { logEvent } from "./log.js";
1839
1841
  const __req = createRequire(import.meta.url);
1840
1842
  let DUTIR = null;
1841
1843
  function loadDutir() {
@@ -1844,8 +1846,11 @@ function loadDutir() {
1844
1846
  // v4.0.1:dutir 词表以 gzip 随包分发(原文件 383KB 超出商店单文件上限),加载时解压
1845
1847
  const p = __req.resolve("./lexicons/dutir_seven.json.gz");
1846
1848
  DUTIR = JSON.parse(gunzipSync(__req("node:fs").readFileSync(p)).toString("utf8"));
1847
- } catch {
1849
+ } catch (e) {
1848
1850
  DUTIR = {};
1851
+ // v6.6.0:内置 27k 词七类情感表加载失败 → 整个 dutir 兜底扫描静默失效
1852
+ //(情感统计退回只有主词表,结果数字变小而没人知道为什么)。只随包分发,故基本是打包/权限问题。
1853
+ logEvent("config", { file: "lib/lexicons/dutir_seven.json.gz", level: "warn", detail: "内置七类情感词表加载失败,dutir 兜底扫描失效:" + (e?.message ?? e) });
1849
1854
  }
1850
1855
  return DUTIR;
1851
1856
  }
package/lib/brief.js CHANGED
@@ -19,6 +19,7 @@ import {
19
19
  bookDir,
20
20
  buildStyleAnchorPackage,
21
21
  buildTolerance,
22
+ resolveBaselineScope,
22
23
  creationDir,
23
24
  CREATION_FILES,
24
25
  featureEnabled,
@@ -42,6 +43,8 @@ import {
42
43
  selectLexiconForBrief
43
44
  } from "./core.js";
44
45
  import { computeBaselineFromPerChapter, judgeAgainstBaseline } from "./style-metrics.js";
46
+ // v6.6.0:事件日志(log.js 无依赖,不会与 core.js 形成循环导入)
47
+ import { logEvent } from "./log.js";
45
48
 
46
49
  // v5.0.0:两档预算——compact 给"够用",full 给"备齐"(characters: 0 语义为"不截断")。
47
50
  // 注意 skeletons 的上限受 buildStyleAnchorPackage 自身产出(最多 4 条)限制,full 的 8 只是"不额外截断"。
@@ -114,7 +117,14 @@ async function readPlotsReadOnly(root, book) {
114
117
  try {
115
118
  const parsed = JSON.parse(await readFile(file, "utf8"));
116
119
  if (Array.isArray(parsed?.entries)) return parsed.entries.map(normalizePlotEntry).filter(Boolean);
117
- } catch { /* 不存在/损坏 → 试下一个位置 */ }
120
+ } catch (e) {
121
+ // v6.6.0:「不存在」与「损坏/读不动」在这里都只表示"试下一个位置",差别只有日志记得下。
122
+ // 本条是 JSON.parse 路径(解析错误的消息会回显文件内容片段),故只记固定标签 + 文件名,绝不带 e.message
123
+ if (e?.code !== "ENOENT") {
124
+ logEvent("config", { book, file, level: "warn", detail: "伏笔表读取失败(按无伏笔处理,继续试下一位置)" });
125
+ }
126
+ /* 不存在/损坏 → 试下一个位置 */
127
+ }
118
128
  }
119
129
  return [];
120
130
  }
@@ -125,6 +135,11 @@ async function readCreationFile(root, book, key, exec) {
125
135
  return await readTextFile(join(creationDir(root, book), CREATION_FILES[key]), exec);
126
136
  } catch (e) {
127
137
  if (isAbort(e)) throw e;
138
+ // v6.6.0:**读不到 ≠ 没写过**。调用方把 null 当作"这本书没有创作资料"(degraded 里会写"未初始化"),
139
+ // 于是权限/编码类失败会伪装成"还没建创作资料"——用户照提示去 init 也没用。ENOENT 不记(常态)。
140
+ if (e?.code !== "ENOENT") {
141
+ logEvent("config", { book, file: CREATION_FILES[key], level: "warn", detail: "创作资料读取失败(调用方按「没有创作资料」处理):" + (e?.message ?? e) });
142
+ }
128
143
  return null;
129
144
  }
130
145
  }
@@ -463,19 +478,44 @@ export async function buildChapterBrief(root, book, chapterArg, opts, exec) {
463
478
  // v5.0.0 修正:锚段/骨架必须取自**其它章**(与 novel_fix_plan 的锚段口径一致)。
464
479
  // 目标是已存在的章(改稿场景)时把本章自身排除——否则"照着原著范本改"可能照着正在改的这一段,
465
480
  // 把待修的病句当成标杆。chapter="next" 时目标章不存在,等价于不排除。
466
- const anchorChapters = targetChapter ? chapters.filter((c) => c.file !== targetChapter.file) : chapters;
467
- const anchorTexts = targetChapter ? texts.filter((t) => t.file !== targetChapter.file) : texts;
481
+ //
482
+ // v6.5.5:这一处的取样范围交给**基线范围**的单一裁决点(按全书 / 按用户选中的部分章)。
483
+ // 为什么锚段也要跟着走:这一段的用途就是给文风当锚(名字就叫「原著锚段」)。
484
+ // 若基线按部分、而锚段仍取全书,用户指定"以这几章为标杆"就没落到**最容易影响生成**的那一处:
485
+ // 书越长、AI 续写占比越高,锚段越会变成 AI 自己的文风——issue #1 的漂移正是这么来的。
486
+ const anchorScope = await resolveBaselineScope(baseRoot, safeBook, chapters, targetChapter ? targetChapter.file : "", exec);
487
+ const anchorFileSet = new Set(anchorScope.items.map((c) => c.file));
488
+ const anchorChapters = anchorScope.items;
489
+ const anchorTexts = texts.filter((t) => anchorFileSet.has(t.file));
490
+ // v6.5.5:这里的措辞**必须跟上裁决点给的原因**(独立验证代理抓到本行是全文唯一残留的「已失效」硬编码):
491
+ // 旧句只要回退就写「自定义基线已失效(N 章改名或删除)」,而另一种原因是"选中的只有本章自身"——
492
+ // 那句话在那条路径上不仅原因说错,还把"按设计排除"量化成"0 章改名或删除",是半句空话。
493
+ // degraded 是**渲染进 <content> 给模型看的**,口径必须与 baselineScope 一致,否则同一份开写包自相矛盾。
494
+ if (anchorScope.fellBack) {
495
+ degraded.push("原著锚段/六维基线:" + (anchorScope.reason === "only-target"
496
+ ? "选中的章只有本章自身(不能作为自己的基线),已回退按全书"
497
+ : "自定义基线已失效(" + anchorScope.missing + " 章已改名或删除),已回退按全书"));
498
+ }
468
499
  let anchors = [];
469
500
  let skeletons = [];
501
+ // v6.6.0:锚包的**生成失败**与**正文不满足抽样条件**(段落 ≥ 40 字)必须可区分。
502
+ // 旧版两者都落到下面那句「抽样要求段落 ≥ 40 字,本书正文不满足」——把系统的失败说成作者的正文太短 ✗。
503
+ // 判据来自 buildStyleAnchorPackage 的 failed 标记(只在失败路径出现,成功路径返回结构未变)。
504
+ let anchorUnavailable = false;
470
505
  try {
471
506
  const pkg = await buildStyleAnchorPackage(baseRoot, safeBook, anchorChapters, exec, anchorTexts);
472
507
  anchors = (pkg.anchors ?? []).slice(0, budget.anchors);
473
508
  skeletons = (pkg.skeletons ?? []).slice(0, budget.skeletons);
509
+ if (pkg?.failed === true) {
510
+ anchorUnavailable = true;
511
+ degraded.push("原著锚段/句式骨架生成失败(内部错误,本次抽样未完成)——与「正文段落不足 40 字」不是一回事,请查 novel_diagnose 的事件日志或重试");
512
+ }
474
513
  } catch (e) {
475
514
  if (isAbort(e)) throw e;
515
+ anchorUnavailable = true;
476
516
  degraded.push("原著锚段/句式骨架不可用:" + errText(e));
477
517
  }
478
- if (anchorChapters.length > 0 && anchors.length === 0 && skeletons.length === 0) {
518
+ if (anchorChapters.length > 0 && anchors.length === 0 && skeletons.length === 0 && !anchorUnavailable) {
479
519
  degraded.push("原著锚段与句式骨架为空(抽样要求段落 ≥ 40 字,本书正文不满足)");
480
520
  }
481
521
 
@@ -491,7 +531,9 @@ export async function buildChapterBrief(root, book, chapterArg, opts, exec) {
491
531
  }
492
532
  // v5.0.0:目标章已存在时把它排除出基线语料(与 novel_style_check 同口径);"下一章"尚未存在,
493
533
  // 全部现有章都是它的参照系。
494
- const baselineCorpus = targetChapter ? perChapter.filter((pc) => pc.file !== targetChapter.file) : perChapter.slice();
534
+ // v6.5.5:范围同样走基线范围的单一裁决点(按全书 / 按用户选中的部分章),与锚段、六维基线一致。
535
+ const baselineScope = await resolveBaselineScope(baseRoot, safeBook, perChapter, targetChapter ? targetChapter.file : "", exec);
536
+ const baselineCorpus = baselineScope.items;
495
537
  let baseline = null;
496
538
  if (baselineCorpus.length > 0) baseline = computeBaselineFromPerChapter(baselineCorpus);
497
539
  else if (perChapter.length === 0) degraded.push("六维基线不可用:全书没有正文达 40 字以上的章节(书太短或章节为空)");
@@ -501,7 +543,10 @@ export async function buildChapterBrief(root, book, chapterArg, opts, exec) {
501
543
  let lastVerdict = "";
502
544
  if (prevChapter) {
503
545
  const prevMetrics = perChapter.find((pc) => pc.file === prevChapter.file);
504
- const others = perChapter.filter((pc) => pc.file !== prevChapter.file);
546
+ // v6.5.5:这里也走同一套基线范围。注意**排除的是上一章**(被判定的是它,不是目标章)——
547
+ // 范围(哪些章算标杆)只有一个,而"排除谁"取决于在判定哪一章,两者不要混为一谈。
548
+ const prevScope = await resolveBaselineScope(baseRoot, safeBook, perChapter, prevChapter.file, exec);
549
+ const others = prevScope.items;
505
550
  // v6.2.0 修正(缺陷 #65①):原来写的是 `!prevMetrics || !prevMetrics.metrics`,而 perChapter 的每一项
506
551
  // 都由 style-metrics.computeBaseline 写成 `{ file, metrics }`(缓存也只存这个形状),后半句恒为 false。
507
552
  // 「上一章正文不足 40 字」的表现是 perChapter 里**没有这一项**(computeBaseline 会跳过短章),
@@ -859,6 +904,12 @@ export async function buildChapterBrief(root, book, chapterArg, opts, exec) {
859
904
  worldview,
860
905
  lexicon,
861
906
  baseline,
907
+ // v6.5.5:**披露基线范围**。锚段与六维基线都可能只取自用户选中的部分章
908
+ //(按部分 = 只用勾选的章节),不写出来,模型会把"你选的 6 章"当成"整本书"来校准语感——
909
+ // 那恰恰是这个功能想避免的事。fellBack(选择全部失效→回退全书)也已含在 scope 文案里。
910
+ // 由独立复核代理指出「开写包原本完全不披露范围」后补上(与 style_check/fix_plan 同口径)。
911
+ baselineScope: baselineScope.scope,
912
+ baselineMode: baselineScope.mode,
862
913
  anchors,
863
914
  skeletons,
864
915
  lastVerdict,