@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,419 @@
1
+ /**
2
+ * v2/core.js —— fancy-story-builder 2.0 的共享基础
3
+ *
4
+ * 这里只放确定性能力:
5
+ * - 阶段/目录/文件契约
6
+ * - 统一 JSON 读写与内容哈希
7
+ * - 阶段状态与 wiki 状态映射
8
+ * - 项目内的相对路径
9
+ *
10
+ * 不在这里做 LLM 调用,也不做任何"猜依赖"的判断。
11
+ */
12
+ import fs from 'node:fs';
13
+ import path from 'node:path';
14
+ import crypto from 'node:crypto';
15
+
16
+ export const SCHEMA_VERSION = '2.1';
17
+ export const CONTRACT_VERSION = '2026-11-06';
18
+
19
+ export const V2_STAGES = [
20
+ {
21
+ id: '00-baseline',
22
+ label: '输入基线',
23
+ dir: '00-baseline',
24
+ upstream: [],
25
+ requiredFiles: ['input.json', 'references.json', 'formulas.json'],
26
+ },
27
+ {
28
+ id: '10-scale',
29
+ label: '数量',
30
+ dir: '10-scale',
31
+ upstream: ['00-baseline'],
32
+ requiredFiles: ['scale.json'],
33
+ },
34
+ {
35
+ id: '20-macro',
36
+ label: '宏观',
37
+ dir: '20-macro',
38
+ upstream: ['10-scale'],
39
+ requiredFiles: ['world.json', 'story.json', 'volumes.json', 'lines.json', 'endings.json', 'emotion.json', 'roles.json'],
40
+ },
41
+ {
42
+ id: '30-cast',
43
+ label: '实体',
44
+ dir: '30-cast',
45
+ upstream: ['10-scale', '20-macro'],
46
+ requiredFiles: [
47
+ 'characters.json', 'factions.json', 'locations.json', 'items.json',
48
+ 'conflicts.json', 'foreshadows.json', 'climaxes.json', 'payoffs.json',
49
+ 'relations.json', 'information.json',
50
+ ],
51
+ },
52
+ {
53
+ id: '40-rhythm',
54
+ label: '节奏',
55
+ dir: '40-rhythm',
56
+ upstream: ['10-scale', '20-macro', '30-cast'],
57
+ requiredFiles: ['chapter_map.json'],
58
+ },
59
+ {
60
+ id: '50-writing',
61
+ label: '正文',
62
+ dir: '50-writing',
63
+ upstream: ['40-rhythm'],
64
+ requiredFiles: ['ledger.json'],
65
+ },
66
+ {
67
+ id: '60-deslop',
68
+ label: '去味',
69
+ dir: '60-deslop',
70
+ upstream: ['50-writing'],
71
+ requiredFiles: [],
72
+ },
73
+ ];
74
+
75
+ export const V2_STAGE_BY_ID = Object.fromEntries(V2_STAGES.map((s) => [s.id, s]));
76
+ export const V2_STAGE_IDS = V2_STAGES.map((s) => s.id);
77
+
78
+ export const STAGE_ORDER = V2_STAGE_IDS.slice();
79
+
80
+ /** 旧命令/旧目录 → 新阶段。迁移与兼容都从这里取,避免两处写死。 */
81
+ export const V1_TO_V2_STAGE = {
82
+ skeleton: '10-scale',
83
+ flesh: '30-cast',
84
+ rhythm: '40-rhythm',
85
+ writing: '50-writing',
86
+ deslop: '60-deslop',
87
+ };
88
+
89
+ export const V1_STAGE_DIR = {
90
+ skeleton: '骨架',
91
+ flesh: '血肉',
92
+ rhythm: '节奏',
93
+ writing: '写作',
94
+ };
95
+
96
+ /**
97
+ * 阶段目录:设定/<stage dir>。
98
+ * 注意:不是 设定/raw/sources/<旧目录>;2.0 的阶段目录是并列的。
99
+ */
100
+ export function stageDir(projectRoot, stageId) {
101
+ const spec = V2_STAGE_BY_ID[stageId];
102
+ if (!spec) throw new Error('未知阶段:' + stageId);
103
+ return path.join(projectRoot, '设定', spec.dir);
104
+ }
105
+
106
+ export function stageFile(projectRoot, stageId, name) {
107
+ return path.join(stageDir(projectRoot, stageId), name);
108
+ }
109
+
110
+ export function legacyStageDir(projectRoot, stageId) {
111
+ const name = V1_STAGE_DIR[stageId];
112
+ if (!name) return null;
113
+ return path.join(projectRoot, '设定', 'raw', 'sources', name);
114
+ }
115
+
116
+ export function isV2Project(projectRoot) {
117
+ if (!projectRoot) return false;
118
+ return ['10-scale', '20-macro', '30-cast', '40-rhythm'].some((id) =>
119
+ fs.existsSync(stageDir(projectRoot, id))
120
+ ) || fs.existsSync(path.join(projectRoot, '设定', 'manifest.json'));
121
+ }
122
+
123
+ export function ensureDir(dir) {
124
+ fs.mkdirSync(dir, { recursive: true });
125
+ return dir;
126
+ }
127
+
128
+ export function readJson(file, fallback = null) {
129
+ try {
130
+ return JSON.parse(fs.readFileSync(file, 'utf-8'));
131
+ } catch {
132
+ return fallback;
133
+ }
134
+ }
135
+
136
+ export function readText(file, fallback = null) {
137
+ try {
138
+ return fs.readFileSync(file, 'utf-8');
139
+ } catch {
140
+ return fallback;
141
+ }
142
+ }
143
+
144
+ export function fileExists(file) {
145
+ try {
146
+ return fs.existsSync(file) && fs.statSync(file).size > 0;
147
+ } catch {
148
+ return false;
149
+ }
150
+ }
151
+
152
+ /** 原子写 JSON:临时文件 → rename。 */
153
+ export function writeJsonAtomic(file, value) {
154
+ ensureDir(path.dirname(file));
155
+ const tmp = file + '.tmp-' + process.pid + '-' + Date.now();
156
+ try {
157
+ fs.writeFileSync(tmp, JSON.stringify(value, null, 2) + '\n', 'utf-8');
158
+ fs.renameSync(tmp, file);
159
+ } finally {
160
+ try {
161
+ if (fs.existsSync(tmp)) fs.unlinkSync(tmp);
162
+ } catch {
163
+ // 清理失败不掩盖主流程
164
+ }
165
+ }
166
+ return file;
167
+ }
168
+
169
+ /** 原子写文本。 */
170
+ export function writeTextAtomic(file, text) {
171
+ ensureDir(path.dirname(file));
172
+ const tmp = file + '.tmp-' + process.pid + '-' + Date.now();
173
+ try {
174
+ fs.writeFileSync(tmp, text, 'utf-8');
175
+ fs.renameSync(tmp, file);
176
+ } finally {
177
+ try {
178
+ if (fs.existsSync(tmp)) fs.unlinkSync(tmp);
179
+ } catch {
180
+ // ignore
181
+ }
182
+ }
183
+ return file;
184
+ }
185
+
186
+ export function nowIso() {
187
+ return new Date().toISOString();
188
+ }
189
+
190
+ /** 去掉 _meta 后,对键名递归排序,得到稳定 JSON。 */
191
+ export function canonicalize(value) {
192
+ if (Array.isArray(value)) return value.map(canonicalize);
193
+ if (value && typeof value === 'object') {
194
+ const out = {};
195
+ for (const key of Object.keys(value).sort()) {
196
+ if (key === '_meta') continue;
197
+ out[key] = canonicalize(value[key]);
198
+ }
199
+ return out;
200
+ }
201
+ return value;
202
+ }
203
+
204
+ export function contentHash(value) {
205
+ const stable = JSON.stringify(canonicalize(value));
206
+ return 'sha256:' + crypto.createHash('sha256').update(stable, 'utf-8').digest('hex');
207
+ }
208
+
209
+ export function bytesHash(bytes) {
210
+ return 'sha256:' + crypto.createHash('sha256').update(bytes).digest('hex');
211
+ }
212
+
213
+ export function hashFile(file) {
214
+ const data = readJson(file, null);
215
+ if (data !== null) return contentHash(data);
216
+ try {
217
+ return bytesHash(fs.readFileSync(file));
218
+ } catch {
219
+ return null;
220
+ }
221
+ }
222
+
223
+ /** 阶段聚合哈希:所有业务 JSON 内容哈希再哈希,顺序固定。 */
224
+ export function stageHash(projectRoot, stageId) {
225
+ const spec = V2_STAGE_BY_ID[stageId];
226
+ if (!spec) return null;
227
+ const dir = stageDir(projectRoot, stageId);
228
+ const entries = [];
229
+ const walk = (current) => {
230
+ let names = [];
231
+ try {
232
+ names = fs.readdirSync(current, { withFileTypes: true });
233
+ } catch {
234
+ return;
235
+ }
236
+ for (const entry of names) {
237
+ if (entry.name.startsWith('.') || entry.name === '_context' || entry.name === 'bundle.json' || entry.name === 'README.md') continue;
238
+ const full = path.join(current, entry.name);
239
+ if (entry.isDirectory()) {
240
+ if (['outlines', 'chapters', 'timelines'].includes(entry.name)) walk(full);
241
+ continue;
242
+ }
243
+ if (!entry.name.endsWith('.json') && !entry.name.endsWith('.md')) continue;
244
+ const rel = path.relative(dir, full).split(path.sep).join('/');
245
+ entries.push(rel + ':' + hashFile(full));
246
+ }
247
+ };
248
+ walk(dir);
249
+ entries.sort();
250
+ if (!entries.length) return null;
251
+ return 'sha256:' + crypto.createHash('sha256').update(entries.join('\n'), 'utf-8').digest('hex');
252
+ }
253
+
254
+ export function relProjectPath(projectRoot, file) {
255
+ return path.relative(projectRoot, file).split(path.sep).join('/');
256
+ }
257
+
258
+ export function wikiStatus(status) {
259
+ switch (status) {
260
+ case 'passed':
261
+ return 'done';
262
+ case 'revised':
263
+ case 'locked':
264
+ return 'revised';
265
+ case 'archived':
266
+ case 'deprecated':
267
+ return 'archived';
268
+ case 'stub':
269
+ return 'stub';
270
+ default:
271
+ return 'draft';
272
+ }
273
+ }
274
+
275
+ export function isTerminalStageStatus(status) {
276
+ return status === 'passed';
277
+ }
278
+
279
+ /**
280
+ * 读 v2 阶段状态。
281
+ * 兼容旧阶段:没有 v2 .stage-status.json 时,读旧骨架/血肉/节奏的 all_check。
282
+ */
283
+ export function readStageStatus(projectRoot, stageId, opts = {}) {
284
+ const file = path.join(stageDir(projectRoot, stageId), '.stage-status.json');
285
+ const data = readJson(file, null);
286
+ if (data && typeof data === 'object') {
287
+ return {
288
+ stage: data.stage || stageId,
289
+ status: data.status || 'draft',
290
+ files: data.files || {},
291
+ checkedAt: data.checked_at || null,
292
+ errors: Array.isArray(data.errors) ? data.errors : [],
293
+ warnings: Array.isArray(data.warnings) ? data.warnings : [],
294
+ source: 'v2',
295
+ };
296
+ }
297
+ const legacy = opts.allowLegacy === false ? null : legacyStageDir(projectRoot, Object.keys(V1_STAGE_DIR).find((k) => V1_TO_V2_STAGE[k] === stageId));
298
+ if (legacy) {
299
+ const old = readJson(path.join(legacy, '.stage-status.json'), null);
300
+ const all = old && old.all_check && old.all_check.status;
301
+ if (all) {
302
+ return {
303
+ stage: stageId,
304
+ status: all === 'PASSED' ? 'passed' : (all === 'FAILED' ? 'stale' : 'draft'),
305
+ files: old.files || {},
306
+ checkedAt: old.all_check && old.all_check.last_check || null,
307
+ errors: [],
308
+ warnings: [],
309
+ source: 'v1',
310
+ };
311
+ }
312
+ }
313
+ return { stage: stageId, status: 'draft', files: {}, checkedAt: null, errors: [], warnings: [], source: null };
314
+ }
315
+
316
+ export function writeStageStatus(projectRoot, stageId, report) {
317
+ const file = path.join(stageDir(projectRoot, stageId), '.stage-status.json');
318
+ const payload = {
319
+ schema_version: SCHEMA_VERSION,
320
+ stage: stageId,
321
+ status: report && report.ok ? 'passed' : (report && report.status) || 'stale',
322
+ checked_at: nowIso(),
323
+ files: report && report.files || {},
324
+ errors: report && report.errors || [],
325
+ warnings: report && report.warnings || [],
326
+ };
327
+ writeJsonAtomic(file, payload);
328
+ return payload;
329
+ }
330
+
331
+ /**
332
+ * 统一错误对象。错误只允许指向项目内文件,不允许出现插件源码路径。
333
+ */
334
+ export function v2Error(code, opts = {}) {
335
+ const out = {
336
+ code,
337
+ severity: opts.severity || 'error',
338
+ path: opts.path || '',
339
+ message: opts.message || '',
340
+ };
341
+ if (opts.expected !== undefined) out.expected = opts.expected;
342
+ if (opts.actual !== undefined) out.actual = opts.actual;
343
+ if (opts.hint) out.hint = String(opts.hint).replace(/node_modules|plugins\/fancy-story-builder|scripts\//g, '项目契约文件');
344
+ if (opts.contract) out.contract = opts.contract;
345
+ if (opts.reference) out.reference = opts.reference;
346
+ return out;
347
+ }
348
+
349
+ /** 读数组字段,永远返回数组。 */
350
+ export function asArray(value) {
351
+ return Array.isArray(value) ? value : [];
352
+ }
353
+
354
+ /** 从任意 JSON 值里取 id → 对象的映射。 */
355
+ export function indexById(list) {
356
+ const map = new Map();
357
+ for (const row of asArray(list)) {
358
+ if (row && typeof row.id === 'string' && row.id) map.set(row.id, row);
359
+ }
360
+ return map;
361
+ }
362
+
363
+ /** 所有字符串里出现过的 id 集合(用于引用检查)。 */
364
+ export function stringsOf(value, out = []) {
365
+ if (typeof value === 'string') out.push(value);
366
+ else if (Array.isArray(value)) for (const item of value) stringsOf(item, out);
367
+ else if (value && typeof value === 'object') for (const item of Object.values(value)) stringsOf(item, out);
368
+ return out;
369
+ }
370
+
371
+ /** 字段路径取值:a.b[0].c */
372
+ export function getByPath(obj, dotted) {
373
+ let cur = obj;
374
+ for (const part of String(dotted || '').split('.')) {
375
+ if (!part) continue;
376
+ const m = /^([^\[\]]+)(?:\[(\d+)\])?$/.exec(part);
377
+ if (!m) return undefined;
378
+ if (cur === null || cur === undefined) return undefined;
379
+ cur = cur[m[1]];
380
+ if (m[2] !== undefined) {
381
+ if (!Array.isArray(cur)) return undefined;
382
+ cur = cur[Number(m[2])];
383
+ }
384
+ }
385
+ return cur;
386
+ }
387
+
388
+ export function setByPath(obj, dotted, value) {
389
+ const parts = String(dotted || '').split('.').filter(Boolean);
390
+ let cur = obj;
391
+ for (let i = 0; i < parts.length - 1; i++) {
392
+ const key = parts[i];
393
+ if (!cur[key] || typeof cur[key] !== 'object') cur[key] = {};
394
+ cur = cur[key];
395
+ }
396
+ if (parts.length) cur[parts[parts.length - 1]] = value;
397
+ return obj;
398
+ }
399
+
400
+ export function formatErrorLine(e) {
401
+ const code = e && e.code ? '[' + e.code + '] ' : '';
402
+ const p = e && e.path ? e.path + ':' : '';
403
+ const msg = e && (e.message || e.detail) ? (e.message || e.detail) : '';
404
+ const hint = e && e.hint ? '(' + e.hint + ')' : '';
405
+ return code + p + msg + hint;
406
+ }
407
+
408
+ export function summarizeReport(report, limit = 12) {
409
+ if (!report) return '未知错误';
410
+ const errors = Array.isArray(report.errors) ? report.errors : [];
411
+ const warnings = Array.isArray(report.warnings) ? report.warnings : [];
412
+ const lines = [];
413
+ for (const e of errors.slice(0, limit)) lines.push('- ' + formatErrorLine(e));
414
+ if (errors.length > limit) lines.push('- … 其余 ' + (errors.length - limit) + ' 个错误');
415
+ if (!errors.length && warnings.length) {
416
+ for (const w of warnings.slice(0, limit)) lines.push('- [warning] ' + formatErrorLine(w));
417
+ }
418
+ return lines.join('\n') || '无错误';
419
+ }