@oh-story/dsh 0.1.10 → 0.1.11

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 (444) hide show
  1. package/README.md +7 -7
  2. package/lib/client.js +7 -3
  3. package/lib/client.js.map +2 -2
  4. package/lib/drama/manifest.json +212 -142
  5. package/lib/drama/skills/short-drama/SKILL.md +14 -2
  6. package/lib/drama/skills/short-drama/assets/project-template/short-drama.json +3 -0
  7. package/lib/drama/skills/short-drama/references/creator-documents.md +3 -0
  8. package/lib/drama/skills/short-drama/references/knowhow-index.md +15 -0
  9. package/lib/drama/skills/short-drama/scripts/project_tool.py +131 -0
  10. package/lib/drama/skills/short-drama-assets/SKILL.md +2 -1
  11. package/lib/drama/skills/short-drama-assets/references/asset-review-checklist.md +2 -0
  12. package/lib/drama/skills/short-drama-assets/references/era-anchors.md +37 -0
  13. package/lib/drama/skills/short-drama-assets/references/stage-contract.md +2 -0
  14. package/lib/drama/skills/short-drama-develop/SKILL.md +10 -4
  15. package/lib/drama/skills/short-drama-develop/assets/adaptation-map.example.jsonl +1 -1
  16. package/lib/drama/skills/short-drama-develop/assets/creative-brief.md +8 -0
  17. package/lib/drama/skills/short-drama-develop/references/adaptation-craft.md +80 -13
  18. package/lib/drama/skills/short-drama-develop/references/episode-design.md +95 -12
  19. package/lib/drama/skills/short-drama-develop/references/genre-and-hook-playbook.md +9 -5
  20. package/lib/drama/skills/short-drama-develop/references/genre-cards//345/244/215/344/273/207/346/211/223/350/204/270.md +1 -1
  21. package/lib/drama/skills/short-drama-develop/references/genre-cards//347/263/273/347/273/237/346/265/201.md +51 -0
  22. package/lib/drama/skills/short-drama-develop/references/genre-cards//351/200/206/350/242/255.md +51 -0
  23. package/lib/drama/skills/short-drama-develop/references/genre-cards//351/207/215/347/224/237/347/251/277/350/266/212.md +51 -0
  24. package/lib/drama/skills/short-drama-develop/references/genre-cards.md +10 -7
  25. package/lib/drama/skills/short-drama-develop/references/premise-devices.md +15 -9
  26. package/lib/drama/skills/short-drama-develop/references/serial-character-and-memory.md +3 -2
  27. package/lib/drama/skills/short-drama-develop/references/stage-contract.md +14 -7
  28. package/lib/drama/skills/short-drama-develop/references/story-craft.md +7 -3
  29. package/lib/drama/skills/short-drama-edit/SKILL.md +43 -20
  30. package/lib/drama/skills/short-drama-edit/assets/remotion/README.md +49 -13
  31. package/lib/drama/skills/short-drama-edit/assets/remotion/package.json +4 -2
  32. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Overlay.tsx +31 -0
  33. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Root.tsx +5 -5
  34. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Subtitles.tsx +68 -69
  35. package/lib/drama/skills/short-drama-edit/assets/remotion/src/font.ts +39 -29
  36. package/lib/drama/skills/short-drama-edit/assets/remotion/src/rules.mjs +188 -0
  37. package/lib/drama/skills/short-drama-edit/assets/remotion/src/schema.ts +26 -13
  38. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/Card.tsx +72 -0
  39. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/CornerChip.tsx +39 -0
  40. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/ScreenText.tsx +36 -0
  41. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/SystemPanel.tsx +44 -0
  42. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/TaskPanel.tsx +100 -0
  43. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/chrome.tsx +197 -0
  44. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/tokens.ts +62 -0
  45. package/lib/drama/skills/short-drama-edit/assets/remotion/tsconfig.json +1 -0
  46. package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +4 -2
  47. package/lib/drama/skills/short-drama-edit/references/cut-craft.md +3 -1
  48. package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +6 -2
  49. package/lib/drama/skills/short-drama-edit/references/generated-footage.md +12 -6
  50. package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +13 -8
  51. package/lib/drama/skills/short-drama-edit/references/stage-contract.md +2 -0
  52. package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1153 -121
  53. package/lib/drama/skills/short-drama-edit/scripts/selftest.py +49 -9
  54. package/lib/drama/skills/short-drama-novel-analyze/SKILL.md +3 -3
  55. package/lib/drama/skills/short-drama-novel-analyze/assets/episode-candidate.example.jsonl +2 -2
  56. package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-triage.md +8 -4
  57. package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-value.md +10 -3
  58. package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +25 -1
  59. package/lib/drama/skills/short-drama-novel-analyze/references/stage-contract.md +1 -0
  60. package/lib/drama/skills/short-drama-review/SKILL.md +2 -2
  61. package/lib/drama/skills/short-drama-review/references/review-method.md +6 -1
  62. package/lib/drama/skills/short-drama-review/references/rubric-assets-prompts.md +11 -0
  63. package/lib/drama/skills/short-drama-review/references/rubric-story-script.md +77 -3
  64. package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +15 -0
  65. package/lib/drama/skills/short-drama-review/references/stage-contract.md +3 -0
  66. package/lib/drama/skills/short-drama-storyboard/SKILL.md +13 -5
  67. package/lib/drama/skills/short-drama-storyboard/references/blocking-playbooks.md +1 -1
  68. package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +2 -2
  69. package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +82 -14
  70. package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +4 -2
  71. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +5 -3
  72. package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +12 -0
  73. package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +4 -5
  74. package/lib/drama/skills/short-drama-video-prompts/references/generability.md +4 -2
  75. package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +40 -1
  76. package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +20 -14
  77. package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +1 -1
  78. package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +8 -7
  79. package/lib/drama/skills/short-drama-video-prompts/references/review-and-fixtures.md +1 -1
  80. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +1 -0
  81. package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +8 -1
  82. package/lib/drama/skills/short-drama-video-prompts/references/wan-3.0.md +103 -0
  83. package/lib/drama/skills/short-drama-video-prompts/scripts/motion_timing_check.py +8 -7
  84. package/lib/drama/skills/short-drama-write/SKILL.md +8 -3
  85. package/lib/drama/skills/short-drama-write/references/dialogue-craft.md +1 -1
  86. package/lib/drama/skills/short-drama-write/references/script-craft.md +56 -3
  87. package/lib/drama/skills/short-drama-write/references/stage-contract.md +4 -0
  88. package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +48 -2
  89. package/lib/index.js +71 -21
  90. package/lib/index.js.map +3 -3
  91. package/lib/oh-story/manifest.json +545 -310
  92. package/lib/oh-story/roles/chapter-extractor.md +11 -11
  93. package/lib/oh-story/roles/character-designer.md +1 -2
  94. package/lib/oh-story/roles/consistency-checker.md +11 -9
  95. package/lib/oh-story/roles/narrative-writer.md +24 -25
  96. package/lib/oh-story/roles/story-architect.md +9 -61
  97. package/lib/oh-story/roles/story-explorer.md +20 -125
  98. package/lib/oh-story/roles/story-researcher.md +0 -1
  99. package/lib/oh-story/skills/story/SKILL.md +6 -6
  100. package/lib/oh-story/skills/story/VERSION +1 -1
  101. package/lib/oh-story/skills/story/references/author-memory-maintenance.md +75 -0
  102. package/lib/oh-story/skills/story/references/author-memory.md +40 -118
  103. package/lib/oh-story/skills/story/scripts/author_memory_commit.py +3 -3
  104. package/lib/oh-story/skills/story-deslop/SKILL.md +24 -25
  105. package/lib/oh-story/skills/story-deslop/references/agent-calls.md +5 -0
  106. package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +1 -1
  107. package/lib/oh-story/skills/story-deslop/references/author-memory-maintenance.md +75 -0
  108. package/lib/oh-story/skills/story-deslop/references/author-memory.md +40 -118
  109. package/lib/oh-story/skills/story-deslop/references/banned-words.md +2 -2
  110. package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +21 -4
  111. package/lib/oh-story/skills/story-deslop/references/style-resolution.md +4 -4
  112. package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +3 -3
  113. package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +155 -8
  114. package/lib/oh-story/skills/story-import/SKILL.md +63 -579
  115. package/lib/oh-story/skills/story-import/references/deep-analysis.md +47 -0
  116. package/lib/oh-story/skills/story-import/references/format-and-structure.md +1 -1
  117. package/lib/oh-story/skills/story-import/references/import-report.md +64 -0
  118. package/lib/oh-story/skills/story-import/references/import-special-cases.md +34 -0
  119. package/lib/oh-story/skills/story-import/references/import-tracking.md +95 -0
  120. package/lib/oh-story/skills/story-import/references/length-routing.md +1 -1
  121. package/lib/oh-story/skills/story-import/references/outline-reverse-rules.md +79 -0
  122. package/lib/oh-story/skills/story-import/references/outline-reverse.md +24 -0
  123. package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +95 -91
  124. package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +24 -12
  125. package/lib/oh-story/skills/story-import/references/tracking-initialization.md +1 -1
  126. package/lib/oh-story/skills/story-import/references/tracking-transaction.md +11 -9
  127. package/lib/oh-story/skills/story-import/scripts/build_outline_brief.py +332 -0
  128. package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +101 -14
  129. package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +278 -23
  130. package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +179 -23
  131. package/lib/oh-story/skills/story-long-analyze/SKILL.md +28 -50
  132. package/lib/oh-story/skills/story-long-analyze/references/author-facing.md +12 -5
  133. package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +1 -1
  134. package/lib/oh-story/skills/story-long-analyze/references/final-checks.md +19 -0
  135. package/lib/oh-story/skills/story-long-analyze/references/index-rebuild.md +11 -0
  136. package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +21 -55
  137. package/lib/oh-story/skills/story-long-analyze/references/stage1-golden-chapters.md +128 -0
  138. package/lib/oh-story/skills/story-long-analyze/references/stage2-extraction.md +229 -0
  139. package/lib/oh-story/skills/story-long-analyze/references/stage3-plot-rhythm.md +480 -0
  140. package/lib/oh-story/skills/story-long-analyze/references/stage4-characters-settings.md +195 -0
  141. package/lib/oh-story/skills/story-long-analyze/references/stage5-report.md +33 -0
  142. package/lib/oh-story/skills/story-long-analyze/references/style-profile-generator.md +17 -0
  143. package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +1 -1
  144. package/lib/oh-story/skills/story-long-analyze/references/synthesis-inputs.md +39 -0
  145. package/lib/oh-story/skills/story-long-analyze/scripts/manage_analysis_run.py +47 -8
  146. package/lib/oh-story/skills/story-long-scan/SKILL.md +41 -193
  147. package/lib/oh-story/skills/story-long-scan/references/platform-ciweimao.md +19 -0
  148. package/lib/oh-story/skills/story-long-scan/references/platform-fanqie.md +45 -0
  149. package/lib/oh-story/skills/story-long-scan/references/platform-jjwxc.md +28 -0
  150. package/lib/oh-story/skills/story-long-scan/references/platform-qidian.md +35 -0
  151. package/lib/oh-story/skills/story-long-scan/references/platform-qimao.md +27 -0
  152. package/lib/oh-story/skills/story-long-scan/references/topic-decision.md +6 -2
  153. package/lib/oh-story/skills/story-long-write/SKILL.md +32 -73
  154. package/lib/oh-story/skills/story-long-write/references/agent-calls.md +47 -0
  155. package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +1 -1
  156. package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +18 -201
  157. package/lib/oh-story/skills/story-long-write/references/author-memory-maintenance.md +75 -0
  158. package/lib/oh-story/skills/story-long-write/references/author-memory.md +40 -118
  159. package/lib/oh-story/skills/story-long-write/references/banned-words.md +2 -2
  160. package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +6 -10
  161. package/lib/oh-story/skills/story-long-write/references/character-relations.md +49 -1
  162. package/lib/oh-story/skills/story-long-write/references/deslop-gates.md +171 -0
  163. package/lib/oh-story/skills/story-long-write/references/emotion-on-page.md +116 -0
  164. package/lib/oh-story/skills/story-long-write/references/emotional-arc-design.md +1 -114
  165. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +1 -1
  166. package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +3 -2
  167. package/lib/oh-story/skills/story-long-write/references/long-format.md +0 -8
  168. package/lib/oh-story/skills/story-long-write/references/long-genre-catalog.md +1 -1
  169. package/lib/oh-story/skills/story-long-write/references/long-genre-mechanics.md +3 -3
  170. package/lib/oh-story/skills/story-long-write/references/opening-design.md +2 -2
  171. package/lib/oh-story/skills/story-long-write/references/outline-conflict.md +4 -4
  172. package/lib/oh-story/skills/story-long-write/references/outline-methods.md +7 -7
  173. package/lib/oh-story/skills/story-long-write/references/outline-rhythm.md +3 -3
  174. package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +14 -69
  175. package/lib/oh-story/skills/story-long-write/references/plot-frameworks.md +1 -1
  176. package/lib/oh-story/skills/story-long-write/references/plot-special-topics.md +2 -2
  177. package/lib/oh-story/skills/story-long-write/references/project-files.md +10 -6
  178. package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +1 -1
  179. package/lib/oh-story/skills/story-long-write/references/reference-index.md +4 -4
  180. package/lib/oh-story/skills/story-long-write/references/stop-and-fix.md +9 -0
  181. package/lib/oh-story/skills/story-long-write/references/style-combat-face.md +1 -58
  182. package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
  183. package/lib/oh-story/skills/story-long-write/references/style-resolution.md +4 -4
  184. package/lib/oh-story/skills/story-long-write/references/tracking-initialization.md +1 -1
  185. package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +11 -9
  186. package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +33 -44
  187. package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +18 -51
  188. package/lib/oh-story/skills/story-long-write/references/workflow-outline.md +157 -0
  189. package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +53 -67
  190. package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +112 -251
  191. package/lib/oh-story/skills/story-long-write/references/workflow-volume.md +142 -0
  192. package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +5 -3
  193. package/lib/oh-story/skills/story-long-write/references/writing-craft.md +18 -56
  194. package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +3 -3
  195. package/lib/oh-story/skills/story-long-write/scripts/build_architect_brief.py +260 -0
  196. package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +96 -16
  197. package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +155 -8
  198. package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +101 -14
  199. package/lib/oh-story/skills/story-long-write/scripts/check-outline-copy.js +7 -2
  200. package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +2 -1
  201. package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +250 -50
  202. package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +278 -23
  203. package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +179 -23
  204. package/lib/oh-story/skills/story-review/SKILL.md +19 -273
  205. package/lib/oh-story/skills/story-review/references/agent-prompts.md +184 -0
  206. package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +1 -1
  207. package/lib/oh-story/skills/story-review/references/author-memory-maintenance.md +75 -0
  208. package/lib/oh-story/skills/story-review/references/author-memory.md +40 -118
  209. package/lib/oh-story/skills/story-review/references/banned-words.md +2 -2
  210. package/lib/oh-story/skills/story-review/references/batch-review.md +19 -0
  211. package/lib/oh-story/skills/story-review/references/character-relations.md +49 -1
  212. package/lib/oh-story/skills/story-review/references/review-quality.md +2 -1
  213. package/lib/oh-story/skills/story-review/references/review-tracking.md +10 -0
  214. package/lib/oh-story/skills/story-review/references/solo.md +52 -0
  215. package/lib/oh-story/skills/story-review/references/style-resolution.md +4 -4
  216. package/lib/oh-story/skills/story-review/references/tracking-initialization.md +1 -1
  217. package/lib/oh-story/skills/story-review/references/tracking-transaction.md +11 -9
  218. package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +3 -3
  219. package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +155 -8
  220. package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +278 -23
  221. package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +179 -23
  222. package/lib/oh-story/skills/story-setup/SKILL.md +69 -458
  223. package/lib/oh-story/skills/story-setup/references/agent-references/agent-quality.md +4 -4
  224. package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +1 -1
  225. package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +2 -2
  226. package/lib/oh-story/skills/story-setup/references/agent-references/benchmark-style-load.md +107 -0
  227. package/lib/oh-story/skills/story-setup/references/agent-references/character-relations.md +49 -1
  228. package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +21 -4
  229. package/lib/oh-story/skills/story-setup/references/agent-references/emotion-on-page.md +116 -0
  230. package/lib/oh-story/skills/story-setup/references/agent-references/emotional-arc-design.md +1 -114
  231. package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +1 -1
  232. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards.md +1 -1
  233. package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-catalog.md +1 -1
  234. package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-mechanics.md +3 -3
  235. package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +7 -5
  236. package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +2 -2
  237. package/lib/oh-story/skills/story-setup/references/agent-references/outline-conflict.md +4 -4
  238. package/lib/oh-story/skills/story-setup/references/agent-references/outline-methods.md +7 -7
  239. package/lib/oh-story/skills/story-setup/references/agent-references/outline-rhythm.md +3 -3
  240. package/lib/oh-story/skills/story-setup/references/agent-references/short-emotional-methods.md +1 -1
  241. package/lib/oh-story/skills/story-setup/references/agent-references/short-genre-formulas.md +0 -15
  242. package/lib/oh-story/skills/story-setup/references/agent-references/short-reversal.md +1 -1
  243. package/lib/oh-story/skills/story-setup/references/agent-references/style-combat-face.md +1 -58
  244. package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
  245. package/lib/oh-story/skills/story-setup/references/agent-references/style-resolution.md +4 -4
  246. package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +18 -56
  247. package/lib/oh-story/skills/story-setup/references/diagnostics.md +3 -3
  248. package/lib/oh-story/skills/story-short-analyze/SKILL.md +37 -110
  249. package/lib/oh-story/skills/story-short-analyze/references/analysis-character-relations.md +1 -1
  250. package/lib/oh-story/skills/story-short-analyze/references/analysis-method.md +42 -0
  251. package/lib/oh-story/skills/story-short-analyze/references/optional-modules.md +97 -0
  252. package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +9 -47
  253. package/lib/oh-story/skills/story-short-analyze/references/quality-checklist.md +80 -0
  254. package/lib/oh-story/skills/story-short-analyze/references/source-story-quality.md +4 -4
  255. package/lib/oh-story/skills/story-short-analyze/references/stage2-3-structure-emotion.md +308 -0
  256. package/lib/oh-story/skills/story-short-analyze/references/{material-decomposition.md → stage4-6-reversal-summary.md} +303 -374
  257. package/lib/oh-story/skills/story-short-scan/SKILL.md +30 -64
  258. package/lib/oh-story/skills/story-short-scan/references/manual-rank-input.md +36 -0
  259. package/lib/oh-story/skills/story-short-scan/references/platform-dianzhong.md +24 -0
  260. package/lib/oh-story/skills/story-short-scan/references/platform-heiyan.md +21 -0
  261. package/lib/oh-story/skills/story-short-scan/references/topic-match.md +38 -0
  262. package/lib/oh-story/skills/story-short-write/SKILL.md +23 -34
  263. package/lib/oh-story/skills/story-short-write/references/agent-calls.md +13 -0
  264. package/lib/oh-story/skills/story-short-write/references/author-memory-maintenance.md +75 -0
  265. package/lib/oh-story/skills/story-short-write/references/author-memory.md +40 -118
  266. package/lib/oh-story/skills/story-short-write/references/banned-words.md +2 -2
  267. package/lib/oh-story/skills/story-short-write/references/benchmark-recall.md +1 -21
  268. package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +1 -1
  269. package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +0 -15
  270. package/lib/oh-story/skills/story-short-write/references/output-contract.md +9 -47
  271. package/lib/oh-story/skills/story-short-write/references/reference-index.md +17 -49
  272. package/lib/oh-story/skills/story-short-write/references/scene-craft.md +40 -0
  273. package/lib/oh-story/skills/story-short-write/references/short-craft.md +7 -43
  274. package/lib/oh-story/skills/story-short-write/references/short-format.md +3 -3
  275. package/lib/oh-story/skills/story-short-write/references/short-prose-quality.md +2 -1
  276. package/lib/oh-story/skills/story-short-write/references/short-reversal.md +1 -1
  277. package/lib/oh-story/skills/story-short-write/references/style-resolution.md +4 -4
  278. package/lib/oh-story/skills/story-short-write/references/submission-craft.md +1 -1
  279. package/lib/oh-story/skills/story-short-write/references/villain-and-reveal.md +5 -10
  280. package/lib/oh-story/skills/story-short-write/references/workflow-design.md +19 -12
  281. package/lib/oh-story/skills/story-short-write/references/workflow-draft.md +22 -32
  282. package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +84 -17
  283. package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +7 -119
  284. package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +3 -3
  285. package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +155 -8
  286. package/lib/oh-story/skills/story-short-write/scripts/check-outline-copy.js +7 -2
  287. package/lib/types/client/video-studio.d.ts.map +1 -1
  288. package/lib/types/native-hooks.d.ts +2 -2
  289. package/lib/types/native-hooks.d.ts.map +1 -1
  290. package/lib/types/role-tool.d.ts.map +1 -1
  291. package/lib/types/skill-provider.d.ts.map +1 -1
  292. package/lib/types/video-project.d.ts.map +1 -1
  293. package/lib/types/workspace-route.d.ts.map +1 -1
  294. package/lib/video-recap/CHANGELOG.md +60 -1
  295. package/lib/video-recap/README.en.md +348 -137
  296. package/lib/video-recap/README.md +340 -136
  297. package/lib/video-recap/manifest.json +406 -251
  298. package/lib/video-recap/skills/video-assemble/SKILL.md +30 -15
  299. package/lib/video-recap/skills/video-assemble/references/audio-modes.md +63 -0
  300. package/lib/video-recap/skills/video-assemble/references/explicit-audio-mix.md +75 -0
  301. package/lib/video-recap/skills/video-assemble/references/foreground-compose.md +118 -0
  302. package/lib/video-recap/skills/video-assemble/references/narration-adoption.md +103 -0
  303. package/lib/video-recap/skills/video-assemble/references/packaging.md +45 -0
  304. package/lib/video-recap/skills/video-assemble/references/pair-media.md +74 -0
  305. package/lib/video-recap/skills/video-assemble/references/source-score.md +147 -0
  306. package/lib/video-recap/skills/video-assemble/references/subtitle-track.md +202 -0
  307. package/lib/video-recap/skills/video-assemble/scripts/adoption/__init__.py +1 -0
  308. package/lib/video-recap/skills/video-assemble/scripts/adoption/audio_mix_binding.py +279 -0
  309. package/lib/video-recap/skills/video-assemble/scripts/adoption/frozen_audio.py +187 -0
  310. package/lib/video-recap/skills/video-assemble/scripts/adoption/narration_binding.py +323 -0
  311. package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_inputs.py +109 -0
  312. package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_publish.py +130 -0
  313. package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +10 -46
  314. package/lib/video-recap/skills/video-assemble/scripts/assemble.py +378 -119
  315. package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +29 -12
  316. package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +95 -44
  317. package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +75 -32
  318. package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +11 -11
  319. package/lib/video-recap/skills/video-assemble/scripts/compose_foreground.py +293 -0
  320. package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +71 -78
  321. package/lib/video-recap/skills/video-assemble/scripts/jianying/__init__.py +1 -0
  322. package/lib/video-recap/skills/video-assemble/scripts/{jianying_builders.py → jianying/builders.py} +39 -67
  323. package/lib/video-recap/skills/video-assemble/scripts/{jianying_model.py → jianying/model.py} +3 -11
  324. package/lib/video-recap/skills/video-assemble/scripts/{jianying_optional.py → jianying/optional.py} +1 -1
  325. package/lib/video-recap/skills/video-assemble/scripts/jianying/schema.py +53 -0
  326. package/lib/video-recap/skills/video-assemble/scripts/{jianying_templates.py → jianying/templates.py} +1 -1
  327. package/lib/video-recap/skills/video-assemble/scripts/{jianying_timeline_contract.py → jianying/timeline_contract.py} +34 -13
  328. package/lib/video-recap/skills/video-assemble/scripts/{jianying_writer.py → jianying/writer.py} +7 -17
  329. package/lib/video-recap/skills/video-assemble/scripts/lib.py +37 -4
  330. package/lib/video-recap/skills/video-assemble/scripts/media.py +12 -21
  331. package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +64 -34
  332. package/lib/video-recap/skills/video-assemble/scripts/packaging.py +122 -0
  333. package/lib/video-recap/skills/video-assemble/scripts/pair_media.py +216 -0
  334. package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +1 -3
  335. package/lib/video-recap/skills/video-assemble/scripts/source_score.py +469 -0
  336. package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +25 -9
  337. package/lib/video-recap/skills/video-assemble/scripts/subtitles/__init__.py +1 -0
  338. package/lib/video-recap/skills/video-assemble/scripts/{subtitle_core.py → subtitles/core.py} +13 -6
  339. package/lib/video-recap/skills/video-assemble/scripts/{subtitle_render.py → subtitles/render.py} +5 -4
  340. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track.py +324 -0
  341. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track_binding.py +200 -0
  342. package/lib/video-recap/skills/video-assemble/scripts/timeline.py +30 -46
  343. package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +91 -7
  344. package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +22 -8
  345. package/lib/video-recap/skills/video-cut/SKILL.md +34 -16
  346. package/lib/video-recap/skills/video-cut/references/shot-review.md +50 -0
  347. package/lib/video-recap/skills/video-cut/scripts/cut.py +2 -28
  348. package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +58 -62
  349. package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +21 -106
  350. package/lib/video-recap/skills/video-cut/scripts/cut_render.py +14 -9
  351. package/lib/video-recap/skills/video-cut/scripts/lib.py +39 -0
  352. package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +2 -155
  353. package/lib/video-recap/skills/video-cut/scripts/narrative_selection.py +244 -0
  354. package/lib/video-recap/skills/video-cut/scripts/shot_review.py +357 -0
  355. package/lib/video-recap/skills/video-recap/SKILL.md +86 -42
  356. package/lib/video-recap/skills/video-recap/references/audio-routing.md +126 -0
  357. package/lib/video-recap/skills/video-recap/references/config-playbook.md +12 -8
  358. package/lib/video-recap/skills/video-recap/references/data-schema.md +44 -33
  359. package/lib/video-recap/skills/video-recap/references/resource-library.md +151 -0
  360. package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +1 -1
  361. package/lib/video-recap/skills/video-recap/scripts/doctor.py +123 -30
  362. package/lib/video-recap/skills/video-recap/scripts/final_qc.py +22 -75
  363. package/lib/video-recap/skills/video-recap/scripts/lib.py +3 -3
  364. package/lib/video-recap/skills/video-recap/scripts/library.py +509 -0
  365. package/lib/video-recap/skills/video-recap/scripts/materials.py +63 -91
  366. package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +8 -70
  367. package/lib/video-recap/skills/video-recap/scripts/project_binding.py +286 -0
  368. package/lib/video-recap/skills/video-recap/scripts/qc/__init__.py +1 -0
  369. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_client.py → qc/mimo_client.py} +1 -1
  370. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_evidence.py → qc/mimo_evidence.py} +33 -66
  371. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_observations.py → qc/mimo_observations.py} +3 -11
  372. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_payload.py → qc/mimo_payload.py} +4 -7
  373. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_report.py → qc/mimo_report.py} +17 -28
  374. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_runner.py → qc/mimo_runner.py} +2 -2
  375. package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +0 -19
  376. package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +119 -50
  377. package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +32 -114
  378. package/lib/video-recap/skills/video-recap/scripts/recap_review.py +9 -23
  379. package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +387 -334
  380. package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +23 -23
  381. package/lib/video-recap/skills/video-recap/scripts/recap_source.py +278 -0
  382. package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +25 -12
  383. package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +59 -52
  384. package/lib/video-recap/skills/video-recap/scripts/resource_lock.py +162 -0
  385. package/lib/video-recap/skills/video-script/SKILL.md +16 -13
  386. package/lib/video-recap/skills/video-script/references/creative-editing-playbook.md +9 -5
  387. package/lib/video-recap/skills/video-script/references/promotional-copy.md +68 -0
  388. package/lib/video-recap/skills/video-script/references/research-guide.md +1 -1
  389. package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +24 -76
  390. package/lib/video-recap/skills/video-script/scripts/lib.py +10 -87
  391. package/lib/video-recap/skills/video-script/scripts/narration_lint.py +63 -39
  392. package/lib/video-recap/skills/video-script/scripts/review.py +0 -2
  393. package/lib/video-recap/skills/video-script/scripts/review_grounding.py +35 -59
  394. package/lib/video-recap/skills/video-script/scripts/review_response.py +66 -128
  395. package/lib/video-recap/skills/video-script/scripts/review_runner.py +12 -20
  396. package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +4 -4
  397. package/lib/video-recap/skills/video-script/scripts/validate.py +53 -42
  398. package/lib/video-recap/skills/video-understanding/SKILL.md +6 -4
  399. package/lib/video-recap/skills/video-understanding/references/data-schema.md +50 -156
  400. package/lib/video-recap/skills/video-understanding/references/research-guide.md +4 -4
  401. package/lib/video-recap/skills/video-understanding/scripts/asr.py +85 -65
  402. package/lib/video-recap/skills/video-understanding/scripts/asr_timing_evidence.py +270 -0
  403. package/lib/video-recap/skills/video-understanding/scripts/brief.py +2 -5
  404. package/lib/video-recap/skills/video-understanding/scripts/briefing/__init__.py +1 -0
  405. package/lib/video-recap/skills/video-understanding/scripts/{agent_brief.py → briefing/builder.py} +38 -35
  406. package/lib/video-recap/skills/{video-script/scripts/brief_context.py → video-understanding/scripts/briefing/context.py} +4 -35
  407. package/lib/video-recap/skills/{video-script/scripts/brief_inputs.py → video-understanding/scripts/briefing/inputs.py} +9 -31
  408. package/lib/video-recap/skills/video-understanding/scripts/{brief_timeline.py → briefing/timeline.py} +5 -6
  409. package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +65 -116
  410. package/lib/video-recap/skills/video-understanding/scripts/detect.py +31 -40
  411. package/lib/video-recap/skills/video-understanding/scripts/lib.py +30 -86
  412. package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +63 -39
  413. package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +4 -4
  414. package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +17 -79
  415. package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +14 -40
  416. package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +64 -46
  417. package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +31 -11
  418. package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +19 -14
  419. package/lib/video-recap/skills/video-understanding/scripts/vlm.py +80 -108
  420. package/lib/video-recap/skills/video-voiceover/SKILL.md +29 -19
  421. package/lib/video-recap/skills/video-voiceover/references/index-tts.md +31 -0
  422. package/lib/video-recap/skills/video-voiceover/scripts/approved_text_policy.py +123 -0
  423. package/lib/video-recap/skills/video-voiceover/scripts/dub.py +51 -59
  424. package/lib/video-recap/skills/video-voiceover/scripts/lib.py +8 -44
  425. package/lib/video-recap/skills/video-voiceover/scripts/providers/__init__.py +1 -0
  426. package/lib/video-recap/skills/video-voiceover/scripts/providers/index_tts.py +184 -0
  427. package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +212 -134
  428. package/package.json +1 -1
  429. package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +0 -538
  430. package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +0 -603
  431. package/lib/oh-story/skills/story-long-scan/references/scan-output-format.md +0 -207
  432. package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +0 -561
  433. package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +0 -98
  434. package/lib/video-recap/skills/video-recap/references/creative-editing-playbook.md +0 -225
  435. package/lib/video-recap/skills/video-recap/references/research-guide.md +0 -74
  436. package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +0 -250
  437. package/lib/video-recap/skills/video-script/scripts/agent_brief.py +0 -399
  438. package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +0 -327
  439. package/lib/video-recap/skills/video-script/scripts/narration.py +0 -12
  440. package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +0 -348
  441. package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +0 -290
  442. /package/lib/video-recap/skills/video-assemble/scripts/{jianying_tracks.py → jianying/tracks.py} +0 -0
  443. /package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_contract.py → qc/mimo_contract.py} +0 -0
  444. /package/lib/video-recap/skills/video-voiceover/scripts/{fish_audio.py → providers/fish_audio.py} +0 -0
