@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,495 @@
1
+ /**
2
+ * fancy-analyze/summary-progress.js —— 技法总结进度(纯本地,零网络、零 LLM)。
3
+ *
4
+ * ## 为什么需要它
5
+ *
6
+ * `/fancy-analyze summary` 的真实动作只有一件:把一段提示词投给模型
7
+ * (plugins/fancy-analyze/index.js 的 summary 分支 → llmFollowup)。
8
+ * 真正的写作由模型在后续 agent loop 里**异步**完成,而 llmFollowup 的
9
+ * 返回值只代表"消息已入队"(lib/infra.js:329-332 明写:"这里的 ok 只代表
10
+ * '消息已入队',不代表模型已处理")。
11
+ *
12
+ * 所以命令返回"✅ 技法总结完成"时,模型一个字都还没写 ——
13
+ * 进度只能从**文件系统事实**里读出来。
14
+ *
15
+ * ## 单一事实源:总结文件头部的「已总结章节」
16
+ *
17
+ * 提示词要求增量写入:每读完一个源文件就把提炼结果追加进
18
+ * `对标/wiki/<技法>/<技法>-总结.md`。
19
+ *
20
+ * 但"增量追加"本身挡不住重复劳动 —— 第二次跑 summary 时,模型会把
21
+ * 已经总结过的章节再读一遍、再追加一遍(提示词里的"去重合并"是软约束,
22
+ * 模型不保证做到)。
23
+ *
24
+ * 所以**头部要记章节清单**,而且它就是唯一事实源:
25
+ *
26
+ * ---
27
+ * 已总结章节:
28
+ * - 全球高武-节奏韵律-第1章
29
+ * - 全球高武-节奏韵律-第2章
30
+ * ---
31
+ *
32
+ * 头部**只有这一个键** —— 早先还写过「标题:」「标签:」,但整个插件里
33
+ * 没有任何代码读它们,是死字段(详见 append-summary-chapter.js)。
34
+ *
35
+ * 由此:
36
+ * · 待总结章节 = 源文件清单 − 头部已总结章节
37
+ * · 派活时**只给目录**,章节由模型自己去目录里比对(几千章时列名字会撑爆提示词)
38
+ * · 待总结章节为空 → 该技法**已完成**
39
+ *
40
+ * ## 为什么不另写状态文件
41
+ *
42
+ * 一切都能从总结文件本身推出来,所以**不写** `.summary-state.json` 之类的东西:
43
+ * 少一个可能与事实漂移的数据源,少一份"写失败要报出来"的复杂度,
44
+ * 而且这个命令可以随时查、查多少次都一样。
45
+ *
46
+ * ## 头部缺字段怎么办
47
+ *
48
+ * 老文件、或模型没按格式写头部 → 章节清单解析为空。
49
+ * 这时**保守退化**:把全部源文件当成"未总结"(等于回到派发整个目录的老行为),
50
+ * 并且在进度报告里明说"头部没有章节清单,无法判断哪些已总结"。
51
+ * 宁可多派一次(模型会去重合并),也不能因为头部格式没写对就卡住不动。
52
+ */
53
+
54
+ import fs from 'node:fs';
55
+ import path from 'node:path';
56
+
57
+ /** 总结文件头部里记录已总结章节的字段名。 */
58
+ export const SUMMARY_FRONTMATTER_KEY = '已总结章节';
59
+
60
+ /** llm_wiki 的摄入产物目录,不是技法目录。 */
61
+ export const EXCLUDED_WIKI_DIRS = ['来源', '来源摘要'];
62
+
63
+ /**
64
+ * 跨章节实体页目录:这些目录也要做 summary,但总结单位是“实体页”,
65
+ * 不是“章节”。提示词会读取每个实体页的 `## 可复用设计规则` 和
66
+ * `## 风险与反例`,提炼跨实体、跨章节的设计原则。
67
+ */
68
+ export const ENTITY_SCOPED_DIRS = [
69
+ '人物关系设计',
70
+ '势力结构',
71
+ '世界观设计',
72
+ '冲突设计',
73
+ '主线支线结构',
74
+ '分卷节奏',
75
+ '伏笔布局',
76
+ '角色弧光设计',
77
+ ];
78
+
79
+ /**
80
+ * 旧版按章结构页目录。它们与新版实体页**不是同一种东西**:
81
+ * · 伏笔回收(按章)→ 伏笔布局(按伏笔实体)
82
+ * · 世界观构建 / 世界观规则设计(按章)→ 世界观设计(按规则/体系实体)
83
+ * · 角色弧光(按章)→ 角色弧光设计(按角色实体)
84
+ *
85
+ * 不能自动迁移,也不能把它们当实体页检查 —— 否则每章一个页面会被误判成
86
+ * “同一个实体有多个页面”。这里只负责让它们不参与 summary 派发,并在
87
+ * status / summary-status 里提示重新 ingest。
88
+ */
89
+ export const LEGACY_STRUCTURE_DIRS = [
90
+ '伏笔回收',
91
+ '世界观构建',
92
+ '世界观规则设计',
93
+ '角色弧光',
94
+ ];
95
+
96
+ /** 进度状态。 */
97
+ export const SUMMARY_STATE = {
98
+ DONE: 'done',
99
+ RUNNING: 'running',
100
+ PENDING: 'pending',
101
+ NO_INPUT: 'no-input',
102
+ };
103
+
104
+ /**
105
+ * 取文件开头的 YAML front-matter 文本。
106
+ *
107
+ * 只做"取开头两段 --- 之间的原文",不做 YAML 解析 ——
108
+ * 本插件不能引入新依赖,而我们需要的东西就是一个键值对列表。
109
+ *
110
+ * @param {string} text - 文件全文
111
+ * @returns {string|null} front-matter 原文;没有 front-matter 时返回 null
112
+ */
113
+ export function extractFrontmatter(text) {
114
+ if (typeof text !== 'string') return null;
115
+ const normalized = text.replace(/^/, '');
116
+ if (!/^---\r?\n/.test(normalized)) return null;
117
+ const rest = normalized.slice(normalized.indexOf('\n') + 1);
118
+ const end = rest.search(/^---[ \t]*(\r?\n|$)/m);
119
+ if (end === -1) return null;
120
+ return rest.slice(0, end);
121
+ }
122
+
123
+ /**
124
+ * 从 front-matter 里读出已总结章节清单。
125
+ *
126
+ * 支持两种写法(容错:模型不一定照抄示例):
127
+ * 块列表(示例里的写法)
128
+ * 已总结章节:
129
+ * - 甲-第1章
130
+ * - 甲-第2章
131
+ * 行内数组
132
+ * 已总结章节: [甲-第1章, 甲-第2章]
133
+ *
134
+ * @param {string} frontmatter - front-matter 原文
135
+ * @returns {string[]} 已总结章节标识(去重、保序)
136
+ */
137
+ export function parseSummarizedChapters(frontmatter) {
138
+ if (typeof frontmatter !== 'string' || frontmatter.length === 0) return [];
139
+ const lines = frontmatter.split(/\r?\n/);
140
+ const out = [];
141
+ const seen = new Set();
142
+ const push = (value) => {
143
+ const v = String(value).trim().replace(/^["']|["']$/g, '').trim();
144
+ if (v.length === 0 || seen.has(v)) return;
145
+ seen.add(v);
146
+ out.push(v);
147
+ };
148
+
149
+ for (let i = 0; i < lines.length; i++) {
150
+ const line = lines[i];
151
+ const keyMatch = line.match(/^\s*已总结章节\s*[::]\s*(.*)$/);
152
+ if (!keyMatch) continue;
153
+ const inline = keyMatch[1].trim();
154
+ if (inline.startsWith('[') && inline.endsWith(']')) {
155
+ for (const part of inline.slice(1, -1).split(',')) push(part);
156
+ continue;
157
+ }
158
+ // 块列表:往下吃所有 "- xxx"
159
+ for (let j = i + 1; j < lines.length; j++) {
160
+ const item = lines[j].match(/^\s*-\s+(.*\S)\s*$/);
161
+ if (!item) {
162
+ // 空行继续;碰到别的键就停
163
+ if (lines[j].trim() === '') continue;
164
+ break;
165
+ }
166
+ push(item[1]);
167
+ }
168
+ break;
169
+ }
170
+ return out;
171
+ }
172
+
173
+ /**
174
+ * 把「已总结章节」的名字对齐到源文件标识。
175
+ *
176
+ * 模型写章节名的格式很不统一(实测):
177
+ * - `全球高武-场景转换-第3章` 精确
178
+ * - `[全球高武-世界观构建-第2章, 第3章]` 带方括号 + 简写
179
+ * - `第7章` 裸章号
180
+ *
181
+ * 对齐策略(从严到宽,只在**唯一**时才认,绝不猜):
182
+ * 1. 精确等于某个源文件标识 → 对齐
183
+ * 2. 尾部是「第N章」,且目录里**只有一个**文件以它结尾 → 对齐
184
+ *
185
+ * 第 2 条能救回简写;第 1 条之外的绝不宽松 —— 上游数据里出现过错名文件
186
+ * (`全球高武-伏文富武-第3章.md`,技法名打错字),宽松匹配会把
187
+ * 真的没总结的章节误判成已总结,于是那一章永远不再被处理。
188
+ *
189
+ * @param {string[]} names - 待对齐的章节名
190
+ * @param {string[]} sourceFiles - 源文件标识集合
191
+ * @returns {{aligned: string[], unmatched: string[]}}
192
+ */
193
+ export function alignChapterIds(names, sourceFiles) {
194
+ const known = Array.isArray(sourceFiles) ? sourceFiles : [];
195
+ const aligned = [];
196
+ const unmatched = [];
197
+ const seen = new Set();
198
+ for (const raw of names) {
199
+ // 去方括号、引号、空白,以及 .md 后缀
200
+ // (实测三种写法都出现过:`甲-第1章`、`[甲-第1章]`、`[甲-第1章.md]`)
201
+ const name = String(raw)
202
+ .trim()
203
+ .replace(/^[[\]]+|[[\]]+$/g, '')
204
+ .trim()
205
+ .replace(/\.md$/i, '')
206
+ .trim();
207
+ if (name.length === 0) continue;
208
+ let hit = null;
209
+ if (known.includes(name)) {
210
+ hit = name;
211
+ } else {
212
+ const tail = name.match(/第\d+章\s*$/);
213
+ if (tail) {
214
+ const candidates = known.filter((f) => f.endsWith(tail[0]));
215
+ if (candidates.length === 1) hit = candidates[0];
216
+ }
217
+ }
218
+ if (hit === null) {
219
+ unmatched.push(name);
220
+ continue;
221
+ }
222
+ if (seen.has(hit)) continue;
223
+ seen.add(hit);
224
+ aligned.push(hit);
225
+ }
226
+ return { aligned, unmatched };
227
+ }
228
+
229
+ /**
230
+ * 从总结正文里**反推**已总结章节。
231
+ *
232
+ * ## 为什么需要反推
233
+ *
234
+ * 「已总结章节」头部是新加的,老总结文件(本次改动之前生成的)里没有。
235
+ * 如果只认头部,所有老项目都会被判成"一个章节都没总结",
236
+ * 下次 summary 会把整本重新跑一遍 —— 对用户来说是灾难性的返工。
237
+ *
238
+ * 好在每条技法条目本身就记着它来自哪些文件:
239
+ *
240
+ * ### 轻叙、爆料与心理停顿交替
241
+ * - 来源章节:全球高武-节奏韵律-第1章
242
+ *
243
+ * 于是可以从正文里反推"哪些章节被总结过"。
244
+ *
245
+ * 反推的结果**不如头部权威**(模型可能写错、可能漏写),
246
+ * 所以调用方要能区分两者:头部优先,缺失时用反推兜底,两者都没有才算
247
+ * "完全不知道"。
248
+ *
249
+ * @param {string} text - 总结文件全文
250
+ * @param {string[]} knownIds - 已知的源章节标识(用于对齐与过滤)
251
+ * @returns {{chapters: string[], unmatched: string[]}}
252
+ */
253
+ export function inferSummarizedChapters(text, knownIds) {
254
+ if (typeof text !== 'string' || text.length === 0) return { chapters: [], unmatched: [] };
255
+ const raw = [];
256
+ for (const match of text.matchAll(/^\s*-\s*来源(?:章节|实体|页面)\s*[::]\s*(.+)$/gm)) {
257
+ // "来源章节:甲-第1章、甲-第2章" → 拆开
258
+ for (const part of String(match[1]).split(/[、,,;;]/)) {
259
+ const v = part.trim();
260
+ if (v.length > 0) raw.push(v);
261
+ }
262
+ }
263
+ return alignChapterIds(raw, knownIds);
264
+ }
265
+
266
+ /**
267
+ * 取出章节标识里的章号。
268
+ *
269
+ * 章节标识长这样:`全球高武-场景转换-第10章`。
270
+ * 章号是稳定语义(技法名可能被打错字,章号不会),所以排序靠它。
271
+ *
272
+ * @param {string} id - 章节标识
273
+ * @returns {number|null} 章号;取不到时返回 null
274
+ */
275
+ export function chapterNumber(id) {
276
+ const match = String(id).match(/第(\d+)章/);
277
+ return match ? Number(match[1]) : null;
278
+ }
279
+
280
+ /**
281
+ * 章节标识的自然排序:先按章号升序,再按名称。
282
+ *
283
+ * 纯字典序会把「第10章」排在「第2章」前面 —— 那样批次推进会跳着来,
284
+ * 模型拿到的章节序列也不连续。
285
+ *
286
+ * @param {string} a - 章节标识
287
+ * @param {string} b - 章节标识
288
+ * @returns {number} 比较结果
289
+ */
290
+ export function compareChapterIds(a, b) {
291
+ const na = chapterNumber(a);
292
+ const nb = chapterNumber(b);
293
+ if (na !== null && nb !== null && na !== nb) return na - nb;
294
+ if (na !== null && nb === null) return -1;
295
+ if (na === null && nb !== null) return 1;
296
+ return String(a).localeCompare(String(b), 'zh-Hans-CN');
297
+ }
298
+
299
+ /**
300
+ * 列出对标/wiki 下的技法目录(排除 llm_wiki 的摄入产物目录)。
301
+ *
302
+ * @param {string} wikiRoot - 对标/wiki 的绝对路径
303
+ * @returns {string[]} 目录名,按字典序
304
+ */
305
+ export function listTechDirs(wikiRoot) {
306
+ let entries;
307
+ try {
308
+ entries = fs.readdirSync(wikiRoot, { withFileTypes: true });
309
+ } catch {
310
+ return [];
311
+ }
312
+ const dirs = [];
313
+ for (const entry of entries) {
314
+ if (!entry.isDirectory()) continue;
315
+ if (EXCLUDED_WIKI_DIRS.includes(entry.name)) continue;
316
+ dirs.push(entry.name);
317
+ }
318
+ // 中文目录名按拼音排(否则 UTF-16 码位序会把「已完成/未开始/进行中」这类
319
+ // 名字排得毫无章法),localeCompare 在 Node 的 ICU 下可用。
320
+ return dirs.sort((a, b) => a.localeCompare(b, 'zh-Hans-CN'));
321
+ }
322
+
323
+ /**
324
+ * 返回需要做 summary 的目录:按章技法目录 + 跨章节实体目录。
325
+ * 旧版按章结构目录仍排除(它们等待迁移,不参与新 summary)。
326
+ */
327
+ export function listSummaryDirs(wikiRoot) {
328
+ return listTechDirs(wikiRoot).filter(
329
+ (name) => !LEGACY_STRUCTURE_DIRS.includes(name));
330
+ }
331
+
332
+ function countEntityPages(wikiRoot, dirName) {
333
+ try {
334
+ const files = fs.readdirSync(path.join(wikiRoot, dirName), { withFileTypes: true });
335
+ return files.filter((file) =>
336
+ file.isFile() &&
337
+ file.name.endsWith('.md') &&
338
+ !file.name.endsWith('-总结.md')
339
+ ).length;
340
+ } catch {
341
+ return 0;
342
+ }
343
+ }
344
+
345
+ /**
346
+ * 读一个技法目录的进度。
347
+ *
348
+ * @param {string} wikiRoot - 对标/wiki 的绝对路径
349
+ * @param {string} dirName - 技法目录名
350
+ * @returns {object|null} 进度对象;目录读不到时返回 null
351
+ */
352
+ export function readDirProgress(wikiRoot, dirName) {
353
+ const dirPath = path.join(wikiRoot, dirName);
354
+ const summaryName = `${dirName}-总结.md`;
355
+ const summaryPath = path.join(dirPath, summaryName);
356
+
357
+ let files;
358
+ try {
359
+ files = fs.readdirSync(dirPath, { withFileTypes: true });
360
+ } catch {
361
+ return null;
362
+ }
363
+
364
+ const sourceFiles = [];
365
+ let sourceBytes = 0;
366
+ for (const file of files) {
367
+ if (!file.isFile()) continue;
368
+ if (!file.name.endsWith('.md')) continue;
369
+ // 总结文件本身不是输入
370
+ if (file.name === summaryName) continue;
371
+ sourceFiles.push(file.name.replace(/\.md$/, ''));
372
+ try {
373
+ sourceBytes += fs.statSync(path.join(dirPath, file.name)).size;
374
+ } catch {
375
+ // 单个文件读不到就当它不存在,不影响其余统计
376
+ }
377
+ }
378
+ sourceFiles.sort(compareChapterIds); // 按章号升序,不是字典序
379
+
380
+ const progress = {
381
+ name: dirName,
382
+ /** chapter = 按章技法;entity = 跨章节实体页。 */
383
+ scope: ENTITY_SCOPED_DIRS.includes(dirName) ? 'entity' : 'chapter',
384
+ summaryPath,
385
+ sourceFiles,
386
+ sourceCount: sourceFiles.length,
387
+ sourceBytes,
388
+ hasInput: sourceFiles.length > 0,
389
+ exists: false,
390
+ summarized: [],
391
+ chapterSource: 'none',
392
+ unmatchedChapters: [],
393
+ pendingFiles: sourceFiles.slice(),
394
+ hasChapterList: false,
395
+ entries: 0,
396
+ bytes: 0,
397
+ updatedAt: 0,
398
+ state: SUMMARY_STATE.PENDING,
399
+ };
400
+
401
+ if (!progress.hasInput) {
402
+ // 一个源文件都没有 → 无输入,不该派发(连总结文件存不存在都无所谓)
403
+ progress.state = SUMMARY_STATE.NO_INPUT;
404
+ return progress;
405
+ }
406
+
407
+ let text;
408
+ try {
409
+ text = fs.readFileSync(summaryPath, 'utf8');
410
+ } catch {
411
+ return progress;
412
+ }
413
+
414
+ let stat;
415
+ try {
416
+ stat = fs.statSync(summaryPath);
417
+ } catch {
418
+ return progress;
419
+ }
420
+
421
+ progress.exists = true;
422
+ progress.bytes = stat.size;
423
+ progress.updatedAt = stat.mtimeMs;
424
+ // 技法条目 = 总结文件里的三级标题(### 开头)。
425
+ // 文件结构是「## 核心技法」下面挂若干「### 技法名」,所以 ### 正好是条目数。
426
+ progress.entries = (text.match(/^###\s+\S/gm) || []).length;
427
+
428
+ const frontmatter = extractFrontmatter(text);
429
+ const fromHeader = alignChapterIds(parseSummarizedChapters(frontmatter), sourceFiles);
430
+ const fromBody = inferSummarizedChapters(text, sourceFiles);
431
+ // 头部权威;头部缺失时用正文反推兜底
432
+ const summarized = fromHeader.aligned.length > 0 ? fromHeader.aligned : fromBody.aligned;
433
+ const chapterSource = fromHeader.aligned.length > 0
434
+ ? 'header'
435
+ : (fromBody.aligned.length > 0 ? 'inferred' : 'none');
436
+ progress.summarized = summarized;
437
+ progress.chapterSource = chapterSource;
438
+ progress.hasChapterList = chapterSource !== 'none';
439
+ progress.unmatchedChapters = [...fromHeader.unmatched, ...fromBody.unmatched];
440
+
441
+ if (!progress.hasChapterList) {
442
+ // 头部没有章节清单 → 保守:全部源文件都算"未总结"
443
+ progress.state = SUMMARY_STATE.PENDING;
444
+ return progress;
445
+ }
446
+
447
+ const done = new Set(summarized);
448
+ progress.pendingFiles = sourceFiles.filter((f) => !done.has(f));
449
+ progress.state = progress.pendingFiles.length === 0
450
+ ? SUMMARY_STATE.DONE
451
+ : SUMMARY_STATE.RUNNING;
452
+ return progress;
453
+ }
454
+
455
+ /**
456
+ * 扫描全部技法目录的进度。
457
+ *
458
+ * @param {string} wikiRoot - 对标/wiki 的绝对路径
459
+ * @returns {{dirs: object[], legacyDirs: object[], done: object[], running: object[], pending: object[], noInput: object[], total: number}}
460
+ */
461
+ export function scanSummaryProgress(wikiRoot) {
462
+ const allDirs = listTechDirs(wikiRoot);
463
+ const legacyDirs = allDirs
464
+ .filter((name) => LEGACY_STRUCTURE_DIRS.includes(name))
465
+ .map((name) => ({ name, pageCount: countEntityPages(wikiRoot, name) }));
466
+ const dirs = allDirs
467
+ .filter((name) => !LEGACY_STRUCTURE_DIRS.includes(name))
468
+ .map((name) => readDirProgress(wikiRoot, name))
469
+ .filter((p) => p !== null);
470
+
471
+ return {
472
+ dirs,
473
+ legacyDirs,
474
+ done: dirs.filter((p) => p.state === SUMMARY_STATE.DONE),
475
+ running: dirs.filter((p) => p.state === SUMMARY_STATE.RUNNING),
476
+ pending: dirs.filter((p) => p.state === SUMMARY_STATE.PENDING),
477
+ noInput: dirs.filter((p) => p.state === SUMMARY_STATE.NO_INPUT),
478
+ total: dirs.length,
479
+ };
480
+ }
481
+
482
+ /**
483
+ * 该派给模型的目录:有输入、且还有章节没总结。
484
+ *
485
+ * 已完成的目录不再重复派发(要重做就用 resummary)——
486
+ * 否则每次 summary 都会让模型把已经提炼过的技法再追加一遍。
487
+ *
488
+ * @param {object} progress - scanSummaryProgress 的结果
489
+ * @returns {object[]} 进度对象数组(按目录名排序)
490
+ */
491
+ export function dirsToDispatch(progress) {
492
+ return progress.dirs.filter(
493
+ (p) => p.hasInput && p.state !== SUMMARY_STATE.DONE && p.pendingFiles.length > 0
494
+ );
495
+ }