dsh-novel-write-engine 0.2.0 → 0.2.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.1
4
+
5
+ - **面板可滚动**:`nwe-root` 改为 `height:100%; overflow-y:auto; overscroll-behavior:contain`,宿主 `overflow:hidden` 不再裁掉下方内容;教程弹框保持 fixed,Esc/遮罩/焦点返回不回归。教程弹框新增「续写下一章」可直接复制给 Agent 的话术:先 `novel_pipeline` 确认状态,缺章卡用全量 `novel_outline`,目标章数满了用 `novel_amend`,再按 P1→P8 推进并在归档前确认 `state_delta`。
6
+ - **指标单一 owner**:`outlineSummary` 接收目标章数与已归档章号,输出规划覆盖、归档覆盖、缺卡数与逐章 `card_coverage`;`novel_status`、`novel_report`、`novel_doctor`、panel、`/novel outline` 全部消费同一结果,40/800 不再显示 100%。剩余字数按实际平均字数估算,并保留最小目标口径。
7
+ - **字数说真话**:`WordMetrics` 新增 `punctuation_chars`;PG-03 失败消息并列汉字/标点/英文词/数字;brief 增加 `word_count_policy` 和 `dialogue_ratio_target`;中文弯引号、破折号、省略号归入中文标点,对话行按四种引号统计。
8
+ - **失败可审计**:通过参数形状校验、进入项目锁后被 gate/delta/state 拒绝的写操作在 journal 写 `status:"aborted"`、`attempt:true` 行,带 `code/reason/hint/blocking`;事务提交失败与崩溃恢复的 aborted 行也带 `code`;锁前纯参数形状错误不写 journal。`AuditRow` 增加 `seq/code/reason/hint/stage/attempt/blocking`;`novel_audit` 支持 chapter/op/status/since/until/cursor。
9
+ - **历史不再只活最后一份**:评审、校验、正文、废弃稿、P2 计划按 pipeline revision 写入 `history/`;`novel_state {kind:"history"}` 全量列出(`total`),按 chapter+正整数 revision 读取;`novel_report` 从历史评审聚合维度样本,`ChapterRecord` 保存 lifetime `rounds`(含 `rollbacks`)/`final_check`/`archived_at`,report 新增 `effort`;`novel_amend` 不能改写这些归档证据。
10
+ - **备份可列可恢复**:事务备份写入 `manifest.json`;P2/P4/P6 在已有旧文件时也建备份,因此计划/评审/校验可从真实事务恢复。`novel_state {kind:"backups"}` 列出 op/chapter/revision/files;新增 `novel_restore`,只恢复活动章且不做 replay(外部再次改动后同一 txid 仍会真正恢复),已归档章先 `novel_reopen`,恢复本身再建备份。`novel_reopen` 的 journal/manifest 现在记录被重开的章节。
11
+ - **计划与摘要分权**:P2 计划文件不再被 P3/P5 写入 summary;活动标题/场景/叙述者/摘要存入 `pipeline.draft`,旧项目回退读 plan.summary;P5 不再改写 P2 证据。归档前校验 `draft.content_hash` 与正文一致,正文被外部改动时拒绝封存(`DRAFT_STALE`/`ENGINE_TXN_ABORTED`)。
12
+ - **修订成本**:`novel_revise` 支持 `patch:{expected_hash,edits}`;hash 不匹配、锚点不唯一/越界/重叠都会零写入拒绝。
13
+ - **简报收据独立**:`state/briefs.yaml` 保存 receipt,pipeline.briefs 仅作旧项目回退;重复 digest 直接 replay,rollback 后不再无条件重签。
14
+ - **伏笔标签与锚点**:`title` 可保留可读短语,新增 `anchor` 作为正文逐字校验证据,且至少 8 个字符(单字锚点不再能绕过);无 anchor 时仍要求 title 出现在正文;P8 brief 给出规则和示例。
15
+ - **质量门禁分层**:`check.machine.violations` 只放 blocker,advisory 放 `machine.advisories`;`acknowledge` 只接受本次 advisory code。新增 `DIALOGUE_RATIO`、`NUMERIC_CONTRADICTION` 与 `NUMERIC_FACT_MISMATCH`;数字矛盾因无法判定主语固定为 advisory,数字事实只匹配“别名+数字+声明单位”的相邻写法;worldbook 支持声明式 `facts`,并进入计数、brief 上下文、搜索与 P6 检查。
16
+ - **初始化与角色**:空 author/description 返回 warning;角色 role 从显式值、P1 计划、worldbook、narrator 推导,不再一律 extra;`insight` 的 cast/维度排序改用稳定 code-unit 比较。
17
+ - **真实模型**:`agnes-2.5-flash` 与 `agnes-3.0-flash` 在临时项目完成 P1→P8、归档、report/doctor/panel、六种导出、`novel_reopen` 与重新归档;P3 366 / 490 字,P2/P3 叙述者分别继承 P1 的林深/林渊;2.5 的 564 字在更早 600 字目标下被 PG-03 真实拒绝,本次 76 字由驱动脚本 300 字下限拦截(未进引擎);详见 `docs/0.2.1-real-model-evidence.md`。
18
+ - **发布前回归收口**:无 target 时 status/stats/panel 的 null 覆盖显示不再伪装 100%;report 对重复评审去重;novel_state pipeline 返回合并后的 briefs、briefs 查询保留 0.2.0 pipeline.briefs 回退;restore review/check 重算 blocking;archive 把 P2 计划纳入事务哈希,reopen 拒绝归档后被改动的计划;doctor 只推荐活动章可恢复备份;amend/rollback 不再用旧 revision 缓存吞掉外部改动;worldbook entries 计入 facts。
19
+ - 版本升 0.2.1;panel payload v3;工具 27 → 28(新增 `novel_restore`),服务操作 31 → 32;文档、教程、任务书与测试同步。
20
+ - 质量:72 个测试文件 / 667 条测试;覆盖率 93.86 / 89.04 / 97.81 / 93.86;per-file 门禁通过。
21
+
3
22
  ## 0.2.0
4
23
 
5
24
  - **整书生命周期**:新增 `outline/outline.yaml`(书级蓝图 + 卷 + 章卡)与 `outline/worldbook.yaml`(世界观设定集)。`novel_init` 自动建立两份空文档;旧项目缺少它们时按空规划读取,不报错。
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  0.2.0 起,它同时管理**整本书**:书级蓝图 → 卷/章大纲 → 世界观设定集 → 单章 P1–P9 流水线 → 归档回写大纲 → 报告/体检/搜索/面板。大纲可以从 Markdown/YAML/JSON 导入,也可以由模型按章卡结构生成;严格模式下 P2/P3 会按章卡校验标题、场景、字数与 cast。
8
8
 
9
- > **当前版本:0.2.0** · 中文界面 · MIT
9
+ > **当前版本:0.2.1** · 中文界面 · MIT
10
10
  >
11
11
  > **宿主范围**:DSH `>=0.2.1-rc.2 <0.3.0-0`;Node `>=24.9.0`。
12
12
 
