@cloud411716/fancy-webnovel 1.0.46 → 3.0.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 (271) hide show
  1. package/client.js +369 -0
  2. package/cordis.patch.yml +4 -13
  3. package/docs/douyin-plugin.md +118 -0
  4. package/docs/material-library/README.md +404 -0
  5. package/docs/material-library/import.js +437 -0
  6. package/docs/material-library/prompt.md +370 -0
  7. package/docs/material-library/queries.surql +94 -0
  8. package/docs/material-library/sample.jsonl +14 -0
  9. package/docs/material-library/schema.surql +46 -0
  10. package/docs/mcp-profile.example.yml +24 -0
  11. package/docs/story-graph-design.md +602 -0
  12. package/docs/story-graph-validation.md +81 -0
  13. package/docs/workspace-mcp.md +284 -0
  14. package/index.js +26 -38
  15. package/lib/atomic-write.js +109 -0
  16. package/lib/chapter-order.js +100 -0
  17. package/lib/chinese-num.js +111 -0
  18. package/lib/confirmation.js +80 -0
  19. package/lib/db-init.js +123 -0
  20. package/lib/deployed-state.js +141 -0
  21. package/lib/direction-labels.js +265 -0
  22. package/lib/error-codes.js +104 -0
  23. package/lib/infra.js +594 -0
  24. package/lib/llm-wiki.js +109 -0
  25. package/lib/material-db.js +149 -0
  26. package/lib/path-guard.js +150 -0
  27. package/lib/projects.js +400 -0
  28. package/lib/scraper-utils.js +305 -0
  29. package/lib/surreal-server.js +338 -0
  30. package/lib/topic-decision.js +860 -0
  31. package/lib/topic-fields.js +251 -0
  32. package/lib/topic-schema.js +225 -0
  33. package/lib/workspace.js +501 -0
  34. package/package.json +62 -5
  35. package/plugins/fancy-analyze/analyze-definitions.md +519 -0
  36. package/plugins/fancy-analyze/command-input.js +65 -0
  37. package/plugins/fancy-analyze/index.js +2024 -0
  38. package/plugins/fancy-analyze/ingest-coverage.js +107 -0
  39. package/plugins/fancy-analyze/schema-analyze.md +627 -0
  40. package/plugins/fancy-analyze/schema-write.md +178 -0
  41. package/plugins/fancy-analyze/scripts/append-summary-chapter.js +612 -0
  42. package/plugins/fancy-analyze/scripts/split_source.js +292 -0
  43. package/plugins/fancy-analyze/summary-progress.js +495 -0
  44. package/plugins/fancy-analyze/templates.js +1179 -0
  45. package/plugins/fancy-bootstrap/index.js +313 -0
  46. package/plugins/fancy-bootstrap/scripts/init.js +177 -0
  47. package/plugins/fancy-bootstrap/scripts/path-utils.js +21 -0
  48. package/plugins/fancy-bootstrap/templates.js +71 -0
  49. package/plugins/fancy-db/index.js +172 -0
  50. package/plugins/fancy-douyin/deploy.js +134 -0
  51. package/plugins/fancy-douyin/index.js +551 -0
  52. package/plugins/fancy-douyin/prompts/01-analysis-prompt.md +84 -0
  53. package/plugins/fancy-douyin/scripts/crawl-note.cjs +194 -0
  54. package/plugins/fancy-douyin/scripts/lib.cjs +197 -0
  55. package/plugins/fancy-douyin/scripts/scan-account.cjs +128 -0
  56. package/plugins/fancy-douyin/scripts/start-chrome.cjs +203 -0
  57. package/plugins/fancy-douyin/state.js +197 -0
  58. package/plugins/fancy-douyin/templates.js +127 -0
  59. package/plugins/fancy-export/exporter.js +805 -0
  60. package/plugins/fancy-export/index.js +63 -0
  61. package/plugins/fancy-import/import-core.js +1118 -0
  62. package/plugins/fancy-import/import-driver.js +604 -0
  63. package/plugins/fancy-import/index.js +738 -0
  64. package/plugins/fancy-import/prompts/01-import-prompt.md +131 -0
  65. package/plugins/fancy-import/templates.js +58 -0
  66. package/plugins/fancy-material/exporter.js +153 -0
  67. package/plugins/fancy-material/genres.js +256 -0
  68. package/plugins/fancy-material/index.js +835 -0
  69. package/plugins/fancy-material/inventory.js +143 -0
  70. package/plugins/fancy-material/kinds.js +313 -0
  71. package/plugins/fancy-material/queue.js +55 -0
  72. package/plugins/fancy-material/scripts/query.js +254 -0
  73. package/plugins/fancy-material/templates.js +258 -0
  74. package/plugins/fancy-material/tool-deploy.js +138 -0
  75. package/plugins/fancy-material/validate.js +149 -0
  76. package/plugins/fancy-project/index.js +133 -0
  77. package/plugins/fancy-scan/existing-block.js +100 -0
  78. package/plugins/fancy-scan/index.js +708 -0
  79. package/plugins/fancy-scan/prompts/01-analysis-prompt.md +302 -0
  80. package/plugins/fancy-scan/scripts/append-block.js +725 -0
  81. package/plugins/fancy-scan/scripts/report-validate.js +824 -0
  82. package/plugins/fancy-scan/scripts/run-scan.js +667 -0
  83. package/plugins/fancy-scan/scripts/scrapers/fanqie-rank-scraper.js +923 -0
  84. package/plugins/fancy-scan/scripts/scrapers/jjwxc-rank-scraper.js +469 -0
  85. package/plugins/fancy-scan/scripts/scrapers/qimao-rank-scraper.js +629 -0
  86. package/plugins/fancy-scan/templates/blocks.js +155 -0
  87. package/plugins/fancy-scan/templates/platforms.js +372 -0
  88. package/plugins/fancy-scan/templates/report-schema.js +90 -0
  89. package/plugins/fancy-scan/templates.js +694 -0
  90. package/plugins/fancy-scan/tool-deploy.js +335 -0
  91. package/plugins/fancy-scan/wait-turn.js +46 -0
  92. package/plugins/fancy-story-builder/db/README.md +106 -0
  93. package/plugins/fancy-story-builder/db/client.js +228 -0
  94. package/plugins/fancy-story-builder/db/commands.js +145 -0
  95. package/plugins/fancy-story-builder/db/ids.js +39 -0
  96. package/plugins/fancy-story-builder/db/import.js +798 -0
  97. package/plugins/fancy-story-builder/db/project.js +299 -0
  98. package/plugins/fancy-story-builder/db/query.js +361 -0
  99. package/plugins/fancy-story-builder/db/schema.js +417 -0
  100. package/plugins/fancy-story-builder/db/stage-sync.js +238 -0
  101. package/plugins/fancy-story-builder/deploy-manifest.js +239 -0
  102. package/plugins/fancy-story-builder/deslop-driver.js +821 -0
  103. package/plugins/fancy-story-builder/flesh-output.js +168 -0
  104. package/plugins/fancy-story-builder/index.js +6861 -0
  105. package/plugins/fancy-story-builder/patch-driver.js +390 -0
  106. package/plugins/fancy-story-builder/prompts/01-skeleton-prompt.md +563 -0
  107. package/plugins/fancy-story-builder/prompts/02-flesh-prompt.md +602 -0
  108. package/plugins/fancy-story-builder/prompts/03-rhythm-prompt.md +539 -0
  109. package/plugins/fancy-story-builder/prompts/04-writing-prompt.md +225 -0
  110. package/plugins/fancy-story-builder/prompts/05-deslop-prompt.md +96 -0
  111. package/plugins/fancy-story-builder/purpose-template.md +83 -0
  112. package/plugins/fancy-story-builder/references/formulas-rhythm.json +302 -0
  113. package/plugins/fancy-story-builder/references/formulas-skeleton.json +402 -0
  114. package/plugins/fancy-story-builder/references/genre-profiles.json +3514 -0
  115. package/plugins/fancy-story-builder/references/glossary.md +594 -0
  116. package/plugins/fancy-story-builder/references/index.md +581 -0
  117. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/00-index.md +174 -0
  118. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/01-scene.md +150 -0
  119. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/02-chapter-workflow.md +99 -0
  120. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/03-opening-chapter.md +104 -0
  121. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/04-dialogue-mastery.md +274 -0
  122. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/05-genre-formulas.md +432 -0
  123. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/06-tone-ensemble.md +259 -0
  124. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/07-ledger-audit.md +222 -0
  125. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/08-diagnosis-revision.md +205 -0
  126. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/09-reading-power.md +135 -0
  127. package/plugins/fancy-story-builder/references//345/206/231/344/275/234/346/214/207/345/257/274/10-deslop.md +129 -0
  128. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/00-index.md +98 -0
  129. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/01-upgrade-rhythm.md +300 -0
  130. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/02-arc-rhythm.md +168 -0
  131. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/03-highpoint-hooks.md +189 -0
  132. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/04-satisfaction.md +226 -0
  133. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/05-foreshadow-density.md +195 -0
  134. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/06-chapter-outline.md +226 -0
  135. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/07-audit.md +181 -0
  136. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/00-index.md +100 -0
  137. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/01-character.md +243 -0
  138. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/02-worldbuilding.md +245 -0
  139. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/03-relations.md +185 -0
  140. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/04-emotion-methods.md +286 -0
  141. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/05-audit.md +187 -0
  142. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/00-index.md +91 -0
  143. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/01-positioning.md +209 -0
  144. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/02-packaging.md +135 -0
  145. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/03-entities.md +305 -0
  146. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/04-conflict.md +328 -0
  147. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/05-quota.md +187 -0
  148. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/06-audit.md +160 -0
  149. package/plugins/fancy-story-builder/rhythm-output.js +140 -0
  150. package/plugins/fancy-story-builder/schema-setting.json +1100 -0
  151. package/plugins/fancy-story-builder/schema-setting.md +232 -0
  152. package/plugins/fancy-story-builder/scripts/ai-patterns.js +619 -0
  153. package/plugins/fancy-story-builder/scripts/brief-validate.js +164 -0
  154. package/plugins/fancy-story-builder/scripts/check-guide-compliance.js +289 -0
  155. package/plugins/fancy-story-builder/scripts/content-quality.js +416 -0
  156. package/plugins/fancy-story-builder/scripts/deslop-validate.js +143 -0
  157. package/plugins/fancy-story-builder/scripts/error-classes.js +112 -0
  158. package/plugins/fancy-story-builder/scripts/flesh-validate.js +1312 -0
  159. package/plugins/fancy-story-builder/scripts/formula-engine.js +112 -0
  160. package/plugins/fancy-story-builder/scripts/graph-utils.js +1484 -0
  161. package/plugins/fancy-story-builder/scripts/label-coverage.js +171 -0
  162. package/plugins/fancy-story-builder/scripts/prompt-drift-check.js +441 -0
  163. package/plugins/fancy-story-builder/scripts/reading-power.js +298 -0
  164. package/plugins/fancy-story-builder/scripts/reference-log.js +110 -0
  165. package/plugins/fancy-story-builder/scripts/rhythm-validate.js +1312 -0
  166. package/plugins/fancy-story-builder/scripts/schema-pages.js +2668 -0
  167. package/plugins/fancy-story-builder/scripts/semantic-lint.js +381 -0
  168. package/plugins/fancy-story-builder/scripts/skeleton-quota.js +145 -0
  169. package/plugins/fancy-story-builder/scripts/skeleton-validate.js +1973 -0
  170. package/plugins/fancy-story-builder/scripts/stage-batches.js +1405 -0
  171. package/plugins/fancy-story-builder/scripts/stage-contract.js +429 -0
  172. package/plugins/fancy-story-builder/scripts/story-artifacts.js +933 -0
  173. package/plugins/fancy-story-builder/scripts/upstream-query.js +198 -0
  174. package/plugins/fancy-story-builder/scripts/v2/baseline.js +112 -0
  175. package/plugins/fancy-story-builder/scripts/v2/cast.js +517 -0
  176. package/plugins/fancy-story-builder/scripts/v2/context.js +434 -0
  177. package/plugins/fancy-story-builder/scripts/v2/contracts.js +493 -0
  178. package/plugins/fancy-story-builder/scripts/v2/core.js +419 -0
  179. package/plugins/fancy-story-builder/scripts/v2/engine.js +1068 -0
  180. package/plugins/fancy-story-builder/scripts/v2/export-markdown.js +794 -0
  181. package/plugins/fancy-story-builder/scripts/v2/genre.js +195 -0
  182. package/plugins/fancy-story-builder/scripts/v2/knowledge.js +321 -0
  183. package/plugins/fancy-story-builder/scripts/v2/macro.js +409 -0
  184. package/plugins/fancy-story-builder/scripts/v2/manifest.js +538 -0
  185. package/plugins/fancy-story-builder/scripts/v2/migrate.js +137 -0
  186. package/plugins/fancy-story-builder/scripts/v2/prompts.js +1335 -0
  187. package/plugins/fancy-story-builder/scripts/v2/rhythm.js +618 -0
  188. package/plugins/fancy-story-builder/scripts/v2/scale.js +843 -0
  189. package/plugins/fancy-story-builder/scripts/v2/snapshot.js +121 -0
  190. package/plugins/fancy-story-builder/scripts/v2/stages.js +273 -0
  191. package/plugins/fancy-story-builder/scripts/v2/writing.js +216 -0
  192. package/plugins/fancy-story-builder/scripts/writing-rules.js +167 -0
  193. package/plugins/fancy-story-builder/scripts/writing-validate.js +682 -0
  194. package/plugins/fancy-story-builder/skeleton-output.js +199 -0
  195. package/plugins/fancy-story-builder/stage-driver.js +1049 -0
  196. package/plugins/fancy-story-builder/storydb/README.md +271 -0
  197. package/plugins/fancy-story-builder/storydb/cli.js +458 -0
  198. package/plugins/fancy-story-builder/storydb/client.js +351 -0
  199. package/plugins/fancy-story-builder/storydb/compile.js +452 -0
  200. package/plugins/fancy-story-builder/storydb/context.js +288 -0
  201. package/plugins/fancy-story-builder/storydb/definitions.js +699 -0
  202. package/plugins/fancy-story-builder/storydb/driver.js +106 -0
  203. package/plugins/fancy-story-builder/storydb/dsh-tools.js +313 -0
  204. package/plugins/fancy-story-builder/storydb/export.js +175 -0
  205. package/plugins/fancy-story-builder/storydb/ids.js +31 -0
  206. package/plugins/fancy-story-builder/storydb/impact.js +170 -0
  207. package/plugins/fancy-story-builder/storydb/import.js +559 -0
  208. package/plugins/fancy-story-builder/storydb/mcp.js +99 -0
  209. package/plugins/fancy-story-builder/storydb/prompts.js +952 -0
  210. package/plugins/fancy-story-builder/storydb/stage-command.js +379 -0
  211. package/plugins/fancy-story-builder/storydb/stage.js +426 -0
  212. package/plugins/fancy-story-builder/storydb/store.js +891 -0
  213. package/plugins/fancy-story-builder/storydb/tools.js +592 -0
  214. package/plugins/fancy-story-builder/storydb/validation.js +183 -0
  215. package/plugins/fancy-story-builder/templates/v2/10-scale/scale-template.json +144 -0
  216. package/plugins/fancy-story-builder/templates/v2/20-macro/emotion-template.json +15 -0
  217. package/plugins/fancy-story-builder/templates/v2/20-macro/endings-template.json +16 -0
  218. package/plugins/fancy-story-builder/templates/v2/20-macro/lines-template.json +28 -0
  219. package/plugins/fancy-story-builder/templates/v2/20-macro/roles-template.json +10 -0
  220. package/plugins/fancy-story-builder/templates/v2/20-macro/story-template.json +19 -0
  221. package/plugins/fancy-story-builder/templates/v2/20-macro/volumes-template.json +24 -0
  222. package/plugins/fancy-story-builder/templates/v2/20-macro/world-template.json +24 -0
  223. package/plugins/fancy-story-builder/templates/v2/30-cast/characters-template.json +49 -0
  224. package/plugins/fancy-story-builder/templates/v2/30-cast/climaxes-template.json +14 -0
  225. package/plugins/fancy-story-builder/templates/v2/30-cast/conflicts-template.json +30 -0
  226. package/plugins/fancy-story-builder/templates/v2/30-cast/factions-template.json +18 -0
  227. package/plugins/fancy-story-builder/templates/v2/30-cast/foreshadows-template.json +29 -0
  228. package/plugins/fancy-story-builder/templates/v2/30-cast/information-template.json +29 -0
  229. package/plugins/fancy-story-builder/templates/v2/30-cast/items-template.json +13 -0
  230. package/plugins/fancy-story-builder/templates/v2/30-cast/locations-template.json +12 -0
  231. package/plugins/fancy-story-builder/templates/v2/30-cast/payoffs-template.json +16 -0
  232. package/plugins/fancy-story-builder/templates/v2/30-cast/relations-template.json +44 -0
  233. package/plugins/fancy-story-builder/templates/v2/40-rhythm/chapter-map-template.json +81 -0
  234. package/plugins/fancy-story-builder/templates/v2/40-rhythm/outline-template.json +162 -0
  235. package/plugins/fancy-story-builder/templates/v2/50-writing/ledger-template.json +46 -0
  236. package/plugins/fancy-story-builder/templates/v2/60-deslop/report-template.json +10 -0
  237. 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 +427 -0
  238. 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
  239. 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
  240. 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
  241. 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 +168 -0
  242. 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 +184 -0
  243. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/205/266/344/273/226/345/256/236/344/275/223/346/250/241/346/235/277.json +62 -0
  244. 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 +131 -0
  245. 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 +85 -0
  246. 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 +103 -0
  247. 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 +68 -0
  248. 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 +142 -0
  249. 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 +292 -0
  250. 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 +266 -0
  251. 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 +336 -0
  252. 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
  253. 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 +308 -0
  254. package/plugins/fancy-story-builder/templates.js +1446 -0
  255. package/plugins/fancy-story-builder/wiki-references.js +540 -0
  256. package/plugins/fancy-story-builder/writing-driver.js +1388 -0
  257. package/plugins/fancy-topic/index.js +1057 -0
  258. package/plugins/fancy-topic/prompts/01-exploration-prompt.md +115 -0
  259. package/plugins/fancy-topic/scripts/save-suggestion.js +219 -0
  260. package/plugins/fancy-topic/scripts/select-topic.js +317 -0
  261. package/plugins/fancy-topic/templates.js +476 -0
  262. package/plugins/fancy-topic/tool-deploy.js +200 -0
  263. package/commands/bootstrap/index.js +0 -164
  264. package/commands/bootstrap/init-project.js +0 -121
  265. package/commands/scan/index.js +0 -283
  266. package/commands/scan/platforms/fanqie.js +0 -146
  267. package/commands/scan/platforms/jinjiang.js +0 -143
  268. package/commands/scan/platforms/qidian.js +0 -102
  269. package/commands/scan/platforms/qimao.js +0 -127
  270. package/commands/scan/scraper.js +0 -195
  271. package/infra.js +0 -160