@@ -16,10 +16,15 @@
16
16
  - 脚本做确定性部分:固定首行、定位、标题行字面量、细纲指针、文风全文路径与裁决、
17
17
  上一章结尾、降档判定与情绪/节奏槽、固定块指针。
18
18
  - 脚本代查作者记忆(prose_style + story_design)并填好 author_preferences。
19
- - 主会话填八槽:执行安排 / 本章意图 / 参考技法 / 本节速记 / 涉及角色 / genre_prose_card /
20
- 必读设定 / style_resolution。降档不成立时情绪与节奏槽也归主会话。
21
- 材料槽对应原流程步骤 3「写前准备」的四项输出(本节速记 / 情绪目标 / 涉及角色 /
22
- 参考技法)加上题材卡、设定补漏与作者偏好——都是判断,脚本做不了。
19
+ - 脚本注入 `设定/题材定位.md`「作者已定」(本书的偏好、红线与否掉的方案),超过 AUTHOR_DECIDED_CHARS
20
+ 可见字截断并注明去原文看;它是书级设定,优先于作者记忆。
21
+ - 主会话填九个固定槽:执行安排 / 本章意图 / 作者本轮要求 / 本章技法 / 本节速记 / 涉及角色 /
22
+ genre_prose_card / 必读设定 / style_resolution。另有两个条件槽,出现时同样标[主会话填]:
23
+ author_preferences 块内的限定范围(--genre/--workflow)作者记忆未代查时,由主会话判断是否适用后补查
24
+ (脚本查询失败时则整块手查);降档不成立时「情绪与节奏召回」也归主会话。
25
+ 核对报告末尾的「待填槽位」给出本次实际个数(9-11)。
26
+ 材料槽对应原流程步骤 3「写前准备」的四项输出(本节速记 / 目标情绪 / 涉及角色 /
27
+ 本章技法)加上作者本轮要求、题材卡、设定补漏与作者偏好——都是判断,脚本做不了。
23
28
  - 续写状态卡校验后由主会话筛选,在「本节速记」槽内写入本章需要的状态。