@@ -49,7 +49,7 @@
49
49
  ```text
50
50
  模型 / 用户
51
51
  │
52
- ├── 27 个 novel_* 工具
52
+ ├── 28 个 novel_* 工具
53
53
  ├── 5 条 /novel* 命令(含 outline/guide/report/doctor/search 子命令)
54
54
  ├── 只读侧边栏「网文工坊」
55
55
  └── 7 份阶段任务书(briefs)
@@ -73,7 +73,7 @@
73
73
  - 不是「装完就自动写完整本书」的生成器;它管流程、门禁和状态,正文仍由模型写。
74
74
  - 不是独立 CLI;必须作为 DSH 插件安装到某个 profile 才能使用。
75
75
  - 不提供云端同步、账号体系、在线书城或多人协作。
76
- - 0.2.0 仍是中文界面与中文任务书,没有 `locale/`,也不读取系统语言。
76
+ - 0.2.x 仍是中文界面与中文任务书,没有 `locale/`,也不读取系统语言。
77
77
  - 不提供云端同步、多人协作、RAG/向量库或富文本编辑器;整书规划与单章流水线都在本地 YAML/Markdown 上完成。
78
78
 
79
79
  ---
@@ -82,10 +82,10 @@
82
82
 
83
83
  | 能力 | 说明 |
84
84
  | ------------------------------- | -------------------------------------------------------------------------------------------- |
85
- | 27 个 `novel_*` 工具 | 书级蓝图/大纲/世界观 + 单章流水线 + 报告/体检/搜索/引导/重开 |
85
+ | 28 个 `novel_*` 工具 | 书级蓝图/大纲/世界观 + 单章流水线 + 报告/体检/搜索/引导/重开/备份恢复 |
86
86
  | 5 条 `/novel*` 命令 | 状态、下一步、统计、导出、隐藏操作;`/novel` 内含 outline/guide/report/doctor/search 子命令 |
87
87
  | 1 个侧边栏面板「网文工坊」 | 只读;Hero、统计、全书规划、世界观、质量趋势、健康、最近活动、九阶段轨、章节卡、门控区 |
88
- | 1 个内置 Agent 预设「网文工坊」 | 27 个工具全开,**不挂载 bash / 文件写入工具**;会话开始后不能切换 |
88
+ | 1 个内置 Agent 预设「网文工坊」 | 28 个工具全开,**不挂载 bash / 文件写入工具**;会话开始后不能切换 |
89
89
  | 1 个入口技能 | 告诉模型什么时候该用引擎、哪些写入口是唯一的 |
90
90
  | 13 条门控 | 任务书、阶段、字数、格式、摘要、场景类型、大纲对齐、评分、问题清零、阅读证明、一致性、轮次、归档原子性 |
91
91
  | 15 维评分 | 满分 150,默认过关线 120;分数必须逐项可核对 |
@@ -93,7 +93,7 @@
93
93
  | 规则包外置 | 词表、格式、标点、语义、跨章阈值都在 `rulepacks/default.yaml`,代码只执行 |
94
94
  | 六种导出 | Markdown、纯文本、EPUB 3、大纲、世界观、书级提案;正文导出附 `cover.svg` 与 `blurb.txt` |
95
95
  | 整书规划层 | 书级蓝图、卷结构、章卡(严格模式)、世界观设定集;支持 Markdown/YAML/JSON 导入 |
96
- | 可观测性 | `novel_status` 书级摘要、`novel_report`、`novel_doctor`、`novel_search`、panel v2、journal 活动 |
96
+ | 可观测性 | `novel_status` 书级摘要、`novel_report`、`novel_doctor`、`novel_search`、panel v3、journal 活动 |
97
97
  | 多项目注册表 | `stateDir/projects.yaml` 记录已知项目与当前项目;不影响项目目录 |
98
98
  | 运行期依赖极少 | 只有 `yaml` 一个运行时依赖;不联网、不调用外部 API、不读环境变量里的密钥 |
99
99
 
@@ -172,7 +172,7 @@ P7/P8/P9 在 `novel_archive` 一次事务里完成。单章归档后状态是 `d
172
172
  | 宿主形态 | Web(面板 + 预设)、Desktop(面板 + 预设,插件由桌面壳管理)、headless(只有工具/命令,无面板/预设) |
173
173
  | 磁盘 | 项目根目录可写;默认 `stateDir` 可写 |
174
174
  | 网络 | 安装时需要访问 npm / Git 托管;运行插件本身不联网 |
175
- | 语言 | 0.2.0 仅中文 |
175
+ | 语言 | 0.2.x 仅中文 |
176
176
 
177
177
  检查命令:
178
178
 
@@ -276,7 +276,7 @@ dsh plugin --profile web add dsh-novel-write-engine
276
276
  固定版本:
277
277
 
278
278
  ```bash
279
- dsh plugin --profile web add dsh-novel-write-engine@0.2.0
279
+ dsh plugin --profile web add dsh-novel-write-engine@0.2.1
280
280
  ```
281
281
 
282
282
  `dsh` 不在 PATH 时,用 `npx @deepseek-ai/dsh` 代替命令开头的 `dsh`。
@@ -331,13 +331,13 @@ corepack pnpm pack
331
331
  会得到类似:
332
332
 
333
333
  ```text
334
- dsh-novel-write-engine-0.2.0.tgz
334
+ dsh-novel-write-engine-0.2.1.tgz
335
335
  ```
336
336
 
337
337
  把这个 tarball 复制到目标机器,放在一个**稳定、不会随手删除**的路径,然后:
338
338
 
339
339
  ```bash
340
- dsh plugin --profile web add /absolute/path/to/dsh-novel-write-engine-0.2.0.tgz
340
+ dsh plugin --profile web add /absolute/path/to/dsh-novel-write-engine-0.2.1.tgz
341
341
  ```
342
342
 
343
343
  tarball 安装不会运行源码构建,也不会要求 `allowBuilds`;包内已经包含 `lib/`、`rulepacks/`、`briefs/`、`skills/`、`presets/` 和两层 patch。它是最接近「发布产物」的安装方式。
@@ -352,9 +352,9 @@ tarball 安装不会运行源码构建,也不会要求 `allowBuilds`;包内
352
352
  2. 点 **添加插件**(或旁边的箭头 → **安装第三方插件**)。
353
353
  3. 在包名字段填入以下任一形式:
354
354
  - npm 包名:`dsh-novel-write-engine`
355
- - 精确版本:`dsh-novel-write-engine@0.2.0`
355
+ - 精确版本:`dsh-novel-write-engine@0.2.1`
356
356
  - 源码目录:`/absolute/path/to/dsh-novel-write-engine`
357
- - tarball:`/absolute/path/to/dsh-novel-write-engine-0.2.0.tgz`
357
+ - tarball:`/absolute/path/to/dsh-novel-write-engine-0.2.1.tgz`
358
358
  4. npm 包名从 npm 官方源安装;本地目录和 tarball 直接使用宿主路径。
359
359
  5. 点 **安装**,等待完成。
360
360
  6. 如果出现 **立即启用**,点它;否则安装后到「已安装插件」里把 **dsh-novel-write-engine** 打开。
@@ -404,7 +404,7 @@ dsh --profile web --dump-config | grep -A8 '# == dsh-novel-write-engine'
404
404
  列出你当前可用的 novel_* 工具。
405
405
  ```
406
406
 
407
- 应能看到 27 个(`toolsMode=core` 时只有 10 个,见[第八节](#八使用方式))。
407
+ 应能看到 28 个(`toolsMode=core` 时只有 10 个,见[第八节](#八使用方式))。
408
408
 
409
409
  ---
410
410
 
@@ -414,7 +414,7 @@ dsh --profile web --dump-config | grep -A8 '# == dsh-novel-write-engine'
414
414
 
415
415
  左侧栏 → 新建会话 → 预设选 **网文工坊**。
416
416
 
417
- 这个预设的工具是「小说专用」的:有 27 个 `novel_*` 工具、任务书、网页、待办、子智能体等,但**不挂载 bash / 文件写入工具**。正文只能经引擎写。
417
+ 这个预设的工具是「小说专用」的:有 28 个 `novel_*` 工具、任务书、网页、待办、子智能体等,但**不挂载 bash / 文件写入工具**。正文只能经引擎写。
418
418
 
419
419
  ### 2. 直接说人话
420
420
 
@@ -426,7 +426,7 @@ dsh --profile web --dump-config | grep -A8 '# == dsh-novel-write-engine'
426
426
  先做全书蓝图、第一卷大纲和世界观设定,再写第一章的世界观约束和章节计划。
427
427
  ```
428
428
 
429
- 模型会调用 `novel_init` 建项目,然后按 `novel_guide` 的提示做书级规划;也可以跳过蓝图直接写第一章,严格大纲是可选开关。0.2.0 推荐顺序:
429
+ 模型会调用 `novel_init` 建项目,然后按 `novel_guide` 的提示做书级规划;也可以跳过蓝图直接写第一章,严格大纲是可选开关。0.2.1 推荐顺序:
430
430
 
