@cloud411716/fancy-webnovel 2.2.0 → 3.0.3

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 (212) hide show
  1. package/README.md +65 -0
  2. package/client.js +201 -15
  3. package/cordis.patch.yml +5 -1
  4. package/docs/douyin-plugin.md +118 -0
  5. package/docs/material-library/README.md +404 -0
  6. package/docs/material-library/import.js +437 -0
  7. package/docs/material-library/prompt.md +370 -0
  8. package/docs/material-library/queries.surql +94 -0
  9. package/docs/material-library/sample.jsonl +14 -0
  10. package/docs/material-library/schema.surql +46 -0
  11. package/docs/mcp-profile.example.yml +24 -0
  12. package/docs/story-graph-design.md +602 -0
  13. package/docs/story-graph-validation.md +81 -0
  14. package/docs/workspace-mcp.md +284 -0
  15. package/index.js +12 -7
  16. package/lib/atomic-write.js +109 -0
  17. package/lib/chapter-order.js +100 -0
  18. package/lib/confirmation.js +80 -0
  19. package/lib/db-init.js +123 -0
  20. package/lib/deployed-state.js +11 -11
  21. package/lib/direction-labels.js +265 -0
  22. package/lib/error-codes.js +31 -31
  23. package/lib/infra.js +29 -4
  24. package/lib/material-db.js +149 -0
  25. package/lib/path-guard.js +150 -0
  26. package/lib/projects.js +400 -0
  27. package/lib/scraper-utils.js +140 -0
  28. package/lib/surreal-server.js +338 -0
  29. package/lib/topic-decision.js +860 -0
  30. package/lib/topic-fields.js +251 -0
  31. package/lib/topic-schema.js +225 -0
  32. package/lib/workspace.js +501 -0
  33. package/package.json +31 -7
  34. package/plugins/fancy-analyze/analyze-definitions.md +519 -0
  35. package/plugins/fancy-analyze/index.js +1128 -77
  36. package/plugins/fancy-analyze/ingest-coverage.js +107 -0
  37. package/plugins/fancy-analyze/schema-analyze.md +355 -107
  38. package/plugins/fancy-analyze/scripts/append-summary-chapter.js +612 -0
  39. package/plugins/fancy-analyze/summary-progress.js +71 -7
  40. package/plugins/fancy-analyze/templates.js +437 -139
  41. package/plugins/fancy-bootstrap/index.js +276 -131
  42. package/plugins/fancy-bootstrap/scripts/init.js +59 -25
  43. package/plugins/fancy-bootstrap/templates.js +35 -18
  44. package/plugins/fancy-db/index.js +172 -0
  45. package/plugins/fancy-douyin/deploy.js +134 -0
  46. package/plugins/fancy-douyin/index.js +551 -0
  47. package/plugins/fancy-douyin/prompts/01-analysis-prompt.md +84 -0
  48. package/plugins/fancy-douyin/scripts/crawl-note.cjs +194 -0
  49. package/plugins/fancy-douyin/scripts/lib.cjs +197 -0
  50. package/plugins/fancy-douyin/scripts/scan-account.cjs +128 -0
  51. package/plugins/fancy-douyin/scripts/start-chrome.cjs +203 -0
  52. package/plugins/fancy-douyin/state.js +197 -0
  53. package/plugins/fancy-douyin/templates.js +127 -0
  54. package/plugins/fancy-export/exporter.js +805 -0
  55. package/plugins/fancy-export/index.js +63 -0
  56. package/plugins/fancy-import/import-driver.js +29 -6
  57. package/plugins/fancy-import/index.js +13 -5
  58. package/plugins/fancy-import/prompts/01-import-prompt.md +39 -14
  59. package/plugins/fancy-import/templates.js +17 -8
  60. package/plugins/fancy-material/exporter.js +153 -0
  61. package/plugins/fancy-material/genres.js +256 -0
  62. package/plugins/fancy-material/index.js +835 -0
  63. package/plugins/fancy-material/inventory.js +145 -0
  64. package/plugins/fancy-material/kinds.js +313 -0
  65. package/plugins/fancy-material/queue.js +55 -0
  66. package/plugins/fancy-material/scripts/query.js +254 -0
  67. package/plugins/fancy-material/templates.js +258 -0
  68. package/plugins/fancy-material/tool-deploy.js +138 -0
  69. package/plugins/fancy-material/validate.js +149 -0
  70. package/plugins/fancy-project/index.js +133 -0
  71. package/plugins/fancy-scan/existing-block.js +100 -0
  72. package/plugins/fancy-scan/index.js +410 -21
  73. package/plugins/fancy-scan/prompts/01-analysis-prompt.md +302 -0
  74. package/plugins/fancy-scan/scripts/append-block.js +725 -0
  75. package/plugins/fancy-scan/scripts/report-validate.js +824 -0
  76. package/plugins/fancy-scan/scripts/run-scan.js +169 -22
  77. package/plugins/fancy-scan/scripts/scrapers/fanqie-rank-scraper.js +145 -16
  78. package/plugins/fancy-scan/scripts/scrapers/jjwxc-rank-scraper.js +179 -49
  79. package/plugins/fancy-scan/scripts/scrapers/qimao-rank-scraper.js +38 -20
  80. package/plugins/fancy-scan/templates/blocks.js +155 -0
  81. package/plugins/fancy-scan/templates/platforms.js +372 -0
  82. package/plugins/fancy-scan/templates/report-schema.js +90 -0
  83. package/plugins/fancy-scan/templates.js +545 -146
  84. package/plugins/fancy-scan/tool-deploy.js +335 -0
  85. package/plugins/fancy-scan/wait-turn.js +46 -0
  86. package/plugins/fancy-story-builder/db/README.md +106 -0
  87. package/plugins/fancy-story-builder/db/client.js +228 -0
  88. package/plugins/fancy-story-builder/db/commands.js +145 -0
  89. package/plugins/fancy-story-builder/db/ids.js +39 -0
  90. package/plugins/fancy-story-builder/db/import.js +798 -0
  91. package/plugins/fancy-story-builder/db/project.js +299 -0
  92. package/plugins/fancy-story-builder/db/query.js +361 -0
  93. package/plugins/fancy-story-builder/db/schema.js +417 -0
  94. package/plugins/fancy-story-builder/db/stage-sync.js +238 -0
  95. package/plugins/fancy-story-builder/flesh-output.js +19 -6
  96. package/plugins/fancy-story-builder/index.js +4105 -396
  97. package/plugins/fancy-story-builder/patch-driver.js +390 -0
  98. package/plugins/fancy-story-builder/prompts/01-skeleton-prompt.md +29 -19
  99. package/plugins/fancy-story-builder/prompts/02-flesh-prompt.md +72 -10
  100. package/plugins/fancy-story-builder/prompts/03-rhythm-prompt.md +40 -5
  101. package/plugins/fancy-story-builder/prompts/04-writing-prompt.md +37 -0
  102. package/plugins/fancy-story-builder/purpose-template.md +42 -66
  103. package/plugins/fancy-story-builder/references/formulas-skeleton.json +229 -56
  104. package/plugins/fancy-story-builder/references/genre-profiles.json +3514 -0
  105. package/plugins/fancy-story-builder/references/glossary.md +15 -6
  106. package/plugins/fancy-story-builder/references/index.md +97 -6
  107. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/00-index.md +1 -1
  108. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/07-audit.md +25 -7
  109. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/01-character.md +5 -1
  110. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/03-relations.md +6 -2
  111. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/05-audit.md +15 -5
  112. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/00-index.md +1 -1
  113. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/01-positioning.md +2 -1
  114. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/03-entities.md +1 -1
  115. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/06-audit.md +19 -1
  116. package/plugins/fancy-story-builder/schema-setting.json +1100 -0
  117. package/plugins/fancy-story-builder/schema-setting.md +201 -293
  118. package/plugins/fancy-story-builder/scripts/brief-validate.js +164 -0
  119. package/plugins/fancy-story-builder/scripts/content-quality.js +4 -1
  120. package/plugins/fancy-story-builder/scripts/flesh-validate.js +135 -12
  121. package/plugins/fancy-story-builder/scripts/graph-utils.js +146 -47
  122. package/plugins/fancy-story-builder/scripts/reference-log.js +110 -0
  123. package/plugins/fancy-story-builder/scripts/rhythm-validate.js +100 -4
  124. package/plugins/fancy-story-builder/scripts/schema-pages.js +2668 -0
  125. package/plugins/fancy-story-builder/scripts/semantic-lint.js +5 -1
  126. package/plugins/fancy-story-builder/scripts/skeleton-validate.js +158 -17
  127. package/plugins/fancy-story-builder/scripts/stage-batches.js +36 -2
  128. package/plugins/fancy-story-builder/scripts/stage-contract.js +12 -0
  129. package/plugins/fancy-story-builder/scripts/story-artifacts.js +933 -0
  130. package/plugins/fancy-story-builder/scripts/upstream-query.js +1 -1
  131. package/plugins/fancy-story-builder/scripts/v2/baseline.js +112 -0
  132. package/plugins/fancy-story-builder/scripts/v2/cast.js +517 -0
  133. package/plugins/fancy-story-builder/scripts/v2/context.js +434 -0
  134. package/plugins/fancy-story-builder/scripts/v2/contracts.js +493 -0
  135. package/plugins/fancy-story-builder/scripts/v2/core.js +419 -0
  136. package/plugins/fancy-story-builder/scripts/v2/engine.js +1068 -0
  137. package/plugins/fancy-story-builder/scripts/v2/export-markdown.js +794 -0
  138. package/plugins/fancy-story-builder/scripts/v2/genre.js +195 -0
  139. package/plugins/fancy-story-builder/scripts/v2/knowledge.js +321 -0
  140. package/plugins/fancy-story-builder/scripts/v2/macro.js +409 -0
  141. package/plugins/fancy-story-builder/scripts/v2/manifest.js +538 -0
  142. package/plugins/fancy-story-builder/scripts/v2/migrate.js +137 -0
  143. package/plugins/fancy-story-builder/scripts/v2/prompts.js +1335 -0
  144. package/plugins/fancy-story-builder/scripts/v2/rhythm.js +618 -0
  145. package/plugins/fancy-story-builder/scripts/v2/scale.js +843 -0
  146. package/plugins/fancy-story-builder/scripts/v2/snapshot.js +121 -0
  147. package/plugins/fancy-story-builder/scripts/v2/stages.js +273 -0
  148. package/plugins/fancy-story-builder/scripts/v2/writing.js +216 -0
  149. package/plugins/fancy-story-builder/storydb/README.md +271 -0
  150. package/plugins/fancy-story-builder/storydb/cli.js +458 -0
  151. package/plugins/fancy-story-builder/storydb/client.js +351 -0
  152. package/plugins/fancy-story-builder/storydb/compile.js +452 -0
  153. package/plugins/fancy-story-builder/storydb/context.js +288 -0
  154. package/plugins/fancy-story-builder/storydb/definitions.js +699 -0
  155. package/plugins/fancy-story-builder/storydb/driver.js +106 -0
  156. package/plugins/fancy-story-builder/storydb/dsh-tools.js +313 -0
  157. package/plugins/fancy-story-builder/storydb/export.js +175 -0
  158. package/plugins/fancy-story-builder/storydb/ids.js +31 -0
  159. package/plugins/fancy-story-builder/storydb/impact.js +170 -0
  160. package/plugins/fancy-story-builder/storydb/import.js +559 -0
  161. package/plugins/fancy-story-builder/storydb/mcp.js +99 -0
  162. package/plugins/fancy-story-builder/storydb/prompts.js +952 -0
  163. package/plugins/fancy-story-builder/storydb/stage-command.js +379 -0
  164. package/plugins/fancy-story-builder/storydb/stage.js +426 -0
  165. package/plugins/fancy-story-builder/storydb/store.js +891 -0
  166. package/plugins/fancy-story-builder/storydb/tools.js +592 -0
  167. package/plugins/fancy-story-builder/storydb/validation.js +183 -0
  168. package/plugins/fancy-story-builder/templates/v2/10-scale/scale-template.json +144 -0
  169. package/plugins/fancy-story-builder/templates/v2/20-macro/emotion-template.json +15 -0
  170. package/plugins/fancy-story-builder/templates/v2/20-macro/endings-template.json +16 -0
  171. package/plugins/fancy-story-builder/templates/v2/20-macro/lines-template.json +28 -0
  172. package/plugins/fancy-story-builder/templates/v2/20-macro/roles-template.json +10 -0
  173. package/plugins/fancy-story-builder/templates/v2/20-macro/story-template.json +19 -0
  174. package/plugins/fancy-story-builder/templates/v2/20-macro/volumes-template.json +24 -0
  175. package/plugins/fancy-story-builder/templates/v2/20-macro/world-template.json +24 -0
  176. package/plugins/fancy-story-builder/templates/v2/30-cast/characters-template.json +49 -0
  177. package/plugins/fancy-story-builder/templates/v2/30-cast/climaxes-template.json +14 -0
  178. package/plugins/fancy-story-builder/templates/v2/30-cast/conflicts-template.json +30 -0
  179. package/plugins/fancy-story-builder/templates/v2/30-cast/factions-template.json +18 -0
  180. package/plugins/fancy-story-builder/templates/v2/30-cast/foreshadows-template.json +29 -0
  181. package/plugins/fancy-story-builder/templates/v2/30-cast/information-template.json +29 -0
  182. package/plugins/fancy-story-builder/templates/v2/30-cast/items-template.json +13 -0
  183. package/plugins/fancy-story-builder/templates/v2/30-cast/locations-template.json +12 -0
  184. package/plugins/fancy-story-builder/templates/v2/30-cast/payoffs-template.json +16 -0
  185. package/plugins/fancy-story-builder/templates/v2/30-cast/relations-template.json +44 -0
  186. package/plugins/fancy-story-builder/templates/v2/40-rhythm/chapter-map-template.json +81 -0
  187. package/plugins/fancy-story-builder/templates/v2/40-rhythm/outline-template.json +162 -0
  188. package/plugins/fancy-story-builder/templates/v2/50-writing/ledger-template.json +46 -0
  189. package/plugins/fancy-story-builder/templates/v2/60-deslop/report-template.json +10 -0
  190. 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 +267 -44
  191. 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 +121 -19
  192. 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 +129 -21
  193. 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
  194. 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 +76 -17
  195. 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 +56 -8
  196. 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 +63 -13
  197. 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 +34 -6
  198. 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 +81 -18
  199. 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 +202 -42
  200. 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 +160 -27
  201. 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 +217 -41
  202. 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 +215 -41
  203. package/plugins/fancy-story-builder/templates.js +272 -74
  204. package/plugins/fancy-story-builder/wiki-references.js +429 -93
  205. package/plugins/fancy-story-builder/writing-driver.js +336 -9
  206. package/plugins/fancy-topic/index.js +414 -61
  207. package/plugins/fancy-topic/prompts/01-exploration-prompt.md +115 -0
  208. package/plugins/fancy-topic/scripts/save-suggestion.js +219 -0
  209. package/plugins/fancy-topic/scripts/select-topic.js +106 -63
  210. package/plugins/fancy-topic/templates.js +225 -122
  211. package/plugins/fancy-topic/tool-deploy.js +200 -0
  212. package/lib/safe-rm.js +0 -94
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Database-native validation.
3
+ *
4
+ * The story graph's invariants are enforced in two places:
5
+ * - `DEFINE EVENT` triggers in compile.js (hard write-time rules)
6
+ * - `fn::fancy::validate($scope)` generated here (read-time checks and
7
+ * quality warnings)
8
+ *
9
+ * Keeping validation in the database means every client (DSH, Hermes, the CLI,
10
+ * or a future MCP client) sees the same result; no client can skip a check by
11
+ * bypassing the JavaScript wrapper.
12
+ */
13
+
14
+ export const SCHEMA_VERSION = 1;
15
+ export const VALIDATION_VERSION = 1;
16
+
17
+ export function validationDdl() {
18
+ return `DEFINE FUNCTION OVERWRITE fn::fancy::validate($scope: option<string>) {
19
+ // ---------------------------------------------------------------
20
+ // scope: '' = whole story, 'chapter:CH001' / 'foreshadow:FS001' = subtree
21
+ // ---------------------------------------------------------------
22
+ LET $sc = IF $scope = NONE { '' } ELSE { $scope };
23
+
24
+ // ---------------------------------------------------------------
25
+ // requirements: pending vs. hard requirement on a completed subject
26
+ // ---------------------------------------------------------------
27
+ LET $reqs = SELECT * FROM requirement WHERE story = story:main AND status = 'pending';
28
+ LET $req_rows = SELECT VALUE {
29
+ code: 'REQUIREMENT_PENDING',
30
+ severity: IF hard AND (SELECT VALUE status FROM type::record($this.subject))[0] = 'complete' { 'error' } ELSE { 'pending' },
31
+ scope: subject,
32
+ message: IF hard AND (SELECT VALUE status FROM type::record($this.subject))[0] = 'complete' { '已完成状态但还有必填待办:' + (label OR kind) } ELSE { '待办:' + (label OR kind) },
33
+ kind: kind,
34
+ due_chapter: due_chapter,
35
+ hard: hard
36
+ } FROM $reqs;
37
+
38
+ // ---------------------------------------------------------------
39
+ // chapter-level checks
40
+ // ---------------------------------------------------------------
41
+ LET $chapters = SELECT chapter_id, display_no, hook.type AS hook_type, hook.strength AS hook_strength, time.story_day AS story_day, time.flashback AS flashback, emotion.value AS emotion_value FROM chapter WHERE story = story:main ORDER BY display_no;
42
+
43
+ LET $hook_errors = SELECT VALUE {
44
+ code: 'HOOK_REPEAT',
45
+ severity: 'error',
46
+ scope: 'chapter:' + $this[2].chapter_id,
47
+ message: '第 ' + <string>$this[0].display_no + '/' + <string>$this[1].display_no + '/' + <string>$this[2].display_no + ' 章连续使用钩子「' + <string>$this[2].hook_type + '」',
48
+ hint: '换一种章尾钩子'
49
+ } FROM array::windows($chapters, 3)
50
+ WHERE $this[0].hook_type != NONE AND $this[0].hook_type = $this[1].hook_type AND $this[1].hook_type = $this[2].hook_type
51
+ AND ($sc = '' OR string::starts_with('chapter:' + $this[2].chapter_id, $sc));
52
+
53
+ LET $time_errors = SELECT VALUE {
54
+ code: 'TIME_REGRESSION',
55
+ severity: 'error',
56
+ scope: 'chapter:' + $this[1].chapter_id,
57
+ message: '第 ' + <string>$this[1].display_no + ' 章 story_day=' + <string>$this[1].story_day + ' 早于上一章 ' + <string>$this[0].story_day,
58
+ hint: '如果是闪回,写 time.flashback=true;否则调整时间'
59
+ } FROM array::windows($chapters, 2)
60
+ WHERE $this[0].story_day != NONE AND $this[1].story_day != NONE AND $this[1].story_day < $this[0].story_day AND $this[1].flashback != true
61
+ AND ($sc = '' OR string::starts_with('chapter:' + $this[1].chapter_id, $sc));
62
+
63
+ LET $emotion_warnings = SELECT VALUE {
64
+ code: 'EMOTION_FLAT',
65
+ severity: 'warning',
66
+ scope: 'chapter:' + $this[2].chapter_id,
67
+ message: '第 ' + <string>$this[0].display_no + '-' + <string>$this[2].display_no + ' 章情绪值连续接近'
68
+ } FROM array::windows($chapters, 3)
69
+ WHERE $this[0].emotion_value != NONE AND $this[1].emotion_value != NONE AND $this[2].emotion_value != NONE
70
+ AND math::max([$this[0].emotion_value, $this[1].emotion_value, $this[2].emotion_value]) - math::min([$this[0].emotion_value, $this[1].emotion_value, $this[2].emotion_value]) < 1
71
+ AND ($sc = '' OR string::starts_with('chapter:' + $this[2].chapter_id, $sc));
72
+
73
+ LET $weak_golden = SELECT VALUE chapter_id FROM chapter WHERE story = story:main AND display_no <= 3 AND (hook.strength != '强' OR hook.strength = NONE);
74
+ LET $golden_warnings = IF array::len($weak_golden) > 0 {
75
+ [{ code: 'GOLDEN_THREE_HOOK', severity: 'warning', scope: 'chapter:1-3', message: '黄金三章中有 ' + <string>array::len($weak_golden) + ' 章不是强钩子' }]
76
+ } ELSE { [] };
77
+
78
+ // ---------------------------------------------------------------
79
+ // foreshadow order / placement
80
+ // ---------------------------------------------------------------
81
+ LET $fs_groups = SELECT to_id, math::min(IF action = 'plant' THEN chapter_no ELSE 999999999 END) AS first_plant, math::min(IF action = 'reveal' THEN chapter_no ELSE 999999999 END) AS first_reveal FROM foreshadow_action WHERE story = story:main GROUP BY to_id;
82
+ LET $fs_errors = SELECT VALUE {
83
+ code: 'FORESHADOW_ORDER',
84
+ severity: 'error',
85
+ scope: 'foreshadow:' + <string>$this.to_id,
86
+ message: '伏笔 ' + <string>$this.to_id + ' 在第 ' + <string>$this.first_reveal + ' 章回收,但之前没有埋设'
87
+ } FROM $fs_groups WHERE $this.first_reveal < 999999999 AND ($this.first_plant >= 999999999 OR $this.first_reveal < $this.first_plant)
88
+ AND ($sc = '' OR string::starts_with('foreshadow:' + <string>$this.to_id, $sc));
89
+ LET $fs_pending = SELECT VALUE {
90
+ code: 'FORESHADOW_PLANT_MISSING',
91
+ severity: 'pending',
92
+ scope: 'foreshadow:' + <string>$this.to_id,
93
+ message: '伏笔 ' + <string>$this.to_id + ' 尚未埋设'
94
+ } FROM $fs_groups WHERE $this.first_plant >= 999999999
95
+ AND ($sc = '' OR string::starts_with('foreshadow:' + <string>$this.to_id, $sc));
96
+ LET $fs_unplaced = SELECT VALUE {
97
+ code: 'FORESHADOW_UNPLACED',
98
+ severity: 'pending',
99
+ scope: 'foreshadow:' + entity_id,
100
+ message: '伏笔「' + name + '」尚无任何章节动作'
101
+ } FROM foreshadow WHERE story = story:main AND entity_id NOT IN (SELECT VALUE to_id FROM foreshadow_action WHERE story = story:main)
102
+ AND ($sc = '' OR string::starts_with('foreshadow:' + entity_id, $sc));
103
+
104
+ // ---------------------------------------------------------------
105
+ // payoff order / placement
106
+ // ---------------------------------------------------------------
107
+ LET $pay_groups = SELECT to_id, math::min(IF action = 'setup' THEN chapter_no ELSE 999999999 END) AS first_setup, math::min(IF action = 'payoff' THEN chapter_no ELSE 999999999 END) AS first_payoff FROM payoff_action WHERE story = story:main GROUP BY to_id;
108
+ LET $pay_errors = SELECT VALUE {
109
+ code: 'PAYOFF_ORDER',
110
+ severity: 'error',
111
+ scope: 'payoff:' + <string>$this.to_id,
112
+ message: '爽点 ' + <string>$this.to_id + ' 的兑现章早于铺垫章'
113
+ } FROM $pay_groups WHERE $this.first_payoff < 999999999 AND $this.first_setup < 999999999 AND $this.first_payoff < $this.first_setup
114
+ AND ($sc = '' OR string::starts_with('payoff:' + <string>$this.to_id, $sc));
115
+ LET $pay_pending = SELECT VALUE {
116
+ code: 'PAYOFF_SETUP_MISSING',
117
+ severity: 'pending',
118
+ scope: 'payoff:' + <string>$this.to_id,
119
+ message: '爽点 ' + <string>$this.to_id + ' 尚未铺垫'
120
+ } FROM $pay_groups WHERE $this.first_payoff < 999999999 AND $this.first_setup >= 999999999
121
+ AND ($sc = '' OR string::starts_with('payoff:' + <string>$this.to_id, $sc));
122
+ LET $pay_unplaced = SELECT VALUE {
123
+ code: 'PAYOFF_UNPLACED',
124
+ severity: 'pending',
125
+ scope: 'payoff:' + entity_id,
126
+ message: '爽点「' + name + '」尚无章节动作'
127
+ } FROM payoff WHERE story = story:main AND entity_id NOT IN (SELECT VALUE to_id FROM payoff_action WHERE story = story:main)
128
+ AND ($sc = '' OR string::starts_with('payoff:' + entity_id, $sc));
129
+
130
+ // ---------------------------------------------------------------
131
+ // coverage warnings
132
+ // ---------------------------------------------------------------
133
+ LET $main_warnings = SELECT VALUE {
134
+ code: 'COVERAGE_MAIN',
135
+ severity: 'warning',
136
+ scope: 'line_node:' + node_id,
137
+ message: '主线节点 ' + node_id + ' 尚无章节落点'
138
+ } FROM line_node WHERE story = story:main AND node_id NOT IN (SELECT VALUE to_id FROM chapter_line_node WHERE story = story:main);
139
+ LET $conflict_warnings = SELECT VALUE {
140
+ code: 'COVERAGE_CONFLICT',
141
+ severity: 'warning',
142
+ scope: 'conflict:' + entity_id,
143
+ message: '冲突「' + name + '」尚无章节落点'
144
+ } FROM conflict WHERE story = story:main AND entity_id NOT IN (SELECT VALUE to_id FROM chapter_conflict WHERE story = story:main);
145
+
146
+ // ---------------------------------------------------------------
147
+ // outline quality warnings
148
+ // ---------------------------------------------------------------
149
+ LET $outline_warnings = SELECT VALUE {
150
+ code: IF array::len(->outline_scene) < 2 { 'OUTLINE_THIN_SCENES' } ELSE { 'OUTLINE_THIN_BEATS' },
151
+ severity: 'warning',
152
+ scope: 'outline:' + chapter_id,
153
+ message: IF array::len(->outline_scene) < 2 { chapter_id + ' 只有 ' + <string>array::len(->outline_scene) + ' 个场景;一般应有 2-4 个' } ELSE { chapter_id + ' 的 must_write 少于 3 条' }
154
+ } FROM outline WHERE story = story:main AND (array::len(->outline_scene) < 2 OR array::len(must_write) < 3);
155
+
156
+ // ---------------------------------------------------------------
157
+ // assemble
158
+ // ---------------------------------------------------------------
159
+ LET $errors = array::concat(
160
+ SELECT VALUE $this FROM $req_rows WHERE $this.severity = 'error' AND ($sc = '' OR string::starts_with($this.scope, $sc)),
161
+ $hook_errors,
162
+ $time_errors,
163
+ $fs_errors,
164
+ $pay_errors
165
+ );
166
+ LET $pending = array::concat(
167
+ SELECT VALUE $this FROM $req_rows WHERE $this.severity = 'pending' AND ($sc = '' OR string::starts_with($this.scope, $sc)),
168
+ $fs_pending,
169
+ $fs_unplaced,
170
+ $pay_pending,
171
+ $pay_unplaced
172
+ );
173
+ LET $warnings = array::concat(
174
+ $main_warnings,
175
+ $conflict_warnings,
176
+ $outline_warnings,
177
+ $emotion_warnings,
178
+ $golden_warnings
179
+ );
180
+
181
+ RETURN { errors: $errors, pending: $pending, warnings: $warnings };
182
+ };`;
183
+ }
@@ -0,0 +1,144 @@
1
+ {
2
+ "meta": {
3
+ "name": "scale-template",
4
+ "stage": "10-scale"
5
+ },
6
+ "capacity": {
7
+ "target_chapters": 300,
8
+ "target_words": 900000,
9
+ "avg_words_per_chapter": 3000,
10
+ "base_words_per_chapter": 2000,
11
+ "chapter_scale": 0.6667
12
+ },
13
+ "structure": {
14
+ "volume_count": 3,
15
+ "total_acts": 9,
16
+ "volumes": [
17
+ {
18
+ "volume_id": "V001",
19
+ "chapters": 100,
20
+ "ratio": 0.333333,
21
+ "act_count": 3,
22
+ "chapter_start": 1,
23
+ "chapter_end": 100,
24
+ "acts": [
25
+ {
26
+ "act_id": "A001",
27
+ "volume_id": "V001",
28
+ "act_index": 1,
29
+ "chapter_start": 1,
30
+ "chapter_end": 34,
31
+ "chapters": 34
32
+ }
33
+ ]
34
+ }
35
+ ],
36
+ "acts": []
37
+ },
38
+ "quotas": {
39
+ "characters": {
40
+ "protagonist": 1,
41
+ "core_supporting": 2,
42
+ "volume_supporting": 3,
43
+ "core_antagonist": 1,
44
+ "volume_antagonist": 3
45
+ },
46
+ "factions": 2,
47
+ "faction_relations": 1,
48
+ "locations": 3,
49
+ "items": {
50
+ "core": 2,
51
+ "branch": 2,
52
+ "symbolic": 1
53
+ },
54
+ "conflicts": {
55
+ "core": 1,
56
+ "phase": 1,
57
+ "branch": 2
58
+ },
59
+ "foreshadows": {
60
+ "small": 3,
61
+ "medium": 1,
62
+ "large": 1
63
+ },
64
+ "climaxes": {
65
+ "small": 5,
66
+ "medium": 1,
67
+ "large": 1
68
+ },
69
+ "pleasures": {
70
+ "small": 8,
71
+ "medium": 3,
72
+ "large": 1
73
+ },
74
+ "endings": 3,
75
+ "world_rules": {
76
+ "core": 1,
77
+ "information": 1,
78
+ "social": 1
79
+ },
80
+ "main_lines": 1,
81
+ "branches": 1,
82
+ "information_gaps": 20,
83
+ "relations": {
84
+ "character": 12,
85
+ "faction": 1,
86
+ "membership": 10
87
+ }
88
+ },
89
+ "rhythm_budget": {
90
+ "hooks": {
91
+ "min": 300,
92
+ "target": 330
93
+ },
94
+ "reveals": {
95
+ "min": 25,
96
+ "target": 37
97
+ },
98
+ "reversals": {
99
+ "min": 20,
100
+ "target": 30
101
+ },
102
+ "emotional_peaks": {
103
+ "min": 25,
104
+ "target": 37
105
+ },
106
+ "climax_chapters": 1,
107
+ "payoff_chapters": 4,
108
+ "golden_three": {
109
+ "hook_strength": "强",
110
+ "must_establish": [
111
+ "世界观入门",
112
+ "主角立住",
113
+ "核心冲突抛出"
114
+ ]
115
+ }
116
+ },
117
+ "distribution": {
118
+ "V001": {
119
+ "chapters": 100,
120
+ "volume_supporting": 1,
121
+ "volume_antagonist": 1,
122
+ "phase_conflicts": 1,
123
+ "foreshadows": {
124
+ "small": 1,
125
+ "medium": 1,
126
+ "large": 1
127
+ },
128
+ "climaxes": {
129
+ "small": 2,
130
+ "medium": 1,
131
+ "large": 1
132
+ },
133
+ "pleasures": {
134
+ "small": 3,
135
+ "medium": 1,
136
+ "large": 1
137
+ }
138
+ }
139
+ },
140
+ "formulas": {},
141
+ "constraints": {},
142
+ "formula_version": "2.1",
143
+ "genre_profile": "default"
144
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "volumes": [
3
+ {
4
+ "volume_id": "V001",
5
+ "arc": "日常 → 不安 → 恐惧 → 反击",
6
+ "peaks": [
7
+ {
8
+ "act": "A001",
9
+ "intensity": 6,
10
+ "note": "第一次代价"
11
+ }
12
+ ]
13
+ }
14
+ ]
15
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "endings": [
3
+ {
4
+ "id": "E001",
5
+ "level": "全书级",
6
+ "type": "代价型圆满",
7
+ "direction": "结局方向",
8
+ "required_foreshadow_recovery": [
9
+ "FS001"
10
+ ],
11
+ "character_destinations": {
12
+ "ROLE-PROTAGONIST": "归宿"
13
+ }
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "main_line": {
3
+ "id": "M001",
4
+ "name": "主线名",
5
+ "nodes": [
6
+ {
7
+ "id": "M001-01",
8
+ "volume": "V001",
9
+ "act": "A001",
10
+ "goal": "节点目标",
11
+ "turn": "节点转折"
12
+ }
13
+ ]
14
+ },
15
+ "branches": [
16
+ {
17
+ "id": "B001",
18
+ "name": "支线名",
19
+ "goal": "支线目标",
20
+ "start": "V001/A001",
21
+ "turning": "V002/A001",
22
+ "resolution": "V003/A003",
23
+ "interweaves": [
24
+ "M001-01"
25
+ ]
26
+ }
27
+ ]
28
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "roles": [
3
+ {
4
+ "role_id": "ROLE-PROTAGONIST",
5
+ "function": "主角",
6
+ "arc_question": "主角要回答的问题",
7
+ "macro_goal": "宏观目标"
8
+ }
9
+ ]
10
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "core_question": "全书核心问题",
3
+ "core_conflict": "核心冲突",
4
+ "structure": {
5
+ "acts": [
6
+ {
7
+ "act_id": "A001",
8
+ "goal": "幕目标",
9
+ "turn": "转折",
10
+ "end_state": "结束状态"
11
+ }
12
+ ],
13
+ "turning_points": [],
14
+ "climax": "全书高潮",
15
+ "resolution": "收束方向"
16
+ },
17
+ "anti_tropes": [],
18
+ "promise": []
19
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "volumes": [
3
+ {
4
+ "id": "V001",
5
+ "title": "卷名",
6
+ "chapter_start": 1,
7
+ "chapter_end": 100,
8
+ "goal": "卷目标",
9
+ "core_conflict": "核心冲突",
10
+ "emotional_arc": "情绪走向",
11
+ "ending_hook": "卷尾钩子",
12
+ "acts": [
13
+ {
14
+ "act_id": "A001",
15
+ "act_index": 1,
16
+ "chapter_start": 1,
17
+ "chapter_end": 34,
18
+ "goal": "幕目标",
19
+ "climax": "幕高潮"
20
+ }
21
+ ]
22
+ }
23
+ ]
24
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "premise": "一句话世界前提",
3
+ "theme": [
4
+ "主题"
5
+ ],
6
+ "tone": "基调",
7
+ "world_rules": [
8
+ {
9
+ "id": "R001",
10
+ "name": "规则名",
11
+ "category": "核心",
12
+ "content": "规则内容",
13
+ "scope": "适用范围",
14
+ "exceptions": [],
15
+ "cost": "代价",
16
+ "impact": "影响"
17
+ }
18
+ ],
19
+ "power_system": {},
20
+ "social_order": {},
21
+ "taboos": [],
22
+ "core_imagery": [],
23
+ "reader_promise": []
24
+ }
@@ -0,0 +1,49 @@
1
+ {
2
+ "characters": [
3
+ {
4
+ "id": "C001",
5
+ "role_slot": "ROLE-PROTAGONIST",
6
+ "name": "人物名",
7
+ "type": "主角",
8
+ "faction": "F001",
9
+ "description": "简介",
10
+ "goal": {
11
+ "short_term": "短期目标",
12
+ "medium_term": "中期目标",
13
+ "long_term": "长期目标"
14
+ },
15
+ "flaw": "缺陷",
16
+ "golden_finger": "金手指",
17
+ "information_state": {
18
+ "known": [
19
+ "已知"
20
+ ],
21
+ "unknown": [
22
+ "未知"
23
+ ],
24
+ "false_belief": [
25
+ "误信"
26
+ ]
27
+ },
28
+ "arc": {
29
+ "type": "发现型",
30
+ "stages": [
31
+ {
32
+ "name": "阶段名",
33
+ "question": "阶段问题",
34
+ "planned_change": "变化"
35
+ }
36
+ ]
37
+ },
38
+ "voice": {
39
+ "catchphrase": "口头禅",
40
+ "rhythm": "语言节奏"
41
+ },
42
+ "traits": [],
43
+ "exit": {
44
+ "type": "未退场",
45
+ "description": ""
46
+ }
47
+ }
48
+ ]
49
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "climaxes": [
3
+ {
4
+ "id": "CL001",
5
+ "name": "高潮名",
6
+ "type": "小",
7
+ "description": "简介",
8
+ "payoff": "兑现",
9
+ "participants": [
10
+ "C001"
11
+ ]
12
+ }
13
+ ]
14
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "conflicts": [
3
+ {
4
+ "id": "X001",
5
+ "name": "冲突名",
6
+ "type": "core",
7
+ "description": "简介",
8
+ "core_goal": "核心目标",
9
+ "stakes": "赌注",
10
+ "escalation_path": [
11
+ {
12
+ "node_type": "引入",
13
+ "description": "引入"
14
+ },
15
+ {
16
+ "node_type": "升级",
17
+ "description": "升级"
18
+ }
19
+ ],
20
+ "resolution_plan": {
21
+ "method": "解决方式",
22
+ "residual_impact": "残留影响"
23
+ },
24
+ "participants": [
25
+ "C001",
26
+ "F001"
27
+ ]
28
+ }
29
+ ]
30
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "factions": [
3
+ {
4
+ "id": "F001",
5
+ "name": "势力名",
6
+ "type": "组织",
7
+ "description": "简介",
8
+ "goal": "目标",
9
+ "resources": [
10
+ "资源"
11
+ ],
12
+ "territory": [
13
+ "地盘"
14
+ ],
15
+ "internal_conflict": "内部矛盾"
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "foreshadows": [
3
+ {
4
+ "id": "FS001",
5
+ "name": "伏笔名",
6
+ "type": "小",
7
+ "setup": "埋设",
8
+ "clues": [
9
+ "线索"
10
+ ],
11
+ "payoff_target": "回收目标",
12
+ "recovery_requirement": "结局前回收",
13
+ "red_herrings": [],
14
+ "scope": "book",
15
+ "initial_knowledge": [
16
+ {
17
+ "character": "C001",
18
+ "state": "partial",
19
+ "coverage": "partial",
20
+ "knowledge": "知道有这条线索,但不知道它指向什么",
21
+ "how": "witnessed",
22
+ "source_character": null,
23
+ "scene": "第一次见到线索的地点",
24
+ "evidence": "看到了具体异常"
25
+ }
26
+ ]
27
+ }
28
+ ]
29
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "information_gaps": [
3
+ {
4
+ "id": "IG-X001-01",
5
+ "conflict": "X001",
6
+ "info_name": "谁知道什么",
7
+ "who_knows": [
8
+ "C002"
9
+ ],
10
+ "who_does_not_know": [
11
+ "C001"
12
+ ],
13
+ "resolution_method": "如何揭示",
14
+ "writing_ban": "解决前 C001 不得说出真相",
15
+ "initial_knowledge": [
16
+ {
17
+ "character": "C001",
18
+ "state": "unknown",
19
+ "coverage": "unknown",
20
+ "knowledge": "只知道传闻,不知道具体细节",
21
+ "how": "none",
22
+ "source_character": null,
23
+ "scene": "开篇之前",
24
+ "evidence": "没有任何可核对的证据"
25
+ }
26
+ ]
27
+ }
28
+ ]
29
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "items": [
3
+ {
4
+ "id": "P001",
5
+ "name": "道具名",
6
+ "category": "core",
7
+ "description": "简介",
8
+ "function": "功能",
9
+ "owner": "C001",
10
+ "limit": "限制"
11
+ }
12
+ ]
13
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "locations": [
3
+ {
4
+ "id": "L001",
5
+ "name": "地点名",
6
+ "type": "场景",
7
+ "description": "简介",
8
+ "narrative_function": "叙事功能",
9
+ "rules": []
10
+ }
11
+ ]
12
+ }