24
29
  - 可选块缺失留标题并写明原因(「没有」与「漏了」在产物上必须长得不一样)。
25
30
  必在块缺失退出码 2 —— 那是数据问题,要修数据后重跑,不是回落手拼的理由。
@@ -39,6 +44,7 @@ from outline_view import parse as parse_volume
39
44
  TAIL_CHARS = 400 # 上一章结尾注入的目标字符数(按整行回退,不切半句)
40
45
  STATE_SECTIONS = ("当前位置", "长期约束", "核心角色状态", "活跃伏笔", "近三章速记", "下一章承诺", "连贯性风险")
41
46
  SLOT_MARK = "[主会话填]"
47
+ AUTHOR_DECIDED_CHARS = 600 # 「作者已定」注入上限(去空白字数);超了截断,写手按提示回原文看
42
48
  WORK_ROOT = (".story", "work")
43
49
 
44
50
 
@@ -164,13 +170,65 @@ def scoped_memory_values(workspace: Path):
164
170
  return values
165
171
 
166
172
 
167
- def book_genres(project: Path):
168
- """「题材类型」行按分隔符切成词;未填的模板占位({…})不算。"""
173
+ def genre_line(project: Path):
174
+ """「题材类型」行的原文;未填的模板占位({…})当没有。"""
169
175
  text = read_text(project / "设定" / "题材定位.md") or ""
