dsh-novel-write-engine 0.2.4 → 0.2.5

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,34 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.5
4
+
5
+ ### Fixed
6
+
7
+ - **语义证据跨句/跨段**:`novel_check` 的引文不再要求落在同一句;支持句末收尾引号、跨段证据,逐条返回 `pass|fail|missing|invalid|not_found|too_short|duplicate|overlap|same_sentence`,并在失败时给出 `nearest`/`conflict_with`。同一段被两条证据复用、或同一句不同子串仍按 `overlap`/`same_sentence` 拒绝;真实 r0076 的四段双句引文全部通过,r0075 的编造 POV 引文只报 `not_found`。
8
+ - **check submission/content 分流**:形状、缺项、引文不存在等提交错误改为 `ENGINE_CHECK_SUBMISSION_INVALID`,不写 check、不增加 blocking、不消耗 `rounds.check`;只有正文/机器门真正的 content failure 才写 check 并 `counted:true`。工具错误行会回传逐条 `items`。
9
+ - **world delta 双真源仲裁**:`summary.new_elements` 只作为意图声明;`state/world.yaml` 的存在性为真源。已有实体写 `created` 归一化为 `updated`,新实体写 `updated`/`no_change` 在 kind/正文可判定时归一化为 `created`;kind/detail 冲突仍拒绝。`archiveChapter` 返回 `delta_warnings` 与 `normalized_world`,warning 不再静默。
10
+ - **pending 只读诊断**:`inspectPending` 分类 `none|complete|untouched|partial`;status/doctor/audit/panel 对 complete/untouched 给 warning、partial 给 `ENGINE_PENDING_PARTIAL`,不再误报 hash mismatch。`novel_init` 会先恢复/拒绝崩溃 init,partial 不被 PROJECT_EXISTS 掩盖。
11
+ - **outline 归档不变量统一**:`novel_blueprint`、`novel_outline_import`(含 dry-run)、`novel_amend(outline)` 与 `novel_outline` 共用 `assertArchivedCardVolumes`/`assertArchivedCardsInVolumes` 和 active 卡保护;即使旧卡缺失或卷重排,归档章也必须由最终卡指向覆盖它的卷。`reindexed`/`would_reindex` 显式回显,omitted 归档卡不再伪造 `archived_at`。
12
+ - **summary-only 修复**:`novel_revise` 可只传 `summary`:正文 hash、章文件和历史不变,不消耗 revise 轮次,保持/推进到 P6;不要求 P4/P6 proof,接下来可直接 check/archive。summary-only 明确拒绝 `issues`/`proof`,避免静默吞掉评审/证明。P8 与工具文档已改为先 summary-only、只有正文需要改才走 P5。
13
+ - **PG-05 前置**:P3/P5 即检查 `summary.new_elements` 名字在正文出现、已在 world ledger 时报“remove/use updated”,并校验 `characters_appeared` 正文/叙述者别名,避免错误留到 P8。
14
+ - **资源余额**:`current_state.resources` 与旧余额深合并,保留兄弟资源;created/updated 都校验有限非负整数,负值/字符串/null/数组拒绝;P8 示例与错误提示改为非负。
15
+
16
+ ### Hardened
17
+
18
+ - brief digest 绑定完整 project/outline/worldbook/plan/worldPlan,而不只是裁剪后的 context。
19
+ - characters/world 重复实体名在 load 与 amend 均拒绝;`strict_explicit` 只允许内部 provenance,公共 outline 写入拒绝该字段。
20
+ - FsStore 将 EACCES/EPERM/EROFS 包装为 `ENGINE_IO_PERMISSION`;rulepack 对每个 section/nested object 递归 knownKeys,包括 `reader_intrusion`/`god_view`、`genres.*` 与 `water_words.scene_thresholds`。
21
+ - worldbook 导出补 `数值事实`;duplicate paragraph 指回原章真实段落号;time regression 指向正文段落;timeline.characters 必须是已声明角色;export 在 artifact 缺失时使 replay 失效并重写。
22
+ - migration 备份 `backups/migrations/` 不参与 retention prune;无硬链接文件系统(FAT/SMB/WSL)用受控 rename fallback 回收 stale lock;面板选择中配置 projectRoot 优先于单行 registry;commands 错误行与 tools 一样保留 blocking/items。
23
+
24
+ ### Migration
25
+
26
+ - check 文档 schema_version 不变;新增字段可选,旧 check 可读。旧项目已消耗的 rounds 不回滚。
27
+ - archive 遇到旧 `summary.new_elements` 与 ledger 冲突时按新规则成功并给 warning;不会回写旧 ledger。
28
+ - summary-only 是新增调用模式,旧 content/patch 修订语义不变。
29
+ - `strict_explicit` 若曾被外部写入,仍可被读取;新的公共写入必须改用 `strict`。
30
+ - 详细验收与真实项目/模型证据见 `docs/0.2.5-acceptance.md`、`docs/0.2.5-real-model-evidence.md`。
31
+
3
32
  ## 0.2.4