431
431
  ```text
432
432
  novel_init
@@ -512,15 +512,15 @@ novel_init
512
512
 
513
513
  | 形态 | 怎么进 | 工具 | 写文件能力 | 面板/预设 |
514
514
  | -------------------- | ------------------------------ | --------------------- | ----------------------------------- | ------------------ |
515
- | 内置预设「网文工坊」 | 新会话选预设 | 27 个全开 | 不挂 bash/fs 写入工具,正文只经引擎 | 有 |
516
- | 普通会话 | 任意标准会话 | 27 个工具在根层注册 | 可能还有 bash/fs;引擎保证会被绕过 | 有面板,无专用预设 |
517
- | headless / 自动化 | `dsh --profile headless "..."` | 27 个(或 core 10 个) | 取决于该 profile | 无面板、无预设 |
515
+ | 内置预设「网文工坊」 | 新会话选预设 | 28 个全开 | 不挂 bash/fs 写入工具,正文只经引擎 | 有 |
516
+ | 普通会话 | 任意标准会话 | 28 个工具在根层注册 | 可能还有 bash/fs;引擎保证会被绕过 | 有面板,无专用预设 |
517
+ | headless / 自动化 | `dsh --profile headless "..."` | 28 个(或 core 10 个) | 取决于该 profile | 无面板、无预设 |
518
518
 
519
519
  **要用「引擎保证」就用「网文工坊」预设。** 普通会话里,模型仍然可以用文件工具直接改 `state/` 或正文;那时你得到的是「引擎工具 + 人工操作」的混合状态,事务、门控、审计都不再完整。
520
520
 
521
521
  预设是会话类型,**会话开始后不能切换**。要换形态就新开一个会话。
522
522
 
523
- ### 8.2 27 个工具
523
+ ### 8.2 28 个工具
524
524
 
525
525
  | 工具 | 阶段 | 做什么 | 写盘 |
526
526
  | -------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- |
@@ -531,16 +531,16 @@ novel_init
531
531
  | `novel_plan_chapter` | P2 | 提交章节计划;标题长度读 rulepack、5–8 个节拍、opening/ending 非空;可选 `cast` 1–8 人 | 是 |
532
532
  | `novel_write_draft` | P3 | 提交初稿;格式/字数/摘要不过整体拒绝 | 是 |
533
533
  | `novel_review` | P4 | 十五维评分、问题清单、三段阅读证明;issue 维度/段落和 blocker waived 会被拒绝 | 是 |
534
- | `novel_revise` | P5/P6 | 提交修订;blocker 必须有正文证据,不能降级或 waived;issue 只能改 status/evidence | 是 |
535
- | `novel_check` | P6 | 机检 + 五个语义一致性项;`scope` 可只跑 `content`/`integrity`,但单 scope 不推进归档 | 是 |
534
+ | `novel_revise` | P5/P6 | 提交修订;支持整章 `content` 或 `patch:{expected_hash,edits}`;blocker 必须有正文证据,不能降级或 waived | 是 |
535
+ | `novel_check` | P6 | 机检 + 五个语义一致性项;`advisories` 与 blocker 分开,可用 `acknowledge` 记录知悉;单 scope 不推进归档 | 是 |
536
536
  | `novel_archive` | P7–P9 | 事务归档;必须带 `state_delta`;旧 review 无 digest 时拒绝 | 事务 |
537
537
  | `novel_word_count` | 任意 | 按引擎唯一口径数字数 | 否 |
538
- | `novel_state` | 任意 | 读账本、计划、评审、检查、handoff、outline/worldbook;`review`/`check` 可按历史章读取;chapters 默认紧凑,支持 `include_summary`/`chapter_range`;损坏 ledger 返回 raw | 否 |
538
+ | `novel_state` | 任意 | 读账本、计划、评审、检查、handoff、outline/worldbook、history/backups/briefs;chapters 默认紧凑,支持 `include_summary`/`chapter_range`;损坏 ledger 返回 raw | 否 |
539
539
  | `novel_amend` | 任意 | 带原因修订 `novel.yaml`、八个 ledger 或 outline/worldbook,带不变量与单目标 repair | 是,入 journal |
540
540
  | `novel_rollback` | 任意 | 只退回当前阶段或更早,保留正文供参考 | 是 |
541
541
  | `novel_diff` | 任意 | 与上次归档备份或两段文本比较 | 否 |
542
542
  | `novel_export` | 任意 | 导出 `md` / `txt` / `epub` / `outline` / `worldbook` / `proposal` | 导出目录 |
543
- | `novel_audit` | 任意 | 读追加式 journal 审计记录 | 否 |
543
+ | `novel_audit` | 任意 | 读追加式 journal 审计记录,含被拒 attempt;支持 chapter/op/status/since/until/cursor | 否 |
544
544
  | `novel_pipeline` | 任意 | 「下一步该做什么」的建议 | 否 |
545
545
  | `novel_blueprint` | 任意 | 写书级蓝图:梗概、主题、基调、视角、读者、卖点、结局、卷结构 | 是 |
546
546
  | `novel_outline` | 任意 | 写卷/章卡;严格模式、归档卡保护、归档章自动回填 | 是 |
@@ -551,6 +551,7 @@ novel_init
551
551
  | `novel_report` | 任意 | 全书报告:进度、大纲、世界观、质量、节奏、角色、伏笔、时间线、关系、活动 | 否 |
552
552
  | `novel_guide` | 任意 | 上手引导:当前状态、已完成步骤、下一步工具、命令与提示;无项目也能用 | 否 |
553
553
  | `novel_reopen` | 已归档章 | 从归档备份把紧邻的上一章重开到 P3;必须带非空 `reason`,活动章未开始、备份存在时才允许 | 是 |
554
+ | `novel_restore` | 活动章 | 从带 manifest 的 txid 备份恢复正文/计划/评审/校验;已归档章拒绝,先 `novel_reopen` | 是 |
554
555
 
555
556
  工具失败时统一返回:
556
557
 
@@ -598,7 +599,7 @@ novel_init, novel_status, novel_guide, novel_brief, novel_outline,
598
599
  novel_write_draft, novel_review, novel_revise, novel_check, novel_archive
599
600
  ```
600
601
 
601
- 其余 17 个非 core 工具不消失,改走命令(`novel_outline` 与 `novel_guide` 同时在 core 中,也可通过 admin 调用):
602
+ 其余 18 个非 core 工具不消失,改走命令;`/novel admin` 可显式调用非 core 操作(core 里只有 `novel_outline` / `novel_guide` 也在 admin 表中):
602
603
 
603
604
  ```text
604
605
  /novel admin novel_plan_world {...}
@@ -612,14 +613,13 @@ novel_write_draft, novel_review, novel_revise, novel_check, novel_archive
612
613
  /novel admin novel_audit {"limit":20}
613
614
  /novel admin novel_pipeline
614
615
  /novel admin novel_blueprint {...}
615
- /novel admin novel_outline {...}
616
616
  /novel admin novel_outline_import {...}
617
617
  /novel admin novel_worldbook {...}
618
618
  /novel admin novel_search {"query":"..."}
619
619
  /novel admin novel_doctor
620
620
  /novel admin novel_report
621
- /novel admin novel_guide
622
621
  /novel admin novel_reopen {"chapter":2,"reason":"..."}
622
+ /novel admin novel_restore {"txid":"..."}
623
623
  ```
624
624
 
625
625
  多项目注册表与设定包导入**只有命令入口**:
@@ -735,7 +735,7 @@ novel_reopen {chapter:3, reason:"结尾需要重写"}
735
735
  | ----------------- | -------------------------------- | ------------------------------------------------------------------------- |
736
736
  | `projectRoot` | `./novel` | 默认项目根;相对路径相对于 DSH 进程工作目录。工具可用 `project_root` 覆盖 |
737
737
  | `minScore` | `120` | PG-07 过关线,整数 0..150;唯一 owner |
738
- | `toolsMode` | `all` | `all` 注册 27 个工具;`core` 只注册 10 个,其余走 `/novel admin` |
738
+ | `toolsMode` | `all` | `all` 注册 28 个工具;`core` 只注册 10 个,其余走 `/novel admin` |
739
739
  | `briefsOverrides` | 空 | 自定义任务书目录;某阶段文件缺失时回退包内 `briefs/<阶段>.md` |
