@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,164 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * brief-validate.js —— 校验“每章一份细纲”是否齐全、引用是否成立
4
+ *
5
+ * 这一层不是替代 rhythm-validate.js:聚合 JSON 仍由 rhythm-validate 判结构。
6
+ * 这里只判**写作阶段真正会读的每章细纲**:
7
+ * - 1..T 每章是否都有文件;
8
+ * - 必须字段是否存在;
9
+ * - cast/地点/道具/冲突/伏笔/高潮/爽点/信息差引用是否都能在上游找到;
10
+ * - must_write / must_not_write 是否为空。
11
+ */
12
+ import fs from 'node:fs';
13
+ import path from 'node:path';
14
+
15
+ const SRC = ['设定', 'raw', 'sources'];
16
+
17
+ function readJson(file, fallback = null) {
18
+ try { return JSON.parse(fs.readFileSync(file, 'utf-8')); } catch { return fallback; }
19
+ }
20
+
21
+ function idsOf(list) {
22
+ return new Set((list || []).filter((x) => x && x.id).map((x) => x.id));
23
+ }
24
+
25
+ export function validateChapterBriefs(projectRoot) {
26
+ const skeletonDir = path.join(projectRoot, ...SRC, '骨架');
27
+ const fleshDir = path.join(projectRoot, ...SRC, '血肉');
28
+ const rhythmDir = path.join(projectRoot, ...SRC, '节奏');
29
+ const briefDir = path.join(rhythmDir, '细纲');
30
+ const framework = readJson(path.join(skeletonDir, '框架.json'), {}) || {};
31
+ const entities = readJson(path.join(skeletonDir, '实体.json'), {}) || {};
32
+ const flesh = readJson(path.join(fleshDir, '冲突.json'), {}) || {};
33
+ const plan = readJson(path.join(rhythmDir, '章节规划.json'), {}) || {};
34
+ const T = Number(framework?.capacity?.target_chapters) || (plan.chapters || []).length || 0;
35
+ const errors = [];
36
+ const warnings = [];
37
+
38
+ const ids = {
39
+ characters: idsOf(entities.characters),
40
+ locations: idsOf(entities.locations),
41
+ items: idsOf(entities.items),
42
+ conflicts: idsOf(entities.conflicts),
43
+ foreshadows: idsOf(entities.foreshadows),
44
+ climaxes: idsOf(entities.climaxes),
45
+ pleasures: idsOf(entities.pleasures),
46
+ endings: idsOf(entities.endings),
47
+ };
48
+ const infoGapIds = new Set();
49
+ const gapDir = path.join(projectRoot, ...SRC, '档案', '信息差');
50
+ try {
51
+ for (const name of fs.readdirSync(gapDir)) {
52
+ if (name.endsWith('.json')) infoGapIds.add(name.slice(0, -5));
53
+ }
54
+ } catch { /* ignore */ }
55
+
56
+ const files = new Map();
57
+ try {
58
+ for (const name of fs.readdirSync(briefDir)) {
59
+ const match = /^第(\d+)章\.json$/.exec(name);
60
+ if (!match) continue;
61
+ files.set(Number(match[1]), path.join(briefDir, name));
62
+ }
63
+ } catch {
64
+ errors.push('细纲目录不存在:设定/raw/sources/节奏/细纲');
65
+ }
66
+
67
+ if (T > 0) {
68
+ for (let ch = 1; ch <= T; ch += 1) {
69
+ if (!files.has(ch)) errors.push('缺第 ' + ch + ' 章细纲文件');
70
+ }
71
+ }
72
+ for (const ch of files.keys()) {
73
+ if (T > 0 && (ch < 1 || ch > T)) warnings.push('细纲章号越界:第 ' + ch + ' 章');
74
+ }
75
+
76
+ for (const [ch, file] of [...files.entries()].sort((a, b) => a[0] - b[0])) {
77
+ const brief = readJson(file);
78
+ if (!brief) {
79
+ errors.push('第 ' + ch + ' 章细纲不是合法 JSON');
80
+ continue;
81
+ }
82
+ for (const key of ['ch', 'title', 'volume', 'scene_focus', 'pov', 'cast', 'must_write', 'must_not_write']) {
83
+ if (brief[key] === undefined || brief[key] === null || brief[key] === '') {
84
+ errors.push('第 ' + ch + ' 章缺字段:' + key);
85
+ }
86
+ }
87
+ if (!Array.isArray(brief.cast) || brief.cast.length === 0) {
88
+ warnings.push('第 ' + ch + ' 章没有 cast(可能只剩主角或上游时间线过窄)');
89
+ }
90
+ for (const c of (brief.cast || [])) {
91
+ if (c && c.id && !ids.characters.has(c.id)) errors.push('第 ' + ch + ' 章 cast 引用了不存在的人物:' + c.id);
92
+ }
93
+ if (brief.location && brief.location.id && !ids.locations.has(brief.location.id)) {
94
+ errors.push('第 ' + ch + ' 章地点引用了不存在的地点:' + brief.location.id);
95
+ }
96
+ for (const item of (brief.items || [])) {
97
+ if (item && item.id && !ids.items.has(item.id)) errors.push('第 ' + ch + ' 章道具引用了不存在的道具:' + item.id);
98
+ }
99
+ for (const conflict of (brief.conflicts || [])) {
100
+ if (conflict && conflict.id && !ids.conflicts.has(conflict.id)) {
101
+ errors.push('第 ' + ch + ' 章冲突引用了不存在的冲突:' + conflict.id);
102
+ }
103
+ }
104
+ for (const rel of (brief.relationship_changes || [])) {
105
+ if (rel && rel.from && !ids.characters.has(rel.from)) {
106
+ errors.push('第 ' + ch + ' 章关系变化引用了不存在的人物:' + rel.from);
107
+ }
108
+ if (rel && rel.to && !ids.characters.has(rel.to)) {
109
+ errors.push('第 ' + ch + ' 章关系变化引用了不存在的人物:' + rel.to);
110
+ }
111
+ }
112
+ for (const f of (brief.foreshadow_actions || [])) {
113
+ if (f && f.id && !ids.foreshadows.has(f.id)) errors.push('第 ' + ch + ' 章伏笔引用了不存在的伏笔:' + f.id);
114
+ }
115
+ if (brief.climax && brief.climax.id && !ids.climaxes.has(brief.climax.id)) {
116
+ errors.push('第 ' + ch + ' 章高潮引用了不存在的高潮:' + brief.climax.id);
117
+ }
118
+ if (brief.pleasure && brief.pleasure.id && !ids.pleasures.has(brief.pleasure.id)) {
119
+ errors.push('第 ' + ch + ' 章爽点引用了不存在的爽点:' + brief.pleasure.id);
120
+ }
121
+ if (brief.ending && brief.ending.id && !ids.endings.has(brief.ending.id)) {
122
+ errors.push('第 ' + ch + ' 章结局引用了不存在的结局:' + brief.ending.id);
123
+ }
124
+ for (const gap of (brief.information_gaps || [])) {
125
+ if (gap && gap.id && !infoGapIds.has(gap.id)) {
126
+ errors.push('第 ' + ch + ' 章信息差引用了不存在的档案:' + gap.id);
127
+ }
128
+ }
129
+ if (Array.isArray(brief.must_write) && brief.must_write.length === 0) {
130
+ warnings.push('第 ' + ch + ' 章 must_write 为空');
131
+ }
132
+ if (Array.isArray(brief.must_not_write) && brief.must_not_write.length === 0) {
133
+ warnings.push('第 ' + ch + ' 章 must_not_write 为空');
134
+ }
135
+ }
136
+
137
+ return {
138
+ status: errors.length ? 1 : 0,
139
+ stage: '章节细纲',
140
+ total: T,
141
+ files: files.size,
142
+ errors: errors.slice(0, 200),
143
+ warnings: warnings.slice(0, 200),
144
+ };
145
+ }
146
+
147
+ function main() {
148
+ const args = process.argv.slice(2);
149
+ const rootArg = args.find((a) => a.startsWith('--project-root='));
150
+ const dirArg = args.find((a) => a.startsWith('--stage-dir='));
151
+ let projectRoot = rootArg ? rootArg.slice('--project-root='.length) : '';
152
+ if (!projectRoot && dirArg) {
153
+ projectRoot = path.resolve(dirArg.slice('--stage-dir='.length), '..', '..', '..', '..');
154
+ }
155
+ if (!projectRoot) {
156
+ process.stderr.write('需要 --project-root=<项目根> 或 --stage-dir=<节奏目录>\n');
157
+ process.exit(2);
158
+ }
159
+ const result = validateChapterBriefs(projectRoot);
160
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
161
+ process.exitCode = result.status;
162
+ }
163
+
164
+ if (process.argv[1] && process.argv[1].endsWith('brief-validate.js')) main();
@@ -0,0 +1,289 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * check-guide-compliance.js v1.2
4
+ * 源码中零反引号。
5
+ *
6
+ * v1.2 变更(修复批次 P2-2 / P2-3 + 消除字面反引号):
7
+ * - 原版 RANGE_SKIP_KEYWORDS、错误代码正则、字符串字面量中含字面反引号,
8
+ * 本身违反本文件检查的规则,且可能破坏 markdown 渲染。
9
+ * 本版统一用 String.fromCharCode(96) 生成反引号字符与正则。
10
+ * - P2-2:路径含 audit 的文件豁免 ERROR_CODE_IN_BODY 检查
11
+ * (audit 文件本质是错误代码处理手册,错误代码出现在正文是必要的)
12
+ * - P2-3:路径含 写作指导 的文件豁免 RANGE_IN_GUIDE 检查
13
+ * (写作指导明确允许具体字数/章节号作为写法参考)
14
+ * - ERROR_CODE_ALLOWED_SECTION 补充 audit 文件的主要节
15
+ *
16
+ * 检查五条禁令:
17
+ * 1. 不出现数字区间(示例节、代码块、表格、引用段除外)
18
+ * 2. 不出现 - [ ] / - [x] 检查清单
19
+ * 3. 错误代码只允许出现在白名单节(audit 文件整体豁免)
20
+ * 4. 每个指导文件必须有"与其他文件的关系"节
21
+ * 5. 不重复枚举值(只检查显式的"X 个值:..."列举模式)
22
+ *
23
+ * 用法:
24
+ * node check-guide-compliance.js --dir=references/骨架指导
25
+ * node check-guide-compliance.js --file=references/骨架指导/01-positioning.md
26
+ * node check-guide-compliance.js --dir=references/骨架指导 --glossary=references/glossary.md
27
+ * 退出码:0=通过 1=有问题 2=脚本异常
28
+ */
29
+
30
+ import fs from 'node:fs';
31
+ import path from 'node:path';
32
+ import { fileURLToPath } from 'node:url';
33
+ import { parseGlossaryFromMd } from './graph-utils.js';
34
+
35
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
36
+
37
+ // ============================================================
38
+ // 反引号字符与正则(零反引号约定)
39
+ // ============================================================
40
+ const BT_CHAR = String.fromCharCode(96);
41
+ const BT_RE_GLOBAL = new RegExp(BT_CHAR, 'g');
42
+
43
+ // ============================================================
44
+ // 参数解析
45
+ // ============================================================
46
+ function parseArgs() {
47
+ const args = process.argv.slice(2);
48
+ const result = { dir: null, file: null, glossary: null };
49
+ for (const arg of args) {
50
+ const m = arg.match(/^--(\w+)=(.+)$/);
51
+ if (m) result[m[1]] = m[2];
52
+ }
53
+ return result;
54
+ }
55
+
56
+ // ============================================================
57
+ // 路径分类(P2-2 / P2-3)
58
+ // ============================================================
59
+ function classifyPath(filePath) {
60
+ const norm = filePath.replace(/\\/g, '/');
61
+ return {
62
+ isAudit: /\/\d+-audit\.md$/i.test(norm) || /\/audit\.md$/i.test(norm),
63
+ isWritingGuide: /\/写作指导\//.test(norm)
64
+ };
65
+ }
66
+
67
+ // ============================================================
68
+ // 检查规则配置
69
+ // ============================================================
70
+
71
+ // 注意:示例类关键词("示例"/"例如"/"比如")只用于 inExampleSection 的补充,
72
+ // 正文段落中如果写"例如:每卷 5-8 个单元"仍会被报(这是正确行为)
73
+ const RANGE_SKIP_KEYWORDS = [
74
+ '见 formulas-', '由 formulas-',
75
+ '见 模板/', '由 模板/',
76
+ '见 glossary', '由 glossary',
77
+ '见 ' + BT_CHAR + 'formulas-',
78
+ '由 ' + BT_CHAR + 'formulas-',
79
+ '见 ' + BT_CHAR + '模板/',
80
+ '由 ' + BT_CHAR + '模板/',
81
+ '见 ' + BT_CHAR + 'glossary'
82
+ ];
83
+
84
+ const RANGE_PATTERNS = [
85
+ { re: /至少\s*\d+\s*[个条卷章人字次位天]/, label: '至少 N 个' },
86
+ { re: /至多\s*\d+/, label: '至多 N' },
87
+ { re: /不超过\s*\d+/, label: '不超过 N' },
88
+ { re: /不低于\s*\d+/, label: '不低于 N' },
89
+ { re: /\d+\s*[-~到至]\s*\d+\s*[个条卷章人字次位天]/, label: 'N-M 区间' },
90
+ { re: /\bceil\(|\bfloor\(/, label: '数学函数' }
91
+ ];
92
+
93
+ // 错误代码前缀
94
+ const ERROR_CODE_PREFIXES = /^(EXTRA_FIELD|REQUIRED|TYPE|PATTERN|ENUM|ENUM_MISSING|ID_DUP|ID_ORPHAN|NAME_DUP|SKELETON_|FLESH_|G_|DERIVATION_|QUANTITY_|CAPACITY_|VOLUME_|SPAN_|RHYTHM_|CHAP_|TIMING|ARC_|XREF|ANTAG_|SELF_|BRANCH_|NODE_|MAIN_|HOOK_|ACT_|FACTION_|ENDING_|BOOK_|LOCATION_|CHARSET_|PLACEHOLDER|ARG|READ|TEMPLATE|GLOSSARY_LOAD|FORMULAS_LOAD|UPSTREAM_|FLESH_GROUP|FLESH_FREETEXT|ENUM_VALUE_|RHYTHM_COVERAGE|RHYTHM_ACT|NO_SELF_RELATION|EMPTY_HARD_CONSTRAINTS|CROSS_TYPE_NAME_DUP)/;
95
+
96
+ // v1.2:白名单节补充 audit 文件的主要节(双保险,audit 文件也会被整体豁免)
97
+ const ERROR_CODE_ALLOWED_SECTION = /与其他文件的关系|错误代码速查|常见错误|故障排查|BLOCKED|全量校验后的处理|跨文件错误的特殊处理|单文件校验失败的处理|前置检查失败的处理/;
98
+
99
+ // 错误代码匹配正则(零反引号:用 BT_CHAR 拼接)
100
+ const ERROR_CODE_RE = new RegExp(BT_CHAR + '([A-Z][A-Z_]{2,})' + BT_CHAR, 'g');
101
+
102
+ // ============================================================
103
+ // 单文件检查
104
+ // ============================================================
105
+ function checkFile(filePath, glossaryEnums) {
106
+ const content = fs.readFileSync(filePath, 'utf-8');
107
+ const lines = content.split('\n');
108
+
109
+ const errors = [];
110
+ const warnings = [];
111
+
112
+ const { isAudit, isWritingGuide } = classifyPath(filePath);
113
+
114
+ let inCodeBlock = false;
115
+ let inExampleSection = false;
116
+ let inErrorCodeAllowedSection = false;
117
+ let hasRelationSection = false;
118
+
119
+ for (let i = 0; i < lines.length; i++) {
120
+ const line = lines[i];
121
+ const lineNum = i + 1;
122
+ const trimmed = line.trim();
123
+
124
+ if (trimmed.startsWith('```')) {
125
+ inCodeBlock = !inCodeBlock;
126
+ continue;
127
+ }
128
+ if (inCodeBlock) continue;
129
+
130
+ if (line.startsWith('## ')) {
131
+ inExampleSection = /示例/.test(line);
132
+ inErrorCodeAllowedSection = ERROR_CODE_ALLOWED_SECTION.test(line);
133
+ if (/与其他文件的关系/.test(line)) hasRelationSection = true;
134
+ continue;
135
+ }
136
+
137
+ // 检查 2:检查清单
138
+ if (/^\s*-\s*\[[ xX]\]/.test(line)) {
139
+ errors.push({
140
+ line: lineNum,
141
+ code: 'CHECKLIST_IN_GUIDE',
142
+ message: '出现检查清单项: ' + trimmed.slice(0, 60)
143
+ });
144
+ continue;
145
+ }
146
+
147
+ // 检查 1:数字区间(P2-3:写作指导豁免)
148
+ if (!isWritingGuide && !inExampleSection && !trimmed.startsWith('|')) {
149
+ let skip = false;
150
+ for (const kw of RANGE_SKIP_KEYWORDS) {
151
+ if (line.includes(kw)) { skip = true; break; }
152
+ }
153
+ if (!skip) {
154
+ for (const { re, label } of RANGE_PATTERNS) {
155
+ if (re.test(line)) {
156
+ warnings.push({
157
+ line: lineNum,
158
+ code: 'RANGE_IN_GUIDE',
159
+ message: '疑似数字区间(' + label + '): ' + trimmed.slice(0, 60)
160
+ });
161
+ break;
162
+ }
163
+ }
164
+ }
165
+ }
166
+
167
+ // 检查 3:错误代码(P2-2:audit 文件整体豁免;其余看白名单节)
168
+ if (!isAudit && !inErrorCodeAllowedSection) {
169
+ ERROR_CODE_RE.lastIndex = 0;
170
+ let m;
171
+ while ((m = ERROR_CODE_RE.exec(line)) !== null) {
172
+ const code = m[1];
173
+ if (ERROR_CODE_PREFIXES.test(code)) {
174
+ warnings.push({
175
+ line: lineNum,
176
+ code: 'ERROR_CODE_IN_BODY',
177
+ message: '正文中出现错误代码 ' + BT_CHAR + code + BT_CHAR + '(应在"与其他文件的关系"或"错误代码速查/常见错误/故障排查/BLOCKED/全量校验后的处理/跨文件错误的特殊处理"节)'
178
+ });
179
+ }
180
+ }
181
+ }
182
+ }
183
+
184
+ // 检查 4:必须有"与其他文件的关系"节
185
+ if (!hasRelationSection) {
186
+ errors.push({
187
+ line: 0,
188
+ code: 'MISSING_RELATION_SECTION',
189
+ message: '缺少"与其他文件的关系"节'
190
+ });
191
+ }
192
+
193
+ // 检查 5:枚举值连续列举
194
+ const listMatches = [...content.matchAll(/(\d+)\s*个值[::]\s*([^\n]+)/g)];
195
+ for (const m of listMatches) {
196
+ const listPart = m[2];
197
+ for (const [name, vals] of Object.entries(glossaryEnums)) {
198
+ if (vals.length < 2) continue;
199
+ if (vals.every(v => listPart.includes(v))) {
200
+ warnings.push({
201
+ line: 0,
202
+ code: 'ENUM_LIST_IN_GUIDE',
203
+ message: '可能重复枚举 ' + name + ' 的所有值(应改为"见 glossary 的 ' + name + '")'
204
+ });
205
+ }
206
+ }
207
+ }
208
+
209
+ return { errors, warnings };
210
+ }
211
+
212
+ // ============================================================
213
+ // 主流程
214
+ // ============================================================
215
+ function main() {
216
+ const args = parseArgs();
217
+
218
+ if (!args.dir && !args.file) {
219
+ process.stderr.write('需要 --dir 或 --file\n');
220
+ return process.exit(2);
221
+ }
222
+
223
+ if (!args.glossary) {
224
+ args.glossary = path.join(__dirname, 'glossary.md');
225
+ }
226
+
227
+ if (!fs.existsSync(args.glossary)) {
228
+ process.stderr.write('glossary.md 不存在: ' + args.glossary + '\n');
229
+ return process.exit(2);
230
+ }
231
+
232
+ let glossaryEnums = {};
233
+ try {
234
+ const parsed = parseGlossaryFromMd(fs.readFileSync(args.glossary, 'utf-8'));
235
+ glossaryEnums = parsed.enums;
236
+ } catch (e) {
237
+ process.stderr.write('glossary.md 解析失败: ' + e.message + '\n');
238
+ return process.exit(2);
239
+ }
240
+
241
+ const files = [];
242
+ if (args.file) {
243
+ files.push(args.file);
244
+ } else {
245
+ if (!fs.existsSync(args.dir)) {
246
+ process.stderr.write('目录不存在: ' + args.dir + '\n');
247
+ return process.exit(2);
248
+ }
249
+ for (const f of fs.readdirSync(args.dir)) {
250
+ if (f.endsWith('.md')) {
251
+ files.push(path.join(args.dir, f));
252
+ }
253
+ }
254
+ }
255
+
256
+ const results = [];
257
+ let totalErrors = 0;
258
+ let totalWarnings = 0;
259
+
260
+ for (const file of files) {
261
+ if (!fs.existsSync(file)) {
262
+ results.push({ file: file, errors: [{ code: 'READ', message: '文件不存在' }], warnings: [] });
263
+ totalErrors++;
264
+ continue;
265
+ }
266
+ try {
267
+ const result = checkFile(file, glossaryEnums);
268
+ results.push({ file: file, errors: result.errors, warnings: result.warnings });
269
+ totalErrors += result.errors.length;
270
+ totalWarnings += result.warnings.length;
271
+ } catch (e) {
272
+ results.push({ file: file, errors: [{ code: 'CHECK_FAILED', message: e.message }], warnings: [] });
273
+ totalErrors++;
274
+ }
275
+ }
276
+
277
+ const output = {
278
+ status: totalErrors === 0 ? 0 : 1,
279
+ total_files: files.length,
280
+ total_errors: totalErrors,
281
+ total_warnings: totalWarnings,
282
+ results: results
283
+ };
284
+
285
+ process.stdout.write(JSON.stringify(output, null, 2) + '\n');
286
+ process.exitCode = totalErrors === 0 ? 0 : 1;
287
+ }
288
+
289
+ main();