170
176
  match = re.search(r"^[ \t]*[-*+]?[ \t]*\**题材(?:类型)?\**[ \t]*[::](.*)$", text, re.M)
171
177
  if not match or "{" in match.group(1):
172
- return set()
173
- return {word.casefold() for word in re.split(r"[\W_丨×]+", match.group(1)) if word}
178
+ return ""
179
+ return match.group(1).strip()
180
+
181
+
182
+ def book_genres(project: Path):
183
+ """「题材类型」行按分隔符切成词。"""
184
+ return {word.casefold() for word in re.split(r"[\W_丨×]+", genre_line(project)) if word}
185
+
186
+
187
+ GENRE_CARD_DIR = Path(__file__).resolve().parent.parent / "references" / "genre-prose-cards"
188
+
189
+
190
+ def match_genre_cards(project: Path):
191
+ """题材正文提示卡缺失时,按「题材类型」原文匹配题材卡(卡头的 genre 与 aliases),替主会话省掉整读索引。
192
+
193
+ 按在题材类型里出现得越靠前越优先(主题材在前),同位置按置信度高→低;最多返回主、辅两张:[(路径, 置信度)]。"""
194
+ raw = genre_line(project).casefold()
195
+ if not raw or not GENRE_CARD_DIR.is_dir():
196
+ return []
197
+ rank = {"high": 0, "medium": 1, "low": 2}
198
+ hits = []
199
+ for card in sorted(GENRE_CARD_DIR.glob("*.md")):
200
+ head = (read_text(card) or "").split("---")
201
+ meta = head[1] if len(head) > 2 else ""
202
+ genre = re.search(r"^genre:\s*(.+)$", meta, re.M)
203
+ aliases = re.search(r"^aliases:\s*\[(.*)\]", meta, re.M)
204
+ confidence = re.search(r"^confidence:\s*(\w+)", meta, re.M)
205
+ names = [genre.group(1).strip()] if genre else [card.stem]
206
+ names += [a.strip() for a in aliases.group(1).split(",")] if aliases else []
207
+ where = [raw.find(name.casefold()) for name in names if name and name.casefold() in raw]
208
+ if where:
209
+ level = confidence.group(1) if confidence else "low"
210
+ hits.append((min(where), rank.get(level, 2), card, level))
211
+ hits.sort(key=lambda row: (row[0], row[1], row[2].name))
212
+ return [(card, level) for _, _, card, level in hits[:2]]
213
+
214
+
215
+ def author_decided(project: Path):
216
+ """`设定/题材定位.md`「## 作者已定」一节:去掉模板占位行;返回 (正文, 是否截断)。没有这一节返回 (None, False)。"""
217
+ text = read_text(project / "设定" / "题材定位.md") or ""
218
+ match = re.search(r"^##[ \t]*作者已定[^\n]*\n(.*?)(?=^#{1,2}[ \t]|\Z)", text, re.M | re.S)
219
+ if not match:
220
+ return None, False
221
+ lines = [line.rstrip() for line in match.group(1).splitlines()
222
+ if line.strip() and not re.fullmatch(r"\s*[-*+]?\s*\{.*\}\s*", line)]
223
+ kept, used, cut = [], 0, False
224
+ for line in lines:
225
+ size = len(re.sub(r"\s", "", line))
226
+ if used + size > AUTHOR_DECIDED_CHARS:
227
+ cut = True
228
+ break
229
+ kept.append(line)
230
+ used += size
231
+ return "\n".join(kept), cut
174
232
 