4
33
 
5
34
  ### Changed
package/README.md CHANGED
@@ -12,7 +12,9 @@
12
12
 
13
13
  0.2.4 根治 `novel_outline` 的“省略即删除”:`chapters` 按 `num`、`volumes` 按 `title` 做 upsert,省略的卡保持不动;删除必须显式 `remove` / `remove_volumes`;新增 `dry_run`、`added`/`updated`/`removed` 回显、`outline.deprecated.yaml` 与 `novel_restore` 的 outline 恢复路径。这是有意的 BREAKING 语义修正:从“提交集合就是全集”变成“提交的是要改的对象”。
14
14
 
15
- > **当前版本:0.2.4** · 中文界面 · MIT
15
+ 0.2.5 根治“证据/账本/恢复”三条真实链路:语义引文支持跨句/跨段并逐条解释;check 的 submission 错误不再消耗轮次;world delta 以 ledger 存在性仲裁并给 warning;pending 只读诊断与 init 崩溃恢复;归档卷/active 卡在 blueprint/import/amend/outline 全入口一致;新增 summary-only 修订(不改正文、不重跑 P4/P6);resources 深合并非负整数。P1–P3 及 S1–S8 硬化见 [0.2.5 验收](docs/0.2.5-acceptance.md)。
16
+
17
+ > **当前版本:0.2.5** · 中文界面 · MIT
16
18
  >
17
19
  > **宿主范围**:DSH `>=0.2.1-rc.2 <0.3.0-0`;Node `>=24.9.0`。
18
20
 
@@ -282,7 +284,7 @@ dsh plugin --profile web add dsh-novel-write-engine
282
284
  固定版本:
283
285
 
284
286
  ```bash
285
- dsh plugin --profile web add dsh-novel-write-engine@0.2.4
287
+ dsh plugin --profile web add dsh-novel-write-engine@0.2.5
286
288
  ```
287
289
 
288
290
  `dsh` 不在 PATH 时,用 `npx @deepseek-ai/dsh` 代替命令开头的 `dsh`。
@@ -337,13 +339,13 @@ corepack pnpm pack
337
339
  会得到类似:
338
340
 
339
341
  ```text
340
- dsh-novel-write-engine-0.2.4.tgz
342
+ dsh-novel-write-engine-0.2.5.tgz
341
343
  ```
342
344
 
343
345
  把这个 tarball 复制到目标机器,放在一个**稳定、不会随手删除**的路径,然后:
344
346
 
345
347
  ```bash
346
- dsh plugin --profile web add /absolute/path/to/dsh-novel-write-engine-0.2.4.tgz
348
+ dsh plugin --profile web add /absolute/path/to/dsh-novel-write-engine-0.2.5.tgz
347
349
  ```
348
350
 
349
351
  tarball 安装不会运行源码构建,也不会要求 `allowBuilds`;包内已经包含 `lib/`、`rulepacks/`、`briefs/`、`skills/`、`presets/` 和两层 patch。它是最接近「发布产物」的安装方式。
@@ -358,9 +360,9 @@ tarball 安装不会运行源码构建,也不会要求 `allowBuilds`;包内
358
360
  2. 点 **添加插件**(或旁边的箭头 → **安装第三方插件**)。
359
361
  3. 在包名字段填入以下任一形式:
360
362
  - npm 包名:`dsh-novel-write-engine`
361
- - 精确版本:`dsh-novel-write-engine@0.2.4`
363
+ - 精确版本:`dsh-novel-write-engine@0.2.5`
362
364
  - 源码目录:`/absolute/path/to/dsh-novel-write-engine`
363
- - tarball:`/absolute/path/to/dsh-novel-write-engine-0.2.4.tgz`
365
+ - tarball:`/absolute/path/to/dsh-novel-write-engine-0.2.5.tgz`
364
366
  4. npm 包名从 npm 官方源安装;本地目录和 tarball 直接使用宿主路径。
365
367
  5. 点 **安装**,等待完成。
366
368
  6. 如果出现 **立即启用**,点它;否则安装后到「已安装插件」里把 **dsh-novel-write-engine** 打开。