740
740
  | `backupRetention` | `5` | 保留的归档备份目录数;只清理引擎自己创建的 `backups/<txid>` |
741
741
  | `stateDir` | 空 → `~/.dsh/novel-write-engine` | 多项目注册表位置;**不要放进项目目录** |
@@ -811,7 +811,7 @@ P8.md
811
811
 
812
812
  ### 9.4 规则包不能运行期切换
813
813
 
814
- `rulepacks/default.yaml` 是随包加载的:词表、格式、标点、语义、跨章阈值、平台默认值都在里面。0.2.0 **不接受**运行期外部规则包路径。
814
+ `rulepacks/default.yaml` 是随包加载的:词表、格式、标点、语义、跨章阈值、平台默认值都在里面。0.2.x **不接受**运行期外部规则包路径。
815
815
 
816
816
  要改规则,只有两条路:
817
817
 
@@ -880,7 +880,7 @@ corepack pnpm test
880
880
 
881
881
  - 正文可以读,但不建议手改;正文一改,评审分数、check、content hash 都会失效。
882
882
  - `state/` 不要手改;要改就用 `novel_amend`,它要求 `reason` 并写入 journal。
883
- - 要回退阶段用 `novel_rollback`;它只允许退回当前阶段或更早,会保留正文为参考,清空任务书签收与轮次。
883
+ - 要回退阶段用 `novel_rollback`;它只允许退回当前阶段或更早,会保留正文为参考,清空轮次;任务书收据留在 state/briefs.yaml,digest 仍匹配时可复用。
884
884
  - plan/review/check 损坏时,status 的 `state_errors` 会指出文件;不要手改。当前阶段晚于重建点时先 `novel_rollback`;已经处在 P2/P4/P6 时直接重新取 brief,再用对应阶段工具重建。八个 ledger、`novel.yaml` 或 outline/worldbook 损坏时,`novel_state` 返回 raw,按 `next` 调 `novel_amend` 修复;当前所有不可读状态文档的 journal hash 会被豁免,其他可读文件的外部改动仍 fail-closed。
885
885
  - 不要在引擎运行时删 `.engine.lock`;锁由引擎回收,手删可能让两个写者同时进入。
886
886
 
@@ -895,7 +895,23 @@ corepack pnpm test
895
895
 
896
896
  ## 十一、升级
897
897
 
898
- ### 11.0 从 0.1.3 升级到 0.2.0
898
+ ### 11.0 从 0.2.0 升级到 0.2.1
899
+
900
+ 0.2.1 不迁移项目数据,但改变几处契约:
901
+
902
+ 1. **面板 payload v3**:旧标签页会显示刷新提示;刷新即可。面板根容器现在自己是滚动区,长内容不再被裁掉。
903
+ 2. **P2 计划不可变**:`plans/chNNN.chapter.yaml` 不再写 `summary`;旧项目仍回退读它。新摘要/标题/场景/叙述者进入 `state/pipeline.yaml` 的 `draft`,`novel_write_draft`/`novel_revise` 后以它为准。
904
+ 3. **评分与证据历史**:评审/校验/正文/废弃稿按 revision 写入 `history/`;`novel_report` 的维度样本会变多,`ChapterRecord` 保存 lifetime rounds(含 rollbacks)/final_check/archived_at,这是历史证据而不是统计错误。amend 不能改写这些字段。
905
+ 4. **审计更完整**:通过参数形状校验、进入项目锁后被 gate/delta/state 拒绝的调用会出现在 `novel_audit`,状态为 `aborted`、`attempt:true` 并带 code;事务失败/崩溃恢复的 aborted 行也带 code;锁前纯参数错误不写 journal。
906
+ 5. **检查文档分层**:advisory 进入 `machine.advisories`,不再混入 `violations`;用 `acknowledge` 记录知悉,blocker 不能 acknowledge。数字矛盾固定 advisory,数字事实只匹配相邻的“别名+数字+单位”。
907
+ 6. **新工具 `novel_restore`**:从 `novel_state {kind:"backups"}` 列出的 txid 恢复活动章的正文/计划/世界观计划/评审/校验;P2/P4/P6 在第二次提交起会留下 pre-image。恢复本身再建备份且不 replay;已归档章先 `novel_reopen`。
908
+ 7. **修订可 patch**:`novel_revise` 可传 `patch:{expected_hash,edits}` 代替整章;hash 不匹配、锚点不唯一或编辑重叠都会拒绝。
909
+ 8. **字数有分解**:`total_word_count` 仍含标点;新字段 `punctuation_chars` 与 PG-03 分解让口径可见。
910
+ 9. **大纲覆盖率改口径**:规划覆盖 = 章卡数 / 目标章数,归档覆盖 = 归档卡数 / 目标章数;`card_coverage` 是逐章“有 archived 卡”的比例;缺卡数单独输出。
911
+ 10. **worldbook facts**:新增声明式数字事实,进入计数、brief 上下文、搜索与 P6 检查;`facts` 只做作者声明的 canon 对账,不等同外部史实库。
912
+ 11. **伏笔 anchor**:可读 `title` 与逐字 `anchor` 分离,anchor 至少 8 个字符;单字子串不再能绕过校验。
913
+
914
+ ### 11.0.1 从 0.1.3 升级到 0.2.0
899
915
 
900
916
  0.2.0 新增 `outline/outline.yaml` 与 `outline/worldbook.yaml`,新增 9 个工具与 PG-13;旧项目缺少两份规划文档时按空规划读取,不会报错。新项目默认写入两份空文档;严格大纲默认关闭,`novel_outline` 首次提交才开启。P6 语义项从 4 条变 5 条(新增 `L2-POV`),旧的 4 条 check 需要重跑。面板 payload v1 → v2,旧标签页显示刷新。所有升级后第一次取 brief 会因上下文新增 `book_plan`/`outline_card`/`worldbook` 而失效,重新 `novel_brief` 即可。
901
917
 
@@ -909,7 +925,7 @@ corepack pnpm test
909
925
  6. **handoff 派生**:归档不再每章重算 `ceil(chapter/20)`;volume/volume_name 继承上一章,act/act_progress 从上一章推进,`handoff.act`/`act_progress` 可覆盖。
910
926
  7. **panel 上限 30 行**:长书面板只送最近 30 章,统计仍用全书总数。
911
927
 
912
- ### 11.0.1 从 0.1.1 升级到 0.1.2
928
+ ### 11.0.2 从 0.1.1 升级到 0.1.2
913
929
 
914
930
  0.1.2 不 bump schema,也不改工具名/参数;但它会主动让三类旧状态暴露出来:
915
931
 
@@ -935,7 +951,7 @@ dsh plugin --profile web add dsh-novel-write-engine@latest
935
951
  也可以固定版本:
936
952
 
937
953
  ```bash
938
- dsh plugin --profile web add dsh-novel-write-engine@0.2.0
954
+ dsh plugin --profile web add dsh-novel-write-engine@0.2.1
939
955
  ```
940
956
 
941
957
  升级后核对:
@@ -977,7 +993,7 @@ corepack pnpm pack
977
993
 
978
994
  ```bash
979
995
  dsh plugin --profile web remove dsh-novel-write-engine
980
- dsh plugin --profile web add /absolute/path/to/dsh-novel-write-engine-0.2.0.tgz
996
+ dsh plugin --profile web add /absolute/path/to/dsh-novel-write-engine-0.2.1.tgz
981
997
  ```
982
998
 
983
999
  ### 11.5 在 Web / Desktop 里升级
@@ -1158,9 +1174,9 @@ DSH bundle 的 Host 半边在 DSH 进程内执行,不受工作区沙箱限制
1158
1174
  ## 十五、已知限制
1159
1175
 
1160
1176
  - **书级大纲是 0.2.0 的新数据源**:`outline/outline.yaml` 与 `outline/worldbook.yaml` 由引擎读写;有章卡时 P2/P3 受 PG-13 约束,严格模式还要求章卡存在。大纲是规划层,不替代 `state/` 台账。
