@cloud411716/fancy-webnovel 1.0.45 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/client.js +183 -0
  2. package/cordis.patch.yml +0 -14
  3. package/index.js +21 -37
  4. package/lib/chinese-num.js +111 -0
  5. package/lib/deployed-state.js +141 -0
  6. package/lib/error-codes.js +104 -0
  7. package/lib/infra.js +569 -0
  8. package/lib/llm-wiki.js +109 -0
  9. package/lib/safe-rm.js +94 -0
  10. package/lib/scraper-utils.js +165 -0
  11. package/package.json +37 -5
  12. package/plugins/fancy-analyze/command-input.js +65 -0
  13. package/plugins/fancy-analyze/index.js +973 -0
  14. package/plugins/fancy-analyze/schema-analyze.md +379 -0
  15. package/plugins/fancy-analyze/schema-write.md +178 -0
  16. package/plugins/fancy-analyze/scripts/split_source.js +292 -0
  17. package/plugins/fancy-analyze/summary-progress.js +431 -0
  18. package/plugins/fancy-analyze/templates.js +881 -0
  19. package/plugins/fancy-bootstrap/index.js +168 -0
  20. package/plugins/fancy-bootstrap/scripts/init.js +143 -0
  21. package/plugins/fancy-bootstrap/scripts/path-utils.js +21 -0
  22. package/plugins/fancy-bootstrap/templates.js +54 -0
  23. package/plugins/fancy-import/import-core.js +1118 -0
  24. package/plugins/fancy-import/import-driver.js +581 -0
  25. package/plugins/fancy-import/index.js +730 -0
  26. package/plugins/fancy-import/prompts/01-import-prompt.md +106 -0
  27. package/plugins/fancy-import/templates.js +49 -0
  28. package/plugins/fancy-scan/index.js +319 -0
  29. package/plugins/fancy-scan/scripts/run-scan.js +520 -0
  30. package/plugins/fancy-scan/scripts/scrapers/fanqie-rank-scraper.js +794 -0
  31. package/plugins/fancy-scan/scripts/scrapers/jjwxc-rank-scraper.js +339 -0
  32. package/plugins/fancy-scan/scripts/scrapers/qimao-rank-scraper.js +611 -0
  33. package/plugins/fancy-scan/templates.js +295 -0
  34. package/plugins/fancy-story-builder/deploy-manifest.js +239 -0
  35. package/plugins/fancy-story-builder/deslop-driver.js +821 -0
  36. package/plugins/fancy-story-builder/flesh-output.js +155 -0
  37. package/plugins/fancy-story-builder/index.js +3152 -0
  38. package/plugins/fancy-story-builder/prompts/01-skeleton-prompt.md +553 -0
  39. package/plugins/fancy-story-builder/prompts/02-flesh-prompt.md +540 -0
  40. package/plugins/fancy-story-builder/prompts/03-rhythm-prompt.md +504 -0
  41. package/plugins/fancy-story-builder/prompts/04-writing-prompt.md +188 -0
  42. package/plugins/fancy-story-builder/prompts/05-deslop-prompt.md +96 -0
  43. package/plugins/fancy-story-builder/purpose-template.md +107 -0
  44. package/plugins/fancy-story-builder/references/formulas-rhythm.json +302 -0
  45. package/plugins/fancy-story-builder/references/formulas-skeleton.json +229 -0
  46. package/plugins/fancy-story-builder/references/glossary.md +585 -0
  47. package/plugins/fancy-story-builder/references/index.md +490 -0
  48. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/00-index.md +174 -0
  49. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/01-scene.md +150 -0
  50. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/02-chapter-workflow.md +99 -0
  51. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/03-opening-chapter.md +104 -0
  52. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/04-dialogue-mastery.md +274 -0
  53. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/05-genre-formulas.md +432 -0
  54. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/06-tone-ensemble.md +259 -0
  55. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/07-ledger-audit.md +222 -0
  56. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/08-diagnosis-revision.md +205 -0
  57. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/09-reading-power.md +135 -0
  58. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/10-deslop.md +129 -0
  59. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/00-index.md +98 -0
  60. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/01-upgrade-rhythm.md +300 -0
  61. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/02-arc-rhythm.md +168 -0
  62. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/03-highpoint-hooks.md +189 -0
  63. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/04-satisfaction.md +226 -0
  64. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/05-foreshadow-density.md +195 -0
  65. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/06-chapter-outline.md +226 -0
  66. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/07-audit.md +163 -0
  67. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/00-index.md +100 -0
  68. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/01-character.md +239 -0
  69. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/02-worldbuilding.md +245 -0
  70. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/03-relations.md +181 -0
  71. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/04-emotion-methods.md +286 -0
  72. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/05-audit.md +177 -0
  73. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/00-index.md +91 -0
  74. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/01-positioning.md +208 -0
  75. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/02-packaging.md +135 -0
  76. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/03-entities.md +305 -0
  77. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/04-conflict.md +328 -0
  78. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/05-quota.md +187 -0
  79. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/06-audit.md +142 -0
  80. package/plugins/fancy-story-builder/rhythm-output.js +140 -0
  81. package/plugins/fancy-story-builder/schema-setting.md +324 -0
  82. package/plugins/fancy-story-builder/scripts/ai-patterns.js +619 -0
  83. package/plugins/fancy-story-builder/scripts/check-guide-compliance.js +289 -0
  84. package/plugins/fancy-story-builder/scripts/content-quality.js +413 -0
  85. package/plugins/fancy-story-builder/scripts/deslop-validate.js +143 -0
  86. package/plugins/fancy-story-builder/scripts/error-classes.js +112 -0
  87. package/plugins/fancy-story-builder/scripts/flesh-validate.js +1189 -0
  88. package/plugins/fancy-story-builder/scripts/formula-engine.js +112 -0
  89. package/plugins/fancy-story-builder/scripts/graph-utils.js +1385 -0
  90. package/plugins/fancy-story-builder/scripts/label-coverage.js +171 -0
  91. package/plugins/fancy-story-builder/scripts/prompt-drift-check.js +441 -0
  92. package/plugins/fancy-story-builder/scripts/reading-power.js +298 -0
  93. package/plugins/fancy-story-builder/scripts/rhythm-validate.js +1216 -0
  94. package/plugins/fancy-story-builder/scripts/semantic-lint.js +377 -0
  95. package/plugins/fancy-story-builder/scripts/skeleton-quota.js +145 -0
  96. package/plugins/fancy-story-builder/scripts/skeleton-validate.js +1832 -0
  97. package/plugins/fancy-story-builder/scripts/stage-batches.js +1371 -0
  98. package/plugins/fancy-story-builder/scripts/stage-contract.js +417 -0
  99. package/plugins/fancy-story-builder/scripts/upstream-query.js +198 -0
  100. package/plugins/fancy-story-builder/scripts/writing-rules.js +167 -0
  101. package/plugins/fancy-story-builder/scripts/writing-validate.js +682 -0
  102. package/plugins/fancy-story-builder/skeleton-output.js +199 -0
  103. package/plugins/fancy-story-builder/stage-driver.js +1049 -0
  104. package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//346/227/266/345/272/217/346/230/240/345/260/204/346/250/241/346/235/277.json +204 -0
  105. package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//347/253/240/350/212/202/347/273/206/347/272/262/346/250/241/346/235/277.json +34 -0
  106. package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//347/253/240/350/212/202/350/247/204/345/210/222/346/250/241/346/235/277.json +24 -0
  107. package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//350/212/202/345/245/217/345/217/202/346/225/260/346/250/241/346/235/277.json +27 -0
  108. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//344/270/226/347/225/214/350/247/202/350/247/204/345/210/231/346/250/241/346/235/277.json +66 -0
  109. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/205/263/351/224/256/351/201/223/345/205/267/346/250/241/346/235/277.json +76 -0
  110. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/206/262/347/252/201/346/250/241/346/235/277.json +72 -0
  111. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/211/247/346/203/205/346/250/241/346/235/277.json +37 -0
  112. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/212/277/345/212/233/346/250/241/346/235/277.json +53 -0
  113. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/234/260/347/202/271/346/250/241/346/235/277.json +40 -0
  114. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//347/273/223/345/261/200/346/250/241/346/235/277.json +79 -0
  115. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//350/247/222/350/211/262/346/250/241/346/235/277.json +132 -0
  116. package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//345/205/263/347/263/273/346/250/241/346/235/277.json +133 -0
  117. package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//345/256/236/344/275/223/346/250/241/346/235/277.json +160 -0
  118. package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//345/261/202/347/272/247/346/250/241/346/235/277.json +65 -0
  119. package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//346/241/206/346/236/266/346/250/241/346/235/277.json +134 -0
  120. package/plugins/fancy-story-builder/templates.js +1248 -0
  121. package/plugins/fancy-story-builder/wiki-references.js +204 -0
  122. package/plugins/fancy-story-builder/writing-driver.js +1061 -0
  123. package/plugins/fancy-topic/index.js +704 -0
  124. package/plugins/fancy-topic/scripts/select-topic.js +274 -0
  125. package/plugins/fancy-topic/templates.js +373 -0
  126. package/commands/bootstrap/index.js +0 -160
  127. package/commands/bootstrap/init-project.js +0 -121
  128. package/commands/scan/index.js +0 -275
  129. package/commands/scan/platforms/fanqie.js +0 -146
  130. package/commands/scan/platforms/jinjiang.js +0 -143
  131. package/commands/scan/platforms/qidian.js +0 -102
  132. package/commands/scan/platforms/qimao.js +0 -127
  133. package/commands/scan/scraper.js +0 -195
  134. package/infra.js +0 -160