@@ -432,7 +434,7 @@ dsh --profile web --dump-config | grep -A8 '# == dsh-novel-write-engine'
432
434
  先做全书蓝图、第一卷大纲和世界观设定,再写第一章的世界观约束和章节计划。
433
435
  ```
434
436
 
435
- 模型会调用 `novel_init` 建项目,然后按 `novel_guide` 的提示做书级规划;也可以跳过蓝图直接写第一章,严格大纲是可选开关。0.2.4 推荐顺序:
437
+ 模型会调用 `novel_init` 建项目,然后按 `novel_guide` 的提示做书级规划;也可以跳过蓝图直接写第一章,严格大纲是可选开关。0.2.5 推荐顺序:
436
438
 
437
439
  ```text
438
440
  novel_init
@@ -1022,7 +1024,7 @@ dsh plugin --profile web add dsh-novel-write-engine@latest
1022
1024
  也可以固定版本:
1023
1025
 
1024
1026
  ```bash
1025
- dsh plugin --profile web add dsh-novel-write-engine@0.2.4
1027
+ dsh plugin --profile web add dsh-novel-write-engine@0.2.5
1026
1028
  ```
1027
1029
 
1028
1030
  升级后核对:
@@ -1064,7 +1066,7 @@ corepack pnpm pack
1064
1066
 
1065
1067
  ```bash
1066
1068
  dsh plugin --profile web remove dsh-novel-write-engine
1067
- dsh plugin --profile web add /absolute/path/to/dsh-novel-write-engine-0.2.4.tgz
1069
+ dsh plugin --profile web add /absolute/path/to/dsh-novel-write-engine-0.2.5.tgz
1068
1070
  ```
1069
1071
 
1070
1072
  ### 11.5 在 Web / Desktop 里升级
@@ -1332,6 +1334,14 @@ tests/ # 单测、集成、falsify、e2e
1332
1334
 
1333
1335
  ### 16.5 当前验证基线
1334
1336
 
1337
+ 0.2.5 在 Node 24.19 / DSH 0.2.1-rc.3 上完成:
1338
+
1339
+ - `npm test` 全绿:**81 个测试文件 / 875 项**;
1340
+ - 覆盖率:**语句 93.51% / 分支 89.07% / 函数 98.05% / 行 93.51%**;`test:coverage` 与逐文件 domain/book/insight/viewmodel 门禁全过;
1341
+ - `typecheck`、`lint`、`build` 通过;`check:hygiene`(144 files,0 v8 ignore)、`test:isolated`(60 源文件)、`validate:rulepack`、`check:pack`(25 files)、`check:artifacts`、`check:install`(26 packages)、`check:preset`(13 packages)、`check:host`、`check:crash` 全绿;
1342
+ - M1–M7、S1–S8 与 P2/P3 硬化见 [0.2.5 验收](docs/0.2.5-acceptance.md);
1343
+ - 真实模型:`agnes-2.5-flash` 与 `agnes-3.0-flash` 各输出一篇正文,经公开引擎 P3→P8/归档成功(score 145,stage done);P1/P2/P4/P6/P8 为确定性夹具,详见 [0.2.5 真实模型证据](docs/0.2.5-real-model-evidence.md)。
1344
+
1335
1345
  0.2.4 在 Node 24.19 / DSH 0.2.1-rc.3 上完成:
1336
1346
 
1337
1347
  - `npm test` 全绿:**80 个测试文件 / 848 项**;
@@ -1362,6 +1372,9 @@ tests/ # 单测、集成、falsify、e2e
1362
1372
  | [PLAN.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/PLAN.md) | 0.1.0 实施计划(历史) |