1161
- - **0.2.0 只有中文**:面板、命令、错误、任务书都是中文;没有 `locale/`,不跟随系统语言。
1177
+ - **0.2.x 只有中文**:面板、命令、错误、任务书都是中文;没有 `locale/`,不跟随系统语言。
1162
1178
  - **通过 npm 分发**:安装与升级统一使用 `dsh plugin add`;源码目录和 tarball 用于开发或离线环境。
1163
- - **面板与预设只在 Web/Desktop**:headless / acp / sdk 没有 `agentPresets` 服务,也没有浏览器面板;27 个工具仍可用。
1179
+ - **面板与预设只在 Web/Desktop**:headless / acp / sdk 没有 `agentPresets` 服务,也没有浏览器面板;28 个工具仍可用。
1164
1180
  - **规则包不可运行期替换**:要改词表/阈值,必须改源码、重新构建、重新安装。
1165
1181
  - **普通会话可能绕过引擎**:只有「网文工坊」预设不挂 bash/fs 写工具;在普通会话里手动改文件,事务与门控保证不再完整。
1166
1182
  - **预设会话开始后不能切换**。
@@ -1205,19 +1221,19 @@ DSH_BUNDLE_ROOT=/path/to/deepseek-harness/packages/bundle/web-app corepack pnpm
1205
1221
 
1206
1222
  `check:preset` 不伪造 bundle root;没有宿主 checkout 时它不会假装通过。
1207
1223
 
1208
- `check:host` 使用临时 `DSH_HOME` 与临时 profile:`build → npm pack → dsh plugin add → --dump-config → 启动 Web → 面板 401/200 → 卸载 → 确认层消失`,并断言宿主声明全部 27 个工具。它需要 PATH 里有 `dsh`(或设置 `DSH_BIN`),不会碰你的真实 profile;没有 DSH 时以退出码 2 明确失败,不伪造通过。
1224
+ `check:host` 使用临时 `DSH_HOME` 与临时 profile:`build → npm pack → dsh plugin add → --dump-config → 启动 Web → 面板 401/200 → 卸载 → 确认层消失`,并断言宿主声明全部 28 个工具。它需要 PATH 里有 `dsh`(或设置 `DSH_BIN`),不会碰你的真实 profile;没有 DSH 时以退出码 2 明确失败,不伪造通过。
1209
1225
 
1210
1226
  ### 16.3 发布前手动验证
1211
1227
 
1212
1228
  1. `corepack pnpm build && corepack pnpm pack`;
1213
1229
  2. 在临时 DSH home 里 `dsh plugin --profile web add <tarball>`;
1214
1230
  3. `--dump-config` 确认插件层;
1215
- 4. 启动 Web,确认面板、预设、27 个工具、`/novel`;
1231
+ 4. 启动 Web,确认面板、预设、28 个工具、`/novel`;
1216
1232
  5. 跑一遍 `novel_init → P1 → … → archive → export`;
1217
1233
  6. `dsh plugin remove`,确认层与面板消失;
1218
1234
  7. 确认项目数据未被卸载动作删除。
1219
1235
 
1220
- `pnpm check:host` 覆盖第 1–4 与第 6 步的自动断言(含 27 个工具的宿主声明与面板结构);`/novel` 命令的注册与路由由 `tests/adapters` 覆盖;第 5 步仍是人工全流程(需要可用模型)。
1236
+ `pnpm check:host` 覆盖第 1–4 与第 6 步的自动断言(含 28 个工具的宿主声明与面板结构);`/novel` 命令的注册与路由由 `tests/adapters` 覆盖;第 5 步仍是人工全流程(需要可用模型)。
1221
1237
 
1222
1238
  ### 16.4 项目结构
1223
1239
 
@@ -1227,7 +1243,7 @@ src/
1227
1243
  ├── rules/ # 规则编译器、注册表、检测流水线
1228
1244
  ├── store/ # FsStore、事务、锁、journal、迁移、注册表
1229
1245
  ├── engine/ # EngineService、门控、任务书、投影
1230
- ├── tools/ # 27 个工具适配器
1246
+ ├── tools/ # 28 个工具适配器
1231
1247
  ├── commands/ # 5 条命令 + admin
1232
1248
  ├── client/ # 侧边栏面板
1233
1249
  ├── remote/ # 只读 panel 路由
@@ -1241,13 +1257,14 @@ tests/ # 单测、集成、falsify、e2e
1241
1257
 
1242
1258
  ### 16.5 当前验证基线
1243
1259
 
1244
- 0.2.0 在 Node 24.19 / DSH 0.2.1-rc.3 上完成过:
1260
+ 0.2.1 在 Node 24.19 / DSH 0.2.1-rc.3 上完成过:
1245
1261
 
1246
- - `corepack pnpm test` 全绿(当前树 625 项,71 个测试文件);
1247
- - 最近一次覆盖率语句 93.94% / 分支 89.16% / 函数 98.02% / 行 93.94%(V8 分支/行计数会有 ±0.01 波动,门禁按单次真实结果判定),`test:coverage` 与逐文件 domain/book/insight 门禁全过;
1262
+ - `corepack pnpm test` 全绿(当前树 667 项,72 个测试文件);
1263
+ - 最近一次覆盖率语句 93.86% / 分支 89.04% / 函数 97.81% / 行 93.86%(V8 分支/行计数会有 ±0.01 波动,门禁按单次真实结果判定),`test:coverage` 与逐文件 domain/book/insight 门禁全过;
1248
1264
  - `typecheck`、`lint`、`check:hygiene`、`test:isolated`、`build`、`validate:rulepack`、`check:pack`、`check:artifacts`、`check:install` 全绿;
1249
1265
  - `check:host` 在临时 DSH profile 中实测:插件层出现、预设可见、面板未认证 401、认证后 200、headless 真实触发 `novel_init` 工具并落盘、`remove` 后层消失;`check:preset` 从真实 bundle 解析 13 个包;
1250
1266
  - `check:crash` 在真实子进程 SIGKILL 后恢复 pending journal:目标项目仍可读,无遗留 pending 事务。
1267
+ - `agnes-2.5-flash` / `agnes-3.0-flash` 在临时项目完成 P1→P8、归档、六种导出、`novel_reopen` 与重新归档;详见 `docs/0.2.1-real-model-evidence.md`。
1251
1268
 
1252
1269
  具体数字会随代码变化;发布前请以重新跑出的结果为准,不要把上面的历史数字当保证。
1253
1270
 
@@ -1260,10 +1277,11 @@ tests/ # 单测、集成、falsify、e2e
1260
1277
  | [DESIGN.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/DESIGN.md) | 完整设计书:架构、数据模型、规则、门控、事务、面板、预设 |
1261
1278
  | [PLAN.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/PLAN.md) | 0.1.0 实施计划(历史) |
1262
1279
  | [docs/0.2.0-plan.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/docs/0.2.0-plan.md) | 0.2.0 整书生命周期:蓝图/大纲/世界观/章卡/观测 |
1280
+ | [docs/0.2.1-plan.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/docs/0.2.1-plan.md) | 0.2.1 可信账本、历史、备份、计划分权与质量门禁 |
1263
1281
  | [docs/0.2.0-real-model-evidence.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/docs/0.2.0-real-model-evidence.md) | 真实模型全链路验证回执 |
1264
1282
  | [docs/0.1.2-plan.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/docs/0.1.2-plan.md) | 0.1.2 长书可运行性与证据闭环方案(历史) |
1265
1283
  | [docs/0.1.1-plan.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/docs/0.1.1-plan.md) | 0.1.1 契约收口与证据加固方案(历史) |
1266
- | [docs/tools.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/docs/tools.md) | 27 个工具与命令面速查 |
1284
+ | [docs/tools.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/docs/tools.md) | 28 个工具与命令面速查 |
1267
1285
  | [docs/states.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/docs/states.md) | 状态机、迁移表、轮次与回退 |
