novel-writer-cli 0.0.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.
Files changed (116) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +103 -0
  3. package/agents/chapter-writer.md +142 -0
  4. package/agents/character-weaver.md +117 -0
  5. package/agents/consistency-auditor.md +85 -0
  6. package/agents/plot-architect.md +128 -0
  7. package/agents/quality-judge.md +232 -0
  8. package/agents/style-analyzer.md +109 -0
  9. package/agents/style-refiner.md +97 -0
  10. package/agents/summarizer.md +128 -0
  11. package/agents/world-builder.md +161 -0
  12. package/dist/__tests__/character-voice.test.js +445 -0
  13. package/dist/__tests__/commit-prototype-pollution.test.js +45 -0
  14. package/dist/__tests__/engagement.test.js +382 -0
  15. package/dist/__tests__/foreshadow-visibility.test.js +131 -0
  16. package/dist/__tests__/hook-ledger.test.js +1028 -0
  17. package/dist/__tests__/naming-lint.test.js +132 -0
  18. package/dist/__tests__/narrative-health-injection.test.js +359 -0
  19. package/dist/__tests__/next-step-prejudge-guardrails.test.js +325 -0
  20. package/dist/__tests__/next-step-title-fix.test.js +153 -0
  21. package/dist/__tests__/platform-profile.test.js +274 -0
  22. package/dist/__tests__/promise-ledger.test.js +189 -0
  23. package/dist/__tests__/readability-lint.test.js +209 -0
  24. package/dist/__tests__/text-utils.test.js +39 -0
  25. package/dist/__tests__/title-policy.test.js +147 -0
  26. package/dist/advance.js +75 -0
  27. package/dist/character-voice.js +805 -0
  28. package/dist/checkpoint.js +126 -0
  29. package/dist/cli.js +563 -0
  30. package/dist/cliche-lint.js +515 -0
  31. package/dist/commit.js +1460 -0
  32. package/dist/consistency-auditor.js +684 -0
  33. package/dist/engagement.js +687 -0
  34. package/dist/errors.js +7 -0
  35. package/dist/fingerprint.js +16 -0
  36. package/dist/foreshadow-visibility.js +214 -0
  37. package/dist/fs-utils.js +68 -0
  38. package/dist/hook-ledger.js +721 -0
  39. package/dist/hook-policy.js +107 -0
  40. package/dist/instruction-gates.js +51 -0
  41. package/dist/instructions.js +406 -0
  42. package/dist/latest-summary-loader.js +29 -0
  43. package/dist/lock.js +121 -0
  44. package/dist/naming-lint.js +531 -0
  45. package/dist/ner.js +73 -0
  46. package/dist/next-step.js +408 -0
  47. package/dist/novel-ask.js +270 -0
  48. package/dist/output.js +9 -0
  49. package/dist/platform-constraints.js +518 -0
  50. package/dist/platform-profile.js +325 -0
  51. package/dist/prejudge-guardrails.js +370 -0
  52. package/dist/project.js +40 -0
  53. package/dist/promise-ledger.js +723 -0
  54. package/dist/readability-lint.js +555 -0
  55. package/dist/safe-parse.js +36 -0
  56. package/dist/safe-path.js +29 -0
  57. package/dist/scoring-weights.js +290 -0
  58. package/dist/steps.js +60 -0
  59. package/dist/text-utils.js +18 -0
  60. package/dist/title-policy.js +251 -0
  61. package/dist/type-guards.js +6 -0
  62. package/dist/validate.js +131 -0
  63. package/docs/user/README.md +17 -0
  64. package/docs/user/guardrails.md +179 -0
  65. package/docs/user/interactive-gates.md +124 -0
  66. package/docs/user/novel-cli.md +289 -0
  67. package/docs/user/ops.md +123 -0
  68. package/docs/user/quick-start.md +97 -0
  69. package/docs/user/spec-system.md +166 -0
  70. package/docs/user/storylines.md +144 -0
  71. package/package.json +48 -0
  72. package/schemas/README.md +18 -0
  73. package/schemas/character-voice-drift.schema.json +135 -0
  74. package/schemas/character-voice-profiles.schema.json +141 -0
  75. package/schemas/engagement-metrics.schema.json +38 -0
  76. package/schemas/hook-ledger.schema.json +108 -0
  77. package/schemas/platform-profile.schema.json +235 -0
  78. package/schemas/promise-ledger.schema.json +97 -0
  79. package/scripts/calibrate-quality-judge.sh +91 -0
  80. package/scripts/compare-regression-runs.sh +86 -0
  81. package/scripts/lib/_common.py +131 -0
  82. package/scripts/lib/calibrate_quality_judge.py +312 -0
  83. package/scripts/lib/compare_regression_runs.py +142 -0
  84. package/scripts/lib/run_regression.py +621 -0
  85. package/scripts/lint-blacklist.sh +201 -0
  86. package/scripts/lint-cliche.sh +370 -0
  87. package/scripts/lint-readability.sh +404 -0
  88. package/scripts/query-foreshadow.sh +252 -0
  89. package/scripts/run-ner.sh +669 -0
  90. package/scripts/run-regression.sh +122 -0
  91. package/skills/cli-step/SKILL.md +158 -0
  92. package/skills/continue/SKILL.md +348 -0
  93. package/skills/continue/references/context-contracts.md +169 -0
  94. package/skills/continue/references/continuity-checks.md +187 -0
  95. package/skills/continue/references/file-protocols.md +64 -0
  96. package/skills/continue/references/foreshadowing.md +130 -0
  97. package/skills/continue/references/gate-decision.md +53 -0
  98. package/skills/continue/references/periodic-maintenance.md +46 -0
  99. package/skills/novel-writing/SKILL.md +77 -0
  100. package/skills/novel-writing/references/quality-rubric.md +140 -0
  101. package/skills/novel-writing/references/style-guide.md +145 -0
  102. package/skills/start/SKILL.md +458 -0
  103. package/skills/start/references/quality-review.md +86 -0
  104. package/skills/start/references/setting-update.md +44 -0
  105. package/skills/start/references/vol-planning.md +61 -0
  106. package/skills/start/references/vol-review.md +58 -0
  107. package/skills/status/SKILL.md +116 -0
  108. package/skills/status/references/sample-output.md +60 -0
  109. package/templates/ai-blacklist.json +79 -0
  110. package/templates/brief-template.md +46 -0
  111. package/templates/genre-weight-profiles.json +90 -0
  112. package/templates/novel-ask/example.answer.json +12 -0
  113. package/templates/novel-ask/example.question.json +51 -0
  114. package/templates/platform-profile.json +148 -0
  115. package/templates/style-profile-template.json +58 -0
  116. package/templates/web-novel-cliche-lint.json +41 -0