1363
1373
  | [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 整书生命周期:蓝图/大纲/世界观/章卡/观测 |
1364
1374
  | [docs/0.2.4-plan.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/docs/0.2.4-plan.md) | 0.2.4 outline upsert、显式删除、dry_run 与恢复路径的根治方案 |
1375
+ | [docs/0.2.5-plan.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/docs/0.2.5-plan.md) | 0.2.5 语义证据、check 轮次、world delta、pending、outline、summary-only、资源与 P2/P3 硬化方案 |
1376
+ | [docs/0.2.5-acceptance.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/docs/0.2.5-acceptance.md) | 0.2.5 验收矩阵、真实项目回归与门禁证据 |
1377
+ | [docs/0.2.5-real-model-evidence.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/docs/0.2.5-real-model-evidence.md) | 0.2.5 agnes 2.5/3.0 正文经引擎 P3→P8/归档回执 |
1365
1378
  | [docs/0.2.4-acceptance.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/docs/0.2.4-acceptance.md) | 0.2.4 验收矩阵、质量门与边界 |
1366
1379
  | [docs/0.2.4-real-model-evidence.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/docs/0.2.4-real-model-evidence.md) | 0.2.4 两条真实模型 P1→P8 链路回执 |
1367
1380
  | [docs/0.2.3-plan.md](https://github.com/ThzxxArt/dsh-novel-write-engine/blob/main/docs/0.2.3-plan.md) | 0.2.3 可信账本、工作流、advisory、面板 v5 与项目卫生 |
package/briefs/P5.md CHANGED
@@ -39,6 +39,8 @@
39
39
 
40
40
  `ready_to_archive` 阶段也允许直接 `novel_revise`:正文会退回 P4 重新评审,不需要先 `novel_rollback`,也不会多写一份废稿快照。
41
41
 
42
+ **summary-only 模式**:如果正文不需要改,只传 `{chapter, summary}`(不要传 content/patch/proof)。它只更新 draft summary,正文 hash、章历史与 revise 轮次都不变;校验 PG-05 后可继续 check/archive。summary 与正文事件不符时优先用这个模式。
43
+
42
44
  ## 常见返工原因
43
45
 
44
46
  - 只改了措辞,没解决 issue 指出的信息缺口。
package/briefs/P6.md CHANGED
@@ -22,7 +22,7 @@
22
22
  ]
23
23
  ```
24
24
 
25
- 五个 id 必须各出现一次;`verdict` 只能是 `pass`/`fail`;`evidence` 至少 8 字、必须逐字出现在正文里,且五条引用必须互不相同、来自不同句子。五条引用同一句话不算五份证据,会被 `SEMANTIC_EVIDENCE_INVALID` 拒绝。
25
+ 五个 id 必须各出现一次;`verdict` 只能是 `pass`/`fail`;`evidence` 至少 8 字、必须逐字出现在正文里,可以跨句/跨段。五条引用必须互不相同、不得复用同一句,也不得与已接受引文的区间重叠;同一句不同子串、重复引用或区间重叠会被按 `same_sentence`/`overlap` 拒绝。引文不存在/太短/形状不合法属于 **submission error**(`ENGINE_CHECK_SUBMISSION_INVALID`):不写 check、不消耗轮次,修正 `semantic` 后仍调用 `novel_check`。
26
26
 
27
27
  | id | 检查 |
28
28
  |---|---|
@@ -38,7 +38,8 @@
38
38
 
39
39
  - 调用时可以传 `scope`:默认 `["content","integrity"]`;只跑 `content` 或只跑 `integrity` 是合法的,空数组/未知值会被拒绝。只跑一类时另一类规则不参与本次 check,但**不会**进入 `ready_to_archive`:管线停在 P6 并返回 `CHECK_SCOPE_PARTIAL`,必须再跑一次完整 scope。
40
40
  - 完整 scope 全过 → 管线进入 `ready_to_archive`,先 `novel_brief P8` 领取终审任务书,再调用 `novel_archive`。
41
- - 任一项 `fail` → 校验不过,先重新领取 `novel_brief P6`(check 已改变上下文,旧 digest 会失效),再调用 `novel_revise` 修订并回到 P4 重评(轮次 +1)。
41
+ - 任一项 `fail` 且引文有效 → content failure,写 check 并消耗一轮;先重新领取 `novel_brief P6`(check 已改变上下文,旧 digest 会失效),再调用 `novel_revise` 修订并回到 P4 重评。
42
+ - 如果只是 summary 字段与正文不符(正文无需改),用 `novel_revise {chapter, summary}` 做 summary-only 修复:不消耗 revise、不改正文,然后可直接继续 check/archive。
42
43
 
43
44
  ## 损坏与恢复
44
45
 
package/briefs/P8.md CHANGED
@@ -81,23 +81,24 @@
81
81
  }
82
82
  ```
83
83
 
84
- 错误会带上 `expected` 形态和收到的字段值,例如 `characters.陈嘉树.current_state must be an object; expected {"resources":{"港币":-3},...}`。不要猜字段名,按报错补齐即可。
84
+ 错误会带上 `expected` 形态和收到的字段值,例如 `characters.陈嘉树.current_state must be an object; expected {"resources":{"港币":3},...}`。不要猜字段名,按报错补齐即可。`current_state.resources` 会与旧余额深合并,余额必须是有限非负整数。
85
85
 
86
86
  ## summary 与 delta 的联动(归档前必核)
87
87
 
88
88
  `novel_archive` **不接受 summary**;它校验的是 P3/P5 已经固定的 draft summary。delta 必须与这份 summary 对齐:
89
89
 
90
90
  - `characters` 里每个 `action:"created"` 的人物名,必须出现在 draft `summary.characters_appeared`。
91
- - `world` 里每个 `action:"created"` 的 location / ability / artifact,必须出现在 draft `summary.new_elements` 的对应数组,且 `kind` 一致;`faction` / `rule` 只需正文出现并按 kind 写入,`summary.new_elements` 没有对应数组。
91
+ - `world` 以 `state/world.yaml` 的存在性为唯一真源:已有实体写 `created` 会按 `updated` 归一化并给 warning;新实体写 `updated` 且正文出现、kind 可确定时按 `created` 归一化;kind 冲突仍拒绝。`summary.new_elements` 只是意图声明,名字必须在正文出现,已存在实体不再强求 `created`。
92
92
  - `timeline` 的键必须是 draft `summary.events` 的下标(字符串)。
93
93
  - `relationships` 的两端必须已在 `characters` 里声明(本章 created 或台账已有),否则先声明人物。
94
+ - `timeline.<key>.characters` 里的每个名字必须已在 `characters` 台账里声明。
94
95
 
95
- 如果 summary 与本章事实不符,**不要直接改 delta 硬凑**:先 `novel_brief P5` → `novel_revise` 修正正文和 summary,再重走 P4/P6/P8。归档阶段没有第二次 summary 入口。
96
+ 如果 summary 与本章事实不符,先走 **summary-only** 修复:`novel_revise {chapter, summary}`(不带 content/patch/proof)只更新 draft.summary,不消耗 revise 轮次、不改正文,然后按当前阶段继续;只有正文需要改时才走 P5 全文修订。
96
97
 
97
98
  - **伏笔的 title 是可读标签,anchor 才是校验证据**:`title` 可以写「船费债」这样的短语;要归档,请把正文原句放进 `anchor`(summary 的 planted 项或 delta 项都行,至少 8 个字符)。没有 `anchor` 时,`title` 必须逐字出现在正文里,否则 `ENGINE_DELTA_UNVERIFIED`。示例:`{"action":"created","title":"船费债","anchor":"三百蚊的船费先欠着","description":"欠船费三百蚊"}`。
98
99
  - 归档失败的报错会指出字段、值和缺失的锚点;不要把 title 改成碎片来“凑命中”,补 `anchor` 即可。
99
100
  - 第一人称的叙述者若在 `narrator` 里声明为 `陈默(第一人称「我」)`,正文用「我」也视为出现,但角色键仍写 `陈默`。
100
- - 每个新实体至少写 `action: "created"`;已存在且没变化写 `action: "no_change", reason: "..."`。
101
+ - 每个新实体写 `action: "created"`(正文出现、kind 可确定);已存在实体写 `updated` 或 `no_change`,引擎会按台账归一化冲突并给 warning。
101
102
  - `world` 新实体还要写 `kind: "location"|"ability"|"artifact"|"faction"|"rule"` 和 `description`。
102
103
  - `state_delta` 可以是对象,也可以是 JSON 字符串;引擎两者都接受。
103
104
  - 未知伏笔 key、未声明角色、`no_change` 缺原因都会被 `ENGINE_DELTA_UNVERIFIED` 拒绝,引擎不会猜。
package/lib/client.js CHANGED
@@ -166,6 +166,7 @@ window.__ModuleLoader__.load({
166
166
  advisories: [],
167
167
  acknowledgedAdvisories: 0,
168
168
  legacy: [],
169
+ pending: null,
169
170
  nextTitle: "",
170
171
  nextDetail: "",
171
172
  nextTool: "",
@@ -392,6 +393,7 @@ window.__ModuleLoader__.load({
392
393
  })),
393
394
  acknowledgedAdvisories: summary.acknowledgedAdvisories ?? 0,
394
395
  legacy: summary.legacy ?? [],
396
+ pending: summary.pending ?? null,
395
397
  nextTitle: finished ? "" : next === void 0 ? "状态已同步" : toolLabel(next.tool),
396
398
  nextDetail: finished ? "" : next === void 0 ? "没有待执行的下一步,刷新查看最新状态。" : NEXT_DETAILS[next.tool] ?? next.reason,
397
399
  nextTool: next?.tool ?? "",
@@ -1005,6 +1007,11 @@ window.__ModuleLoader__.load({
1005
1007
  color: var(--nwe-muted);
1006
1008
  font-size: 11px;
1007
1009
  }
1010
+
1011
+ .nwe-pending-section { border-left: 3px solid #c9a227; padding-left: 10px; }
1012
+ .nwe-pending-error { border-left-color: #b3261e; }
1013
+ .nwe-pending-detail { margin: 8px 0 0; color: var(--nwe-ink); font-size: 11px; line-height: 1.6; }
1014
+ .nwe-pending-hint { margin: 4px 0 0; color: var(--nwe-muted); font-size: 10px; line-height: 1.5; }
1008
1015
  .nwe-legacy {
1009
1016
  display: flex;
1010
1017
  gap: 8px;
@@ -1266,6 +1273,14 @@ brief P6 → check → brief P8 → archive 的顺序推进。
1266
1273
  key: `${item.code}-${item.detail}`
1267
1274
  }, (0, react.createElement)("code", { className: "nwe-advisory-code" }, item.code), (0, react.createElement)("span", { className: "nwe-advisory-detail" }, item.detail), item.hint === "" ? null : (0, react.createElement)("span", { className: "nwe-advisory-hint" }, item.hint)))));
1268
1275
  };