1268
1286
  | [docs/rulepack.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/docs/rulepack.md) | 规则包结构、编译器断言、改词表流程 |
1269
1287
  | [CHANGELOG.md](CHANGELOG.md) | 版本变更 |
package/briefs/P6.md CHANGED
@@ -6,6 +6,7 @@
6
6
 
7
7
  - 格式与字数:引号、分号、省略号、长段、标题。
8
8
  - 跨章对账:重复段落、伏笔休眠、资源余额、时间倒退、关系未声明、锁定描写漂移。
9
+ - 质量提示:对话比例、数字/单位矛盾、数字事实不一致。`advisory` 不阻塞归档,可用 `acknowledge` 记录知悉;`blocker` 不会被 acknowledge 放行。
9
10
 
10
11
  ## 语义项(你提供)
11
12
 
package/briefs/P8.md CHANGED
@@ -28,6 +28,8 @@
28
28
  - `timeline` 的键是 `summary.events` 的下标(字符串)。
29
29
  - `relationships` 的键是 `"甲|乙"`;每项至少 `{ "action": "created", "type": "关系类型", "description": "..." }`,缺 `type` 会被拒绝。
30
30
  - `characters` 的键是人物名;`world` 的键是新地点/能力/物品名;`foreshadows` 的键是 summary 里的 planted key 或已分配的 `FS-xxx`。
31
+ - **伏笔的 title 是可读标签,anchor 才是校验证据**:`title` 可以写「船费债」这样的短语;要归档,请把正文原句放进 `anchor`(summary 的 planted 项或 delta 项都行,至少 8 个字符)。没有 `anchor` 时,`title` 必须逐字出现在正文里,否则 `ENGINE_DELTA_UNVERIFIED`。示例:`{"action":"created","title":"船费债","anchor":"三百蚊的船费先欠着","description":"欠船费三百蚊"}`。
32
+ - 归档失败的报错会指出字段、值和缺失的锚点;不要把 title 改成碎片来“凑命中”,补 `anchor` 即可。
31
33
  - 第一人称的叙述者若在 `narrator` 里声明为 `陈默(第一人称「我」)`,正文用「我」也视为出现,但角色键仍写 `陈默`。
32
34
  - 每个新实体至少写 `action: "created"`;已存在且没变化写 `action: "no_change", reason: "..."`。
33
35
  - `world` 新实体还要写 `kind: "location"|"ability"|"artifact"` 和 `description`。