@@ -0,0 +1,169 @@
1
+ # Agent Context Manifest 字段契约
2
+
3
+ ## 概述
4
+
5
+ 入口 Skill 为每个 Agent 组装一份 **context manifest**,包含两类字段:
6
+
7
+ - **inline**(内联):由编排器确定性计算,直接写入 Task prompt——适用于需要预处理/裁剪/跨文件聚合的数据
8
+ - **paths**(文件路径):指向项目目录下的文件,由 subagent 用 Read 工具自行读取——适用于大段原文内容
9
+
10
+ 设计原则:
11
+ - 同一输入 + 同一项目文件 = 同一 manifest(确定性)
12
+ - paths 中的文件均为项目目录下的相对路径
13
+ - 可选字段缺失时不出现在 manifest 中(非 null)
14
+ - subagent 读取的文件内容不再需要 `<DATA>` 标签包裹(由 agent frontmatter 中的安全约束处理)
15
+
16
+ ---
17
+
18
+ ## ChapterWriter manifest
19
+
20
+ ```
21
+ chapter_writer_manifest = {
22
+ # ── inline(编排器计算) ──
23
+ chapter: int,
24
+ volume: int,
25
+ storyline_id: str,
26
+ chapter_outline_block: str, # 从 outline.md 提取的本章区块文本
27
+ storyline_context: { # 从 chapter_contract/schedule 解析
28
+ last_chapter_summary: str,
29
+ chapters_since_last: int,
30
+ line_arc_progress: str,
31
+ },
32
+ concurrent_state: {str: str}, # 其他活跃线一句话状态
33
+ transition_hint: obj | null, # 切线过渡
34
+ hard_rules_list: [str], # L1 禁止项列表(已格式化)
35
+ foreshadowing_tasks: [obj], # 本章伏笔任务子集
36
+ foreshadow_light_touch_tasks?: [ # 可选:伏笔沉默超阈值时的“轻触提醒”(非剧透、不兑现);为空则省略该字段
37
+ {id: str, scope: str, status: str, chapters_since_last_update: int, instruction: str}
38
+ ],
39
+ foreshadow_light_touch_degraded?: bool, # 可选:若为 true 表示“轻触提醒”注入降级(如伏笔数据不可读),不等同于“没有需要提醒的条目”
40
+ ai_blacklist_top10: [str], # 有效黑名单前 10 词
41
+ style_drift_directives: [str] | null, # 漂移纠偏指令(active 时注入)
42
+ engagement_report_summary?: obj, # 可选:爽点/信息密度窗口报告摘要(logs/engagement/latest.json 裁剪)
43
+ promise_ledger_report_summary?: obj, # 可选:承诺台账窗口报告摘要(logs/promises/latest.json 裁剪)
44
+ engagement_report_summary_degraded?: bool, # 可选:为 true 表示 latest.json 存在但摘要裁剪失败
45
+ promise_ledger_report_summary_degraded?: bool, # 可选:为 true 表示 latest.json 存在但摘要裁剪失败
46
+
47
+ # ── paths(subagent 自读) ──
48
+ paths: {
49
+ style_profile: "style-profile.json", # 必读(含 style_exemplars + writing_directives)
50
+ platform_profile: "platform-profile.json", # 可选(平台字数/钩子策略/信息负载等)
51
+ style_drift: "style-drift.json", # 可选
52
+ chapter_contract: "volumes/vol-{V:02d}/chapter-contracts/chapter-{C:03d}.json",
53
+ volume_outline: "volumes/vol-{V:02d}/outline.md",
54
+ current_state: "state/current-state.json",
55
+ world_rules: "world/rules.json", # 可选
56
+ recent_summaries: ["summaries/chapter-{C-1:03d}-summary.md", ...], # 近 3 章
57
+ storyline_memory: "storylines/{storyline_id}/memory.md", # 可选
58
+ adjacent_memories: ["storylines/{adj_id}/memory.md", ...], # 可选
59
+ character_contracts: ["characters/active/{slug}.json", ...], # 裁剪后选取
60
+ project_brief: "brief.md",
61
+ writing_methodology: "skills/novel-writing/references/style-guide.md", # 可选
62
+ engagement_report_latest: "logs/engagement/latest.json", # 可选(如存在;用于读取完整报告)
63
+ promise_ledger_report_latest: "logs/promises/latest.json", # 可选(如存在;用于读取完整报告)
64
+ }
65
+ }
66
+ ```
67
+
68
+ ### 修订模式追加字段
69
+
70
+ ```
71
+ chapter_writer_revision_manifest = chapter_writer_manifest + {
72
+ # ── inline 追加 ──
73
+ required_fixes: [{target: str, instruction: str}], # QualityJudge 最小修订指令(与 eval 输出格式一致)
74
+ high_confidence_violations: [obj], # confidence="high" 的违约条目
75
+
76
+ # ── paths 追加 ──
77
+ paths += {
78
+ chapter_draft: "staging/chapters/chapter-{C:03d}.md", # 待修订的现有正文
79
+ chapter_eval: "staging/evaluations/chapter-{C:03d}-eval.json", # 可选(hook-fix/修订时提供的评估上下文)
80
+ }
81
+ }
82
+ ```
83
+
84
+ ---
85
+
86
+ ## Summarizer manifest
87
+
88
+ ```
89
+ summarizer_manifest = {
90
+ # ── inline ──
91
+ chapter: int,
92
+ volume: int,
93
+ storyline_id: str,
94
+ foreshadowing_tasks: [obj],
95
+ entity_id_map: {slug_id: display_name},
96
+ hints: [str] | null, # ChapterWriter 输出的 hints JSON(编排器从 ChapterWriter 输出末尾的 ```json{"chapter":N,"hints":[...]}``` 块解析;解析失败则为 null)
97
+
98
+ # ── paths ──
99
+ paths: {
100
+ chapter_draft: "staging/chapters/chapter-{C:03d}.md",
101
+ current_state: "state/current-state.json",
102
+ }
103
+ }
104
+ ```
105
+
106
+ ---
107
+
108
+ ## StyleRefiner manifest
109
+
110
+ ```
111
+ style_refiner_manifest = {
112
+ # ── inline ──
113
+ chapter: int,
114
+ style_drift_directives: [str] | null,
115
+ engagement_report_summary?: obj, # 可选:爽点/信息密度窗口报告摘要(logs/engagement/latest.json 裁剪)
116
+ promise_ledger_report_summary?: obj, # 可选:承诺台账窗口报告摘要(logs/promises/latest.json 裁剪)
117
+ engagement_report_summary_degraded?: bool,
118
+ promise_ledger_report_summary_degraded?: bool,
119
+
120
+ # ── paths ──
121
+ paths: {
122
+ chapter_draft: "staging/chapters/chapter-{C:03d}.md",
123
+ style_profile: "style-profile.json", # 必读(含 style_exemplars)
124
+ style_drift: "style-drift.json", # 可选
125
+ ai_blacklist: "ai-blacklist.json",
126
+ style_guide: "skills/novel-writing/references/style-guide.md",
127
+ previous_change_log: "staging/logs/style-refiner-chapter-{C:03d}-changes.json", # 仅二次润色时出现;首次润色不含此字段
128
+ engagement_report_latest: "logs/engagement/latest.json", # 可选
129
+ promise_ledger_report_latest: "logs/promises/latest.json", # 可选
130
+ }
131
+ }
132
+ ```
133
+
134
+ ---
135
+
136
+ ## QualityJudge manifest
137
+
138
+ ```
139
+ quality_judge_manifest = {
140
+ # ── inline ──
141
+ chapter: int,
142
+ volume: int,
143
+ chapter_outline_block: str,
144
+ hard_rules_list: [str],
145
+ blacklist_lint: obj | null, # scripts/lint-blacklist.sh 输出
146
+ ner_entities: obj | null, # scripts/run-ner.sh 输出
147
+ continuity_report_summary: obj | null, # logs/continuity/latest.json 裁剪
148
+
149
+ # ── paths ──
150
+ paths: {
151
+ chapter_draft: "staging/chapters/chapter-{C:03d}.md",
152
+ style_profile: "style-profile.json",
153
+ ai_blacklist: "ai-blacklist.json",
154
+ chapter_contract: "volumes/vol-{V:02d}/chapter-contracts/chapter-{C:03d}.json",
155
+ world_rules: "world/rules.json", # 可选
156
+ prev_summary: "summaries/chapter-{C-1:03d}-summary.md", # 可选(首章无)
157
+ character_profiles: ["characters/active/{slug}.md", ...], # 裁剪后选取(叙述档案)
158
+ character_contracts: ["characters/active/{slug}.json", ...], # 裁剪后选取(L2 结构化契约)
159
+ storyline_spec: "storylines/storyline-spec.json", # 可选
160
+ storyline_schedule: "volumes/vol-{V:02d}/storyline-schedule.json", # 可选
161
+ cross_references: "staging/state/chapter-{C:03d}-crossref.json",
162
+ quality_rubric: "skills/novel-writing/references/quality-rubric.md",
163
+ }
164
+ }
165
+ ```
166
+
167
+ ---
168
+
169
+ 另见:`continuity-checks.md`(NER schema + 一致性报告 schema + LS-001 结构化输入约定)。
@@ -0,0 +1,187 @@
1
+ # NER 与一致性检查(M3)
2
+
3
+ 本文件定义 **NER 实体抽取输出** 与 **一致性报告输出** 的最小 schema,用于:
4
+ - `/novel:start` 的「质量回顾 / 卷末回顾」生成可回归的一致性报告;
5
+ - `/novel:continue` 在调用 QualityJudge 时提供 LS-001 的结构化输入(摘要 + 证据);
6
+ - 可选确定性工具 `scripts/run-ner.sh` 的 stdout JSON 协议。
7
+
8
+ ## 1) 实体抽取输出 schema(`run-ner.sh` / LLM fallback)
9
+
10
+ `scripts/run-ner.sh <chapter.md>` stdout JSON(exit 0)**必须**满足:
11
+
12
+ ```json
13
+ {
14
+ "schema_version": 1,
15
+ "chapter_path": "chapters/chapter-048.md",
16
+ "entities": {
17
+ "characters": [
18
+ {
19
+ "text": "林枫",
20
+ "slug_id": null,
21
+ "confidence": "high",
22
+ "mentions": [
23
+ {"line": 120, "snippet": "林枫抬头望向幽暗森林深处……"}
24
+ ]
25
+ }
26
+ ],
27
+ "locations": [
28
+ {
29
+ "text": "幽暗森林",
30
+ "confidence": "high",
31
+ "mentions": [
32
+ {"line": 120, "snippet": "林枫抬头望向幽暗森林深处……"}
33
+ ]
34
+ }
35
+ ],
36
+ "time_markers": [
37
+ {
38
+ "text": "第三年冬末",
39
+ "normalized": "第三年冬末",
40
+ "confidence": "high",
41
+ "mentions": [
42
+ {"line": 12, "snippet": "第三年冬末,魔都风雪未歇。"}
43
+ ]
44
+ }
45
+ ],
46
+ "events": [
47
+ {
48
+ "text": "王国内战爆发",
49
+ "confidence": "medium",
50
+ "mentions": [
51
+ {"line": 33, "snippet": "王国内战爆发,边关烽烟四起。"}
52
+ ]
53
+ }
54
+ ]
55
+ }
56
+ }
57
+ ```
58
+
59
+ 字段约定:
60
+ - `schema_version`:当前为 `1`。
61
+ - `chapter_path`:输入文件路径(原样回显,便于追溯)。
62
+ - `entities.characters[].slug_id`:**可选**。`run-ner.sh` 无法可靠映射时可省略;LLM fallback 可以结合 `characters/active/*.json` 与 `state/current-state.json` 补全。
63
+ - `confidence`:枚举值为 `"high"` / `"medium"` / `"low"`。
64
+ - `mentions[]`:证据片段(建议最多 5 条);`snippet` 需截断到 ≤160 字符,避免报告膨胀。
65
+ - `run-ner.sh` 输出中 `slug_id` 为 `null`(脚本无法可靠映射名字→slug);LLM fallback 可结合 `characters/active/*.json` 补全。
66
+
67
+ > 说明:`run-ner.sh` 的目标是 **稳定可回归** 的候选实体与证据,不追求完美 NER;一致性判断由入口 Skill + QualityJudge 的语义校验兜底。
68
+
69
+ ## 2) 一致性报告输出 schema(周期性检查 / 卷末回顾)
70
+
71
+ 一致性报告建议写入 `logs/continuity/`(允许创建子目录,保持历史可追溯),stdout 展示简报即可。
72
+
73
+ 最小 JSON 格式:
74
+
75
+ ```json
76
+ {
77
+ "schema_version": 1,
78
+ "generated_at": "2026-02-24T12:00:00Z",
79
+ "scope": "periodic",
80
+ "volume": 2,
81
+ "chapter_range": [39, 48],
82
+ "issues": [
83
+ {
84
+ "id": "location_contradiction:char=lin-feng:time=第三年冬末:loc=魔都|幽暗森林",
85
+ "type": "location_contradiction",
86
+ "severity": "high",
87
+ "confidence": "high",
88
+ "entities": {
89
+ "characters": ["lin-feng"],
90
+ "locations": ["魔都", "幽暗森林"],
91
+ "time_markers": ["第三年冬末"],
92
+ "storylines": ["main-arc"]
93
+ },
94
+ "description": "同一 time_marker 下角色位置出现矛盾或疑似瞬移。",
95
+ "evidence": [
96
+ {"chapter": 47, "source": "chapter", "line": 12, "snippet": "第三年冬末,林枫仍在魔都……"},
97
+ {"chapter": 48, "source": "chapter", "line": 120, "snippet": "林枫抬头望向幽暗森林深处……"}
98
+ ],
99
+ "suggestions": [
100
+ "确认时间标尺是否应推进(例如从'第三年冬末'推进到'翌日清晨')。",
101
+ "若确为跨地移动,补一段赶路/传送的因果说明。"
102
+ ]
103
+ }
104
+ ],
105
+ "stats": {
106
+ "chapters_checked": 10,
107
+ "issues_total": 3,
108
+ "issues_by_severity": {"high": 1, "medium": 1, "low": 1}
109
+ }
110
+ }
111
+ ```
112
+
113
+ 字段约定:
114
+ - `type`:枚举值为 `"character_mapping"` / `"relationship_jump"` / `"location_contradiction"` / `"timeline_contradiction"`。
115
+ - `severity`:枚举值为 `"high"` / `"medium"` / `"low"`。
116
+ - `confidence`:枚举值为 `"high"` / `"medium"` / `"low"`。
117
+ - `stats.chapters_missing`(可选):章节文件缺失数(在 `chapter_range` 内但对应 `chapters/chapter-*.md` 不存在)。
118
+ - `stats.ner_ok / stats.ner_failed`(可选):NER 成功/失败的章节数(用于降级诊断;不影响 schema 最小要求)。
119
+ - `stats.ner_failed_sample`(可选):首个 NER 失败原因的截断字符串(便于快速定位脚本缺失/解析失败)。
120
+
121
+ 稳定性(回归友好)要求:
122
+ - `issues[].id` 必须可由 **type + 关键实体 + time_marker** 确定性生成(同批次重复运行应保持一致)。
123
+ - `issues` 输出顺序:`severity (high→low)` → `type` → `id`。
124
+
125
+ ### Issue 类型说明(最小集合)
126
+
127
+ - `character_mapping`:同一 `display_name` 映射到多个 slug,或 state/档案 display_name 不一致。
128
+ - `relationship_jump`:关系值单章剧烈变化(例如从 80 跳到 -50),或出现互相矛盾的关系描述(需给 evidence)。
129
+ - `location_contradiction`:同一 time_marker 下角色在多个地点(高置信要求提供明确 time_marker + 角色 + 地点证据)。
130
+ - `timeline_contradiction`:跨故事线并发状态(concurrent_state)与本章 time_marker/事件顺序矛盾(用于 LS-001 输入)。
131
+
132
+ > 注:当前 TypeScript 确定性实现(`src/consistency-auditor.ts`)为最小可用版本,稳定产出 `location_contradiction` / `timeline_contradiction`;`character_mapping` / `relationship_jump` 为预留类型,后续再补全。
133
+
134
+ ## 3) 检测规则建议(实现导向)
135
+
136
+ 本节提供"最小但不乱报"的实现建议,避免检查逻辑断链。你可以更严格,但不得牺牲可用性(误报过多会导致用户疲劳)。
137
+
138
+ ### 3.1 角色一致性(`character_mapping` / `relationship_jump`)
139
+
140
+ - **display_name ↔ slug 映射冲突**(高优先级):
141
+ - 基于 `characters/active/*.json.display_name` + `state/current-state.json.characters.{id}.display_name` 构建 `display_name -> [slug_id...]`;
142
+ - 若同一 `display_name` 映射到多个 slug → `character_mapping`(severity=high, confidence=high)。
143
+ - **state/档案 display_name 不一致**:
144
+ - 若 `characters/active/{id}.json.display_name != state/current-state.json.characters.{id}.display_name` → `character_mapping`(severity=medium, confidence=high)。
145
+ - **关系值剧烈跳变(仅标记,不直接 hard gate)**:
146
+ - 从 `state/changelog.jsonl` 中筛选章节范围内、且 path 匹配 `characters.{id}.relationships.{other}` 的 ops;
147
+ - 若单章 `set` 发生**符号翻转**(例如 80 → -10)或 `inc` 的绝对值 ≥ 60 → `relationship_jump`(severity=medium, confidence=medium);
148
+ - evidence:至少提供 1 条 changelog 证据(或章节片段)说明变化发生的章节与上下文。
149
+
150
+ ### 3.2 空间一致性(`location_contradiction`)
151
+
152
+ - 为每章生成 `primary_time_marker`(优先 `confidence=high` 的 time_marker;缺失则降级为 medium;仍无则置空)。
153
+ - 从 NER `mentions` 中提取"共现证据":
154
+ - 将同一行 snippet 里同时出现的 **角色名 + 地点名** 视为一个候选"位置事实"(fact),并附带该行的 time_marker(若该行或本章存在)。
155
+ - **高置信矛盾**(满足才输出 severity=high):
156
+ - 同一角色,在相同 `primary_time_marker`(精确文本相等)下,出现两个不同地点事实(地点 text 不同),且两侧均有明确 evidence(含行号/片段)。
157
+ - **中低置信提示**:
158
+ - time_marker 缺失但 1-2 章窗口内位置多次跳变 → severity=low/medium(用于提醒,避免误判"正常赶路")。
159
+
160
+ ### 3.3 时间线一致性(`timeline_contradiction`,对齐 LS-001 hard 输入)
161
+
162
+ 优先使用 L3 契约的并发状态(`volumes/vol-{V:02d}/chapter-contracts/chapter-{C:03d}.json.storyline_context.concurrent_state`):
163
+
164
+ - 尝试从 concurrent_state 文本中解析章节号(常见格式:`(ch25)` / `(ch25)`),regex:`/[((]\s*ch\s*(\d+)\s*[))]/giu`。
165
+ - 为"当前章"与"并发线引用章"分别取 `primary_time_marker`(来自各自章节的 NER time_markers)。
166
+ - **矛盾判定**(保守):
167
+ - 若双方都为高置信 time_marker,且包含明确的"年/季节"差异(例如 `第三年冬` vs `第三年夏`)→ `timeline_contradiction`(severity=high, confidence=high)
168
+ - 若仅相对时间词(次日/当晚)或缺失 → 降级为 medium/low(提示,不应直接硬阻断)
169
+
170
+ ## 4) 提供给 QualityJudge 的结构化输入(LS-001)
171
+
172
+ 入口 Skill 在调用 QualityJudge 前,应将"一致性报告中与当前章相关的 timeline/location issues"裁剪为小体积摘要注入:
173
+
174
+ ```json
175
+ {
176
+ "ls_001_signals": [
177
+ {
178
+ "issue_id": "timeline_contradiction:storylines=jiangwang-dao|jinghai-plan:time=第三年冬末",
179
+ "confidence": "high",
180
+ "evidence": [{"chapter": 48, "snippet": "……"}],
181
+ "suggestion": "补齐并发线的时空锚点,或调整事件发生顺序。"
182
+ }
183
+ ]
184
+ }
185
+ ```
186
+
187
+ > 约束:仅 `confidence="high"` 的信号建议作为 hard gate 的强输入;`medium/low` 仅提示,不应直接阻断。
@@ -0,0 +1,64 @@
1
+ # M3 风格漂移与黑名单(文件协议)
2
+
3
+ **1) `style-drift.json`(项目根目录,可选)**
4
+
5
+ - 用途:当检测到风格漂移时写入,用于后续章节对 ChapterWriter/StyleRefiner 进行"正向纠偏"注入
6
+ - 注入规则:仅当 `active=true` 时注入;`active=false` 视为历史记录,不再注入
7
+ - 注入目标:当前固定为 `["ChapterWriter", "StyleRefiner"]`;若未来新增消费方,需扩展 `injected_to` 并同步 Step 2.6 context assembly
8
+
9
+ 最小格式:
10
+ ```json
11
+ {
12
+ "active": true,
13
+ "detected_chapter": 25,
14
+ "window": [21, 25],
15
+ "drifts": [
16
+ {"metric": "avg_sentence_length", "baseline": 18, "current": 24, "directive": "句子过长,回归短句节奏"},
17
+ {"metric": "dialogue_ratio", "baseline": 0.4, "current": 0.28, "directive": "对话偏少,增加角色互动"}
18
+ ],
19
+ "injected_to": ["ChapterWriter", "StyleRefiner"],
20
+ "created_at": "2026-02-24T05:00:00Z",
21
+ "cleared_at": null,
22
+ "cleared_reason": null
23
+ }
24
+ ```
25
+
26
+ **2) `ai-blacklist.json`(项目根目录)**
27
+
28
+ - `words[]`:生效黑名单(生成时禁止、润色时替换、评估时计入命中率)
29
+ - `whitelist[]`(可选):豁免词条(不替换、不计入命中率、不得自动加入 words)
30
+ - `update_log[]`(可选,append-only):记录每次自动/手动变更(added/exempted/removed)的 evidence 与时间戳,便于审计
31
+
32
+ 建议扩展(兼容模板;无则视为空):
33
+ ```json
34
+ {
35
+ "whitelist": [],
36
+ "update_log": [
37
+ {
38
+ "timestamp": "2026-02-24T05:00:00Z",
39
+ "chapter": 47,
40
+ "source": "auto",
41
+ "added": [
42
+ {"phrase": "值得一提的是", "count_in_chapter": 3, "examples": ["例句 1", "例句 2"]}
43
+ ],
44
+ "exempted": [
45
+ {"phrase": "老子", "reason": "style_profile.preferred_expressions", "examples": ["例句 1"]}
46
+ ],
47
+ "note": "本次变更摘要"
48
+ }
49
+ ]
50
+ }
51
+ ```
52
+
53
+ **3) `${NOVEL_CLI_ROOT}/scripts/lint-blacklist.sh`(可选)**
54
+
55
+ - 输入:`<chapter.md> <ai-blacklist.json>`
56
+ - 输出:stdout JSON(exit 0),至少包含:
57
+ - `total_hits`、`hits_per_kchars`(次/千字)、`hits[]`(word/count/lines/snippets)
58
+ - 失败回退:脚本不存在 / 退出码非 0 / stdout 非 JSON → 不阻断,QualityJudge 改为启发式估计并标注"估计值"
59
+
60
+ **4) `${NOVEL_CLI_ROOT}/scripts/run-ner.sh`(可选)**
61
+
62
+ - 输入:`<chapter.md>`
63
+ - 输出:stdout JSON(exit 0),至少包含:schema_version、chapter_path、entities(characters/locations/time_markers/events + evidence);完整 schema 见 `continuity-checks.md`
64
+ - 失败回退:脚本不存在 / 退出码非 0 / stdout 非 JSON → 不阻断;入口 Skill/QualityJudge 走 LLM fallback(抽取实体 + 输出 confidence)
@@ -0,0 +1,130 @@
1
+ # 伏笔追踪(事实索引 + 卷内计划)
2
+
3
+ > 本文用于约束 `/novel:continue` 的 **foreshadowing_tasks 注入** 与 **commit 阶段 global.json 合并**。
4
+ > 源头定义:`docs/dr-workflow/novel-writer-tool/final/prd/09-data.md`(schema 示例)与 `docs/dr-workflow/novel-writer-tool/final/spec/06-extensions.md`(query-foreshadow.sh 扩展点)。
5
+
6
+ ## 1) `foreshadowing/global.json`(事实层,跨卷)
7
+
8
+ 最小格式(JSON object):
9
+
10
+ ```json
11
+ {
12
+ "foreshadowing": []
13
+ }
14
+ ```
15
+
16
+ 条目 schema(每个 item):
17
+
18
+ ```json
19
+ {
20
+ "id": "ancient_prophecy",
21
+ "description": "远古预言暗示主角命运",
22
+ "scope": "short | medium | long",
23
+ "status": "planted | advanced | resolved",
24
+ "planted_chapter": 3,
25
+ "planted_storyline": "main-arc",
26
+ "target_resolve_range": [10, 20],
27
+ "last_updated_chapter": 48,
28
+ "history": [
29
+ {"chapter": 3, "action": "planted", "detail": "…"},
30
+ {"chapter": 15, "action": "advanced", "detail": "…"},
31
+ {"chapter": 48, "action": "advanced", "detail": "…"}
32
+ ]
33
+ }
34
+ ```
35
+
36
+ 语义规则:
37
+
38
+ - `status` 仅允许:`planted` → `advanced`(可多次)→ `resolved`。
39
+ - `scope` 分层语义(用于风险提示,而不是强制门控):
40
+ - `short`:卷内(约 3-10 章)应回收;**超期**规则见 §4。
41
+ - `medium`:跨 1-3 卷回收。
42
+ - `long`:全书级,无固定回收期限(**不触发超期警告**)。
43
+ - `target_resolve_range`:
44
+ - 可缺失(表示未设定明确回收窗口)。
45
+ - 若存在,必须是 `[start, end]` 且 `start/end` 为整数、`start <= end`。
46
+ - `history[]`:
47
+ - `action` ∈ `{"planted","advanced","resolved"}`。
48
+ - 允许多次 `advanced`(不同 chapter)。
49
+ - 建议按 `chapter` 升序;commit 阶段追加即可(无需重排)。
50
+
51
+ ## 2) `volumes/vol-{V:02d}/foreshadowing.json`(计划层,本卷)
52
+
53
+ 用途:
54
+ - 提供本卷“新增 + 延续”的伏笔计划基线,用于:
55
+ 1) 组装 `foreshadowing_tasks`(章节写作注入)
56
+ 2) 卷末回顾对照(计划 vs 事实)
57
+
58
+ 建议格式(JSON object;`volume` 可选):
59
+
60
+ ```json
61
+ {
62
+ "volume": 2,
63
+ "foreshadowing": [
64
+ {
65
+ "id": "ancient_prophecy",
66
+ "description": "远古预言暗示主角命运",
67
+ "scope": "long",
68
+ "status": "advanced",
69
+ "planted_chapter": 3,
70
+ "target_resolve_range": [40, 55],
71
+ "history": []
72
+ }
73
+ ]
74
+ }
75
+ ```
76
+
77
+ 说明:
78
+ - 计划层条目字段允许与 global 结构一致(便于复用与对照),但它不是事实源:
79
+ - 对“本卷新增伏笔”,`planted_chapter/target_resolve_range` 表示**计划章范围**;
80
+ - 对“上卷延续伏笔”,`planted_chapter/status` 应与 global 事实一致,`target_resolve_range` 可被 PlotArchitect 调整为本卷计划窗口。
81
+
82
+ ## 3) Commit 阶段:从 `staging/state/chapter-{C:03d}-delta.json` 合并 foreshadow ops → `foreshadowing/global.json`
83
+
84
+ 输入:`staging/state/chapter-{C:03d}-delta.json.ops[]` 中 `op == "foreshadow"` 的记录:
85
+
86
+ ```json
87
+ {"op":"foreshadow","path":"ancient_prophecy","value":"advanced","detail":"主角梦见预言碎片"}
88
+ ```
89
+
90
+ 合并策略(幂等、去重、不中断写作):
91
+
92
+ 1. 读取 `foreshadowing/global.json`;若不存在则初始化为 `{"foreshadowing":[]}`。
93
+ 2. 读取(可选)本卷计划 `volumes/vol-{V:02d}/foreshadowing.json`,用于在 global 缺条目/缺元数据时补齐 `description/scope/target_resolve_range`(**仅在缺失时回填,不覆盖已有事实字段**)。
94
+ 3. 对每条 `foreshadow` op(按 ops 顺序处理):
95
+ - `id = path`,`action = value`,`detail = detail || ""`
96
+ - 找到对应 item(按 `id` 精确匹配);若不存在则创建:
97
+ - `id`: op.path
98
+ - `description/scope/target_resolve_range`: 优先从本卷计划同 ID 条目回填;否则写入占位(`description = id`,`scope = "medium"`,`target_resolve_range = null`)
99
+ - `planted_chapter`: 若 `action=="planted"` 则写入 `C`,否则留空
100
+ - `planted_storyline`: 从 delta 顶层 `storyline_id` 回填(如存在)
101
+ - `status`: 先设为 `action`
102
+ - `last_updated_chapter = C`
103
+ - `history = []`
104
+ - `history` 去重(保证重复 commit 不重复追加):
105
+ - 若已存在 `{chapter: C, action: action}` 的记录 → 跳过追加
106
+ - 否则追加 `{chapter: C, action: action, detail: detail}`
107
+ - 字段更新:
108
+ - `last_updated_chapter = max(existing, C)`
109
+ - `planted_chapter`:若 `action=="planted"` 且为空 → 设为 `C`(不回退更早 planted)
110
+ - `planted_storyline`:若为空且 delta.storyline_id 存在 → 设为该值
111
+ - `status`:只允许单调推进:
112
+ - 若 `action=="resolved"` → 设为 `resolved`
113
+ - 若 `action=="advanced"` 且当前非 `resolved` → 设为 `advanced`
114
+ - 若 `action=="planted"` 且当前为空/`planted` → 设为 `planted`(不得把 `advanced/resolved` 降级为 `planted`)
115
+ 4. 写回 `foreshadowing/global.json`(JSON,UTF-8)。
116
+ 5. 若遇到异常数据(例如 action 非法、JSON 解析失败、global.json 结构不是 object/foreshadowing 不是 list):
117
+ - 不要悄悄吞掉:输出明确错误与修复建议;
118
+ - 但**尽量避免阻断整章 commit**:可降级为“跳过本章 foreshadow 合并并写入 warning”,保证章节与 state 合并仍可完成。
119
+
120
+ ## 4) Overdue(超期)判定(用于 `/novel:status` 与回顾报告)
121
+
122
+ - 仅对 `scope == "short"` 且 `status != "resolved"` 的条目做超期提示。
123
+ - 若存在 `target_resolve_range = [start, end]` 且 `last_completed_chapter > end`:标记为 **超期**。
124
+ - `scope == "long"`:不做超期提示。
125
+
126
+ ## 5) `${NOVEL_CLI_ROOT}/scripts/query-foreshadow.sh`(可选确定性扩展点)
127
+
128
+ - 输入:`<chapter_num>`
129
+ - 输出:stdout JSON(exit 0),其中 `.items` 为“本章相关伏笔条目子集”(list of objects,字段建议与 global 条目一致:`id/description/scope/status/target_resolve_range/...`)
130
+ - 失败回退:脚本不存在 / 退出码非 0 / stdout 非 JSON / JSON 缺 `.items` → 不阻断流水线,入口 Skill 必须回退规则过滤(见 `skills/continue/SKILL.md` Step 2.5 第 6 项)
@@ -0,0 +1,53 @@
1
+ # 质量门控决策引擎(Gate Decision Engine)
2
+
3
+ ## high_violation 函数定义与 hard gate 输入(仅认 high confidence)
4
+
5
+ - high_violation(eval) := 任一 contract_verification.{l1,l2,l3}_checks 中存在 status="violation" 且 confidence="high"
6
+ 或任一 contract_verification.ls_checks 中存在 status="violation" 且 confidence="high" 且(constraint_type 缺失或 == "hard")
7
+ - has_high_confidence_violation:取自 Step 4 的计算结果(关键章=双裁判 OR 合并,普通章=单裁判)
8
+ > confidence=medium/low 仅记录警告,不触发 hard gate(避免误报疲劳)
9
+
10
+ ## 固化门控决策函数(输出 gate_decision)
11
+
12
+ ```
13
+ if has_high_confidence_violation:
14
+ gate_decision = "revise"
15
+ else:
16
+ if overall_final >= 4.0: gate_decision = "pass"
17
+ elif overall_final >= 3.5: gate_decision = "polish"
18
+ elif overall_final >= 3.0: gate_decision = "revise"
19
+ elif overall_final >= 2.0: gate_decision = "pause_for_user"
20
+ else: gate_decision = "pause_for_user_force_rewrite"
21
+ ```
22
+
23
+ ## 自动修订闭环(max revisions = 2)
24
+
25
+ - 若 gate_decision="revise" 且 revision_count < 2:
26
+ - 更新 checkpoint: orchestrator_state="CHAPTER_REWRITE", pipeline_stage="revising", revision_count += 1
27
+ - 调用 ChapterWriter 修订模式(Task(subagent_type="chapter-writer", model="opus")):
28
+ - 输入: chapter_writer_revision_manifest(在 chapter_writer_manifest 基础上追加 inline 字段 `required_fixes` + `high_confidence_violations`,paths 追加 `chapter_draft` 指向 staging 中的现有正文)
29
+ - 修订指令:以 eval.required_fixes 作为最小修订指令;若 required_fixes 为空,则用 high_confidence_violations 生成 3-5 条最小修订指令兜底;若两者均为空(score 3.0-3.4 无 violation 触发),则从 eval 的 8 维度中取最低分 2 个维度的 feedback 作为修订方向
30
+ - 约束:定向修改 required_fixes 指定段落,尽量保持其余内容不变
31
+ - 回到步骤 2 重新走 Summarizer -> StyleRefiner -> QualityJudge -> 门控(保证摘要/state/crossref 与正文一致)
32
+
33
+ - 若 gate_decision="revise" 且 revision_count == 2(次数耗尽):
34
+ - 若 has_high_confidence_violation=false 且 overall_final >= 3.0:
35
+ - 设置 force_passed=true,允许提交(避免无限循环)
36
+ - 记录:eval metadata + log 中标记 force_passed=true(门控被上限策略终止)
37
+ - 将 gate_decision 覆写为 "pass"
38
+ - 否则:
39
+ - 释放并发锁(rm -rf .novel.lock)并暂停,提示用户在 `/novel:start` 决策下一步(手动修订/重写/接受)
40
+
41
+ ## 其他决策的后续动作
42
+
43
+ - gate_decision="pass":直接进入 commit
44
+ - gate_decision="polish":更新 checkpoint: pipeline_stage="revising" -> StyleRefiner 二次润色后进入 commit(不再重复 QualityJudge 以控成本)
45
+ - gate_decision="pause_for_user" / "pause_for_user_force_rewrite":释放并发锁(rm -rf .novel.lock)并暂停,等待用户通过 `/novel:start` 决策
46
+
47
+ ## 写入评估与门控元数据(可追溯)
48
+
49
+ - 写入 staging/evaluations/chapter-{C:03d}-eval.json:
50
+ - 内容:eval_used(普通章=primary_eval;关键章=overall 更低的一次)+ metadata
51
+ - metadata 至少包含:
52
+ - judges: {primary:{model,overall}, secondary?:{model,overall}, used, overall_final}
53
+ - gate: {decision: gate_decision, revisions: revision_count, force_passed: bool}
@@ -0,0 +1,46 @@
1
+ # M3 周期性维护
2
+
3
+ ## AI 黑名单动态维护(不阻断)
4
+
5
+ - 从 eval_used.anti_ai.blacklist_update_suggestions[] 读取新增候选(必须包含:phrase + count_in_chapter + examples)
6
+ - 增长上限检查:若 `words[]` 长度 >= 80,跳过自动追加,仅记录到 `update_log[]`(source="auto_skipped_cap"),并在 `/novel:start` 质量回顾中提示用户审核黑名单规模
7
+ - 自动追加门槛(保守,避免误伤):
8
+ - `confidence in {medium, high}` 且 `count_in_chapter >= 3` -> 才允许自动追加
9
+ - 其余仅记录为"候选建议",不自动写入(可在 `/novel:start` 质量回顾中提示用户手动处理)
10
+ - 注意:当前门槛为单章统计;跨章高频但单章 < 3 的词不会自动追加,依赖用户在质量回顾中审核候选列表
11
+ - 更新 `ai-blacklist.json`(按文件协议;幂等、可追溯):
12
+ - 确保存在 `whitelist[]` 与 `update_log[]`(不存在则创建为空)
13
+ - added:追加到 `words[]`(去重;若已存在于 words/whitelist 则跳过)
14
+ - exempted(误伤保护,自动豁免,不作为命中/不替换):
15
+ - 若候选短语命中 `style-profile.json.preferred_expressions[]`(样本高频表达)或用户显式 `ai-blacklist.json.whitelist[]`:
16
+ - 将其加入 whitelist(若未存在)
17
+ - 记录为 exempted,并且**不得加入** words
18
+ - 更新 `last_updated`(YYYY-MM-DD)与 `version`(若存在且为合法 semver 则 patch bump;字段缺失或不可解析时仅更新 `last_updated`,不创建 `version`)
19
+ - 追加 `update_log[]`(append-only):记录 timestamp/chapter/source="auto"/added/exempted + evidence(例句)
20
+ - 用户可控入口:
21
+ - 用户可手动编辑 `ai-blacklist.json` 的 `words[]/whitelist[]`
22
+ - 若用户删除某词但不希望未来被自动再加回,请将其加入 `whitelist[]`(系统不得自动加入 whitelist 内词条)
23
+
24
+ ## 风格漂移检测与纠偏(每 5 章触发)
25
+
26
+ - 触发条件:last_completed_chapter % 5 == 0
27
+ - 窗口:读取最近 5 章 `chapters/chapter-{C-4..C}.md`
28
+ - 调用 StyleAnalyzer 提取当前 metrics(仅需 avg_sentence_length / dialogue_ratio;其余字段可忽略)
29
+ - 与 `style-profile.json` 基线对比(相对偏移,确定性公式):
30
+ - 前置检查:若 `base.avg_sentence_length` 为 null/0 或 `base.dialogue_ratio` 为 null/0,跳过对应维度的漂移检测(记录日志 "baseline metric unavailable, skipping drift check")
31
+ - `sentence_dev = abs(curr.avg_sentence_length - base.avg_sentence_length) / base.avg_sentence_length`
32
+ - `dialogue_dev = abs(curr.dialogue_ratio - base.dialogue_ratio) / base.dialogue_ratio`
33
+ - 漂移判定:
34
+ - `sentence_dev > 0.20` 或 `dialogue_dev > 0.15` -> drift=true
35
+ - 回归判定:`sentence_dev < 0.10` 且 `dialogue_dev < 0.10` -> recovered=true
36
+ - drift=true:
37
+ - 写入/更新 `style-drift.json`(按文件协议;active=true)
38
+ - drifts[].directive 生成规则(最多 3 条,短句可执行):
39
+ - 句长偏长:强调短句/动作推进/拆句
40
+ - 句长偏短:允许适度长句与节奏变化(但仍以 style-profile 为准)
41
+ - 对话偏少:强调通过对话推进(交给 ChapterWriter;StyleRefiner 不得硬造新对白)
42
+ - 对话偏多:加强叙述性承接与内心活动(不删对白,仅调整段落与叙述衔接)
43
+ - recovered=true:
44
+ - 清除纠偏:删除 `style-drift.json` 或标记 `active=false`,并写入 `cleared_at/cleared_reason="metrics_recovered"`
45
+ - 超时清除:若当前章 - `style-drift.json.detected_chapter` > 15(即纠偏指令已注入超过 15 章仍未回归),自动标记 `active=false`,`cleared_reason="stale_timeout"`
46
+ - 其余情况:保持现状(不新增、不清除),避免频繁抖动