@@ -0,0 +1,540 @@
1
+ /**
2
+ * wiki-references.js
3
+ * 对标参考(对标/wiki/)的目录清单、阶段映射与注入文本。
4
+ *
5
+ * 这里有三件事必须分开:
6
+ * 1. **有哪些目录**:22 个可能目录(14 个按章技法 + 8 个跨章节实体;没有源页的目录不会有总结)。
7
+ * 2. **哪个阶段/批次该看哪些**:阶段级清单 + 批次级重点。
8
+ * 3. **怎么保证模型真的看到**:除了列路径,还要把摘要正文注入批次提示词。
9
+ *
10
+ * 为什么第 3 点单独做:只给路径时,模型可以声称"已参考"但完全不读;
11
+ * 插件把摘要直接放进 prompt 后,"跳过参考文件"这件事在物理上就不成立了。
12
+ */
13
+ import { existsSync, readFileSync } from 'fs';
14
+ import { join } from 'path';
15
+
16
+ // 各目录的作用说明(注入提示词时用)
17
+ export const DIRECTORY_DESCRIPTIONS = {
18
+ '叙事视角': '叙事视角与信息差:谁在看、读者知道多少、视角如何限制或释放信息',
19
+ '章节结构': '单章起承转合:一章内部如何组织冲突、转折、收束',
20
+ '节奏韵律': '叙事节奏:张弛、信息密度、呼吸位、加压与泄压',
21
+ '对话技巧': '对话功能:推进剧情、塑造人物、传递设定、制造潜台词',
22
+ '描写技法': '描写:环境、动作、心理、感官细节如何服务人物与制度',
23
+ '情节机制': '情节推进机制:信息差、资源门槛、因果链、金手指边界',
24
+ '开头钩子': '开篇/章首钩子:如何在前几百字制造问题与期待',
25
+ '结尾收束': '章末/卷末收束:如何留悬念、转目标、兑现情绪',
26
+ '句式变化': '句式节奏:长短句、问句、短句冲击、解释句铺陈',
27
+ '段落布局': '段落功能与视觉节奏:分段、独段、留白、信息焦点',
28
+ '场景转换': '场景切换:时空标定、焦点交接、转场如何推进调查或关系',
29
+ '情绪渲染': '情绪曲线:群体/个体、希望/挫败、自嘲/爆发、共情',
30
+ '张力构建': '张力与压力:信息缺口、金额/时限、门槛叠加、赌注',
31
+ '章节功能设计': '章节功能:开局、揭示、推进、转折、收束、下一门槛',
32
+ '人物关系设计': '关系设计:试探、互助、家庭分工、师徒、竞争、信任边界',
33
+ '势力结构': '势力设计:资源/武力双轨、组织信用、区域议价、成员动员',
34
+ '世界观设计': '世界观设计:规则、力量体系、测量/资格、阶层与制度',
35
+ '冲突设计': '冲突设计:公开节点、赌注、制度门槛、资源困境、组织对抗',
36
+ '主线支线结构': '主线/支线:核心目标稳定、难度结构改写、支线服务主线',
37
+ '分卷节奏': '分卷/幕结构:卷目标、卷内起伏、卷末升级',
38
+ '伏笔布局': '伏笔:埋设、分层揭示、预约兑现、传闻层、问题清单',
39
+ '角色弧光设计': '角色弧光:目标—成本—修正、缺陷、动摇不改、配角功能'
40
+ };
41
+
42
+ // 各阶段对应的 wiki 目录(阶段级“本阶段可以看哪些”,不是“本批必须全读”)
43
+ //
44
+ // 判据:**这一层产物要回答什么问题**,不是“主题沾边”。
45
+ // skeleton:有哪些实体、怎么连、有多少、分几卷几幕
46
+ // flesh:实体长什么样(角色/关系/势力/世界/情绪/道具)
47
+ // rhythm:什么时间发生(升级、弧线、高潮、钩子、伏笔密度、章节切分)
48
+ // writing:具体怎么写(场景、视角、对话、描写、句式、段落、开篇、收束)
49
+ export const PHASE_DIRECTORIES = {
50
+ skeleton: [
51
+ '主线支线结构', '冲突设计', '世界观设计', '势力结构',
52
+ '人物关系设计', '角色弧光设计', '伏笔布局', '情节机制',
53
+ '张力构建', '分卷节奏'
54
+ ],
55
+ flesh: [
56
+ '角色弧光设计', '人物关系设计', '势力结构', '世界观设计',
57
+ '冲突设计', '情绪渲染', '描写技法', '对话技巧',
58
+ '伏笔布局', '情节机制', '主线支线结构'
59
+ ],
60
+ rhythm: [
61
+ '节奏韵律', '章节结构', '章节功能设计', '开头钩子',
62
+ '结尾收束', '张力构建', '冲突设计', '伏笔布局',
63
+ '角色弧光设计', '主线支线结构', '分卷节奏', '情绪渲染',
64
+ '情节机制', '场景转换'
65
+ ],
66
+ writing: [
67
+ '叙事视角', '场景转换', '对话技巧', '描写技法',
68
+ '句式变化', '段落布局', '情绪渲染', '节奏韵律',
69
+ '开头钩子', '结尾收束', '张力构建', '伏笔布局',
70
+ '章节结构'
71
+ ],
72
+ // 去味阶段不注入对标参考:改写的是“像不像人话”,不是“写得好不好”,
73
+ // 拿别人的写法当参照反而会把旧稿的毛病一起抄进去。
74
+ deslop: []
75
+ };
76
+
77
+ /**
78
+ * 批次级映射:同一阶段里,不同产物该看哪几篇。
79
+ *
80
+ * 阶段级清单解决“本阶段大概有哪些参考”,批次级映射解决“这一批到底该看哪几篇”。
81
+ * 两者都保留:阶段级用于建立全局地图,批次级用于把摘要压到单批可消化的量。
82
+ */
83
+ export const BATCH_DIRECTORIES = {
84
+ skeleton: {
85
+ '框架': ['主线支线结构', '冲突设计', '世界观设计', '分卷节奏', '张力构建'],
86
+ '实体': ['角色弧光设计', '势力结构', '世界观设计', '人物关系设计', '冲突设计', '伏笔布局'],
87
+ '关系': ['人物关系设计', '势力结构', '冲突设计', '角色弧光设计'],
88
+ '层级': ['主线支线结构', '分卷节奏', '势力结构', '冲突设计']
89
+ },
90
+ flesh: {
91
+ '角色': ['角色弧光设计', '人物关系设计', '情绪渲染', '对话技巧'],
92
+ '冲突': ['冲突设计', '角色弧光设计', '张力构建'],
93
+ '势力': ['势力结构', '世界观设计', '人物关系设计'],
94
+ '剧情': ['主线支线结构', '情节机制', '伏笔布局', '人物关系设计'],
95
+ '地点': ['世界观设计', '描写技法', '场景转换'],
96
+ '结局': ['主线支线结构', '情绪渲染', '结尾收束'],
97
+ '世界观规则': ['世界观设计', '势力结构', '冲突设计'],
98
+ '关键道具': ['伏笔布局', '世界观设计', '描写技法']
99
+ },
100
+ rhythm: {
101
+ '章节规划': ['节奏韵律', '章节功能设计', '章节结构', '分卷节奏', '主线支线结构'],
102
+ '节奏参数': ['节奏韵律', '张力构建', '冲突设计', '情绪渲染'],
103
+ '时序映射': ['角色弧光设计', '主线支线结构', '伏笔布局', '分卷节奏'],
104
+ '章节细纲': ['开头钩子', '结尾收束', '章节结构', '章节功能设计', '张力构建', '伏笔布局']
105
+ },
106
+ writing: {
107
+ default: [
108
+ '叙事视角', '场景转换', '对话技巧', '描写技法',
109
+ '句式变化', '段落布局', '情绪渲染', '节奏韵律',
110
+ '开头钩子', '结尾收束', '张力构建', '伏笔布局',
111
+ '章节结构'
112
+ ]
113
+ }
114
+ };
115
+
116
+ /**
117
+ * 各阶段的“插件指导目录”与本阶段重点。
118
+ *
119
+ * 用来在提示词里把两层参考分开讲清楚:
120
+ * references/<阶段>指导/ = 高维方法论(这一层该怎么设计)
121
+ * 对标/wiki/ = 市场验证过的落地样本(某招在成品里怎么生效)
122
+ */
123
+ export const PHASE_GUIDANCE = {
124
+ skeleton: {
125
+ guideDir: 'references/骨架指导',
126
+ focus: '有哪些实体、实体怎么连、有多少、分几卷几幕'
127
+ },
128
+ flesh: {
129
+ guideDir: 'references/血肉指导',
130
+ focus: '实体长什么样:目标、缺陷、资源、关系、情绪怎么落到实处'
131
+ },
132
+ rhythm: {
133
+ guideDir: 'references/节奏指导',
134
+ focus: '什么时间发生:升级、弧线、高潮钩子、爽点、伏笔密度、章节怎么切'
135
+ },
136
+ writing: {
137
+ guideDir: 'references/写作指导',
138
+ focus: '具体怎么写:场景、章节工作流、开篇、对话、句式、语气'
139
+ }
140
+ };
141
+
142
+ /**
143
+ * 各阶段「方法论 vs 对标样本」的大致创作权重。
144
+ *
145
+ * 不是硬配额:契约/选题/上游永远 100% 服从;这里的百分比只用于回答
146
+ * “当两者都可用时,创作判断更该听谁”。
147
+ */
148
+ export const STAGE_REFERENCE_WEIGHTS = {
149
+ skeleton: { method: '60%', benchmark: '40%', note: '结构、实体、关系模式可借样本校准;配额、图结构、字段以方法论和校验器为准' },
150
+ flesh: { method: '55%', benchmark: '45%', note: '人物、关系、世界质感可多借样本;字段与引用完整性以契约为准' },
151
+ rhythm: { method: '50%', benchmark: '50%', note: '节奏最依赖市场手感;公式与联合约束仍是硬门槛' },
152
+ writing: { method: '65%', benchmark: '35%', note: 'craft 下限优先;样本只校准题材语气与桥段,防止照抄' }
153
+ };
154
+
155
+ /** 已登记的总结目录集合(测试用来防止映射里写出不存在的目录)。 */
156
+ export const KNOWN_SUMMARY_DIRS = Object.keys(DIRECTORY_DESCRIPTIONS);
157
+
158
+ /** 取出某阶段的目录清单(保留映射顺序)。 */
159
+ function phaseDirs(phase) {
160
+ return PHASE_DIRECTORIES[phase] || PHASE_DIRECTORIES.skeleton;
161
+ }
162
+
163
+ /** 取出某阶段某批次的重点目录;没有批次映射时退回阶段清单。 */
164
+ function batchDirs(phase, batchType) {
165
+ const map = BATCH_DIRECTORIES[phase];
166
+ if (map && batchType && map[batchType]) return map[batchType];
167
+ if (map && map.default) return map.default;
168
+ return phaseDirs(phase);
169
+ }
170
+
171
+ /** 按给定目录顺序检查总结文件是否存在。 */
172
+ function collectDirs(projectRoot, dirs) {
173
+ const wikiDir = join(projectRoot, '对标', 'wiki');
174
+ const available = [];
175
+ const missing = [];
176
+ for (const dir of dirs) {
177
+ const summaryFile = join(wikiDir, dir, dir + '-总结.md');
178
+ if (existsSync(summaryFile)) {
179
+ available.push({ dir, path: '对标/wiki/' + dir + '/' + dir + '-总结.md' });
180
+ } else {
181
+ missing.push(dir);
182
+ }
183
+ }
184
+ return { available, missing };
185
+ }
186
+
187
+ /**
188
+ * 找出当前阶段真正可读的对标总结文件。
189
+ *
190
+ * 只认 `<目录名>-总结.md`;目录在但总结文件不在的,记为 missing。
191
+ * 返回顺序按 PHASE_DIRECTORIES 的映射顺序(方便摘要按优先级取前几篇)。
192
+ *
193
+ * @returns {{ available: Array<{dir:string, path:string}>, missing: string[] }}
194
+ */
195
+ export function collectWikiReferences(projectRoot, phase = 'skeleton') {
196
+ return collectDirs(projectRoot, phaseDirs(phase));
197
+ }
198
+
199
+ /**
200
+ * 找出当前阶段、当前批次真正可读的对标总结文件。
201
+ *
202
+ * 批次映射优先;没有映射时退回阶段清单。
203
+ *
204
+ * @param {string} projectRoot
205
+ * @param {string} phase
206
+ * @param {string} [batchType] 例如“框架/实体/关系/层级”“角色/冲突/…”“章节规划/… ”
207
+ * @returns {{ available: Array<{dir:string, path:string}>, missing: string[] }}
208
+ */
209
+ export function collectBatchWikiReferences(projectRoot, phase = 'skeleton', batchType = '') {
210
+ return collectDirs(projectRoot, batchDirs(phase, batchType));
211
+ }
212
+
213
+ /**
214
+ * 生成注入提示词的对标参考文本(只列路径,不展开正文)。
215
+ *
216
+ * 只注入**实际存在**的文件;缺失的目录明确告知“没有”,避免 LLM 去读不存在的
217
+ * 路径后自行编造内容。
218
+ */
219
+ export function readWikiReferences(projectRoot, phase = 'skeleton') {
220
+ const wikiDir = join(projectRoot, '对标', 'wiki');
221
+ if (!existsSync(wikiDir)) return '';
222
+
223
+ const { available, missing } = collectWikiReferences(projectRoot, phase);
224
+ if (available.length === 0 && missing.length === 0) return '';
225
+
226
+ const output = [];
227
+ output.push('## 对标参考(本次可读的对标分析文件)\n\n');
228
+ for (const a of available) {
229
+ const desc = DIRECTORY_DESCRIPTIONS[a.dir] || '写作技法分析';
230
+ output.push('- `' + a.path + '`:' + desc + '\n');
231
+ }
232
+ if (missing.length > 0) {
233
+ output.push('\n**以下对标目录没有对应的总结文件,本阶段请不要引用它们**:' + missing.join('、') + '\n');
234
+ }
235
+ return output.join('');
236
+ }
237
+
238
+ /**
239
+ * 生成“参考优先级”说明段,注入到各阶段提示词的参考小节。
240
+ *
241
+ * 这一段的职责不是“多列几个文件”,而是把四种参考的**优先级和边界**讲死:
242
+ *
243
+ * 1. 硬契约(模板/glossary/公式/校验器/上游 JSON)—— 100% 服从
244
+ * 2. 本项目选题与上游产物 —— 创作主权;对标不能改设定
245
+ * 3. references/<阶段>指导/ —— 方法论,决定下限
246
+ * 4. 对标/wiki/ 总结 —— 市场样本,校准上限
247
+ *
248
+ * 为什么不能 50/50:方法论和样本回答的是不同问题。
249
+ * 方法论回答“这一层必须有什么、字段怎么成立”;样本回答“这一招在成品里怎么落地”。
250
+ * 把两者等权,模型就会拿样本里的桥段去覆盖模板/选题,最后写出一本“像对标但不像本项目”的书。
251
+ *
252
+ * @param {string} projectRoot 项目根目录
253
+ * @param {string} phase 阶段(skeleton / flesh / rhythm / writing)
254
+ * @param {string} [batchType] 批次类型(可选;给了就额外列“本批优先”)
255
+ * @returns {string} 注入文本(没有对标目录时返回空串)
256
+ */
257
+ export function renderBenchmarkGuidance(projectRoot, phase = 'skeleton', batchType = '') {
258
+ const guide = PHASE_GUIDANCE[phase];
259
+ if (!guide) return '';
260
+ const wikiDir = join(projectRoot, '对标', 'wiki');
261
+ if (!existsSync(wikiDir)) return '';
262
+
263
+ const phaseRefs = collectWikiReferences(projectRoot, phase);
264
+ const batchRefs = batchType ? collectBatchWikiReferences(projectRoot, phase, batchType) : null;
265
+ const weights = STAGE_REFERENCE_WEIGHTS[phase] || STAGE_REFERENCE_WEIGHTS.skeleton;
266
+
267
+ const lines = [];
268
+ lines.push('### 参考优先级(不是 50/50)');
269
+ lines.push('');
270
+ lines.push('| 优先级 | 参考 | 作用 | 冲突时怎么判 |');
271
+ lines.push('|---|---|---|---|');
272
+ lines.push('| 1 | `模板/`、`references/glossary.md`、`formulas-*.json`、校验器 | 字段、枚举、公式、结构 | **100% 服从**,不能拿样本改契约 |');
273
+ lines.push('| 2 | 本项目选题、`input_snapshot`、上游 JSON | 这本书的创作主权 | 对标不能改设定;硬门禁不能破 |');
274
+ lines.push('| 3 | `' + guide.guideDir + '/` | ' + guide.focus + '的通则 | **主参考**:决定下限与字段质量 |');
275
+ lines.push('| 4 | `对标/wiki/` 的 `<技法>-总结.md` | 真实商业小说里某一招怎么落地 | **校准参考**:只在可行域内选点 |');
276
+ lines.push('');
277
+ lines.push('**本阶段的创作权重**:方法论约 **' + weights.method + '**,对标样本约 **' + weights.benchmark + '**。' + weights.note + '。');
278
+ lines.push('');
279
+ lines.push('**匹配度折扣**:对标样本还要看它和本项目的距离——同题材 + 同平台 + 同读者层按上表;同题材不同平台约 ×0.7;不同题材约 ×0.3(只借通用结构,不硬套桥段)。');
280
+ lines.push('');
281
+ lines.push('**读法**:先读 `' + guide.guideDir + '/`(决定“该怎么想”);本批对标摘要只用于定位,**决策前必须核对原文件里的对应条目**,摘要不能替代原文。');
282
+ lines.push('');
283
+ lines.push('**本批强制**:摘要正文会直接注入,但摘要只负责定位;写作时必须先在原文件中核对 **1–2 条与本批字段/场景直接相关**的原则,落到具体设计上,并把参考文件和条目写进 `设定/.reference-decisions.md`。不要只收藏路径,也不要声称“已读取”却没有体现。确实没有适用条目时按契约写,不要硬套。');
284
+ lines.push('');
285
+ lines.push('**红线**:对标样本是**别人的成品**,只学结构与手法——禁止照搬专名、情节、句子、金手指设定;样本里的角色名/地名/机构名一律不许进本项目。');
286
+ lines.push('');
287
+
288
+ if (phaseRefs.available.length) {
289
+ lines.push('**本阶段可读的对标文件**(阶段级地图,不等于每批都要全读):\n');
290
+ for (const a of phaseRefs.available) {
291
+ const desc = DIRECTORY_DESCRIPTIONS[a.dir] || '写作技法分析';
292
+ lines.push('- `' + a.path + '` —— ' + desc);
293
+ }
294
+ lines.push('');
295
+ } else {
296
+ lines.push('**本阶段没有可读的对标文件**(对标/wiki 下尚无总结文件):不要凭空编造“对标分析”,照插件指导写。\n');
297
+ }
298
+
299
+ if (batchRefs) {
300
+ if (batchRefs.available.length) {
301
+ lines.push('**本批优先参考**(插件已按本批产物筛过;摘要正文会注入其中最相关的前几篇):\n');
302
+ for (const a of batchRefs.available) {
303
+ const desc = DIRECTORY_DESCRIPTIONS[a.dir] || '写作技法分析';
304
+ lines.push('- `' + a.path + '` —— ' + desc);
305
+ }
306
+ lines.push('');
307
+ } else {
308
+ lines.push('**本批没有匹配到可读的对标文件**:按方法论与硬契约写,不要临时扩大到无关题材的样本。\n');
309
+ }
310
+ }
311
+
312
+ const missing = Array.from(new Set([...(phaseRefs.missing || []), ...((batchRefs && batchRefs.missing) || [])]));
313
+ if (missing.length) {
314
+ lines.push('**以下对标目录本阶段/本批没有总结文件,不要引用**:' + missing.join('、'));
315
+ lines.push('');
316
+ }
317
+ return lines.join('\n');
318
+ }
319
+
320
+ /** 摘要注入的体量上限(按阶段)。 */
321
+ const DIGEST_LIMITS = {
322
+ skeleton: { maxFiles: 6, maxEntriesPerFile: 5, maxDetailFiles: 2, maxDetailEntries: 2, maxChars: 8000 },
323
+ flesh: { maxFiles: 4, maxEntriesPerFile: 8, maxDetailFiles: 2, maxDetailEntries: 2, maxChars: 7000 },
324
+ rhythm: { maxFiles: 6, maxEntriesPerFile: 6, maxDetailFiles: 2, maxDetailEntries: 2, maxChars: 8000 },
325
+ writing: { maxFiles: 10, maxEntriesPerFile: 6, maxDetailFiles: 2, maxDetailEntries: 1, maxChars: 9000 }
326
+ };
327
+
328
+ /** 截断文本,保留前 n 个字符。 */
329
+ function truncate(text, n) {
330
+ const s = String(text || '').replace(/\s+/g, ' ').trim();
331
+ if (s.length <= n) return s;
332
+ return s.slice(0, n).replace(/[,。;、,.;:\s]+$/, '') + '…';
333
+ }
334
+
335
+ /**
336
+ * 解析一个总结文件里的条目。
337
+ *
338
+ * 只解析 `## 核心技法` / `## 核心设计原则` 下的 `###` 条目;
339
+ * `- 来源章节:` / `- 来源实体:` 是登记字段,不进入摘要正文。
340
+ *
341
+ * @param {string} text
342
+ * @returns {Array<{title:string, fields:Object<string,string>}>}
343
+ */
344
+ export function parseSummaryEntries(text) {
345
+ const source = String(text || '');
346
+ const sectionRe = /^##\s+(核心技法|核心设计原则)\s*$/m;
347
+ const m = sectionRe.exec(source);
348
+ if (!m) return [];
349
+
350
+ const rest = source.slice(m.index + m[0].length);
351
+ const nextH2 = /^##\s+/m.exec(rest);
352
+ const body = nextH2 ? rest.slice(0, nextH2.index) : rest;
353
+ const chunks = body.split(/^###\s+/m).slice(1);
354
+ const entries = [];
355
+
356
+ for (const chunk of chunks) {
357
+ const lines = chunk.split('\n');
358
+ const title = String(lines[0] || '').trim();
359
+ if (!title) continue;
360
+ const fields = {};
361
+ for (const line of lines.slice(1)) {
362
+ const fm = /^-\s*([^:]+):\s*(.*)$/.exec(line.trim());
363
+ if (!fm) continue;
364
+ const key = fm[1].trim();
365
+ if (key.startsWith('来源')) continue; // 登记字段不进摘要
366
+ fields[key] = fm[2].trim();
367
+ }
368
+ entries.push({ title, fields });
369
+ }
370
+ return entries;
371
+ }
372
+
373
+ /** 判断这个总结文件是实体页总结还是按章技法总结。 */
374
+ function isEntityEntries(entries) {
375
+ return entries.some((e) => Object.prototype.hasOwnProperty.call(e.fields, '为什么这么设计'));
376
+ }
377
+
378
+ /** 条目的一句话索引(优先取“为什么/核心要点”)。 */
379
+ function entryOneLine(entry, entity) {
380
+ const keys = entity
381
+ ? ['为什么这么设计', '设计好处', '迁移到骨架/血肉/节奏']
382
+ : ['核心要点', '应用方式', '使用场景'];
383
+ for (const key of keys) {
384
+ if (entry.fields[key]) return truncate(entry.fields[key], 100);
385
+ }
386
+ return '';
387
+ }
388
+
389
+ /** 从批次里提取用于匹配条目的关键词。 */
390
+ function batchTokens(batch) {
391
+ const tokens = [];
392
+ const add = (v) => {
393
+ const s = String(v || '').trim();
394
+ if (s && s.length >= 2 && !tokens.includes(s)) tokens.push(s);
395
+ };
396
+ if (batch) {
397
+ add(batch.type);
398
+ add(batch.groupKey);
399
+ for (const item of (batch.items || [])) {
400
+ add(item.name);
401
+ add(item.type);
402
+ add(item.category);
403
+ add(item.level);
404
+ add(item.scale);
405
+ }
406
+ }
407
+ return tokens;
408
+ }
409
+
410
+ /**
411
+ * 选本批最该展开的条目。
412
+ *
413
+ * 没有语义模型可用,所以用“批次类型 / 条目名 / 类型词”做轻量匹配;
414
+ * 匹配不到时按原顺序取前几条(摘要本身已经按条目顺序组织)。
415
+ */
416
+ function selectDetailEntries(entries, entity, batch, limit) {
417
+ const tokens = batchTokens(batch);
418
+ const scored = entries.map((entry, index) => {
419
+ const hay = entry.title + ' ' + Object.values(entry.fields).join(' ');
420
+ let score = 0;
421
+ for (const t of tokens) {
422
+ if (entry.title.includes(t)) score += 5;
423
+ else if (hay.includes(t)) score += 2;
424
+ }
425
+ return { entry, index, score };
426
+ });
427
+ scored.sort((a, b) => (b.score - a.score) || (a.index - b.index));
428
+ return scored.slice(0, Math.max(1, limit)).map((x) => x.entry);
429
+ }
430
+
431
+ /** 把一个条目展开成摘要正文。 */
432
+ function renderEntryDetail(entry, entity) {
433
+ const lines = ['#### ' + entry.title];
434
+ const keys = entity
435
+ ? ['为什么这么设计', '设计好处', '如何服务剧情/人物/主题', '风险与反例', '迁移到骨架/血肉/节奏']
436
+ : ['核心要点', '使用场景', '应用方式'];
437
+ for (const key of keys) {
438
+ if (entry.fields[key]) lines.push('- ' + key + ':' + entry.fields[key]);
439
+ }
440
+ return lines.join('\n');
441
+ }
442
+
443
+ /**
444
+ * 生成“本批对标参考摘要”,直接注入批次提示词。
445
+ *
446
+ * 这是“防止模型跳过参考文件”的核心手段:
447
+ * - 只给路径:模型可以声称读过但完全不看;
448
+ * - 注入摘要:本批相关原则已经在上下文里,模型不需要(也不能假装)去读;
449
+ * - 保留原文件路径:需要更细的条目时仍可回读,但本批不依赖它。
450
+ *
451
+ * @param {string} projectRoot
452
+ * @param {string} phase
453
+ * @param {Object} [batch] 批次对象(至少含 type;可选 items/groupKey)
454
+ * @param {Object} [opts] 覆盖默认上限:maxFiles/maxEntriesPerFile/maxDetailFiles/maxDetailEntries/maxChars
455
+ * @returns {string}
456
+ */
457
+ export function renderBenchmarkDigest(projectRoot, phase = 'skeleton', batch = null, opts = {}) {
458
+ const wikiDir = join(projectRoot, '对标', 'wiki');
459
+ if (!existsSync(wikiDir)) return '';
460
+
461
+ const dirs = batchDirs(phase, batch && batch.type);
462
+ const { available } = collectDirs(projectRoot, dirs);
463
+ const limits = Object.assign({}, DIGEST_LIMITS[phase] || DIGEST_LIMITS.skeleton, opts || {});
464
+
465
+ if (available.length === 0) {
466
+ return [
467
+ '## 本批对标参考(已注入摘要;决策前仍需核对原文件)',
468
+ '',
469
+ '(本批没有可用的对标总结文件。按 `' + ((PHASE_GUIDANCE[phase] || {}).guideDir || 'references/') + '/` 与硬契约写,不要编造“对标分析”。)'
470
+ ].join('\n');
471
+ }
472
+
473
+ const selected = available.slice(0, Math.max(1, limits.maxFiles));
474
+ const indexBlocks = [];
475
+ const detailBlocks = [];
476
+
477
+ selected.forEach((entry, fileIndex) => {
478
+ let text = '';
479
+ try {
480
+ text = readFileSync(join(projectRoot, entry.path), 'utf-8');
481
+ } catch {
482
+ return;
483
+ }
484
+ const entries = parseSummaryEntries(text);
485
+ if (entries.length === 0) return;
486
+ const entity = isEntityEntries(entries);
487
+ const visible = entries.slice(0, Math.max(1, limits.maxEntriesPerFile));
488
+
489
+ const indexLines = [];
490
+ indexLines.push('### `' + entry.path + '`');
491
+ indexLines.push('');
492
+ indexLines.push('**原则索引**');
493
+ for (const e of visible) {
494
+ const one = entryOneLine(e, entity);
495
+ indexLines.push('- ' + e.title + (one ? ':' + one : ''));
496
+ }
497
+ if (entries.length > visible.length) {
498
+ indexLines.push('- ……还有 ' + (entries.length - visible.length) + ' 条(需要时读原文件)');
499
+ }
500
+ indexLines.push('');
501
+ indexBlocks.push(indexLines.join('\n'));
502
+
503
+ if (fileIndex < limits.maxDetailFiles) {
504
+ const details = selectDetailEntries(entries, entity, batch, limits.maxDetailEntries);
505
+ if (details.length) {
506
+ const dLines = ['**本批重点**', ''];
507
+ for (const d of details) dLines.push(renderEntryDetail(d, entity), '');
508
+ detailBlocks.push('### `' + entry.path + '` 的重点条目\n\n' + dLines.join('\n'));
509
+ }
510
+ }
511
+ });
512
+
513
+ const header = [
514
+ '## 本批对标参考(已注入摘要;决策前仍需核对原文件)',
515
+ '',
516
+ '> 下面是插件按本批任务从 `对标/wiki/` 筛出的原则索引和重点条目。它们是**校准样本**,不是硬规则:',
517
+ '> 与模板、选题、上游产物冲突时,以硬契约和本项目设定为准;禁止照搬专名、情节、句子。',
518
+ '> 这些摘要不是可选背景:本批至少选 1 条与本批字段/场景直接相关的原则,落到具体设计上;没有适用条目就按契约写,不要硬套。',
519
+ '> 摘要只覆盖本批重点,不能替代原文;做本批决策前必须核对原文件里的对应条目,并把参考文件和条目写进 `设定/.reference-decisions.md`。',
520
+ ''
521
+ ].join('\n');
522
+
523
+ if (indexBlocks.length === 0) {
524
+ return header + '(选中的总结文件里没有可解析的条目;按硬契约写,不要编造“对标分析”。)';
525
+ }
526
+
527
+ let out = header;
528
+ for (const block of indexBlocks) {
529
+ if (out.length + block.length > limits.maxChars) break;
530
+ out += block + '\n';
531
+ }
532
+ for (const block of detailBlocks) {
533
+ if (out.length + block.length > limits.maxChars) break;
534
+ out += block + '\n';
535
+ }
536
+ if (out.length > limits.maxChars) {
537
+ out = out.slice(0, limits.maxChars).replace(/[,。;、,.;:\s]+$/, '') + '\n\n(摘要已按上下文预算截断,需要完整条目时读原文件。)';
538
+ }
539
+ return out.trim();
540
+ }