package/lib/client.js CHANGED
@@ -42,7 +42,7 @@ window.__ModuleLoader__.load({
42
42
  novel_word_count: "按引擎字数口径核对正文长度。"
43
43
  };
44
44
  // src/client/viewmodel.ts
45
- const PANEL_SCHEMA_VERSION = 2;
45
+ const PANEL_SCHEMA_VERSION = 3;
46
46
  const PHASE_LABELS = {
47
47
  P1: "世界观",
48
48
  P2: "章节计划",
@@ -82,6 +82,7 @@ window.__ModuleLoader__.load({
82
82
  novel_doctor: "项目体检",
83
83
  novel_report: "全书报告",
84
84
  novel_reopen: "重开章节",
85
+ novel_restore: "恢复备份",
85
86
  novel_brief: "领取任务书",
86
87
  novel_plan_world: "补全世界观",
87
88
  novel_plan_chapter: "规划本章",
@@ -125,6 +126,10 @@ window.__ModuleLoader__.load({
125
126
  outlineTotal: 0,
126
127
  archivedCards: 0,
127
128
  coverage: 0,
129
+ missingCards: 0,
130
+ plannedCoverage: null,
131
+ archivedCoverage: null,
132
+ cardCoverage: 0,
128
133
  volumes: [],
129
134
  worldbook: {
130
135
  characters: 0,
@@ -246,6 +251,10 @@ window.__ModuleLoader__.load({
246
251
  outlineTotal: summary.book?.outline.total ?? 0,
247
252
  archivedCards: summary.book?.outline.archived ?? 0,
248
253
  coverage: summary.book?.outline.coverage ?? 0,
254
+ missingCards: summary.book?.outline.missing_cards ?? 0,
255
+ plannedCoverage: summary.book?.outline.planned_coverage ?? null,
256
+ archivedCoverage: summary.book?.outline.archived_coverage ?? null,
257
+ cardCoverage: summary.book?.outline.card_coverage ?? 0,
249
258
  volumes: (summary.book?.outline.volumes ?? []).map((volume) => ({
250
259
  title: volume.title,
251
260
  range: `第${volume.start}-${volume.end}章`,
@@ -258,7 +267,8 @@ window.__ModuleLoader__.load({
258
267
  factions: 0,
259
268
  rules: 0,
260
269
  glossary: 0,
261
- backstory: 0
270
+ backstory: 0,
271
+ facts: 0
262
272
  }
263
273
  },
264
274
  quality: {
@@ -328,7 +338,12 @@ window.__ModuleLoader__.load({
328
338
  box-sizing: border-box;
329
339
  width: 100%;
330
340
  max-width: 1120px;
331
- min-height: 100%;
341
+ height: 100%;
342
+ min-height: 0;
343
+ overflow-x: hidden;
344
+ overflow-y: auto;
345
+ overscroll-behavior: contain;
346
+ scrollbar-gutter: stable;
332
347
  margin: 0 auto;
333
348
  padding: clamp(18px, 3.2vw, 38px);
334
349
  color: var(--nwe-ink);
@@ -844,6 +859,14 @@ brief P3 → write_draft → brief P4 → review → 有 blocker 就修订 →
844
859
  brief P6 → check → brief P8 → archive 的顺序推进。
845
860
  正文只通过 write_draft/revise 写;不要直接改 state/、chapters/ 或 outline/。
846
861
  每次提交前先领取当前阶段任务书,失败时按返回的 code 和 blocking 一次改完。`;
862
+ const TUTORIAL_NEXT_PROMPT = `继续写当前项目的下一章。先调用 novel_pipeline(或 novel_status)确认当前阶段、项目根目录和下一步工具,不要猜阶段。
863
+
864
+ 如果下一章还没有章卡:先用 novel_state {kind:"outline"} 取出全部章卡,再在末尾加上下一章的卡,用 novel_outline 整体提交;不要只提交新卡,也不要把已有 planned 卡丢掉。
865
+ 如果 targets.total_chapters 已经写满:先用 novel_amend 把 total_chapters 调到至少包含下一章,不能低于已归档章数。
866
+
867
+ 之后严格按:novel_brief P1 → novel_plan_world → brief P2 → novel_plan_chapter → brief P3 → novel_write_draft → brief P4 → novel_review →(有 blocker)brief P5 → novel_revise → 回 P4 → brief P6 → novel_check → brief P8 → novel_archive 的顺序推进。
868
+
869
+ 归档前把完整的 state_delta 给我确认;正文只通过 write_draft/revise 写,不要直接改 state/、chapters/ 或 outline/,不要跳过 brief。`;
847
870
  const stat = (label, value, unit) => (0, react.createElement)("div", {
848
871
  className: "nwe-stat",
849
872
  key: label
@@ -881,13 +904,14 @@ brief P6 → check → brief P8 → archive 的顺序推进。
881
904
  ["势力", view.book.worldbook.factions],
882
905
  ["规则", view.book.worldbook.rules],
883
906
  ["术语", view.book.worldbook.glossary],
884
- ["前史", view.book.worldbook.backstory]
907
+ ["前史", view.book.worldbook.backstory],
908
+ ["数值事实", view.book.worldbook.facts ?? 0]
885
909
  ];
886
910
  const bars = view.quality.scores.slice(-10);
887
911
  return (0, react.createElement)("section", {
888
912
  className: "nwe-section",
889
913
  "aria-label": "全书规划与质量"
890
- }, (0, react.createElement)("div", { className: "nwe-section-head" }, (0, react.createElement)("h2", { className: "nwe-section-title" }, "全书规划"), (0, react.createElement)("span", { className: "nwe-section-note" }, view.book.present ? `${view.book.strict ? "严格大纲" : "宽松大纲"} · 覆盖率 ${Math.round(view.book.coverage * 100)}%` : "还没有章卡")), (0, react.createElement)("div", { className: "nwe-book-grid" }, (0, react.createElement)("div", { className: "nwe-book-card" }, (0, react.createElement)("div", { className: "nwe-book-head" }, (0, react.createElement)("span", { className: "nwe-book-title" }, view.book.premise === "" ? "全书进度" : view.book.premise), (0, react.createElement)("span", { className: "nwe-book-note" }, `${view.book.planned} 计划 / ${view.book.archivedCards} 归档`)), (0, react.createElement)("div", {
914
+ }, (0, react.createElement)("div", { className: "nwe-section-head" }, (0, react.createElement)("h2", { className: "nwe-section-title" }, "全书规划"), (0, react.createElement)("span", { className: "nwe-section-note" }, view.book.present ? `${view.book.strict ? "严格大纲" : "宽松大纲"} · 规划 ${view.book.plannedCoverage === null ? "—" : `${Math.round(view.book.plannedCoverage * 1e3) / 10}%`} · 归档 ${view.book.archivedCoverage === null ? "—" : `${Math.round(view.book.archivedCoverage * 1e3) / 10}%`}${view.book.missingCards > 0 ? ` · 缺卡 ${view.book.missingCards}` : ""}` : "还没有章卡")), (0, react.createElement)("div", { className: "nwe-book-grid" }, (0, react.createElement)("div", { className: "nwe-book-card" }, (0, react.createElement)("div", { className: "nwe-book-head" }, (0, react.createElement)("span", { className: "nwe-book-title" }, view.book.premise === "" ? "全书进度" : view.book.premise), (0, react.createElement)("span", { className: "nwe-book-note" }, `${view.book.planned} 计划 / ${view.book.archivedCards} 归档`)), (0, react.createElement)("div", {
891
915
  className: "nwe-progress",
892
916
  role: "progressbar",
893
917
  "aria-valuemin": 0,
@@ -987,7 +1011,7 @@ brief P6 → check → brief P8 → archive 的顺序推进。
987
1011
  ref: closeRef,
988
1012
  onClick: onClose,
989
1013
  "aria-label": "关闭使用教程"
990
- }, (0, react.createElement)("span", { "aria-hidden": "true" }, "×"))), (0, react.createElement)("div", { className: "nwe-modal-body" }, (0, react.createElement)("div", { className: "nwe-tutorial-intro" }, (0, react.createElement)("p", null, "网文工坊不是一个“帮你想剧情”的按钮,而是一台有状态的写作引擎:你提供内容,引擎负责阶段、字数、门控、评审、归档和文件。"), (0, react.createElement)("p", null, "每一步都先领取当前阶段的任务书,再提交;提交后看引擎返回的 blocking 与下一步。面板只读,真正的写操作都在对话里的工具调用中。")), (0, react.createElement)("section", { className: "nwe-tutorial-block" }, (0, react.createElement)("h3", null, "开始前:记住三件事"), (0, react.createElement)("div", { className: "nwe-tutorial-cards" }, (0, react.createElement)("div", { className: "nwe-tutorial-card" }, (0, react.createElement)("strong", null, "只用引擎入口写"), (0, react.createElement)("span", null, "正文只走 novel_write_draft / novel_revise;状态只走工具。手改 chapters/、state/、outline/ 会破坏事务与校验。")), (0, react.createElement)("div", { className: "nwe-tutorial-card" }, (0, react.createElement)("strong", null, "每一步先领 brief"), (0, react.createElement)("span", null, "任务书携带当前上下文和 digest;跳过它会撞 PG-01。任务书过期就重新领取,不要复用旧上下文。")), (0, react.createElement)("div", { className: "nwe-tutorial-card" }, (0, react.createElement)("strong", null, "失败先读 code"), (0, react.createElement)("span", null, "参数错改参数,门控没过改正文,系统故障查目录/稍后重试。blocking 会一次列全,一次改完再交。"))), (0, react.createElement)("p", { className: "nwe-tutorial-note" }, (0, react.createElement)("strong", null, "用哪个会话:"), " 想获得完整的引擎保证,请新开一个「网文工坊」预设会话;普通会话里模型可能还挂着文件工具,能绕过引擎直接改稿。")), (0, react.createElement)("section", { className: "nwe-tutorial-block" }, (0, react.createElement)("h3", null, "一本书的完整流程"), (0, react.createElement)("p", null, "前四步是开书准备,后面是每章循环。第一次先按顺序走完一章,熟悉后再批量推进整本。"), (0, react.createElement)("ol", { className: "nwe-steps" }, ...TUTORIAL_STEPS.map((step, index) => tutorialStep(step, index)))), (0, react.createElement)("section", { className: "nwe-tutorial-block" }, (0, react.createElement)("h3", null, "面板怎么看"), (0, react.createElement)("div", { className: "nwe-tutorial-cards" }, (0, react.createElement)("div", { className: "nwe-tutorial-card" }, (0, react.createElement)("strong", null, "顶部统计"), (0, react.createElement)("span", null, "已归档章、总字数、平均分、待处理门控。数字来自引擎,不是正文估算。")), (0, react.createElement)("div", { className: "nwe-tutorial-card" }, (0, react.createElement)("strong", null, "九阶段轨与下一步"), (0, react.createElement)("span", null, "亮起的阶段是当前进度,“下一步”卡会直接给出该调用的工具名。")), (0, react.createElement)("div", { className: "nwe-tutorial-card" }, (0, react.createElement)("strong", null, "章节与门控"), (0, react.createElement)("span", null, "章节卡展开可看字数、评分与状态;门控区按 code/detail/hint 列出所有阻塞。"))), (0, react.createElement)("p", { className: "nwe-tutorial-note" }, (0, react.createElement)("strong", null, "刷新:"), " 面板每 15 秒静默同步一次,也可以点右上角刷新。它是只读投影;不会替你改稿、归档或修状态。")), (0, react.createElement)("section", { className: "nwe-tutorial-block" }, (0, react.createElement)("h3", null, "卡住时先看这里"), (0, react.createElement)("ul", { className: "nwe-faq" }, (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "为什么一直让我先领 brief?"), (0, react.createElement)("p", null, "这是正常门控。任务书决定当前阶段可用的字段与上下文 digest;先按返回值调用 novel_brief,再继续。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "门控失败,改一条还是全改?"), (0, react.createElement)("p", null, "一次看完所有 blocking,再一起改。逐条试会反复烧掉阶段轮次;错误里的 code 会告诉你属于参数、门控还是系统故障。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "字数和评分为什么对不上?"), (0, react.createElement)("p", null, "字数以 engine 返回为准,可用 novel_word_count 核对;review 的 total 必须等于 150 减全部扣分,不能另算一套。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "P4/P6 过不去怎么办?"), (0, react.createElement)("p", null, "P4 有 blocker 就领 P5 任务书修订,再回 P4 重评;P6 失败同样回修订。不要 waive blocker,也不要只跑半个 scope 就归档。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "想改已经归档的章节?"), (0, react.createElement)("p", null, "先 novel_reopen,带非空 reason。它会从归档备份恢复,重开后重新走 P3→P8;不要直接编辑归档卡或正文文件。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "状态文档坏了能手工修吗?"), (0, react.createElement)("p", null, "先用 novel_state 看 raw 和 next;需要修复时用 novel_amend,并在 reason 写清原因。手改 YAML 会丢失事务保护。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "有多个项目,面板看的是哪本?"), (0, react.createElement)("p", null, "面板跟随最近一次工具调用的项目。调用时显式传 project_root;切换活跃项目用 /novel admin novel_registry_activate。")))), (0, react.createElement)("section", { className: "nwe-tutorial-block" }, (0, react.createElement)("h3", null, "可以直接复制给模型的起手话术"), (0, react.createElement)("pre", { className: "nwe-prompt" }, TUTORIAL_PROMPT)), (0, react.createElement)("section", { className: "nwe-tutorial-block" }, (0, react.createElement)("h3", null, "一章完成检查清单"), (0, react.createElement)("ul", { className: "nwe-checklist" }, (0, react.createElement)("li", null, "本章 P1/P2 计划已提交,P3 正文已通过格式与字数门控。"), (0, react.createElement)("li", null, "P4 评审 total 对账无误,没有未修 blocker。"), (0, react.createElement)("li", null, "P6 五个语义项都有不同句子的正文引用,完整 scope 通过。"), (0, react.createElement)("li", null, "P8 任务书当前,novel_archive 带完整 state_delta 成功。"), (0, react.createElement)("li", null, "面板出现已归档章节,门控区没有新的阻塞。")))), (0, react.createElement)("div", { className: "nwe-modal-foot" }, "教程基于 0.2.0 · 具体字段以当前任务书和工具返回为准 · 面板只读")));
1014
+ }, (0, react.createElement)("span", { "aria-hidden": "true" }, "×"))), (0, react.createElement)("div", { className: "nwe-modal-body" }, (0, react.createElement)("div", { className: "nwe-tutorial-intro" }, (0, react.createElement)("p", null, "网文工坊不是一个“帮你想剧情”的按钮,而是一台有状态的写作引擎:你提供内容,引擎负责阶段、字数、门控、评审、归档和文件。"), (0, react.createElement)("p", null, "每一步都先领取当前阶段的任务书,再提交;提交后看引擎返回的 blocking 与下一步。面板只读,真正的写操作都在对话里的工具调用中。")), (0, react.createElement)("section", { className: "nwe-tutorial-block" }, (0, react.createElement)("h3", null, "开始前:记住三件事"), (0, react.createElement)("div", { className: "nwe-tutorial-cards" }, (0, react.createElement)("div", { className: "nwe-tutorial-card" }, (0, react.createElement)("strong", null, "只用引擎入口写"), (0, react.createElement)("span", null, "正文只走 novel_write_draft / novel_revise;状态只走工具。手改 chapters/、state/、outline/ 会破坏事务与校验。")), (0, react.createElement)("div", { className: "nwe-tutorial-card" }, (0, react.createElement)("strong", null, "每一步先领 brief"), (0, react.createElement)("span", null, "任务书携带当前上下文和 digest;跳过它会撞 PG-01。任务书过期就重新领取,不要复用旧上下文。")), (0, react.createElement)("div", { className: "nwe-tutorial-card" }, (0, react.createElement)("strong", null, "失败先读 code"), (0, react.createElement)("span", null, "参数错改参数,门控没过改正文,系统故障查目录/稍后重试。blocking 会一次列全,一次改完再交。"))), (0, react.createElement)("p", { className: "nwe-tutorial-note" }, (0, react.createElement)("strong", null, "用哪个会话:"), " 想获得完整的引擎保证,请新开一个「网文工坊」预设会话;普通会话里模型可能还挂着文件工具,能绕过引擎直接改稿。")), (0, react.createElement)("section", { className: "nwe-tutorial-block" }, (0, react.createElement)("h3", null, "一本书的完整流程"), (0, react.createElement)("p", null, "前四步是开书准备,后面是每章循环。第一次先按顺序走完一章,熟悉后再批量推进整本。"), (0, react.createElement)("ol", { className: "nwe-steps" }, ...TUTORIAL_STEPS.map((step, index) => tutorialStep(step, index)))), (0, react.createElement)("section", { className: "nwe-tutorial-block" }, (0, react.createElement)("h3", null, "面板怎么看"), (0, react.createElement)("div", { className: "nwe-tutorial-cards" }, (0, react.createElement)("div", { className: "nwe-tutorial-card" }, (0, react.createElement)("strong", null, "顶部统计"), (0, react.createElement)("span", null, "已归档章、总字数、平均分、待处理门控。数字来自引擎,不是正文估算。")), (0, react.createElement)("div", { className: "nwe-tutorial-card" }, (0, react.createElement)("strong", null, "九阶段轨与下一步"), (0, react.createElement)("span", null, "亮起的阶段是当前进度,“下一步”卡会直接给出该调用的工具名。")), (0, react.createElement)("div", { className: "nwe-tutorial-card" }, (0, react.createElement)("strong", null, "章节与门控"), (0, react.createElement)("span", null, "章节卡展开可看字数、评分与状态;门控区按 code/detail/hint 列出所有阻塞。"))), (0, react.createElement)("p", { className: "nwe-tutorial-note" }, (0, react.createElement)("strong", null, "刷新:"), " 面板每 15 秒静默同步一次,也可以点右上角刷新。它是只读投影;不会替你改稿、归档或修状态。")), (0, react.createElement)("section", { className: "nwe-tutorial-block" }, (0, react.createElement)("h3", null, "卡住时先看这里"), (0, react.createElement)("ul", { className: "nwe-faq" }, (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "为什么一直让我先领 brief?"), (0, react.createElement)("p", null, "这是正常门控。任务书决定当前阶段可用的字段与上下文 digest;先按返回值调用 novel_brief,再继续。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "门控失败,改一条还是全改?"), (0, react.createElement)("p", null, "一次看完所有 blocking,再一起改。逐条试会反复烧掉阶段轮次;错误里的 code 会告诉你属于参数、门控还是系统故障。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "字数和评分为什么对不上?"), (0, react.createElement)("p", null, "字数以 engine 返回为准,可用 novel_word_count 核对;review 的 total 必须等于 150 减全部扣分,不能另算一套。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "P4/P6 过不去怎么办?"), (0, react.createElement)("p", null, "P4 有 blocker 就领 P5 任务书修订,再回 P4 重评;P6 失败同样回修订。不要 waive blocker,也不要只跑半个 scope 就归档。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "想改已经归档的章节?"), (0, react.createElement)("p", null, "先 novel_reopen,带非空 reason。它会从归档备份恢复,重开后重新走 P3→P8;不要直接编辑归档卡或正文文件。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "状态文档坏了能手工修吗?"), (0, react.createElement)("p", null, "先用 novel_state 看 raw 和 next;需要修复时用 novel_amend,并在 reason 写清原因。手改 YAML 会丢失事务保护。")), (0, react.createElement)("li", null, (0, react.createElement)("strong", null, "有多个项目,面板看的是哪本?"), (0, react.createElement)("p", null, "面板跟随最近一次工具调用的项目。调用时显式传 project_root;切换活跃项目用 /novel admin novel_registry_activate。")))), (0, react.createElement)("section", { className: "nwe-tutorial-block" }, (0, react.createElement)("h3", null, "可以直接复制给模型的起手话术"), (0, react.createElement)("pre", { className: "nwe-prompt" }, TUTORIAL_PROMPT)), (0, react.createElement)("section", { className: "nwe-tutorial-block" }, (0, react.createElement)("h3", null, "续写下一章时,直接复制这段给 Agent"), (0, react.createElement)("p", null, "这段会先确认状态,再处理缺章卡和目标章数已满,最后按 P1→P8 推进;归档前会要求你确认 state_delta。"), (0, react.createElement)("pre", { className: "nwe-prompt" }, TUTORIAL_NEXT_PROMPT)), (0, react.createElement)("section", { className: "nwe-tutorial-block" }, (0, react.createElement)("h3", null, "一章完成检查清单"), (0, react.createElement)("ul", { className: "nwe-checklist" }, (0, react.createElement)("li", null, "本章 P1/P2 计划已提交,P3 正文已通过格式与字数门控。"), (0, react.createElement)("li", null, "P4 评审 total 对账无误,没有未修 blocker。"), (0, react.createElement)("li", null, "P6 五个语义项都有不同句子的正文引用,完整 scope 通过。"), (0, react.createElement)("li", null, "P8 任务书当前,novel_archive 带完整 state_delta 成功。"), (0, react.createElement)("li", null, "面板出现已归档章节,门控区没有新的阻塞。")))), (0, react.createElement)("div", { className: "nwe-modal-foot" }, "教程基于 0.2.1 · 具体字段以当前任务书和工具返回为准 · 面板只读")));
991
1015
  const NovelHelp = () => {
992
1016
  const [open, setOpen] = (0, react.useState)(false);
993
1017
  const triggerRef = (0, react.useRef)(null);