175
233
 
176
234
  def query_author_memory(project: Path):
@@ -369,7 +427,10 @@ def build(project: Path, chapter: int, report: list):
369
427
  needs = {"题材正文提示卡": card_text, "目标情绪": target_emotion,
370
428
  "单元情绪引擎": engine, "单元节拍/章功能分配": tempo}
371
429
  why = [name + "缺有效内容" for name, value in needs.items() if not substantive(value)]
372
- if not custom_style:
430
+ # 无对标(项目与工作区都没有对标书)时文风本就没有对标可召回:不因 custom_style 缺失拒绝降档
431
+ no_benchmark = not any(d.is_dir() for base in (project / "对标", project / "拆文库", project.parent / "拆文库")
432
+ if base.is_dir() for d in base.iterdir())
433
+ if not custom_style and not no_benchmark:
373
434
  why.append("custom_style=false")
374
435
  downgrade = not why
375
436
 
@@ -384,7 +445,7 @@ def build(project: Path, chapter: int, report: list):
384
445
  if tempo:
385
446
  lines.append(f" {tempo}")
386
447
  slot_recall = "——— 情绪与节奏召回 ———\n" + "\n".join(lines)
387
- report.append(f"召回降档:成立,文风、题材卡、目标情绪和单元 {unit_id} 情绪/节拍均可用")
448
+ report.append(f"召回降档:成立,{'无对标,' if no_benchmark and not custom_style else '文风、'}题材卡、目标情绪和单元 {unit_id} 情绪/节拍均可用")
388
449
  else:
389
450
  slot_recall = ("——— 情绪与节奏召回 ———\n"
390
451
  f"{SLOT_MARK} 降档不成立(" + "、".join(why) +
@@ -396,19 +457,21 @@ def build(project: Path, chapter: int, report: list):
396
457
  parts.append(
397
458
  "——— 执行安排 ———\n"
398
459
  f"{SLOT_MARK} 全章细纲用于整体编排。默认按自然转场或因果停顿分前后两组,"
399
- "填写当前组的情节点/片段;先只写前组,父流程测一次 checkpoint 后"
460
+ "填写当前组的情节点/片段;先只写前组,主会话测一次 checkpoint 后"
400
461
  "再给后组和机器剩余区间。只有用户明确要求一次成文时才填「全章,直接写最终路径」。\n"
401
462
  f"分组临时文件:前组 {work_dir / '前组.md'},后组 {work_dir / '后组.md'}"
402
463
  "(只写这里,不写 /tmp 或 正文/)。")
403
464
  parts.append(f"——— 本章意图(一句话)———\n{SLOT_MARK}")
465
+ parts.append("——— 作者本轮要求 ———\n"
466
+ f"{SLOT_MARK} 作者这次明确说的:字数范围、必须发生、不许发生、写到哪里停、章尾要留什么;没有写「无」。")
404
467
  parts.append(slot_recall)
405
468
  # 伏笔与卷级禁忌走「主会话筛选后写进速记」这条原设计路线(步骤 3 状态筛选),
406
469
  # 不由脚本整栏注入——筛选是判断,而整栏注入还会把伏笔栏里的作者侧真相一并下放。
407
470
  # 代价是它依赖主会话逐章想起来,所以这里把提示语写成写死的三问清单。
408
471
  parts.append(
409
- "——— 参考技法 ———\n"
410
- f"{SLOT_MARK} 借鉴哪个参考文件的哪个技法、用在哪些段落;没有就写「无」。"
411
- "按 reference 表的任务条件读取;本书文风只覆盖冲突表达条款,不停读整份文件。")
472
+ "——— 本章技法 ———\n"
473
+ f"{SLOT_MARK} 按细纲主功能填 打脸/反转/感情/悬疑/日常/无 之一(对照 SKILL.md「本章技法」),"
474
+ "再写一句用在哪几段;写手按自己的读取表读对应文件,这里不摘技法。本书文风只覆盖冲突表达条款,不停读整份文件。")
412
475
  parts.append(
413
476
  "——— 本节速记 ———\n"
414
477
  f"{SLOT_MARK} 按 workflow-chapter 步骤 3「状态筛选」产出(`追踪/上下文.md` 不注入"
@@ -420,9 +483,26 @@ def build(project: Path, chapter: int, report: list):
420
483
  "——— 涉及角色 ———\n"
421
484
  f"{SLOT_MARK} 按细纲「人物出场顺序」与「镜头准入」的台词位/动作位分配,"
422
485
  "列出本章要读的角色卡;本节速记已给全状态的不必再列。")
486
+ if not substantive(card_text):
487
+ cards = match_genre_cards(project)
488
+ if cards:
489
+ named = ";".join(f"{'主' if i == 0 else '辅'}题材 {card}(置信度 {level})" for i, (card, level) in enumerate(cards))
490
+ report.append(f"题材卡:本书还没有题材正文提示卡,按「题材类型」匹配到 {named};生成后落盘,后续章直接读")
491
+ else:
492
+ report.append("题材卡:本书还没有题材正文提示卡,题材卡无匹配——读 style-genre-modules.md"
493
+ "「题材正文提示卡」一节与最接近的流派一节生成后落盘")
423
494
  parts.append("——— 题材正文提示卡(genre_prose_card,只含本章相关条目)———\n"
424
495
  f"{SLOT_MARK} 主题材抽 3-5 条、辅题材 1-2 条;只作内部校准,不进正文")
425
496
  parts.append(slot_setting)
497
+ decided, cut = author_decided(project)
498
+ if decided:
499
+ note = (f"\n(超过 {AUTHOR_DECIDED_CHARS} 字已截断,其余见 {project / '设定' / '题材定位.md'}「作者已定」,动笔前读完)"
500
+ if cut else "")
501
+ parts.append("——— 作者已定(本书的决定,高于作者记忆,与细纲冲突以细纲为准并在交付摘要里说明)———\n"
502
+ + decided + note)
503
+ report.append("作者已定:已注入" + ("(超长截断,写手需回原文读完)" if cut else ""))
504
+ else:
505
+ report.append("作者已定:题材定位里没有或为空,未注入")
426
506
  items, omitted, skipped, memory_error = query_author_memory(project)
427
507
  if memory_error:
428
508
  memory_block = (f"{SLOT_MARK} 组装脚本查询作者记忆失败({memory_error}),"
@@ -438,14 +518,14 @@ def build(project: Path, chapter: int, report: list):
438
518
  if skipped and not memory_error:
439
519
  named = ";".join(f"{'题材' if k == 'genre' else '流程'}:{'、'.join(v)}" for k, v in skipped.items())
440
520
  memory_block += (f"\n{SLOT_MARK} 另有限定范围的作者记忆未代查({named}):本书适用的,"
441
- "按 author-memory.md 带 --genre/--workflow 查询后补进本块")
521
+ "带 --genre/--workflow 跑 SKILL.md「核心方法」的 query 命令(--kind prose_style --kind story_design)后补进本块")
442
522
  report.append(f"作者记忆:限定范围未代查({named}),归主会话判断是否适用")
443
523
  parts.append("——— author_preferences(作者记忆,低优先级倾向,不逐条追求命中)———\n" + memory_block)
444
524
  parts.append("——— style_resolution ———\n"
445
525
  f"{SLOT_MARK} 本轮请求里作者对表达的明确要求及其覆盖的默认条款(没有写「无」);"
446
526
  "本书文风与作者记忆的裁决见上方各块,同一裁决传去味与审稿。")
447
527
 
448
- parts.append("检查分工:写手负责编排、内容覆盖和格式自检;父流程质量阶段负责语义去味及最终文件扫描。写手不提前重复整轮去味或相同检查链,保留情节点落点、新增申报与原定交付。")
528
+ parts.append("检查分工:写手负责编排、内容覆盖和格式自检;主会话质量阶段负责语义去味及最终文件扫描。写手不提前重复整轮去味或相同检查链,保留情节点落点、新增申报与原定交付。")
449
529
 
450
530
  # ---- 固定块:压成指针,不重述 agent 定义 ----