@@ -0,0 +1,504 @@
1
+ # 03 节奏阶段产物契约
2
+
3
+ > 本文件是节奏阶段的**产物契约**:它定义每个文件长什么样、哪些写法会被判错。
4
+ >
5
+ > **流程由插件驱动**:插件会分批派活,每轮告诉你写哪个文件、哪些章/哪些条目、
6
+ > 写到哪里;插件自己跑校验脚本、判定通过与否,并把判定出来的错误清单回喂给你。
7
+ > 你**不需要**自己调校验脚本,**不要**自己判断"通过没通过",**不要**输出"已完成"之类的结论。
8
+ >
9
+ > 本阶段依赖骨架 + 血肉两个上游,任一未完成不得启动。
10
+
11
+ @meta-only
12
+
13
+ ---
14
+
15
+ <!-- contract:overview -->
16
+ ## 你的职责
17
+
18
+ 你只做一件事:**按插件每轮给的指令,把指定的条目写成指定格式的 JSON**。
19
+
20
+ - 每轮指令会明确给出:要写的文件路径、要写的章号/条目、字段要求。
21
+ - 你把结果**写进指令指定的那个文件**,不要写别的文件,不要改别的文件。
22
+ - 插件会校验你写的内容。如果没过,插件会把错误清单发回给你,让你**只改被点名的那几条**。
23
+ - 写完就停。不要附带解释、不要输出总结、不要宣布完成。
24
+
25
+ **不要做的事**:
26
+
27
+ - 不要自己运行校验脚本(插件会跑,跑了也没有意义)。
28
+ - 不要修改 `模板/`、`references/` 下的任何文件。
29
+ - 不要修改 `设定/raw/sources/骨架/`、`设定/raw/sources/血肉/` 下的任何文件(只读上游)。
30
+ - 不要写指令范围之外的文件。
31
+
32
+ ---
33
+
34
+ <!-- contract:input -->
35
+ ## 一、任务输入
36
+
37
+ > 本节由插件注入,只读,不得修改。
38
+ > 数据源:骨架阶段 `设定/raw/sources/骨架/框架.json` 的 `input_snapshot`(目标章节/目标字数取自同文件的 `capacity`)。
39
+
40
+ ${input_snapshot_block}
41
+
42
+ **注**:
43
+
44
+ - 节奏阶段的**实体信息**从骨架 + 血肉的上游产物读(校验脚本实际读 6 个:骨架 `框架.json`/`实体.json`/`层级.json` + 血肉 `角色.json`/`地点.json`/`关键道具.json`),不从任务输入读。
45
+ - **目标章节 T** 和 **目标字数**是节奏阶段的硬约束:章节数、高潮/爽点/伏笔的数量配额都必须对得上。
46
+ - **硬门禁**和**反套路**同样适用于节奏阶段:例如"每 8 至 12 章必须完成一个案件单元闭环"这类硬门禁,直接决定章节切分粒度。
47
+ - 书名 / 题材 / 一句话故事 / 核心冲突只用于确认"读的是同一个项目"以及把握全剧走向。
48
+
49
+ ---
50
+
51
+ <!-- contract:output-list -->
52
+ ## 二、本阶段产出清单
53
+
54
+ | # | 文件 | 路径 | 顶层键 |
55
+ |---|---|---|---|
56
+ | 1 | 章节规划.json | `设定/raw/sources/节奏/章节规划.json` | `chapters` |
57
+ | 2 | 节奏参数.json | `设定/raw/sources/节奏/节奏参数.json` | 纯对象(无顶层数组) |
58
+ | 3 | 时序映射.json | `设定/raw/sources/节奏/时序映射.json` | 11 个 `*_timeline` 数组 |
59
+ | 4 | 章节细纲.json | `设定/raw/sources/节奏/章节细纲.json` | `outlines` |
60
+
61
+ **生成顺序(严格按依赖,不可跳)**:章节规划 → 节奏参数 → 时序映射 → 章节细纲。
62
+
63
+ 原因:**时序映射受节奏参数约束**——`main_line_timeline.key_nodes` 的实际章数必须等于 `main_progress_chapter_count`;`branch_timeline` 的实际章数之和必须等于 `branch_chapter_total`。先定参数,再写时序映射,可以**一次填对**。
64
+
65
+ 章节规划与节奏参数**无依赖关系**,可以互换顺序。
66
+
67
+ **本阶段是唯一允许出现章节号的阶段。**所有章节号集中在 4 个 JSON 中,尤其是 `时序映射.json`。
68
+
69
+ ---
70
+
71
+ <!-- contract:rules -->
72
+ ## 三、核心铁律(6 条,违反任意一条都会校验失败)
73
+
74
+ 1. **章节号必须在 `[1, T]` 内**(`T = target_chapters`)。`RHYTHM_CHAP_RANGE` 会拦截越界。
75
+
76
+ 2. **不得引用不存在的实体 id**。所有 `char_id` / `conflict_id` / `fs_id` / `climax_id` / `pleasure_id` / `main_id` / `branch_id` / `loc_id` / `item_id` / `ending_id` / `volume` 必须存在于骨架或血肉。`RHYTHM_REF_MISSING` 会拦截。
77
+
78
+ 3. **`branch_chapter_total` / `overlap_chapter_count` / `main_progress_chapter_count` 三参数必须联合满足 7 条约束**(见「节奏参数」那一节的步骤 1-7)。`RHYTHM_INFEASIBLE` 会拦截。
79
+
80
+ 4. **节奏参数 `derivations` 的 key 必须是"约束名"**(`branch_chapter_feasible` 等 7 条),不是"参数名"(`branch_chapter_total` 等 3 个)。`RHYTHM_DERIVATION_MISSING` / `RHYTHM_DERIVATION_EXTRA` 会告警(warning,不阻塞)。
81
+
82
+ 5. **钩子数必须 ≥ `ceil(T * rate)`**(如果 `is_minimum` 为 true)。`RHYTHM_HOOK_MIN` 会拦截。
83
+
84
+ 6. **所有引用 id(角色/冲突/伏笔/高潮/爽点/主线/支线/地点/道具/结局)必须来自前两阶段产物**。严禁自创实体或新编号。
85
+
86
+ ---
87
+
88
+ <!-- contract:refs -->
89
+ ## 四、参考文件
90
+
91
+ ### 4.1 必读
92
+
93
+ - `references/glossary.md` —— 术语、枚举、**章节号字段清单(第六节)**
94
+ - `references/formulas-rhythm.json` —— 共 10 个数据节(1 个 `constraints` + 1 个 `arc_phase_ratio` + 8 个 `*-guides`);`constraints` 里的 **7 条约束名见第六节**,那里是 `derivations` 的 key 清单
95
+ - 本轮要写的那个文件的模板(`模板/节奏/*.json`)
96
+
97
+ ### 4.2 按本轮文件选读
98
+
99
+ | 本轮生成文件 | 读 |
100
+ |---|---|
101
+ | 章节规划.json | `references/节奏指导/01-upgrade-rhythm.md` |
102
+ | 时序映射.json | `references/节奏指导/02-arc-rhythm.md`、`references/节奏指导/05-foreshadow-density.md` |
103
+ | 节奏参数.json | `references/节奏指导/04-satisfaction.md`、`references/节奏指导/02-arc-rhythm.md` |
104
+ | 章节细纲.json | `references/节奏指导/03-highpoint-hooks.md`、`references/节奏指导/06-chapter-outline.md` |
105
+ | 全部 | `references/节奏指导/00-index.md` |
106
+
107
+ ${benchmark_refs_block}
108
+
109
+ ### 4.3 规则
110
+
111
+ - 必读文件不可读时,停止本轮,把原因写进 `设定/raw/sources/节奏/.rhythm-build/备注.md`。
112
+ - 选读文件不可读时,跳过它继续写。
113
+ - **不要整文件读取上游 JSON**(上下文会爆炸)。插件已经把本批要用的 id+name 和相关关系列在指令里了。
114
+ - 需要某个上游实体的完整档案时,用只读查询脚本精确取:
115
+
116
+ ```bash
117
+ node references/upstream-query.js --stage=骨架 --file=实体 --id=FS042
118
+ node references/upstream-query.js --stage=血肉 --file=角色 --id=C001
119
+ ```
120
+
121
+ - 不得声称"已读取"参考文件。
122
+
123
+ ---
124
+
125
+ <!-- contract:file:章节规划 -->
126
+ ## 五、章节规划.json 的字段契约
127
+
128
+ **顶层数组**:`chapters` **模板**:`模板/节奏/章节规划模板.json`
129
+
130
+ **核心要求**:`chapters` 数组必须**覆盖 1..T 的每一章**,每章一行。
131
+
132
+ | 字段 | 写什么 |
133
+ |---|---|
134
+ | `ch` | 章节号,1..T 连续,不跳号不重复 |
135
+ | `volume` | 卷 id(骨架 `层级.json` 的 `volumes[].id`) |
136
+ | `act` | 该卷内的幕序号(1..该卷幕数) |
137
+ | `scene_focus` | 一句话,描述该章的核心场景 |
138
+
139
+ **怎么填**:
140
+
141
+ 1. 从骨架读 T 和卷列表
142
+ 2. 从 `框架.json` 的 `volume_ratio` 读每卷的章节比例
143
+ 3. 按比例分配每卷的章数(容差 ±1 章)
144
+ 4. 为每章填 `volume`(从卷列表选)和 `act`(1..卷内幕数,幕数从骨架 `层级.json` 的 acts 数推断)
145
+ 5. 为每章填 `scene_focus`
146
+
147
+ **关键约束**:
148
+
149
+ - 所有 `ch` 从 1 到 T 连续,不跳号,不重复(`RHYTHM_CHAPTER_DUP` / `RHYTHM_CHAPTER_MISSING`)
150
+ - `volume` 必须是骨架 `层级.json` 中的卷 id(`RHYTHM_REF_MISSING`)
151
+ - `act` 必须在骨架 `层级.json` 的该卷的幕集合中(`RHYTHM_ACT_INVALID`)
152
+ - 每卷的章数与 `volume_ratio` 的容差 ±1 章(`RHYTHM_VOLUME_DIST` warning)
153
+
154
+ **示例**(T=10,V=1):
155
+
156
+ ```json
157
+ {
158
+ "chapters": [
159
+ { "ch": 1, "volume": "V001", "act": 1, "scene_focus": "灭门当夜" },
160
+ { "ch": 2, "volume": "V001", "act": 1, "scene_focus": "逃出城" },
161
+ { "ch": 10, "volume": "V001", "act": 4, "scene_focus": "终局" }
162
+ ]
163
+ }
164
+ ```
165
+
166
+ ---
167
+
168
+ <!-- contract:file:节奏参数 -->
169
+ ## 六、节奏参数.json 的字段契约
170
+
171
+ **顶层**:纯对象(没有顶层数组) **模板**:`模板/节奏/节奏参数模板.json`
172
+
173
+ **这是节奏阶段最关键的文件**。三个参数必须联合满足 `formulas-rhythm.json` 的 `constraints` 节中的 7 条约束。
174
+
175
+ **必须按以下顺序反推可行区间,不得随便填再试错**:
176
+
177
+ **步骤 1**:读骨架的 T、BC(`branch_count`)
178
+
179
+ **步骤 2**:计算 `branch_chapter_total` 的可行区间:
180
+
181
+ ```
182
+ lower = max(BC * 3, floor(T * 0.3))
183
+ upper = min(BC * 30, ceil(T * 0.5))
184
+ ```
185
+
186
+ **必须在 `[lower, upper]` 内选一个值**。如果 `lower > upper`,说明骨架参数矛盾(BC 太大或太小),应回骨架阶段修复,不在节奏阶段硬凑。
187
+
188
+ **步骤 3**:从 `[lower, upper]` 内选一个值 → `branch_chapter_total`
189
+
190
+ **步骤 4**:计算 `overlap_chapter_count` 的可行区间:
191
+
192
+ ```
193
+ lower = max(0, branch_chapter_total - T + 1)
194
+ upper = branch_chapter_total
195
+ ```
196
+
197
+ **必须在 `[lower, upper]` 内选一个值**。
198
+
199
+ **步骤 5**:从区间内选一个值 → `overlap_chapter_count`
200
+
201
+ **步骤 6**:计算 `main_progress_chapter_count`:
202
+
203
+ ```
204
+ main_progress_chapter_count = T - branch_chapter_total + overlap_chapter_count
205
+ ```
206
+
207
+ **这个值由前两个参数唯一确定,不得手动指定**。
208
+
209
+ **步骤 7**:验证 7 条约束(`rhythm-validate.js` 会自动检查,所以步骤 2-6 的算法已自动满足全部 7 条):
210
+
211
+ - `branch_chapter_feasible`:`branch_chapter_total ∈ [BC*3, BC*30]`
212
+ - `branch_chapter_ratio`:`branch_chapter_total ∈ [floor(T*0.3), ceil(T*0.5)]`
213
+ - `overlap_le_branch`:`overlap <= branch_chapter_total`
214
+ - `overlap_le_main`:`overlap <= main`(由 `branch <= T` 自动满足)
215
+ - `main_progress_ge_1`:`main >= 1`
216
+ - `main_progress_consistency`:`main = T - branch + overlap`
217
+ - `main_progress_le_T`:`main <= T`
218
+
219
+ **注意**:`overlap` 可等于 `branch_chapter_total`(即所有支线章都是主线关键节点)。这是合法但极端——相当于所有主线都集中在支线区间,节奏上不推荐。脚本不拦截,由你按题材自定。
220
+
221
+ **步骤 8**:`chapter_hook_rate`:
222
+
223
+ - `rate`:通常 0.8~1.0(每章都有钩子率),按题材微调
224
+ - `is_minimum`:通常 `true`(钩子数不低于 `ceil(T * rate)`)
225
+
226
+ **步骤 9**:`derivations`:
227
+
228
+ - **key 为 `formulas-rhythm.json` 的 `constraints` 节中的 7 条约束名**(不是参数名):
229
+
230
+ - `branch_chapter_feasible`
231
+ - `branch_chapter_ratio`
232
+ - `overlap_le_branch`
233
+ - `overlap_le_main`
234
+ - `main_progress_ge_1`
235
+ - `main_progress_consistency`
236
+ - `main_progress_le_T`
237
+
238
+ - **value 为一段 `note`**,说明该约束是如何被满足的
239
+ - 示例(T=300, BC=6):
240
+
241
+ ```json
242
+ "derivations": {
243
+ "branch_chapter_feasible": "选 120 章,落在 [18, 180] 内",
244
+ "branch_chapter_ratio": "120 落在 [90, 150] 内",
245
+ "overlap_le_branch": "overlap=60 <= branch=120",
246
+ "overlap_le_main": "overlap=60 <= main=240",
247
+ "main_progress_ge_1": "main=240 >= 1",
248
+ "main_progress_consistency": "300 - 120 + 60 = 240",
249
+ "main_progress_le_T": "240 <= 300"
250
+ }
251
+ ```
252
+
253
+ - **不要用参数名作 key**(如 `"branch_chapter_total": "..."`)——会报 `RHYTHM_DERIVATION_EXTRA`(多余 key)和 `RHYTHM_DERIVATION_MISSING`(缺约束名 key)
254
+
255
+ **如果校验报 `RHYTHM_INFEASIBLE`**,按以下方向调整:
256
+
257
+ | 失败的约束 | 调整方向 |
258
+ |---|---|
259
+ | `branch_chapter_feasible` | `branch_chapter_total` 不在 `[BC*3, BC*30]`,重算步骤 2 |
260
+ | `branch_chapter_ratio` | `branch_chapter_total` 不在 `[floor(T*0.3), ceil(T*0.5)]`,重算步骤 2 |
261
+ | `overlap_le_branch` | `overlap > branch`,调小 overlap 或调大 branch |
262
+ | `overlap_le_main` | 通常不会失败(自动满足),若失败说明 `branch > T`,检查步骤 3 |
263
+ | `main_progress_ge_1` | `main < 1`,调大 overlap(不要超过 branch) |
264
+ | `main_progress_consistency` | 公式不满足,重算 `main_progress_chapter_count`(不能手动指定) |
265
+ | `main_progress_le_T` | `main > T`,调小 overlap |
266
+
267
+ ---
268
+
269
+ <!-- contract:file:时序映射 -->
270
+ ## 七、时序映射.json 的字段契约
271
+
272
+ **顶层**:11 个 `*_timeline` 数组 **模板**:`模板/节奏/时序映射模板.json`
273
+
274
+ **核心要求**:11 类时序映射必须**引用骨架/血肉中真实存在的 id**,且**布局严格匹配节奏参数**。
275
+
276
+ **步骤 1:character_timeline**
277
+
278
+ 为每个角色填:
279
+
280
+ - `char_id`:从骨架取
281
+ - `appear_start` / `appear_end`:出场区间(含)
282
+ - `exit_chapter`:有退场的填(血肉 `角色.json` 的 `exit.type !== "未退场"` 时填),必须在 `[appear_start, appear_end]` 内
283
+ - `arc_stages`:从血肉 `角色.json` 的 `arc_change_descriptions` 取 stage 名,为每个 stage 分配章节号。**章节号必须递增**(`start < turning < midpoint < end`),且必须在 `[appear_start, min(exit_chapter, appear_end)]` 内
284
+
285
+ **步骤 2:conflict_timeline**
286
+
287
+ 为每个冲突填:
288
+
289
+ - `conflict_id`:从骨架取
290
+ - `escalation_nodes`:从血肉 `冲突.json` 的 `escalation_path` 取每个节点的 `node_type`,为每个节点分配章节号。**章节号必须递增**(升级路径不允许回退)
291
+ - `resolution_chapter`:`>=` 最后一个升级节点章
292
+ - `info_gaps`:从血肉 `冲突.json` 的 `information_gaps` 取每条 `info_name`,分配 `created` / `resolved`。**`created < resolved`**
293
+
294
+ **步骤 3:foreshadow_timeline**
295
+
296
+ 为每个伏笔填:
297
+
298
+ - `fs_id`:从骨架取
299
+ - `buried_chapter` / `recovered_chapter`:**`buried_chapter < recovered_chapter`**
300
+ - 伏笔类型的回收窗口参考(小伏笔跨几章,中伏笔跨十几章,大伏笔跨卷),由血肉指导的方法论决定
301
+
302
+ **步骤 4:climax_timeline / pleasure_timeline**(id 字段分别是 `climax_id` / `pleasure_id`;`character_timeline[].arc_stages[]` 的阶段名字段是 `stage_name`)
303
+
304
+ 为每个高潮/爽点填 `chapter`。
305
+
306
+ **步骤 5:hook_timeline**
307
+
308
+ 为每章填一行(至少覆盖 `ceil(T * rate)` 章,如果 `is_minimum`):
309
+
310
+ - `ch`:1..T 中的某些章
311
+ - `hook_head`:从 glossary 的 `hook_form_head` 选
312
+ - `hook_tail`:从 glossary 的 `hook_form_tail` 选
313
+ - `hook_strength`:从 glossary 的 `hook_strength` 选
314
+ - **连续 2 章章尾同类会报 warning**,连续 3 章及以上会报 error(高潮期可豁免,在细纲中注明)
315
+
316
+ **步骤 6:main_line_timeline —— 必须严格匹配节奏参数**
317
+
318
+ 为每条主线填 `key_nodes` 数组:
319
+
320
+ - **`key_nodes` 的长度必须等于 `main_progress_chapter_count`**(节奏参数的第二个字段)
321
+ - 每个 `chapter` 必须在 `[1, T]` 内
322
+ - **不能有重复章节**(`RHYTHM_NODE_DUP` 检查)
323
+ - `event_ref`:引用某个血肉实体的 `description` 或 `escalation_path` 的节点(自由文本,建议带上下文,如 `"打脸赵天宇(参见 X002 escalation 第 3 节点)"`)
324
+
325
+ **示例**(T=300, `main_progress_chapter_count = 240`):
326
+
327
+ ```json
328
+ "main_line_timeline": [{
329
+ "main_id": "M001",
330
+ "key_nodes": [
331
+ { "chapter": 1, "event_ref": "灭门当夜" },
332
+ { "chapter": 300, "event_ref": "终局对决" }
333
+ ]
334
+ }]
335
+ ```
336
+
337
+ **共 240 个节点**(不重复章节),`chapter` 值从 1..300 中选 240 个。
338
+
339
+ **步骤 7:branch_timeline —— 必须严格匹配节奏参数**
340
+
341
+ 为每条支线填:
342
+
343
+ - `branch_id`:从骨架取
344
+ - `start` / `turning` / `resolution`:**`start <= turning <= resolution`**
345
+ - **所有支线的 `(resolution - start + 1)` 之和必须等于 `branch_chapter_total`**
346
+ - **任意两条支线的区间不能重叠**(`RHYTHM_BRANCH_OVERLAP` 检查)
347
+ - `interweaves`:交织点数组,每个 `chapter` 必须在 `[start, resolution]` 内
348
+
349
+ **步骤 8:location_timeline / item_timeline**(id 字段分别是 `loc_id` / `item_id`)
350
+
351
+ 为每个地点/道具填 `appear_start` / `appear_end`。
352
+
353
+ **步骤 9:ending_timeline**
354
+
355
+ 为每个结局填:
356
+
357
+ - `ending_id`:从骨架取
358
+ - `chapter`:**必须在主线 `key_nodes` 的章节中**(`RHYTHM_COVERAGE` 检查)
359
+ - `foreshadow_recovery`:每个 `fs_id` 从骨架取,`chapter` 为该伏笔的回收章
360
+ - **全书级结局必须回收所有大伏笔**(`RHYTHM_LARGE_FS_NOT_RECOVERED` 检查)
361
+
362
+ ---
363
+
364
+ <!-- contract:file:章节细纲 -->
365
+ ## 八、章节细纲.json 的字段契约
366
+
367
+ **顶层数组**:`outlines` **模板**:`模板/节奏/章节细纲模板.json`
368
+
369
+ **核心要求**:为**重点章**写细纲。非重点章可省略(`word_target` 由写作阶段根据 `avg_words_per_chapter` 现场生成)。
370
+
371
+ **重点章定义**(插件按这套规则算,并在指令里把本批的重点章号列出来):
372
+
373
+ - 第 1 章
374
+ - 每卷第 1 章和最后 1 章
375
+ - 大高潮章(骨架 `climaxes[].type = "大高潮"` 对应的章节)
376
+ - 结局章(`ending_timeline` 里出现的章)
377
+ - 钩子章(`hook_timeline` 里出现的章 —— 细纲的 hook 字段必须与它对齐)
378
+
379
+ **只写指令里列出的章**,不要自己加章,也不要漏。
380
+ (主线 `key_nodes` 的"转折"节点不单独算重点章:它们是自由文本,插件无法可靠识别;
381
+ 转折章如果同时是卷首/卷尾/高潮/结局/钩子章,本来就会出现在上面的清单里。)
382
+
383
+ **每行细纲填**:
384
+
385
+ | 字段 | 写什么 |
386
+ |---|---|
387
+ | `ch` | 章节号 |
388
+ | `function_main` | 主功能("推进主线"/"埋伏笔"/"爽点兑现"/"过渡"/"人物塑造") |
389
+ | `function_aux` | 辅功能 |
390
+ | `emotion_value` | 1-10(情绪值) |
391
+ | `emotion_phase` | 调动/释放/蓄力 |
392
+ | `satisfaction_type` | 爽点类型(如果本章有爽点),取 glossary `pleasure_kind` |
393
+ | `satisfaction_scale` | 爽点规模,取 glossary `pleasure_scale` |
394
+ | `satisfaction_audience` | 爽点面向谁 |
395
+ | `foreshadow_action` | 本章埋/推/收哪些伏笔(如 `"埋 FS003"`) |
396
+ | `faction_appearance` | 本章出场哪些势力 |
397
+ | `hook_head` / `hook_tail` / `hook_strength` | 钩子(与 `hook_timeline` 中该章的钩子对应) |
398
+ | `word_target` | 字数目标,偏离 `avg_words_per_chapter` 超过 ±20% 会报 warning |
399
+
400
+ ---
401
+
402
+ <!-- contract:consistency -->
403
+ ## 九、跨文件一致性(本阶段最容易翻车的地方)
404
+
405
+ 节奏阶段的多数校验是**全量口径**的——单看一个文件查不出来,必须四个文件对得上。
406
+
407
+ | 一致性 | 检查代码 | 类型 |
408
+ |---|---|---|
409
+ | 主线 key_nodes 数 = `main_progress_chapter_count` | `RHYTHM_NODES_COUNT` | error |
410
+ | 支线章数之和 = `branch_chapter_total` | `RHYTHM_BRANCH_TOTAL` | error |
411
+ | 支线章集 ∩ 主线 key_node 章集 = `overlap_chapter_count` | `RHYTHM_OVERLAP_COUNT` | error |
412
+ | 结局章 ∈ 主线 key_nodes | `RHYTHM_COVERAGE` | error |
413
+ | 全书级结局回收所有大伏笔 | `RHYTHM_LARGE_FS_NOT_RECOVERED` | error |
414
+ | 伏笔埋 < 收 | `RHYTHM_TIMING` | error |
415
+ | 冲突升级递增 | `RHYTHM_TIMING` | error |
416
+ | 弧光顺序递增 | `RHYTHM_TIMING` | error |
417
+ | 弧光章在出场区间内 | `RHYTHM_ARC_OUT_OF_SPAN` | error |
418
+ | 退场章在出场区间内 | `RHYTHM_EXIT_IN_SPAN` | error |
419
+ | 章节号在 [1, T] 内 | `RHYTHM_CHAP_RANGE` | error |
420
+ | 区间 start <= end | `RHYTHM_CHAP_ORDER` | error |
421
+ | 主线节点重复 | `RHYTHM_NODE_DUP` | error |
422
+ | 支线重叠 | `RHYTHM_BRANCH_OVERLAP` | error |
423
+ | 章节重复 / 缺失 / 孤立 | `RHYTHM_CHAPTER_DUP` / `RHYTHM_CHAPTER_MISSING` / `RHYTHM_CHAPTER_UNREF` | error |
424
+ | 钩子下限 / 连续同类 | `RHYTHM_HOOK_MIN` / `RHYTHM_HOOK_DUP` | error |
425
+ | 卷章数分布 | `RHYTHM_VOLUME_DIST` | warning |
426
+ | 钩子同类(2 章) | `RHYTHM_HOOK_DUP_WARN` | warning |
427
+ | 字数目标 | `RHYTHM_WORD_TARGET` | warning |
428
+ | derivations | `RHYTHM_DERIVATION_MISSING` / `RHYTHM_DERIVATION_EXTRA` | warning |
429
+
430
+ **记住三条最容易忘的**:
431
+
432
+ 1. **时序映射必须严格匹配节奏参数的三个值**(`key_nodes` 数、支线章数之和、交集大小)。
433
+ 2. **结局章必须在主线 `key_nodes` 里**——如果结局章不在,先改 key_nodes 或改结局章。
434
+ 3. **钩子数不能低于 `ceil(T * rate)`**——`is_minimum` 为 true 时这是硬下限。
435
+
436
+ ---
437
+
438
+ <!-- contract:errors -->
439
+ ## 十、常见错误代码速查
440
+
441
+ 插件回喂给你的错误清单里的 `code` 对应下表。**只改被点名的字段**,其余不要动。
442
+
443
+ | code | 含义 | 修复方向 |
444
+ |---|---|---|
445
+ | `RHYTHM_INFEASIBLE` | 3 个参数不满足 7 条约束 | 按「节奏参数」那节的步骤 2-6 重算 |
446
+ | `RHYTHM_CONSTRAINT_EVAL` | 约束求值失败 | 检查 formulas-rhythm.json |
447
+ | `RHYTHM_CHAP_RANGE` | 章节号越界 | 改到 `[1, T]` |
448
+ | `RHYTHM_CHAP_ORDER` | 区间 start > end | 交换或调整 |
449
+ | `RHYTHM_TIMING` | 时序错误 | 调整章节号 |
450
+ | `RHYTHM_ARC_OUT_OF_SPAN` | 弧光章不在出场区间 | 调整 `arc_stages[].chapter` |
451
+ | `RHYTHM_EXIT_IN_SPAN` | 退场章不在出场区间 | 调整 `exit_chapter` |
452
+ | `RHYTHM_REF_MISSING` | 引用的 id 不存在 | 改 id 或补上游实体 |
453
+ | `RHYTHM_ACT_INVALID` | 章节规划的 act 不在骨架幕集合中 | 改为骨架该卷的幕 index |
454
+ | `RHYTHM_CHAPTER_DUP` | 章节号重复 | 去重 |
455
+ | `RHYTHM_CHAPTER_MISSING` | 缺章节 | 补章节规划 |
456
+ | `RHYTHM_CHAPTER_UNREF` | 章节没有引用 | 补时序映射或细纲 |
457
+ | `RHYTHM_NODES_COUNT` | key_nodes 数 ≠ 参数 | 改时序映射或改参数 |
458
+ | `RHYTHM_BRANCH_TOTAL` | 支线章数之和 ≠ 参数 | 改时序映射或改参数 |
459
+ | `RHYTHM_OVERLAP_COUNT` | 交集大小 ≠ 参数 | 改时序映射或改参数 |
460
+ | `RHYTHM_NODE_DUP` | 主线 key_node 章重复 | 改章节 |
461
+ | `RHYTHM_BRANCH_OVERLAP` | 支线章集重叠 | 调整区间 |
462
+ | `RHYTHM_COVERAGE` | 结局章不在 key_nodes | 改结局章或补 key_nodes |
463
+ | `RHYTHM_LARGE_FS_NOT_RECOVERED` | 全书级结局缺大伏笔 | 补 `foreshadow_recovery` |
464
+ | `RHYTHM_HOOK_MIN` | 钩子数不足 | 补钩子 |
465
+ | `RHYTHM_HOOK_DUP` | 连续 3 章同类钩子 | 换钩子类型 |
466
+ | `RHYTHM_VOLUME_DIST`(warning) | 卷章数分布偏离 | 调整章节规划的 volume 分配 |
467
+ | `RHYTHM_HOOK_DUP_WARN`(warning) | 连续 2 章同类钩子 | 换一章的钩子 |
468
+ | `RHYTHM_WORD_TARGET`(warning) | 字数目标偏离 | 调整 word_target |
469
+ | `RHYTHM_DERIVATION_MISSING`(warning) | 缺约束名 key | 用 7 条约束名作 key |
470
+ | `RHYTHM_DERIVATION_EXTRA`(warning) | 多了参数名 key | 删掉,只留 7 条约束名 |
471
+ | `RHYTHM_COVERAGE_SKIPPED`(warning) | 覆盖检查被跳过 | 不是你的错 |
472
+ | `NEED_ALL_CHECK`(warning) | 本文件单检通过,跨文件一致性要等全量校验 | 不用管,插件会自己跑全量校验 |
473
+ | `REL_KIND_META_DRIFT` | 关系类型词表与元数据不同步 | 同样是参考文件/脚本的一致性问题,不是你写错了。照 glossary 第五节的词表填,并把这个码原样报给用户 |
474
+ | `ENUM_VALUE_MISSING`(warning) | 业务常量缺失 | 不是你的错 |
475
+ | `EXTRA_FIELD` / `REQUIRED` / `TYPE` / `PATTERN` / `ENUM` / `ENUM_MISSING` | 模板结构不符 | 按 `模板/节奏/` 模板与 `references/glossary.md` 改 |
476
+ | `CHARSET_KO` / `CHARSET_JP` / `PLACEHOLDER` | 字符集 / 占位符 | 删除 |
477
+ | `UPSTREAM_MISSING` / `UPSTREAM_NOT_READY` / `UPSTREAM_NOT_PASSED` | 上游骨架或血肉没准备好 | 不是你写错了。停下这一轮,把这三个码原样报给用户,让用户先修上游阶段 |
478
+
479
+ ---
480
+
481
+ <!-- contract:forbidden -->
482
+ ## 十一、本阶段禁令(11 条)
483
+
484
+ 1. **章节号必须在 `[1, T]` 内**。`RHYTHM_CHAP_RANGE` 会拦截越界。
485
+
486
+ 2. **不得引用不存在的实体 id**。所有 `char_id` / `conflict_id` / `fs_id` / `climax_id` / `pleasure_id` / `main_id` / `branch_id` / `loc_id` / `item_id` / `ending_id` / `volume` 必须存在于骨架/血肉中。`RHYTHM_REF_MISSING` 会拦截。
487
+
488
+ 3. **`branch_chapter_total` 必须与支线实际章数之和一致**。全量 `RHYTHM_BRANCH_TOTAL` 会检查。
489
+
490
+ 4. **`main_progress_chapter_count` 必须与主线 key_nodes 实际数一致**。全量 `RHYTHM_NODES_COUNT` 会检查。
491
+
492
+ 5. **`overlap_chapter_count` 必须与支线章集 ∩ 主线 key_node 章集的实际交集大小一致**。全量 `RHYTHM_OVERLAP_COUNT` 会检查。
493
+
494
+ 6. **结局章必须在主线 key_nodes 中**。`RHYTHM_COVERAGE` 会检查。
495
+
496
+ 7. **全书级结局必须回收所有大伏笔**。`RHYTHM_LARGE_FS_NOT_RECOVERED` 会检查。
497
+
498
+ 8. **钩子数必须 ≥ `ceil(T * rate)`**(如果 `is_minimum` 为 true)。`RHYTHM_HOOK_MIN` 会检查。
499
+
500
+ 9. **derivations 的 key 必须是 7 条约束名,不是 3 个参数名**。`RHYTHM_DERIVATION_MISSING` / `RHYTHM_DERIVATION_EXTRA` 会告警(warning,不阻塞)。
501
+
502
+ 10. **不得输出"通过/完成/PASS/✅"等结论词**。阶段成败由插件判定。
503
+
504
+ 11. **不得修改 `模板/` 和 `references/` 下的任何文件**。`模板/` 是节奏阶段的结构来源,`references/` 是规则/公式/枚举/glossary 来源,两者都是**只读输入**。严禁为了"通过校验"而修改 validator 或规则文件——所有脚本与参考文件的修改由项目维护者通过 plugin 升级路径完成。**骨架阶段产物 `设定/raw/sources/骨架/` 和血肉阶段产物 `设定/raw/sources/血肉/` 的 JSON 是只读上游**,你**只能引用**,不能修改。**本阶段的输出目录 `设定/raw/sources/节奏/` 是你要写的产物**。