1276
+ const pendingSection = (view) => {
1277
+ if (view.pending === null) return null;
1278
+ const partial = view.pending.state === "partial";
1279
+ return (0, react.createElement)("section", {
1280
+ className: `nwe-section nwe-pending-section${partial ? " nwe-pending-error" : ""}`,
1281
+ "aria-label": "待恢复事务"
1282
+ }, (0, react.createElement)("div", { className: "nwe-section-head" }, (0, react.createElement)("h2", { className: "nwe-section-title" }, partial ? "事务部分完成" : "待恢复事务"), (0, react.createElement)("span", { className: "nwe-section-note" }, view.pending.state)), (0, react.createElement)("p", { className: "nwe-pending-detail" }, view.pending.detail), (0, react.createElement)("p", { className: "nwe-pending-hint" }, partial ? `检查 backups 与 .staging/${view.pending.txid}/before/,不要整项目恢复。` : "下一次写操作会自动收尾;只读面不会改动事务。"));
1283
+ };
1269
1284
  const legacySection = (view) => {
1270
1285
  if (view.legacy.length === 0) return null;
1271
1286
  return (0, react.createElement)("section", {
@@ -1553,7 +1568,7 @@ brief P6 → check → brief P8 → archive 的顺序推进。
1553
1568
  className: "nwe-stats",
1554
1569
  role: "group",
1555
1570
  "aria-label": "项目统计"
1556
- }, stat("已归档", String(view.stats.chapters), "章"), stat("总字数", view.stats.words.toLocaleString("zh-CN"), "字"), stat("全书均分", view.stats.chapters === 0 ? "—" : String(view.stats.averageScore), "/150"), stat("待处理", String(view.stats.blocking), "项")) : null, view.state === "loading" ? loadingState() : null, view.state === "choose" ? choosePanel(view, onSelectProject) : null, view.state === "missing" ? missingPanel(view) : null, view.state === "error" || view.state === "empty" || view.state === "outdated" ? statePanel(view.state, view, loading, onRefresh) : null, ready ? (0, react.createElement)("div", { className: "nwe-desk" }, (0, react.createElement)("div", { className: "nwe-desk-main" }, (0, react.createElement)("div", { className: "nwe-desk-heading" }, (0, react.createElement)("span", { className: "nwe-desk-kicker" }, "当前一章"), (0, react.createElement)("span", { className: "nwe-desk-note" }, view.stageLabel)), phases(view), blockingSection(view), advisoriesSection(view), legacySection(view), chaptersSection(view)), (0, react.createElement)("aside", {
1571
+ }, stat("已归档", String(view.stats.chapters), "章"), stat("总字数", view.stats.words.toLocaleString("zh-CN"), "字"), stat("全书均分", view.stats.chapters === 0 ? "—" : String(view.stats.averageScore), "/150"), stat("待处理", String(view.stats.blocking), "项")) : null, view.state === "loading" ? loadingState() : null, view.state === "choose" ? choosePanel(view, onSelectProject) : null, view.state === "missing" ? missingPanel(view) : null, view.state === "error" || view.state === "empty" || view.state === "outdated" ? statePanel(view.state, view, loading, onRefresh) : null, ready ? (0, react.createElement)("div", { className: "nwe-desk" }, (0, react.createElement)("div", { className: "nwe-desk-main" }, (0, react.createElement)("div", { className: "nwe-desk-heading" }, (0, react.createElement)("span", { className: "nwe-desk-kicker" }, "当前一章"), (0, react.createElement)("span", { className: "nwe-desk-note" }, view.stageLabel)), phases(view), blockingSection(view), advisoriesSection(view), pendingSection(view), legacySection(view), chaptersSection(view)), (0, react.createElement)("aside", {
1557
1572
  className: "nwe-desk-side",
1558
1573
  "aria-label": "全书与活动"
1559
1574
  }, bookSection(view), activitySection(view))) : null, footer());
package/lib/index.d.ts CHANGED
@@ -68,6 +68,9 @@ declare const ERROR_CODES: {
68
68
  /** The exact same keyed submission already failed in the same context. */
69
69
  readonly RETRY_UNCHANGED: "ENGINE_RETRY_UNCHANGED";
70
70
  readonly GATE_BLOCKED: "ENGINE_GATE_BLOCKED";
71
+ readonly CHECK_SUBMISSION_INVALID: "ENGINE_CHECK_SUBMISSION_INVALID";
72
+ readonly PENDING_PARTIAL: "ENGINE_PENDING_PARTIAL";
73
+ readonly IO_PERMISSION: "ENGINE_IO_PERMISSION";
71
74
  readonly READING_PROOF_INVALID: "ENGINE_READING_PROOF_INVALID";
72
75
  readonly SCORE_MISMATCH: "ENGINE_SCORE_MISMATCH";
73
76
  /** A revision tried to rewrite reviewer-owned issue fields. */
@@ -178,9 +181,14 @@ interface CheckDocument {
178
181
  evidence: string;
179
182
  note: string;
180
183
  }>;
184
+ /** 0.2.5: explain a persisted content failure; historical checks omit it. */
185
+ findings?: BlockingItem[];
181
186
  };
182
187
  /** Advisory codes the author has explicitly acknowledged for this content. */
183
188
  acknowledged?: string[];
189
+ /** 0.2.5: content failures consume the check budget; submission errors never persist. */
190
+ failure_class?: 'submission' | 'content';
191
+ counted?: boolean;
184
192
  verdict: 'pass' | 'fail';
185
193
  }