451
531
  parts.append(
@@ -6,6 +6,7 @@ const { loadStyleWhitelist, maskStyleText } = require('./style-whitelist.js');
6
6
  const path = require('path');
7
7
 
8
8
  const USAGE = `Usage: node check-ai-patterns.js [--check] [--json] [--fail-on=blocking|all] <file...>
9
+ node check-ai-patterns.js --list-review-classes
9
10
 
10
11
  Detect high-risk AI-flavor prose patterns that need human rewrite:
11
12
  - negative setup followed by positive flip in the same sentence
@@ -33,8 +34,10 @@ Detect high-risk AI-flavor prose patterns that need human rewrite:
33
34
 
34
35
  Book-local .deslop-whitelist literal spans are excluded from style scanning (no regex or ancestor inheritance).
35
36
  Each finding carries severity: blocking by default for generation/deslop cleanup (not-is-comparison / em-dash / voice-contrast / negation-parade / reverse-not-is / trailer-ending / trailer-summary). This is a local style/readability gate, not an AIGC detector score; functional human text can be marked for review instead of hard-edited for a detector.
36
- 或 advisory (period-stutter / long-paragraph / micro-action-tic / stock-reaction-tic / action-list-tic / abstract-summary-tic / cliche-density-tic / metaphor-density-tic / reasoning-chain-tic / system-notice-formality-tic / overcompressed-prose-tic / low-connective-density-tic / quote-emphasis-tic / formulaic-parallelism,是提示,justified 的长推理/氛围段可保留)。
37
+ 或 advisory (period-stutter / long-paragraph / micro-action-tic / negation-pair-tic / stock-reaction-tic / action-list-tic / abstract-summary-tic / cliche-density-tic / metaphor-density-tic / reasoning-chain-tic / system-notice-formality-tic / overcompressed-prose-tic / low-connective-density-tic / quote-emphasis-tic / formulaic-parallelism,是提示,justified 的长推理/氛围段可保留)。
37
38
  --fail-on=blocking 只在出现 blocking finding 时退出 1;默认 --fail-on=all 有任何 finding 即退出 1。
39
+ Each finding also carries review: mechanical(表面形态确定、就地改写即可)或 semantic(可能承担叙事功能,需读上下文判断是否保留);blocking 一律 mechanical。--list-review-classes 输出完整分类表(JSON)。
40
+ HTML comments (<!-- ... -->, e.g. the 去味:跳过 exemption marker) are metadata, not prose, and are never scanned.
38
41
 
39
42
  The script reports findings only. It never rewrites text, because the safe fix is
40
43
  contextual: usually delete the negative setup, write the positive term directly,
@@ -58,6 +61,10 @@ const LONG_PARAGRAPH_CHARS = 200;
58
61
  // 单次出现是正常中文。
59
62
  const MICRO_TIC_PATTERN = /了(?:[一两三几半])?[下阵圈道声眼口气会]/g;
60
63
  const MICRO_TIC_MIN_HITS = 5;
64
+ // 「没A,也没B」「不A,也不B」否定对偶:七猫人类长篇约 0.07 处/千字,模型稿 0.4-1.5 处/千字;
65
+ // 写人物没做什么是英文式节奏,一章 3 处以上报一条提示。
66
+ const NEGATION_PAIR_PATTERN = /(?:没有|没|不)[^,。!?;,!?\n]{1,10}[,,;]\s*[^,。!?;,!?\n]{0,6}?也(?:没有|没|不)/g;
67
+ const NEGATION_PAIR_MIN_HITS = 3;
61
68
  const MICRO_TIC_PER_KILO = 6;
62
69
 
63
70
  // 套式反应细节:不是禁写身体,而是提示成片出现的“部位 + 轻微动作/状态”、
@@ -239,7 +246,7 @@ const REVERSE_NOT_IS_PREV_EXCLUDE = new Set([...COMPACT_EITHER_OR_PREV, '还', '
239
246
  const TRAILER_ENDING_PATTERN = /没人知道|谁也不知道|谁也没想到|殊不知|(?:这)?才刚刚开(?:始|头)|正(?:朝着|向着)[^。!?!?\n]{0,24}(?:压|涌|袭|逼)(?:了?过去|了?过来|来)|(?<!正式)拉开(?:序幕|帷幕)|即将(?:开始|来临|降临)/g;
240
247
  const TRAILER_ENDING_WINDOW_CHARS = 600;
241
248
 
242
- // 章尾状态总结体:把细纲「结尾设定/收束状态」原样写成总结句收章(「这一夜注定无人入眠」
249
+ // 章尾状态总结体:把细纲「结尾设定」原样写成总结句收章(「这一夜注定无人入眠」
243
250
  // 「这一切都结束了」「新的人生才刚刚开始」「命运的齿轮」)。与 trailer-ending 共用文末窗口,
244
251
  // 区别是它盖章过去、trailer-ending 预告将来;收的都是 banned-words 已按名禁掉的形态。
245
252
  // 不收「(这|那)一刻…终于明白」:真人语料里那是正常的认知节拍,短篇第一人称审判句还是卖点
@@ -266,6 +273,58 @@ const QUOTE_EMPHASIS_MIN_HITS = 3;
266
273
  const QUOTE_EMPHASIS_MAX_VISIBLE = 4;
267
274
  const QUOTE_EMPHASIS_SPEECH_VERB_PATTERN = /[说道问喊答念叫回吼骂写读唱嘀咕]/;
268
275
 
276
+ // review 分类:下游按它分流。mechanical = 命中的表面形态本身就是问题,写作端就地改写即可;
277
+ // semantic = 同一形态可能承担叙事功能,要读上下文判断保留还是改(交语义审查)。blocking 只能是
278
+ // mechanical(blocking 的含义就是不需判断、必须清零)。新增 finding type 必须在此登记,否则运行期
279
+ // 直接报错退出(scripts/test-ai-patterns.sh 另从源码抽 type 与本表双向核对)。
280
+ // type review 理由
281
+ // not-is-comparison mechanical 固定句式模板「不是A,而是B」,blocking
282
+ // voice-contrast mechanical 固定反差模板「声音不大…却」,blocking
283
+ // negation-parade mechanical 固定否定排比模板,blocking
284
+ // reverse-not-is mechanical 固定反序对比模板「是A,不是B」,blocking
285
+ // trailer-ending mechanical 文末窗口固定预告短语,blocking
286
+ // trailer-summary mechanical 文末窗口固定总结短语,blocking
287
+ // em-dash mechanical 标点,blocking
288
+ // long-paragraph mechanical 段长阈值,断段即可
289
+ // period-stutter mechanical 连续短句计数,合句即可
290
+ // micro-action-tic mechanical 固定补语「了下/了一下」密度
291
+ // negation-pair-tic semantic 「没A,也没B」否定对偶,留真正要强调的
292
+ // cliche-density-tic mechanical 固定禁用词表密度
293
+ // stock-reaction-tic semantic 身体反应可能承担物理后果或情绪,要逐处做删除测试
294
+ // action-list-tic semantic 打斗/追逐等功能性动作编排可保留
295
+ // abstract-summary-tic semantic 作者总结/上帝视角,是否越位要看语境
296
+ // metaphor-density-tic semantic 比喻是否服务画面、删哪几个要读上下文
297
+ // reasoning-chain-tic semantic 替读者推理的解释链,长推理段可能是正当的
298
+ // system-notice-formality-tic semantic 系统/规则文本本就可以公文腔,看体裁功能
299
+ // overcompressed-prose-tic semantic 短段过密可能是节奏选择,禁止机械注水
300
+ // low-connective-density-tic semantic 同上,连接词补不补要看读感
301
+ // quote-emphasis-tic semantic 标语/转述载体的引号是正常写法
302
+ // formulaic-parallelism semantic 工整并列可能承担辩解或悬念排除
303
+ const REVIEW_CLASSES = Object.freeze({
304
+ 'not-is-comparison': 'mechanical',
305
+ 'voice-contrast': 'mechanical',
306
+ 'negation-parade': 'mechanical',
307
+ 'reverse-not-is': 'mechanical',
308
+ 'trailer-ending': 'mechanical',
309
+ 'trailer-summary': 'mechanical',
310
+ 'em-dash': 'mechanical',
311
+ 'long-paragraph': 'mechanical',
312
+ 'period-stutter': 'mechanical',
313
+ 'micro-action-tic': 'mechanical',
314
+ 'negation-pair-tic': 'semantic',
315
+ 'cliche-density-tic': 'mechanical',
316
+ 'stock-reaction-tic': 'semantic',
317
+ 'action-list-tic': 'semantic',
318
+ 'abstract-summary-tic': 'semantic',
319
+ 'metaphor-density-tic': 'semantic',
320
+ 'reasoning-chain-tic': 'semantic',
321
+ 'system-notice-formality-tic': 'semantic',
322
+ 'overcompressed-prose-tic': 'semantic',
323
+ 'low-connective-density-tic': 'semantic',
324
+ 'quote-emphasis-tic': 'semantic',
325
+ 'formulaic-parallelism': 'semantic',
326
+ });
327
+
269
328
  const options = {
270
329
  json: false,
271
330
  files: [],
@@ -276,6 +335,9 @@ for (let i = 2; i < process.argv.length; i += 1) {
276
335
  const arg = process.argv[i];
277
336
  if (arg === '--check') {
278
337
  // Accepted for symmetry with normalize-punctuation.js; detection is always check-only.
338
+ } else if (arg === '--list-review-classes') {
339
+ process.stdout.write(`${JSON.stringify(REVIEW_CLASSES, null, 2)}\n`);
340
+ process.exit(0);
279
341
  } else if (arg === '--json') {
280
342
  options.json = true;
281
343
  } else if (arg.startsWith('--fail-on=')) {
@@ -313,7 +375,7 @@ for (const file of options.files) {
313
375
  let whitelist;
314
376
  try { whitelist = loadStyleWhitelist(fullPath); }
315
377
  catch (error) { die(`${file}: unable to read .deslop-whitelist (${error.message})`); }
316
- const findings = scanDocument(maskStyleText(input, whitelist)).map((finding) => ({ file, ...finding }));
378
+ const findings = scanDocument(maskStyleText(input, whitelist)).map((finding) => ({ file, ...finding, review: reviewClass(finding.type) }));
317
379
  allFindings.push(...findings);
318
380
  }
319
381
 
@@ -330,6 +392,15 @@ if (failed) process.exit(2);
330
392
  const hasBlocking = allFindings.some((f) => f.severity === 'blocking');
331
393
  if (options.failOn === 'blocking' ? hasBlocking : allFindings.length > 0) process.exit(1);
332
394
 
395
+ function reviewClass(type) {
396
+ const review = REVIEW_CLASSES[type];
397
+ if (!review) {
398
+ console.error(`internal error: finding type '${type}' has no review class (add it to REVIEW_CLASSES)`);
399
+ process.exit(2);
400
+ }
401
+ return review;
402
+ }
403
+
333
404
  function escapeRegExp(text) {
334
405
  return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
335
406
  }
@@ -344,11 +415,15 @@ function die(message) {
344
415
  process.exit(2);
345
416
  }
346
417
 
347
- function scanDocument(input) {
418
+ // unclosedOpenings:到文末都没闭合的 `<!--` 位置("行号:列号")。未闭合的注释不是注释,
419
+ // 否则一个漏写的 `-->` 会把后面整章藏起来;这些位置按正文扫描,只把 `<!--` 本身抹掉。
420
+ function scanDocument(input, unclosedOpenings = new Set()) {
348
421
  const lines = input.split(/\r?\n/);
349
422
  const findings = [];
350
423
  let fence = null;
351
424
  let inFrontMatter = hasYamlFrontMatter(lines);
425
+ let commentOpen = false;
426
+ let commentStart = null;
352
427
  let block = [];
353
428
  const proseLines = [];
354
429
 
@@ -375,14 +450,25 @@ function scanDocument(input) {
375
450
  continue;
376
451
  }
377
452
 
378
- if (fenceMarker) {
453
+ if (fenceMarker && !commentOpen) {
379
454
  flushBlock();
380
455
  fence = fenceMarker;
381
456
  continue;
382
457
  }
383
458
 
384
- block.push({ text: line, lineNo: index + 1 });
385
- proseLines.push({ text: line, lineNo: index + 1 });
459
+ // HTML 注释(如 `<!-- 去味:跳过 -->` 豁免标记)是元信息不是正文:注释段等长换成空格
460
+ // 保列号,整行都是注释的直接跳过;注释可跨行。
461
+ const masked = maskHtmlComments(line, commentOpen, index, unclosedOpenings);
462
+ commentOpen = masked.open;
463
+ if (masked.openedAt !== null) commentStart = masked.openedAt;
464
+ if (masked.text !== line && !masked.text.trim()) continue;
465
+
466
+ block.push({ text: masked.text, lineNo: index + 1 });
467
+ proseLines.push({ text: masked.text, lineNo: index + 1 });
468
+ }
469
+
470
+ if (commentOpen && commentStart !== null) {
471
+ return scanDocument(input, new Set([...unclosedOpenings, commentStart]));
386
472
  }
387
473
 
388
474
  flushBlock();
@@ -432,6 +518,7 @@ function scanProsePatterns(proseLines) {
432
518
  findings.push(...findQuoteEmphasisTic(proseLines));
433
519
  findings.push(...findPeriodStutter(proseLines));
434
520
  findings.push(...findMicroActionTic(proseLines));
521
+ findings.push(...findNegationPairTic(proseLines));
435
522
  findings.push(...findStockReactionTic(proseLines));
436
523
  findings.push(...findActionListTic(proseLines));
437
524
  findings.push(...findAbstractSummaryTic(proseLines));
@@ -638,7 +725,7 @@ function findTrailerEnding(proseLines) {
638
725
  column: summaryMatch.index + 1,
639
726
  type: 'trailer-summary',
640
727
  severity: 'blocking',
641
- message: '章尾状态总结体:「这一夜注定…/这一切都结束了/新的人生才刚刚开始/命运的齿轮」是把细纲的收束状态原样写成了总结句;收束状态是规划口径,正文落到最后一个具体动作、画面或台词上,别替读者盖章。',
728
+ message: '章尾状态总结体:「这一夜注定…/这一切都结束了/新的人生才刚刚开始/命运的齿轮」是把细纲的结尾设定原样写成了总结句;结尾设定要落成最后一个具体动作、画面或台词,别替读者盖章。',
642
729
  excerpt: compact(text.slice(summaryMatch.index, summaryMatch.index + summaryMatch[0].length)),
643
730
  });
644
731
  }
@@ -691,6 +778,34 @@ function findQuoteEmphasisTic(proseLines) {
691
778
  }];
692
779
  }
693
780
 
781
+ // 否定对偶:统计引号外叙述里「没A,也没B」式句子,全文只报一条。
782
+ function findNegationPairTic(proseLines) {
783
+ let hits = 0;
784
+ let firstLine = null;
785
+ const samples = [];
786
+ for (const { text, lineNo } of proseLines) {
787
+ const trimmed = text.trim();
788
+ if (!trimmed || isDivider(trimmed) || isStructural(trimmed)) continue;
789
+ const narrative = stripQuoted(trimmed);
790
+ NEGATION_PAIR_PATTERN.lastIndex = 0;
791
+ let match;
792
+ while ((match = NEGATION_PAIR_PATTERN.exec(narrative)) !== null) {
793
+ hits += 1;
794
+ if (firstLine === null) firstLine = lineNo;
795
+ if (samples.length < 4) samples.push(match[0]);
796
+ }
797
+ }
798
+ if (hits < NEGATION_PAIR_MIN_HITS) return [];
799
+ return [{
800
+ line: firstLine,
801
+ column: 1,
802
+ type: 'negation-pair-tic',
803
+ severity: 'advisory',
804
+ message: `否定对偶偏多:「没A,也没B」式叙述 ${hits} 处;写人物没做什么是英文式节奏,改成他做了什么(留一两处真正要强调的)。`,
805
+ excerpt: compact(samples.join(' | ')),
806
+ }];
807
+ }
808
+
694
809
  // 微动作复读:统计引号外叙述里「了X量词」轻量补语的密度。次数与每千字密度双门槛,
695
810
  // 全文只报一条(这是分布级指纹,不是逐处问题)。
696
811
  function findMicroActionTic(proseLines) {
@@ -1242,6 +1357,38 @@ function countTerms(text, terms) {
1242
1357
  return count;
1243
1358
  }
1244
1359
 
1360
+ // 把行内 HTML 注释(含 `<!--`、`-->` 本身)换成等长空格;openBefore 表示上一行留下未闭合注释。
1361
+ // unclosedOpenings 里的 `<!--` 到文末都没闭合:只抹掉这四个字符,后面照常当正文。
1362
+ // openedAt 是本行最后一个开始注释的 `<!--` 位置("行号:列号"),没有则为 null。
1363
+ function maskHtmlComments(line, openBefore, lineIndex = 0, unclosedOpenings = new Set()) {
1364
+ let out = '';
1365
+ let open = openBefore;
1366
+ let cursor = 0;
1367
+ let openedAt = null;
1368
+ while (cursor < line.length) {
1369
+ if (open) {
1370
+ const close = line.indexOf('-->', cursor);
1371
+ const end = close === -1 ? line.length : close + 3;
1372
+ out += ' '.repeat(end - cursor);
1373
+ cursor = end;
1374
+ if (close !== -1) open = false;
1375
+ continue;
1376
+ }
1377
+ const start = line.indexOf('<!--', cursor);
1378
+ if (start === -1) {
1379
+ out += line.slice(cursor);
1380
+ break;
1381
+ }
1382
+ out += line.slice(cursor, start) + ' ';
1383
+ cursor = start + 4;
1384
+ const key = `${lineIndex}:${start}`;
1385
+ if (unclosedOpenings.has(key)) continue;
1386
+ open = true;
1387
+ openedAt = key;
1388
+ }
1389
+ return { text: out, open, openedAt };
1390
+ }
1391
+
1245
1392
  function parseFenceMarker(trimmedLine) {
1246
1393
  const match = /^(?:`{3,}|~{3,})/.exec(trimmedLine);
