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 +29 -0
- package/README.md +22 -9
- package/briefs/P5.md +2 -0
- package/briefs/P6.md +3 -2
- package/briefs/P8.md +5 -4
- package/lib/client.js +16 -1
- package/lib/index.d.ts +68 -2
- package/lib/index.js +876 -199
- package/lib/types/client/dto.d.ts +6 -0
- package/lib/types/client/viewmodel.d.ts +6 -0
- package/lib/types/types.d.ts +14 -0
- package/package.json +1 -1
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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` →
|
|
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":{"港币"
|
|
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`
|
|
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
|
|
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
|
-
-
|
|
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;
|
|
1788
|
-
proof
|
|
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.
|