186
194
  /** Every persisted document whose damage status/panel can report. */
@@ -953,6 +961,10 @@ interface GateInput {
953
961
  summary: ChapterSummary | null;
954
962
  /** P5 may omit `summary`; when omitted, the previous plan summary is reused. */
955
963
  summaryProvided?: boolean;
964
+ /** Summary-only repair: no manuscript change, no P4/P6 proof requirement. */
965
+ summaryOnly?: boolean;
966
+ /** The last write was a summary-only repair; P6 may continue without a new brief. */
967
+ summaryOnlyRevision?: boolean;
956
968
  review: ReviewDocument | null;
957
969
  check: CheckDocument | null;
958
970
  /** When set, result-bearing gates run only the named check scopes. */
@@ -1030,6 +1042,8 @@ interface StatusReport {
1030
1042
  stage: Stage;
1031
1043
  rounds: RoundCounters;
1032
1044
  blocking: BlockingItem[];
1045
+ /** Non-blocking recovery notes, e.g. a recoverable pending transaction. */
1046
+ warnings?: Warning[];
1033
1047
  /** plan/review/check/ledger files that exist but cannot be read safely. */
1034
1048
  state_errors: StateError[];
1035
1049
  metrics: WordMetrics | null;
@@ -1122,6 +1136,20 @@ interface DiffReport {
1122
1136
  }
1123
1137
  /** Export capabilities; the last three are book-planning deliverables. */
1124
1138
  type ExportKind = 'md' | 'txt' | 'epub' | 'outline' | 'worldbook' | 'proposal';
1139
+ type SemanticItemState = 'pass' | 'fail' | 'missing' | 'invalid' | 'not_found' | 'too_short' | 'duplicate' | 'overlap' | 'same_sentence' | 'boundary';
1140
+ interface SemanticNearest {
1141
+ paragraph: number;
1142
+ quote: string;
1143
+ score: number;
1144
+ }
1145
+ interface SemanticItemResult {
1146
+ id: string;
1147
+ state: SemanticItemState;
1148
+ quote?: string;
1149
+ detail?: string;
1150
+ nearest?: SemanticNearest;
1151
+ conflict_with?: string;
1152
+ }
1125
1153
  /** One blacklisted word. */
1126
1154
  interface TermEntry {
1127
1155
  term: string;
@@ -1454,6 +1482,12 @@ interface PanelSnapshot {
1454
1482
  code: string;
1455
1483
  detail: string;
1456
1484
  }>;
1485
+ /** Crash-left pending transaction; complete/untouched are warnings. */
1486
+ pending?: {
1487
+ state: string;
1488
+ txid: string;
1489
+ detail: string;
1490
+ };
1457
1491
  }
1458
1492
  interface PanelBook {
1459
1493
  premise: string;
@@ -1555,6 +1589,12 @@ interface PanelSummaryDto {
1555
1589
  code: string;
1556
1590
  detail: string;
1557
1591
  }>;
1592
+ /** Crash-left pending transaction; complete/untouched are warnings. */
1593
+ pending?: {
1594
+ state: string;
1595
+ txid: string;
1596
+ detail: string;
1597
+ };
1558
1598
  }
1559
1599
  interface PanelChapterRow {
1560
1600
  num: number;
@@ -1784,8 +1824,8 @@ interface RevisionSubmission extends Omit<RevisionInput, 'summary'> {
1784
1824
  summary?: ChapterSummary;
1785
1825
  /** Updated issue statuses/evidence; the review document is patched in place. */
1786
1826
  issues?: ReviewDocument['issues'];
1787
- /** Proof that the revised text was read; PG-09 verifies it against the new text. */
1788
- proof: ReadingProof;
1827
+ /** Proof that the revised text was read; required for content/patch revisions. */
1828
+ proof?: ReadingProof;
1789
1829
  root?: string;
1790
1830
  }
1791
1831
  interface CheckSubmission {
@@ -1875,6 +1915,7 @@ interface OutlineMutationResult {
1875
1915
  volumes: number;
1876
1916
  added: number[];
1877
1917
  updated: number[];
1918
+ reindexed: number[];
1878
1919
  removed: number[];
1879
1920
  volumes_added: string[];
1880
1921
  volumes_updated: string[];
@@ -1882,6 +1923,7 @@ interface OutlineMutationResult {
1882
1923
  dry_run?: boolean;
1883
1924
  would_add?: number[];
1884
1925
  would_update?: number[];
1926
+ would_reindex?: number[];
1885
1927
  would_remove?: number[];
1886
1928
  }
1887
1929
  interface OutlineSubmission extends OutlineInput {
@@ -1931,6 +1973,8 @@ declare class EngineService {
1931
1973
  private readonly stores;
1932
1974
  private readonly mutex;
1933
1975
  private readonly replay;
1976
+ /** Bumped when an export artifact disappeared; makes the replay key miss. */
1977
+ private readonly exportArtifactEpoch;
1934
1978
  /** Status, panel and gates ask for the same stage context repeatedly. */
1935
1979
  private readonly briefContextCache;
1936
1980
  private readonly briefs;
@@ -2126,6 +2170,8 @@ declare class EngineService {
2126
2170
  chapter: number;
2127
2171
  stage: Stage;
2128
2172
  metrics: WordMetrics;
2173
+ summary_only?: boolean;
2174
+ rounds_consumed?: 0 | 1;
2129
2175
  }>>;
2130
2176
  runCheck(input: CheckSubmission): Promise<OpResult<{
2131
2177
  chapter: number;
@@ -2136,12 +2182,26 @@ declare class EngineService {
2136
2182
  acknowledged: string[];
2137
2183
  metrics: WordMetrics;
2138
2184
  stage: Stage;
2185
+ semantic: {
2186
+ items: SemanticItemResult[];
2187
+ findings: BlockingItem[];
2188
+ };
2189
+ rounds: {
2190
+ check: number;
2191
+ };
2192
+ counted: boolean;
2139
2193
  }>>;
2140
2194
  archiveChapter(input: ArchiveSubmission): Promise<OpResult<{
2141
2195
  chapter: number;
2142
2196
  assigned_ids: Record<string, string>;
2143
2197
  metrics: WordMetrics;
2144
2198
  score: number;
2199
+ delta_warnings?: Warning[];
2200
+ normalized_world?: Record<string, {
2201
+ from: 'created' | 'updated' | 'no_change';
2202
+ to: 'created' | 'updated' | 'no_change';
2203
+ reason: 'existing-created-as-updated' | 'missing-updated-as-created' | 'missing-no-change-as-created';
2204
+ }>;
2145
2205
  }>>;
2146
2206
  amend(input: AmendSubmission): Promise<OpResult<{
2147
2207
  path: string;
@@ -2320,6 +2380,12 @@ declare class EngineService {
2320
2380
  * could not read.
2321
2381
  */
2322
2382
  private assertPlanningDocumentReadable;
2383
+ /**
2384
+ * `strict_explicit` is an internal provenance bit, not an author choice.
2385
+ * Exposing it lets a caller forge "the author chose strict:false" without
2386
+ * going through the strict option. Stored documents may still contain it.
2387
+ */
2388
+ private assertPublicBookHasNoStrictExplicit;
2323
2389
  /**
2324
2390
  * An import is a full replacement, unlike novel_outline's keyed upsert. It
2325
2391
  * must not silently lose the active card while strict mode still requires it.