1247
1394
  if (!match) return null;
@@ -18,7 +18,7 @@
18
18
  const fs = require('fs')
19
19
  const path = require('path')
20
20
 
21
- // 权威模板:references/workflow-setup.md「排纲自查与细纲」里的细纲模板
21
+ // 权威模板:references/workflow-outline.md 的细纲模板
22
22
  // 必填只留有证据或事故撑腰的核心字段;阶段位置、章节定位、结构公式、本章标价、闭环状态、
23
23
  // 写手自由区、契约风险、镜头准入、信息差触发点与「分辨率」列是可选的阅读体验字段——
24
24
  // 写了照样消费,没写不拦(#383 隔离实验:只补目标情绪与主角目标就复现了补满全部字段的收益)。
@@ -32,6 +32,69 @@ const PLOT_HEADER_FIRST = /^(?:#|序号)$/
32
32
  // 这两个字段实测直接影响正文质量,必须有实际内容
33
33
  const INTENT_FIELDS = ['目标情绪', '主角目标/关键选择']
34
34
  const CALIBER = 'visible_chars_v1'
35
+ // 可选字段「契约风险」只收三档;值未知可写 [待补充]。旧稿里的「风险等级」按同一字段认(旧名)。
36
+ const CONTRACT_RISK_LEVELS = ['契约安全', '需补强', '契约破坏']
37
+
38
+ // 「字数目标」「字数范围」「字数口径」的写法语法与 wordcount_core.py 逐字同构(scripts/test-storyctl.py 双跑同一批写法),
39
+ // 细纲验收通过的写法,storyctl chapter check 不能再拒。值里一个数字都没有的目标/范围行是说明文字,不参与取值。
40
+ const NUMBER = '([1-9][0-9]{0,2}(?:[,,][0-9]{3})+|[1-9][0-9]*)'
41
+ const NOTE = '(?:[((][^()()]*[))])?'
42
+ const TARGET_VALUE = new RegExp(`^(?:约|大约)?[ \\t]*${NUMBER}[ \\t]*字?(?:左右)?[ \\t]*${NOTE}$`)
43
+ const RANGE_VALUE = new RegExp(`^${NUMBER}[ \\t]*字?[ \\t]*(?:-|~|~|—|–|-|至|到)[ \\t]*${NUMBER}[ \\t]*字?[ \\t]*${NOTE}$`)
44
+ const METRIC_VALUE = new RegExp(`^([A-Za-z0-9_-]+)[ \\t]*${NOTE}$`)
45
+ const HAS_DIGIT = /[0-90-9]/
46
+
47
+ function fieldValues(text, label) {
48
+ const pattern = new RegExp(`^[ \\t>]*(?:[-*+][ \\t]*)?(?:\\*\\*)?${label}(?:\\*\\*)?[ \\t]*[::][ \\t]*(.*?)[ \\t]*$`, 'gm')
49
+ const values = []
50
+ let match
51
+ const normalized = text.replace(/\r\n?/g, '\n').replace(/^\uFEFF/, '')
52
+ while ((match = pattern.exec(normalized)) !== null) values.push(match[1].replace(/\*\*/g, '').trim())
53
+ return values
54
+ }
55
+
56
+ const numericFieldValues = (text, label) => fieldValues(text, label).filter((value) => HAS_DIGIT.test(value))
57
+ const toNumber = (raw) => Number(raw.replace(/[,,]/g, ''))
58
+
59
+ function parseWordcountTarget(text) {
60
+ const raw = numericFieldValues(text, '字数目标')
61
+ if (!raw.length) return { status: 'missing', value: null, raw }
62
+ const matches = raw.map((value) => value.match(TARGET_VALUE))
63
+ if (!matches.every(Boolean)) return { status: 'invalid', value: null, raw }
64
+ const values = [...new Set(matches.map((match) => toNumber(match[1])))]
65
+ if (values.length !== 1) return { status: 'conflict', value: null, raw }
66
+ return { status: 'ok', value: values[0], raw }
67
+ }
68
+
69
+ function parseWordcountRange(text) {
70
+ const raw = numericFieldValues(text, '字数范围')
71
+ if (!raw.length) return { status: 'missing', value: null, raw }
72
+ const matches = raw.map((value) => value.match(RANGE_VALUE))
73
+ if (!matches.every(Boolean)) return { status: 'invalid', value: null, raw }
74
+ const pairs = [...new Set(matches.map((match) => `${toNumber(match[1])}-${toNumber(match[2])}`))]
75
+ if (pairs.length !== 1) return { status: 'invalid', value: null, raw }
76
+ const [min, max] = pairs[0].split('-').map(Number)
77
+ if (min > max) return { status: 'invalid', value: null, raw }
78
+ return { status: 'ok', value: { min, max }, raw }
79
+ }
80
+
81
+ function parseWordcountCaliber(text) {
82
+ const raw = fieldValues(text, '字数口径')
83
+ const matches = raw.map((value) => value.match(METRIC_VALUE))
84
+ return raw.length > 0 && matches.every(Boolean) && new Set(matches.map((match) => match[1])).size === 1 && matches[0][1] === CALIBER
85
+ }
86
+
87
+ function parseContractRisk(text) {
88
+ let raw = fieldValues(text, '契约风险')
89
+ if (!raw.length) raw = fieldValues(text, '风险等级')
90
+ if (!raw.length) return null
91
+ const value = raw[raw.length - 1].replace(/`/g, '')
92
+ if (/^\[待补充\]$/.test(value)) return { status: 'unknown', level: null, detail: '', raw: value }
93
+ const level = CONTRACT_RISK_LEVELS.find((name) => value.startsWith(name))
94
+ if (!level) return { status: 'invalid', level: null, detail: '', raw: value }
95
+ const detail = value.slice(level.length).replace(/^[\s;;,,。::—\-((]+/, '').replace(/[))]$/, '').trim()
96
+ return { status: 'ok', level, detail, raw: value }
97
+ }
35
98
 
36
99
  function fieldPattern(name) {
37
100
  // 允许 -/*/+ 项目符号、可选 ** 加粗、全角或半角冒号
@@ -56,7 +119,7 @@ function makeCheck(id, ok, file, evidence, expected, repair, severity = 'blockin
56
119
  file,
57
120
  evidence,
58
121
  expected,
59
- references: ['references/workflow-setup.md', 'references/artifact-protocols.md'],
122
+ references: ['references/workflow-outline.md'],
60
123
  repair,
61
124
  }
62
125
  }
@@ -251,22 +314,45 @@ function verify(file, projectRoot = null) {
251
314
 
252
315
  checks.push(checkSettingRefs(text, name, file, projectRoot))
253
316
 
254
- const targetMatch = text.match(/字数目标\s*[::]\s*(?:约\s*)?([\d,,]+)/)
255
- const target = targetMatch ? Number(targetMatch[1].replace(/[,,]/g, '')) : null
256
- const caliberOk = new RegExp(`字数口径\\s*[::]\\s*${CALIBER}`).test(text)
317
+ const targetParse = parseWordcountTarget(text)
318
+ const rangeParse = parseWordcountRange(text)
319
+ const target = targetParse.value
320
+ const caliberOk = parseWordcountCaliber(text)
321
+ const targetLabel = { ok: target, missing: '未识别', invalid: `写法认不出(${targetParse.raw.join('、')})`, conflict: `多个不同的值(${targetParse.raw.join('、')})` }[targetParse.status]
322
+ const rangeLabel = rangeParse.status === 'invalid' ? `;字数范围写法不对(${rangeParse.raw.join('、')})` : (rangeParse.value ? `;字数范围:${rangeParse.value.min}-${rangeParse.value.max}` : '')
257
323
  checks.push(makeCheck(
258
324
  'outline.wordcount-target',
259
- Boolean(target) && Number.isFinite(target) && target >= 500 && target <= 20000 && caliberOk,
325
+ targetParse.status === 'ok' && target >= 500 && target <= 20000 && caliberOk && rangeParse.status !== 'invalid',
260
326
  name,
261
- `字数目标:${target === null ? '未识别' : target};字数口径 ${CALIBER}:${caliberOk}`,
262
- `字数目标为 500-20000 的正整数,并声明 字数口径:${CALIBER}`,
263
- '只补字数目标或字数口径行,不调整情节安排。'
327
+ `字数目标:${targetLabel};字数口径 ${CALIBER}:${caliberOk}${rangeLabel}`,
328
+ `字数目标为 500-20000 的正整数(可写「2300 字」「约 2300」「2,300」,可带括号备注),并声明 字数口径:${CALIBER};作者给了上下限时另写一行「字数范围:2000-2600」`,
329
+ '只补字数目标、字数范围或字数口径行,不调整情节安排。'
264
330
  ))
265
331
 
266
- return report(file, checks)
332
+ const risk = parseContractRisk(text)
333
+ if (risk) {
334
+ checks.push(makeCheck(
335
+ 'outline.contract-risk',
336
+ risk.status !== 'invalid',
337
+ name,
338
+ risk.status === 'invalid' ? `契约风险写的是「${risk.raw}」` : `契约风险:${risk.level || '[待补充]'}`,
339
+ `契约风险只写 ${CONTRACT_RISK_LEVELS.join(' / ')} 三档之一(需补强时后面写补强方式),未定写 [待补充]`,
340
+ '只把契约风险改成三档之一,不改其他字段。'
341
+ ))
342
+ }
343
+
344
+ return report(file, checks, {
345
+ wordcount: {
346
+ target,
347
+ target_status: targetParse.status,
348
+ range: rangeParse.value,
349
+ range_status: rangeParse.status,
350
+ },
351
+ contract_risk: risk ? { level: risk.level, detail: risk.detail, status: risk.status } : null,
352
+ })
267
353
  }
268
354
 
269
- function report(file, checks) {
355
+ function report(file, checks, extra = {}) {
270
356
  const failures = checks.filter((check) => !check.ok && check.severity === 'blocking')
271
357
  const advisories = checks.filter((check) => !check.ok && check.severity === 'advisory')
272
358
  return {
@@ -274,6 +360,7 @@ function report(file, checks) {
274
360
  verifier: 'story-long-write.outline-contract',
275
361
  file: path.resolve(file),
276
362
  ok: failures.length === 0,
363
+ ...extra,
277
364
  checks,
278
365
  failures,
279
366
  advisories,
@@ -320,7 +407,7 @@ function verifySupply(volumeFile, unitId) {
320
407
  const ok = hasSupplyHeading(draft.text)
321
408
  return {
322
409
  schema_version: 1, verifier: 'story-long-write.outline-supply', file: path.resolve(draftFile), unit: unitId, ok,
323
- evidence: ok ? '排纲底稿含「供给自查」小节' : `${path.basename(draftFile)} 里没有「供给自查」小节——每批出细纲前须做供给自查(含「无缺口」情形),见 workflow-setup.md「排纲自查与细纲」步骤 3`,
410
+ evidence: ok ? '排纲底稿含「供给自查」小节' : `${path.basename(draftFile)} 里没有「供给自查」小节——每批出细纲前须做供给自查(含「无缺口」情形),见 workflow-outline.md 步骤 3`,
324
411
  }
325
412
  }
326
413
  const read = readUtf8(volumeFile)
@@ -363,7 +450,7 @@ function verifySupply(volumeFile, unitId) {
363
450
  file: path.resolve(volumeFile),
364
451
  unit: unitId,
365
452
  ok,
366
- evidence: ok ? '单元卡含「供给自查」小节' : `没有 排纲底稿_${unitId}.md,剧情单元 ${unitId} 的卡内也没有「供给自查」小节——每批出细纲前须做供给自查(含「无缺口」情形),见 workflow-setup.md「排纲自查与细纲」步骤 3`,
453
+ evidence: ok ? '单元卡含「供给自查」小节' : `没有 排纲底稿_${unitId}.md,剧情单元 ${unitId} 的卡内也没有「供给自查」小节——每批出细纲前须做供给自查(含「无缺口」情形),见 workflow-outline.md 步骤 3`,
367
454
  }
368
455
  }
369
456
 
@@ -430,4 +517,4 @@ function main(argv) {
430
517
 
431
518
  if (require.main === module) process.exitCode = main(process.argv.slice(2))
432
519
 
433
- module.exports = { verify, FIELDS, SUBSECTIONS, FIVE_ACT }
520
+ module.exports = { verify, parseWordcountTarget, parseWordcountRange, parseContractRisk, FIELDS, SUBSECTIONS, FIVE_ACT }