@oh-story/dsh 0.1.9 → 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 (529) hide show
  1. package/README.md +14 -14
  2. package/lib/client.js +209 -73
  3. package/lib/client.js.map +3 -3
  4. package/lib/drama/manifest.json +248 -168
  5. package/lib/drama/skills/short-drama/SKILL.md +16 -4
  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/creator_markdown_check.py +14 -0
  10. package/lib/drama/skills/short-drama/scripts/project_tool.py +131 -0
  11. package/lib/drama/skills/short-drama-assets/SKILL.md +2 -1
  12. package/lib/drama/skills/short-drama-assets/references/asset-review-checklist.md +2 -0
  13. package/lib/drama/skills/short-drama-assets/references/character-and-look.md +10 -0
  14. package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +6 -9
  15. package/lib/drama/skills/short-drama-assets/references/era-anchors.md +37 -0
  16. package/lib/drama/skills/short-drama-assets/references/stage-contract.md +2 -0
  17. package/lib/drama/skills/short-drama-assets/references/voice-direction.md +28 -0
  18. package/lib/drama/skills/short-drama-develop/SKILL.md +10 -4
  19. package/lib/drama/skills/short-drama-develop/assets/adaptation-map.example.jsonl +1 -1
  20. package/lib/drama/skills/short-drama-develop/assets/creative-brief.md +8 -0
  21. package/lib/drama/skills/short-drama-develop/references/adaptation-craft.md +80 -13
  22. package/lib/drama/skills/short-drama-develop/references/episode-design.md +95 -12
  23. package/lib/drama/skills/short-drama-develop/references/genre-and-hook-playbook.md +9 -5
  24. 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
  25. package/lib/drama/skills/short-drama-develop/references/genre-cards//347/263/273/347/273/237/346/265/201.md +51 -0
  26. package/lib/drama/skills/short-drama-develop/references/genre-cards//351/200/206/350/242/255.md +51 -0
  27. 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
  28. package/lib/drama/skills/short-drama-develop/references/genre-cards.md +10 -7
  29. package/lib/drama/skills/short-drama-develop/references/premise-devices.md +15 -9
  30. package/lib/drama/skills/short-drama-develop/references/serial-character-and-memory.md +3 -2
  31. package/lib/drama/skills/short-drama-develop/references/stage-contract.md +14 -7
  32. package/lib/drama/skills/short-drama-develop/references/story-craft.md +7 -3
  33. package/lib/drama/skills/short-drama-edit/SKILL.md +65 -44
  34. package/lib/drama/skills/short-drama-edit/assets/remotion/README.md +49 -13
  35. package/lib/drama/skills/short-drama-edit/assets/remotion/package.json +4 -2
  36. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Overlay.tsx +31 -0
  37. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Root.tsx +5 -5
  38. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Subtitles.tsx +68 -69
  39. package/lib/drama/skills/short-drama-edit/assets/remotion/src/font.ts +39 -29
  40. package/lib/drama/skills/short-drama-edit/assets/remotion/src/rules.mjs +188 -0
  41. package/lib/drama/skills/short-drama-edit/assets/remotion/src/schema.ts +26 -13
  42. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/Card.tsx +72 -0
  43. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/CornerChip.tsx +39 -0
  44. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/ScreenText.tsx +36 -0
  45. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/SystemPanel.tsx +44 -0
  46. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/TaskPanel.tsx +100 -0
  47. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/chrome.tsx +197 -0
  48. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/tokens.ts +62 -0
  49. package/lib/drama/skills/short-drama-edit/assets/remotion/tsconfig.json +1 -0
  50. package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +6 -4
  51. package/lib/drama/skills/short-drama-edit/references/cut-craft.md +26 -67
  52. package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +31 -56
  53. package/lib/drama/skills/short-drama-edit/references/generated-footage.md +42 -78
  54. package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +44 -92
  55. package/lib/drama/skills/short-drama-edit/references/stage-contract.md +14 -16
  56. package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1272 -125
  57. package/lib/drama/skills/short-drama-edit/scripts/selftest.py +113 -9
  58. package/lib/drama/skills/short-drama-image-prompts/SKILL.md +2 -0
  59. package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +11 -0
  60. package/lib/drama/skills/short-drama-novel-analyze/SKILL.md +3 -3
  61. package/lib/drama/skills/short-drama-novel-analyze/assets/episode-candidate.example.jsonl +2 -2
  62. package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-triage.md +8 -4
  63. package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-value.md +10 -3
  64. package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +25 -1
  65. package/lib/drama/skills/short-drama-novel-analyze/references/stage-contract.md +1 -0
  66. package/lib/drama/skills/short-drama-produce/SKILL.md +5 -1
  67. package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +14 -20
  68. package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +8 -26
  69. package/lib/drama/skills/short-drama-produce/references/providers/minimax-speech.md +41 -0
  70. package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +142 -1
  71. package/lib/drama/skills/short-drama-review/SKILL.md +4 -5
  72. package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +6 -19
  73. package/lib/drama/skills/short-drama-review/references/review-method.md +6 -1
  74. package/lib/drama/skills/short-drama-review/references/rubric-assets-prompts.md +11 -0
  75. package/lib/drama/skills/short-drama-review/references/rubric-story-script.md +77 -3
  76. package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +24 -35
  77. package/lib/drama/skills/short-drama-review/references/stage-contract.md +3 -0
  78. package/lib/drama/skills/short-drama-storyboard/SKILL.md +23 -11
  79. package/lib/drama/skills/short-drama-storyboard/references/blocking-playbooks.md +1 -1
  80. package/lib/drama/skills/short-drama-storyboard/references/lighting-craft.md +39 -0
  81. package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +2 -2
  82. package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +139 -34
  83. package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +5 -3
  84. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +15 -12
  85. package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +29 -0
  86. package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +4 -5
  87. package/lib/drama/skills/short-drama-video-prompts/references/generability.md +4 -2
  88. package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +52 -39
  89. package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +24 -21
  90. package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +9 -23
  91. package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +14 -26
  92. package/lib/drama/skills/short-drama-video-prompts/references/review-and-fixtures.md +1 -1
  93. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +2 -3
  94. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +34 -9
  95. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +3 -2
  96. package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +8 -1
  97. package/lib/drama/skills/short-drama-video-prompts/references/wan-3.0.md +103 -0
  98. package/lib/drama/skills/short-drama-video-prompts/scripts/motion_timing_check.py +8 -7
  99. package/lib/drama/skills/short-drama-write/SKILL.md +10 -7
  100. package/lib/drama/skills/short-drama-write/references/dialogue-craft.md +1 -1
  101. package/lib/drama/skills/short-drama-write/references/script-craft.md +56 -3
  102. package/lib/drama/skills/short-drama-write/references/stage-contract.md +4 -0
  103. package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +48 -2
  104. package/lib/index.js +346 -98
  105. package/lib/index.js.map +4 -4
  106. package/lib/novel-to-game/README.md +299 -59
  107. package/lib/novel-to-game/README_ZH.md +299 -56
  108. package/lib/novel-to-game/VERSION +1 -1
  109. package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +3 -1
  110. package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +11 -3
  111. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +397 -8
  112. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +0 -31
  113. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +71 -73
  114. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +78 -89
  115. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +37 -14
  116. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +5 -5
  117. package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +2 -1
  118. package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +11 -16
  119. package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +11 -11
  120. package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +23 -0
  121. package/lib/novel-to-game/manifest.json +77 -87
  122. package/lib/novel-to-game/skills/game-art-direction/SKILL.md +7 -12
  123. package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +27 -106
  124. package/lib/novel-to-game/skills/game-build/SKILL.md +13 -14
  125. package/lib/novel-to-game/skills/game-build/references/blender-asset-workflow.md +24 -0
  126. package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +45 -82
  127. package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +14 -45
  128. package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +30 -65
  129. package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +13 -27
  130. package/lib/novel-to-game/skills/game-concept/SKILL.md +13 -15
  131. package/lib/novel-to-game/skills/game-concept/references/concept-method.md +28 -75
  132. package/lib/novel-to-game/skills/game-qa/SKILL.md +6 -7
  133. package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +9 -5
  134. package/lib/novel-to-game/skills/game-world-design/SKILL.md +12 -16
  135. package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +23 -72
  136. package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +14 -48
  137. package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +36 -103
  138. package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +4 -10
  139. package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +4 -4
  140. package/lib/novel-to-game/skills/novel-to-game/SKILL.md +9 -21
  141. package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +5 -4
  142. package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +5 -8
  143. package/lib/oh-story/manifest.json +607 -327
  144. package/lib/oh-story/roles/chapter-extractor.md +177 -237
  145. package/lib/oh-story/roles/character-designer.md +1 -2
  146. package/lib/oh-story/roles/consistency-checker.md +15 -13
  147. package/lib/oh-story/roles/narrative-writer.md +36 -38
  148. package/lib/oh-story/roles/story-architect.md +9 -61
  149. package/lib/oh-story/roles/story-explorer.md +20 -125
  150. package/lib/oh-story/roles/story-researcher.md +0 -1
  151. package/lib/oh-story/skills/story/SKILL.md +15 -9
  152. package/lib/oh-story/skills/story/VERSION +1 -1
  153. package/lib/oh-story/skills/story/references/author-memory-maintenance.md +75 -0
  154. package/lib/oh-story/skills/story/references/author-memory.md +46 -94
  155. package/lib/oh-story/skills/story/scripts/author_memory_commit.py +934 -170
  156. package/lib/oh-story/skills/story-cover/SKILL.md +10 -1
  157. package/lib/oh-story/skills/story-deslop/SKILL.md +59 -73
  158. package/lib/oh-story/skills/story-deslop/references/agent-calls.md +5 -0
  159. package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +12 -11
  160. package/lib/oh-story/skills/story-deslop/references/author-memory-maintenance.md +75 -0
  161. package/lib/oh-story/skills/story-deslop/references/author-memory.md +46 -94
  162. package/lib/oh-story/skills/story-deslop/references/banned-words.md +2 -2
  163. package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +22 -5
  164. package/lib/oh-story/skills/story-deslop/references/style-resolution.md +4 -4
  165. package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +934 -170
  166. package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +155 -8
  167. package/lib/oh-story/skills/story-import/SKILL.md +64 -584
  168. package/lib/oh-story/skills/story-import/references/character-state-reverse.md +1 -1
  169. package/lib/oh-story/skills/story-import/references/deep-analysis.md +47 -0
  170. package/lib/oh-story/skills/story-import/references/format-and-structure.md +1 -1
  171. package/lib/oh-story/skills/story-import/references/import-report.md +64 -0
  172. package/lib/oh-story/skills/story-import/references/import-special-cases.md +34 -0
  173. package/lib/oh-story/skills/story-import/references/import-tracking.md +95 -0
  174. package/lib/oh-story/skills/story-import/references/length-routing.md +1 -1
  175. package/lib/oh-story/skills/story-import/references/outline-reverse-rules.md +79 -0
  176. package/lib/oh-story/skills/story-import/references/outline-reverse.md +24 -0
  177. package/lib/oh-story/skills/story-import/references/state-tracking.md +4 -69
  178. package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +98 -92
  179. package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +24 -12
  180. package/lib/oh-story/skills/story-import/references/tracking-initialization.md +1 -1
  181. package/lib/oh-story/skills/story-import/references/tracking-transaction.md +15 -11
  182. package/lib/oh-story/skills/story-import/scripts/build_outline_brief.py +332 -0
  183. package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +132 -27
  184. package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +478 -15
  185. package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +182 -23
  186. package/lib/oh-story/skills/story-long-analyze/SKILL.md +81 -270
  187. package/lib/oh-story/skills/story-long-analyze/references/author-facing.md +252 -0
  188. package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +3 -3
  189. package/lib/oh-story/skills/story-long-analyze/references/final-checks.md +19 -0
  190. package/lib/oh-story/skills/story-long-analyze/references/index-rebuild.md +11 -0
  191. package/lib/oh-story/skills/story-long-analyze/references/inspiration-library.md +105 -0
  192. package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +138 -64
  193. package/lib/oh-story/skills/story-long-analyze/references/semantic-acceptance-fixtures.md +84 -0
  194. package/lib/oh-story/skills/story-long-analyze/references/stage1-golden-chapters.md +128 -0
  195. package/lib/oh-story/skills/story-long-analyze/references/stage2-extraction.md +229 -0
  196. package/lib/oh-story/skills/story-long-analyze/references/stage3-plot-rhythm.md +480 -0
  197. package/lib/oh-story/skills/story-long-analyze/references/stage4-characters-settings.md +195 -0
  198. package/lib/oh-story/skills/story-long-analyze/references/stage5-report.md +33 -0
  199. package/lib/oh-story/skills/story-long-analyze/references/style-profile-generator.md +55 -128
  200. package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +6 -6
  201. package/lib/oh-story/skills/story-long-analyze/references/synthesis-inputs.md +39 -0
  202. package/lib/oh-story/skills/story-long-analyze/scripts/build_chapter_index.py +466 -0
  203. package/lib/oh-story/skills/story-long-analyze/scripts/inspect_existing_assets.py +702 -0
  204. package/lib/oh-story/skills/story-long-analyze/scripts/inspiration_index.py +864 -0
  205. package/lib/oh-story/skills/story-long-analyze/scripts/manage_analysis_run.py +1145 -0
  206. package/lib/oh-story/skills/story-long-analyze/scripts/render_relation_chart.py +425 -0
  207. package/lib/oh-story/skills/story-long-scan/SKILL.md +45 -194
  208. package/lib/oh-story/skills/story-long-scan/references/platform-ciweimao.md +19 -0
  209. package/lib/oh-story/skills/story-long-scan/references/platform-fanqie.md +45 -0
  210. package/lib/oh-story/skills/story-long-scan/references/platform-jjwxc.md +28 -0
  211. package/lib/oh-story/skills/story-long-scan/references/platform-qidian.md +35 -0
  212. package/lib/oh-story/skills/story-long-scan/references/platform-qimao.md +27 -0
  213. package/lib/oh-story/skills/story-long-scan/references/topic-decision.md +6 -2
  214. package/lib/oh-story/skills/story-long-write/SKILL.md +49 -81
  215. package/lib/oh-story/skills/story-long-write/references/agent-calls.md +47 -0
  216. package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +12 -11
  217. package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +24 -202
  218. package/lib/oh-story/skills/story-long-write/references/author-memory-maintenance.md +75 -0
  219. package/lib/oh-story/skills/story-long-write/references/author-memory.md +46 -94
  220. package/lib/oh-story/skills/story-long-write/references/banned-words.md +2 -2
  221. package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +7 -11
  222. package/lib/oh-story/skills/story-long-write/references/character-relations.md +49 -1
  223. package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +26 -0
  224. package/lib/oh-story/skills/story-long-write/references/deslop-gates.md +171 -0
  225. package/lib/oh-story/skills/story-long-write/references/dialogue-mastery.md +4 -0
  226. package/lib/oh-story/skills/story-long-write/references/emotion-on-page.md +116 -0
  227. package/lib/oh-story/skills/story-long-write/references/emotional-arc-design.md +1 -114
  228. package/lib/oh-story/skills/story-long-write/references/emotional-methods.md +2 -2
  229. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +1 -1
  230. package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +7 -15
  231. package/lib/oh-story/skills/story-long-write/references/long-format.md +0 -8
  232. package/lib/oh-story/skills/story-long-write/references/long-genre-catalog.md +1 -1
  233. package/lib/oh-story/skills/story-long-write/references/long-genre-mechanics.md +3 -3
  234. package/lib/oh-story/skills/story-long-write/references/opening-design.md +2 -2
  235. package/lib/oh-story/skills/story-long-write/references/outline-conflict.md +4 -4
  236. package/lib/oh-story/skills/story-long-write/references/outline-methods.md +7 -7
  237. package/lib/oh-story/skills/story-long-write/references/outline-rhythm.md +3 -3
  238. package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +14 -69
  239. package/lib/oh-story/skills/story-long-write/references/plot-frameworks.md +1 -1
  240. package/lib/oh-story/skills/story-long-write/references/plot-special-topics.md +2 -2
  241. package/lib/oh-story/skills/story-long-write/references/project-files.md +19 -13
  242. package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +13 -5
  243. package/lib/oh-story/skills/story-long-write/references/reference-index.md +5 -6
  244. package/lib/oh-story/skills/story-long-write/references/stop-and-fix.md +9 -0
  245. package/lib/oh-story/skills/story-long-write/references/style-combat-face.md +1 -58
  246. package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
  247. package/lib/oh-story/skills/story-long-write/references/style-resolution.md +4 -4
  248. package/lib/oh-story/skills/story-long-write/references/tracking-initialization.md +1 -1
  249. package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +15 -11
  250. package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +81 -121
  251. package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +41 -139
  252. package/lib/oh-story/skills/story-long-write/references/workflow-outline.md +157 -0
  253. package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +57 -67
  254. package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +112 -253
  255. package/lib/oh-story/skills/story-long-write/references/workflow-volume.md +142 -0
  256. package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +6 -4
  257. package/lib/oh-story/skills/story-long-write/references/writing-craft.md +19 -65
  258. package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +934 -170
  259. package/lib/oh-story/skills/story-long-write/scripts/build_architect_brief.py +260 -0
  260. package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +226 -25
  261. package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +155 -8
  262. package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +132 -27
  263. package/lib/oh-story/skills/story-long-write/scripts/check-outline-copy.js +7 -2
  264. package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +7 -4
  265. package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +304 -46
  266. package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +478 -15
  267. package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +182 -23
  268. package/lib/oh-story/skills/story-review/SKILL.md +31 -302
  269. package/lib/oh-story/skills/story-review/references/agent-prompts.md +184 -0
  270. package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +12 -11
  271. package/lib/oh-story/skills/story-review/references/author-memory-maintenance.md +75 -0
  272. package/lib/oh-story/skills/story-review/references/author-memory.md +46 -94
  273. package/lib/oh-story/skills/story-review/references/banned-words.md +2 -2
  274. package/lib/oh-story/skills/story-review/references/batch-review.md +19 -0
  275. package/lib/oh-story/skills/story-review/references/character-relations.md +49 -1
  276. package/lib/oh-story/skills/story-review/references/dialogue-mastery.md +4 -0
  277. package/lib/oh-story/skills/story-review/references/review-quality.md +6 -11
  278. package/lib/oh-story/skills/story-review/references/review-tracking.md +10 -0
  279. package/lib/oh-story/skills/story-review/references/solo.md +52 -0
  280. package/lib/oh-story/skills/story-review/references/style-resolution.md +4 -4
  281. package/lib/oh-story/skills/story-review/references/tracking-initialization.md +1 -1
  282. package/lib/oh-story/skills/story-review/references/tracking-transaction.md +15 -11
  283. package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +934 -170
  284. package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +155 -8
  285. package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +478 -15
  286. package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +182 -23
  287. package/lib/oh-story/skills/story-setup/SKILL.md +71 -452
  288. package/lib/oh-story/skills/story-setup/references/agent-references/agent-quality.md +4 -4
  289. package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +12 -11
  290. package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +2 -2
  291. package/lib/oh-story/skills/story-setup/references/agent-references/benchmark-style-load.md +107 -0
  292. package/lib/oh-story/skills/story-setup/references/agent-references/character-relations.md +49 -1
  293. package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +22 -5
  294. package/lib/oh-story/skills/story-setup/references/agent-references/dialogue-mastery.md +4 -0
  295. package/lib/oh-story/skills/story-setup/references/agent-references/emotion-on-page.md +116 -0
  296. package/lib/oh-story/skills/story-setup/references/agent-references/emotional-arc-design.md +1 -114
  297. package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +1 -1
  298. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards.md +1 -1
  299. package/lib/oh-story/skills/story-setup/references/agent-references/long-emotional-methods.md +2 -2
  300. package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-catalog.md +1 -1
  301. package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-mechanics.md +3 -3
  302. package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +7 -5
  303. package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +2 -2
  304. package/lib/oh-story/skills/story-setup/references/agent-references/outline-conflict.md +4 -4
  305. package/lib/oh-story/skills/story-setup/references/agent-references/outline-methods.md +7 -7
  306. package/lib/oh-story/skills/story-setup/references/agent-references/outline-rhythm.md +3 -3
  307. package/lib/oh-story/skills/story-setup/references/agent-references/short-emotional-methods.md +1 -1
  308. package/lib/oh-story/skills/story-setup/references/agent-references/short-genre-formulas.md +0 -15
  309. package/lib/oh-story/skills/story-setup/references/agent-references/short-reversal.md +1 -1
  310. package/lib/oh-story/skills/story-setup/references/agent-references/style-combat-face.md +1 -58
  311. package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
  312. package/lib/oh-story/skills/story-setup/references/agent-references/style-resolution.md +4 -4
  313. package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +19 -65
  314. package/lib/oh-story/skills/story-setup/references/diagnostics.md +4 -4
  315. package/lib/oh-story/skills/story-short-analyze/SKILL.md +56 -110
  316. package/lib/oh-story/skills/story-short-analyze/references/analysis-character-relations.md +1 -1
  317. package/lib/oh-story/skills/story-short-analyze/references/analysis-method.md +42 -0
  318. package/lib/oh-story/skills/story-short-analyze/references/optional-modules.md +97 -0
  319. package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +9 -47
  320. package/lib/oh-story/skills/story-short-analyze/references/quality-checklist.md +80 -0
  321. package/lib/oh-story/skills/story-short-analyze/references/source-story-quality.md +4 -4
  322. package/lib/oh-story/skills/story-short-analyze/references/stage2-3-structure-emotion.md +308 -0
  323. package/lib/oh-story/skills/story-short-analyze/references/stage4-6-reversal-summary.md +611 -0
  324. package/lib/oh-story/skills/story-short-scan/SKILL.md +35 -65
  325. package/lib/oh-story/skills/story-short-scan/references/manual-rank-input.md +36 -0
  326. package/lib/oh-story/skills/story-short-scan/references/platform-dianzhong.md +24 -0
  327. package/lib/oh-story/skills/story-short-scan/references/platform-heiyan.md +21 -0
  328. package/lib/oh-story/skills/story-short-scan/references/topic-match.md +38 -0
  329. package/lib/oh-story/skills/story-short-write/SKILL.md +23 -175
  330. package/lib/oh-story/skills/story-short-write/references/agent-calls.md +13 -0
  331. package/lib/oh-story/skills/story-short-write/references/author-memory-maintenance.md +75 -0
  332. package/lib/oh-story/skills/story-short-write/references/author-memory.md +46 -94
  333. package/lib/oh-story/skills/story-short-write/references/banned-words.md +2 -2
  334. package/lib/oh-story/skills/story-short-write/references/benchmark-recall.md +1 -21
  335. package/lib/oh-story/skills/story-short-write/references/dialogue-mastery.md +4 -0
  336. package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +1 -1
  337. package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +0 -15
  338. package/lib/oh-story/skills/story-short-write/references/output-contract.md +9 -47
  339. package/lib/oh-story/skills/story-short-write/references/reference-index.md +17 -47
  340. package/lib/oh-story/skills/story-short-write/references/scene-craft.md +40 -0
  341. package/lib/oh-story/skills/story-short-write/references/short-craft.md +7 -43
  342. package/lib/oh-story/skills/story-short-write/references/short-format.md +3 -3
  343. package/lib/oh-story/skills/story-short-write/references/short-prose-quality.md +2 -1
  344. package/lib/oh-story/skills/story-short-write/references/short-reversal.md +1 -1
  345. package/lib/oh-story/skills/story-short-write/references/style-resolution.md +4 -4
  346. package/lib/oh-story/skills/story-short-write/references/submission-craft.md +1 -1
  347. package/lib/oh-story/skills/story-short-write/references/villain-and-reveal.md +5 -10
  348. package/lib/oh-story/skills/story-short-write/references/workflow-design.md +21 -13
  349. package/lib/oh-story/skills/story-short-write/references/workflow-draft.md +116 -0
  350. package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +99 -0
  351. package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +7 -119
  352. package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +934 -170
  353. package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +155 -8
  354. package/lib/oh-story/skills/story-short-write/scripts/check-outline-copy.js +7 -2
  355. package/lib/types/client/drama-production-view.d.ts.map +1 -1
  356. package/lib/types/client/file-activity.d.ts +19 -1
  357. package/lib/types/client/file-activity.d.ts.map +1 -1
  358. package/lib/types/client/index.d.ts.map +1 -1
  359. package/lib/types/client/production-prompts.d.ts +7 -0
  360. package/lib/types/client/production-prompts.d.ts.map +1 -1
  361. package/lib/types/client/production-runtime.d.ts +16 -0
  362. package/lib/types/client/production-runtime.d.ts.map +1 -1
  363. package/lib/types/client/video-studio.d.ts.map +1 -1
  364. package/lib/types/drama-adapters.d.ts +5 -3
  365. package/lib/types/drama-adapters.d.ts.map +1 -1
  366. package/lib/types/native-hooks.d.ts +53 -1
  367. package/lib/types/native-hooks.d.ts.map +1 -1
  368. package/lib/types/role-identity.d.ts +17 -0
  369. package/lib/types/role-identity.d.ts.map +1 -0
  370. package/lib/types/role-tool.d.ts.map +1 -1
  371. package/lib/types/skill-provider.d.ts.map +1 -1
  372. package/lib/types/video-project.d.ts.map +1 -1
  373. package/lib/types/workspace-route.d.ts.map +1 -1
  374. package/lib/video-recap/CHANGELOG.md +60 -1
  375. package/lib/video-recap/README.en.md +348 -137
  376. package/lib/video-recap/README.md +340 -136
  377. package/lib/video-recap/manifest.json +406 -251
  378. package/lib/video-recap/skills/video-assemble/SKILL.md +30 -15
  379. package/lib/video-recap/skills/video-assemble/references/audio-modes.md +63 -0
  380. package/lib/video-recap/skills/video-assemble/references/explicit-audio-mix.md +75 -0
  381. package/lib/video-recap/skills/video-assemble/references/foreground-compose.md +118 -0
  382. package/lib/video-recap/skills/video-assemble/references/narration-adoption.md +103 -0
  383. package/lib/video-recap/skills/video-assemble/references/packaging.md +45 -0
  384. package/lib/video-recap/skills/video-assemble/references/pair-media.md +74 -0
  385. package/lib/video-recap/skills/video-assemble/references/source-score.md +147 -0
  386. package/lib/video-recap/skills/video-assemble/references/subtitle-track.md +202 -0
  387. package/lib/video-recap/skills/video-assemble/scripts/adoption/__init__.py +1 -0
  388. package/lib/video-recap/skills/video-assemble/scripts/adoption/audio_mix_binding.py +279 -0
  389. package/lib/video-recap/skills/video-assemble/scripts/adoption/frozen_audio.py +187 -0
  390. package/lib/video-recap/skills/video-assemble/scripts/adoption/narration_binding.py +323 -0
  391. package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_inputs.py +109 -0
  392. package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_publish.py +130 -0
  393. package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +10 -46
  394. package/lib/video-recap/skills/video-assemble/scripts/assemble.py +378 -119
  395. package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +29 -12
  396. package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +95 -44
  397. package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +75 -32
  398. package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +11 -11
  399. package/lib/video-recap/skills/video-assemble/scripts/compose_foreground.py +293 -0
  400. package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +71 -78
  401. package/lib/video-recap/skills/video-assemble/scripts/jianying/__init__.py +1 -0
  402. package/lib/video-recap/skills/video-assemble/scripts/{jianying_builders.py → jianying/builders.py} +39 -67
  403. package/lib/video-recap/skills/video-assemble/scripts/{jianying_model.py → jianying/model.py} +3 -11
  404. package/lib/video-recap/skills/video-assemble/scripts/{jianying_optional.py → jianying/optional.py} +1 -1
  405. package/lib/video-recap/skills/video-assemble/scripts/jianying/schema.py +53 -0
  406. package/lib/video-recap/skills/video-assemble/scripts/{jianying_templates.py → jianying/templates.py} +1 -1
  407. package/lib/video-recap/skills/video-assemble/scripts/{jianying_timeline_contract.py → jianying/timeline_contract.py} +34 -13
  408. package/lib/video-recap/skills/video-assemble/scripts/{jianying_writer.py → jianying/writer.py} +7 -17
  409. package/lib/video-recap/skills/video-assemble/scripts/lib.py +37 -4
  410. package/lib/video-recap/skills/video-assemble/scripts/media.py +12 -21
  411. package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +64 -34
  412. package/lib/video-recap/skills/video-assemble/scripts/packaging.py +122 -0
  413. package/lib/video-recap/skills/video-assemble/scripts/pair_media.py +216 -0
  414. package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +1 -3
  415. package/lib/video-recap/skills/video-assemble/scripts/source_score.py +469 -0
  416. package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +25 -9
  417. package/lib/video-recap/skills/video-assemble/scripts/subtitles/__init__.py +1 -0
  418. package/lib/video-recap/skills/video-assemble/scripts/{subtitle_core.py → subtitles/core.py} +13 -6
  419. package/lib/video-recap/skills/video-assemble/scripts/{subtitle_render.py → subtitles/render.py} +5 -4
  420. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track.py +324 -0
  421. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track_binding.py +200 -0
  422. package/lib/video-recap/skills/video-assemble/scripts/timeline.py +30 -46
  423. package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +91 -7
  424. package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +22 -8
  425. package/lib/video-recap/skills/video-cut/SKILL.md +34 -16
  426. package/lib/video-recap/skills/video-cut/references/shot-review.md +50 -0
  427. package/lib/video-recap/skills/video-cut/scripts/cut.py +2 -28
  428. package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +58 -62
  429. package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +21 -106
  430. package/lib/video-recap/skills/video-cut/scripts/cut_render.py +14 -9
  431. package/lib/video-recap/skills/video-cut/scripts/lib.py +39 -0
  432. package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +2 -155
  433. package/lib/video-recap/skills/video-cut/scripts/narrative_selection.py +244 -0
  434. package/lib/video-recap/skills/video-cut/scripts/shot_review.py +357 -0
  435. package/lib/video-recap/skills/video-recap/SKILL.md +86 -42
  436. package/lib/video-recap/skills/video-recap/references/audio-routing.md +126 -0
  437. package/lib/video-recap/skills/video-recap/references/config-playbook.md +12 -8
  438. package/lib/video-recap/skills/video-recap/references/data-schema.md +44 -33
  439. package/lib/video-recap/skills/video-recap/references/resource-library.md +151 -0
  440. package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +1 -1
  441. package/lib/video-recap/skills/video-recap/scripts/doctor.py +123 -30
  442. package/lib/video-recap/skills/video-recap/scripts/final_qc.py +22 -75
  443. package/lib/video-recap/skills/video-recap/scripts/lib.py +3 -3
  444. package/lib/video-recap/skills/video-recap/scripts/library.py +509 -0
  445. package/lib/video-recap/skills/video-recap/scripts/materials.py +63 -91
  446. package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +8 -70
  447. package/lib/video-recap/skills/video-recap/scripts/project_binding.py +286 -0
  448. package/lib/video-recap/skills/video-recap/scripts/qc/__init__.py +1 -0
  449. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_client.py → qc/mimo_client.py} +1 -1
  450. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_evidence.py → qc/mimo_evidence.py} +33 -66
  451. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_observations.py → qc/mimo_observations.py} +3 -11
  452. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_payload.py → qc/mimo_payload.py} +4 -7
  453. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_report.py → qc/mimo_report.py} +17 -28
  454. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_runner.py → qc/mimo_runner.py} +2 -2
  455. package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +0 -19
  456. package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +119 -50
  457. package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +32 -114
  458. package/lib/video-recap/skills/video-recap/scripts/recap_review.py +9 -23
  459. package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +387 -334
  460. package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +23 -23
  461. package/lib/video-recap/skills/video-recap/scripts/recap_source.py +278 -0
  462. package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +25 -12
  463. package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +59 -52
  464. package/lib/video-recap/skills/video-recap/scripts/resource_lock.py +162 -0
  465. package/lib/video-recap/skills/video-script/SKILL.md +16 -13
  466. package/lib/video-recap/skills/video-script/references/creative-editing-playbook.md +9 -5
  467. package/lib/video-recap/skills/video-script/references/promotional-copy.md +68 -0
  468. package/lib/video-recap/skills/video-script/references/research-guide.md +1 -1
  469. package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +24 -76
  470. package/lib/video-recap/skills/video-script/scripts/lib.py +10 -87
  471. package/lib/video-recap/skills/video-script/scripts/narration_lint.py +63 -39
  472. package/lib/video-recap/skills/video-script/scripts/review.py +0 -2
  473. package/lib/video-recap/skills/video-script/scripts/review_grounding.py +35 -59
  474. package/lib/video-recap/skills/video-script/scripts/review_response.py +66 -128
  475. package/lib/video-recap/skills/video-script/scripts/review_runner.py +12 -20
  476. package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +4 -4
  477. package/lib/video-recap/skills/video-script/scripts/validate.py +53 -42
  478. package/lib/video-recap/skills/video-understanding/SKILL.md +6 -4
  479. package/lib/video-recap/skills/video-understanding/references/data-schema.md +50 -156
  480. package/lib/video-recap/skills/video-understanding/references/research-guide.md +4 -4
  481. package/lib/video-recap/skills/video-understanding/scripts/asr.py +85 -65
  482. package/lib/video-recap/skills/video-understanding/scripts/asr_timing_evidence.py +270 -0
  483. package/lib/video-recap/skills/video-understanding/scripts/brief.py +2 -5
  484. package/lib/video-recap/skills/video-understanding/scripts/briefing/__init__.py +1 -0
  485. package/lib/video-recap/skills/{video-script/scripts/agent_brief.py → video-understanding/scripts/briefing/builder.py} +38 -35
  486. package/lib/video-recap/skills/video-understanding/scripts/{brief_context.py → briefing/context.py} +4 -35
  487. package/lib/video-recap/skills/{video-script/scripts/brief_inputs.py → video-understanding/scripts/briefing/inputs.py} +9 -31
  488. package/lib/video-recap/skills/{video-script/scripts/brief_timeline.py → video-understanding/scripts/briefing/timeline.py} +5 -6
  489. package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +65 -116
  490. package/lib/video-recap/skills/video-understanding/scripts/detect.py +31 -40
  491. package/lib/video-recap/skills/video-understanding/scripts/lib.py +30 -86
  492. package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +63 -39
  493. package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +4 -4
  494. package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +17 -79
  495. package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +14 -40
  496. package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +64 -46
  497. package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +31 -11
  498. package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +19 -14
  499. package/lib/video-recap/skills/video-understanding/scripts/vlm.py +80 -108
  500. package/lib/video-recap/skills/video-voiceover/SKILL.md +29 -19
  501. package/lib/video-recap/skills/video-voiceover/references/index-tts.md +31 -0
  502. package/lib/video-recap/skills/video-voiceover/scripts/approved_text_policy.py +123 -0
  503. package/lib/video-recap/skills/video-voiceover/scripts/dub.py +51 -59
  504. package/lib/video-recap/skills/video-voiceover/scripts/lib.py +8 -44
  505. package/lib/video-recap/skills/video-voiceover/scripts/providers/__init__.py +1 -0
  506. package/lib/video-recap/skills/video-voiceover/scripts/providers/index_tts.py +184 -0
  507. package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +212 -134
  508. package/package.json +45 -45
  509. package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +0 -58
  510. package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +0 -53
  511. package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +0 -38
  512. package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +0 -530
  513. package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +0 -559
  514. package/lib/oh-story/skills/story-long-scan/references/scan-output-format.md +0 -207
  515. package/lib/oh-story/skills/story-long-write/references/state-tracking.md +0 -108
  516. package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +0 -678
  517. package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +0 -561
  518. package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +0 -98
  519. package/lib/video-recap/skills/video-recap/references/creative-editing-playbook.md +0 -225
  520. package/lib/video-recap/skills/video-recap/references/research-guide.md +0 -74
  521. package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +0 -250
  522. package/lib/video-recap/skills/video-script/scripts/brief_context.py +0 -348
  523. package/lib/video-recap/skills/video-script/scripts/narration.py +0 -12
  524. package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +0 -399
  525. package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +0 -290
  526. package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +0 -327
  527. /package/lib/video-recap/skills/video-assemble/scripts/{jianying_tracks.py → jianying/tracks.py} +0 -0
  528. /package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_contract.py → qc/mimo_contract.py} +0 -0
  529. /package/lib/video-recap/skills/video-voiceover/scripts/{fish_audio.py → providers/fish_audio.py} +0 -0
@@ -58,8 +58,31 @@ CONTEXT_HEADINGS = (
58
58
  FORESHADOW_STATUSES = ("已埋", "已回收", "已过期", "放弃")
59
59
  FORESHADOW_IMPORTANCE = ("高", "中", "低")
60
60
  REVEAL_STATUSES = ("未揭示", "部分揭示", "已揭示")
61
+ # 伏笔与时间线条目里模型常用的同义写法:含义明确的直接改成正式名,不让一次提交为措辞失败。
62
+ ACTION_ALIASES = {
63
+ "add": "upsert", "new": "upsert", "create": "upsert", "update": "upsert", "plant": "upsert",
64
+ "advance": "upsert", "resolve": "upsert", "insert": "upsert",
65
+ "新增": "upsert", "埋设": "upsert", "更新": "upsert", "推进": "upsert", "回收": "upsert",
66
+ "remove": "delete", "删除": "delete",
67
+ }
68
+ # 带状态含义的伏笔动作词:它们也映射成 upsert,但动作词本身说了伏笔走到哪一步。
69
+ # status 缺省时按动作词补上(意图只有这一个来源,不会猜错);status 与动作词矛盾时退回,
70
+ # 因为无法判断是动作词用错还是状态写错——静默改任何一边都可能把没回收的伏笔记成已回收。
71
+ FORESHADOW_ACTION_STATUS = {
72
+ "resolve": "已回收", "回收": "已回收",
73
+ "advance": "已埋", "推进": "已埋",
74
+ }
75
+ FORESHADOW_KEY_ALIASES = {
76
+ "planned_chapter": "planned_resolution_chapter", "planned_payoff_chapter": "planned_resolution_chapter",
77
+ "payoff_chapter": "planned_resolution_chapter", "resolution_chapter": "planned_resolution_chapter",
78
+ "resolve_chapter": "planned_resolution_chapter", "plant_chapter": "planted_chapter",
79
+ "planted": "planted_chapter", "description": "summary", "content": "summary",
80
+ }
81
+ TIMELINE_KEY_ALIASES = {"time": "story_time", "fact": "objective_fact", "reveal": "reveal_status"}
61
82
  INVALID_FILE_CHARS = re.compile(r"[<>:\"/\\|?*\x00-\x1f]")
62
83
  FORESHADOW_ID = re.compile(r"^F\d{3,}$")
84
+ SNAPSHOT_TEXT_FIELDS = ("identity", "location", "goal", "state")
85
+ SNAPSHOT_LIST_FIELDS = ("abilities_resources", "relationships", "knowledge", "open_threads")
63
86
  EVENT_ID = re.compile(r"^E\d{3,}$")
64
87
  WINDOWS_RESERVED_NAMES = {
65
88
  "CON",
@@ -112,16 +135,56 @@ def as_int(value: object, label: str, *, minimum: int = 0) -> int:
112
135
  return value
113
136
 
114
137
 
138
+ def as_chapter(value: object, label: str, *, minimum: int = 1) -> int:
139
+ """章号:模型常写成 "22"。纯数字字符串按整数收,语义不变;其余照旧报错。"""
140
+ if isinstance(value, str) and value.strip().isdecimal():
141
+ value = int(value.strip())
142
+ return as_int(value, label, minimum=minimum)
143
+
144
+
145
+ def with_aliases(row: dict[str, Any], aliases: dict[str, str]) -> dict[str, Any]:
146
+ """把含义明确的同义字段名改成正式字段名;正式字段已在时不动,留给未知字段报错。"""
147
+ row = dict(row)
148
+ for alias, canonical in aliases.items():
149
+ if alias in row and canonical not in row:
150
+ row[canonical] = row.pop(alias)
151
+ return row
152
+
153
+
154
+ def normalize_action(value: object, label: str, *, allow_delete: bool) -> str:
155
+ action = clean_text(value, label, max_bytes=24)
156
+ action = ACTION_ALIASES.get(action.lower(), action.lower())
157
+ allowed = ("upsert", "delete") if allow_delete else ("upsert",)
158
+ require(action in allowed, f"{label} is invalid: use {' or '.join(allowed)}")
159
+ return action
160
+
161
+
115
162
  def require_known_keys(mapping: dict[str, Any], allowed: set[str], label: str) -> None:
116
163
  unknown = set(mapping) - allowed
117
- require(not unknown, f"{label} contains unsupported fields: {', '.join(sorted(unknown))}")
164
+ require(not unknown, f"{label} contains unsupported fields: {', '.join(sorted(unknown))}"
165
+ f"(只收 {', '.join(sorted(allowed))})")
166
+
167
+
168
+ # 事务校验期间收集全部超长字段,一次报完;None 表示逐条立即报错(初始化、状态读取等路径)。
169
+ _LENGTH_ISSUES: list[str] | None = None
170
+
171
+
172
+ def length_hint(label: str, text: str, max_bytes: int) -> str:
173
+ """把字节上限换算成字数:模型写的是中文,只看得懂「要删几个字」。"""
174
+ size = len(text.encode("utf-8"))
175
+ allowed = max(1, max_bytes * len(text) // size)
176
+ return (f"{label} exceeds {max_bytes} bytes(现 {len(text)} 字,上限约 {allowed} 字,"
177
+ f"至少删 {len(text) - allowed} 字)")
118
178
 
119
179
 
120
180
  def clean_text(value: object, label: str, *, allow_empty: bool = False, max_bytes: int = 768) -> str:
121
181
  require(isinstance(value, str), f"{label} must be a string")
122
182
  cleaned = " ".join(value.replace("|", "|").split())
123
183
  require(allow_empty or bool(cleaned), f"{label} must not be empty")
124
- require(len(cleaned.encode("utf-8")) <= max_bytes, f"{label} exceeds {max_bytes} bytes")
184
+ if len(cleaned.encode("utf-8")) > max_bytes:
185
+ if _LENGTH_ISSUES is None:
186
+ raise TrackingError(length_hint(label, cleaned, max_bytes))
187
+ _LENGTH_ISSUES.append(length_hint(label, cleaned, max_bytes))
125
188
  return cleaned
126
189
 
127
190
 
@@ -277,9 +340,15 @@ def archive_retired_tracking_paths(tracking: Path) -> list[str]:
277
340
  return retired
278
341
 
279
342
 
343
+ def _both_blank(position: dict[str, Any]) -> bool:
344
+ return all(isinstance(position.get(key), str) and not position[key].strip() for key in ("story_time", "scene"))
345
+
346
+
280
347
  def validate_position(value: object, label: str = "context.position") -> dict[str, Any]:
281
348
  position = as_mapping(value, label)
282
349
  require_known_keys(position, {"volume", "volume_start_chapter", "story_time", "scene"}, label)
350
+ require(not _both_blank(position),
351
+ f"{label}.story_time and {label}.scene must not be empty: fill where this chapter ends")
283
352
  return {
284
353
  "volume": safe_file_component(position.get("volume"), f"{label}.volume"),
285
354
  "volume_start_chapter": as_int(
@@ -290,8 +359,22 @@ def validate_position(value: object, label: str = "context.position") -> dict[st
290
359
  }
291
360
 
292
361
 
362
+ def joined_text(value: object) -> object:
363
+ """单句字段写成了字符串列表时按「;」连成一句,内容不丢。"""
364
+ if isinstance(value, list) and value and all(isinstance(item, str) for item in value):
365
+ return ";".join(value)
366
+ return value
367
+
368
+
369
+ def as_text_list(value: object) -> object:
370
+ """列表字段只写了一句时当作一项。"""
371
+ return [value] if isinstance(value, str) and value.strip() else value
372
+
373
+
293
374
  def normalize_snapshot(value: object, label: str) -> dict[str, Any]:
294
375
  snapshot = as_mapping(value, label)
376
+ snapshot = {key: joined_text(item) if key in SNAPSHOT_TEXT_FIELDS else as_text_list(item)
377
+ if key in SNAPSHOT_LIST_FIELDS else item for key, item in snapshot.items()}
295
378
  require_known_keys(
296
379
  snapshot,
297
380
  {"identity", "location", "goal", "state", "abilities_resources", "relationships", "knowledge", "open_threads"},
@@ -358,23 +441,33 @@ def normalize_foreshadow_change(
358
441
  allow_delete: bool,
359
442
  through_chapter: int,
360
443
  ) -> dict[str, Any]:
361
- row = as_mapping(value, label)
444
+ row = with_aliases(as_mapping(value, label), FORESHADOW_KEY_ALIASES)
362
445
  require_known_keys(
363
446
  row,
364
447
  {"action", "id", "summary", "planted_chapter", "planned_resolution_chapter", "status", "importance"},
365
448
  label,
366
449
  )
367
- action = clean_text(row.get("action", "upsert"), f"{label}.action", max_bytes=24)
368
- require(action in ({"upsert", "delete"} if allow_delete else {"upsert"}), f"{label}.action is invalid")
450
+ raw_action = row.get("action", "upsert")
451
+ action = normalize_action(raw_action, f"{label}.action", allow_delete=allow_delete)
369
452
  identifier = clean_text(row.get("id"), f"{label}.id", max_bytes=24)
370
453
  require(FORESHADOW_ID.fullmatch(identifier) is not None, f"{label}.id must look like F001")
371
454
  if action == "delete":
372
455
  return {"action": action, "id": identifier}
373
- planted_chapter = as_int(row.get("planted_chapter"), f"{label}.planted_chapter", minimum=1)
456
+ implied = FORESHADOW_ACTION_STATUS.get(" ".join(str(raw_action).split()).lower())
457
+ if implied is not None:
458
+ if row.get("status") in (None, ""):
459
+ row["status"] = implied
460
+ right = "回收了就写 status=已回收" if implied == "已回收" else "只是推进、还没回收就写 status=已埋"
461
+ require(
462
+ row["status"] == implied,
463
+ f"{label}.action={raw_action} 与 status={row['status']} 矛盾:{right};"
464
+ "不是这个意思就把 action 改成 upsert 并写实际状态",
465
+ )
466
+ planted_chapter = as_chapter(row.get("planted_chapter"), f"{label}.planted_chapter")
374
467
  require(planted_chapter <= through_chapter, f"{label}.planted_chapter cannot be in the future")
375
468
  planned_raw = row.get("planned_resolution_chapter")
376
469
  planned_chapter = (
377
- None if planned_raw is None else as_int(planned_raw, f"{label}.planned_resolution_chapter", minimum=1)
470
+ None if planned_raw in (None, "") else as_chapter(planned_raw, f"{label}.planned_resolution_chapter")
378
471
  )
379
472
  require(
380
473
  planned_chapter is None or planned_chapter >= planted_chapter,
@@ -450,14 +543,13 @@ def normalize_timeline_change(
450
543
  allow_delete: bool,
451
544
  through_chapter: int,
452
545
  ) -> dict[str, Any]:
453
- event = as_mapping(value, label)
546
+ event = with_aliases(as_mapping(value, label), TIMELINE_KEY_ALIASES)
454
547
  require_known_keys(
455
548
  event,
456
549
  {"action", "id", "story_time", "objective_fact", "reader_knowledge", "reveal_status", "reveal_chapter", "characters"},
457
550
  label,
458
551
  )
459
- action = clean_text(event.get("action", "upsert"), f"{label}.action", max_bytes=24)
460
- require(action in ({"upsert", "delete"} if allow_delete else {"upsert"}), f"{label}.action is invalid")
552
+ action = normalize_action(event.get("action", "upsert"), f"{label}.action", allow_delete=allow_delete)
461
553
  identifier = clean_text(event.get("id"), f"{label}.id", max_bytes=24)
462
554
  require(EVENT_ID.fullmatch(identifier) is not None, f"{label}.id must look like E001")
463
555
  if action == "delete":
@@ -465,7 +557,7 @@ def normalize_timeline_change(
465
557
  reveal_status = clean_text(event.get("reveal_status"), f"{label}.reveal_status", max_bytes=24)
466
558
  require(reveal_status in REVEAL_STATUSES, f"{label}.reveal_status must be one of {REVEAL_STATUSES}")
467
559
  reveal_raw = event.get("reveal_chapter")
468
- reveal_chapter = None if reveal_raw is None else as_int(reveal_raw, f"{label}.reveal_chapter", minimum=1)
560
+ reveal_chapter = None if reveal_raw in (None, "") else as_chapter(reveal_raw, f"{label}.reveal_chapter")
469
561
  if reveal_status == "未揭示":
470
562
  require(reveal_chapter is None, f"{label} must not put a future reveal chapter in established timeline facts")
471
563
  else:
@@ -687,7 +779,8 @@ def normalize_delta(
687
779
  # 本章退役的角色记录最后一次变化即可,不必再交一份马上要删的快照。
688
780
  require(
689
781
  not is_core or name in snapshots or portable_name_key(name) in retiring,
690
- f"core character {name} changed but has no current snapshot",
782
+ f"core character {name} changed but has no current snapshot: 把 draft 输出 current_snapshots 里"
783
+ f"「{name}」的快照整份改成本章结束时的状态,放进 character_snapshots",
691
784
  )
692
785
  character_changes.append(
693
786
  {"name": name, "change": clean_text(change.get("change"), f"delta.character_changes[{index}].change", max_bytes=360)}
@@ -734,11 +827,18 @@ def normalize_delta(
734
827
  }
735
828
 
736
829
 
830
+ def render_commitments(commitments: list[str]) -> list[str]:
831
+ # 常见情形一行用「;」连写;有承诺原文自带「;」时连写就拆不回来,改成逐条缩进子项。
832
+ if any(";" in item for item in commitments):
833
+ return ["- 下一章承诺:"] + [f" - {item}" for item in commitments]
834
+ return ["- 下一章承诺:" + (";".join(commitments) or "无")]
835
+
836
+
737
837
  def render_delta(chapter: int, title: str, delta: dict[str, Any], core_names: set[str]) -> str:
738
838
  lines = [
739
839
  f"# 第{chapter:03d}章 · {title}",
740
840
  f"- 结果:{delta['result']}",
741
- "- 下一章承诺:" + (";".join(delta["next_chapter_commitments"]) or "无"),
841
+ *render_commitments(delta["next_chapter_commitments"]),
742
842
  "",
743
843
  "## 角色变化",
744
844
  ]
@@ -781,7 +881,13 @@ def render_delta(chapter: int, title: str, delta: dict[str, Any], core_names: se
781
881
  lines.extend(f"- {item}" for item in retired)
782
882
  payload = "\n".join(lines) + "\n"
783
883
  size = byte_size(payload)
784
- require(size <= DELTA_MAX_BYTES, f"chapter delta is {size} bytes; hard cap is {DELTA_MAX_BYTES}")
884
+ if size > DELTA_MAX_BYTES:
885
+ longest = sorted((line for line in lines if line.startswith("- ")), key=len, reverse=True)[:3]
886
+ allowed = DELTA_MAX_BYTES * len(payload) // size
887
+ raise TrackingError(
888
+ f"chapter delta is {size} bytes; hard cap is {DELTA_MAX_BYTES}(逐章记录现 {len(payload)} 字,"
889
+ f"上限约 {allowed} 字,至少压缩 {len(payload) - allowed} 字;最长几项:"
890
+ + ";".join(f"{line[2:18]}…({len(line) - 2} 字)" for line in longest) + ")")
785
891
  return payload
786
892
 
787
893
 
@@ -901,6 +1007,22 @@ def normalize_initial_document(document: object) -> dict[str, Any]:
901
1007
 
902
1008
 
903
1009
  def normalize_transaction(project: Path, state: dict[str, Any], document: object) -> dict[str, Any]:
1010
+ global _LENGTH_ISSUES
1011
+ _LENGTH_ISSUES = []
1012
+ try:
1013
+ transaction = _normalize_transaction(project, state, document)
1014
+ finally:
1015
+ issues, _LENGTH_ISSUES = _LENGTH_ISSUES, None
1016
+ require(not issues, f"{len(issues)} 个字段超长,一次改完再提交:" + ";".join(issues))
1017
+ return transaction
1018
+
1019
+
1020
+ # 本文件在 story-long-write / story-review / story-import 三份逐字相同;只有 story-long-write 带 storyctl.py。
1021
+ RECOUNT_HINT = ("修订请用 story-long-write 的 storyctl.py chapter commit(带外用 chapter accept-current-length)"
1022
+ "提交同一份事务,它会重新计数并清理本章工作目录")
1023
+
1024
+
1025
+ def _normalize_transaction(project: Path, state: dict[str, Any], document: object) -> dict[str, Any]:
904
1026
  root = as_mapping(document, "transaction")
905
1027
  require_known_keys(
906
1028
  root,
@@ -940,6 +1062,15 @@ def normalize_transaction(project: Path, state: dict[str, Any], document: object
940
1062
  wordcount = wordcount_value(
941
1063
  wordcount_core.validate_current_wordcount_record, project, chapter, wordcount_input
942
1064
  )
1065
+ elif mode == "revision" and str(chapter) in state["wordcount_records"]:
1066
+ # 已提交字数记录的章节:正文或目标改过却绕开 storyctl 提交修订,会留下过期的 actual/body_sha256。
1067
+ # 只核对记录钉住的正文与目标;细纲事后补的「字数范围」不算过期。
1068
+ try:
1069
+ drift = wordcount_core.wordcount_record_drift(project, chapter, state["wordcount_records"][str(chapter)])
1070
+ except wordcount_core.WordcountError as exc:
1071
+ raise TrackingError(f"第{chapter}章的字数记录无法核对({exc});{RECOUNT_HINT}") from exc
1072
+ if drift:
1073
+ raise TrackingError(f"第{chapter}章{'和'.join(drift)}已改;{RECOUNT_HINT}")
943
1074
  return {
944
1075
  "mode": mode,
945
1076
  "chapter": chapter,
@@ -999,6 +1130,13 @@ def merge_transaction(state: dict[str, Any], transaction: dict[str, Any]) -> dic
999
1130
  "a revision must resubmit every current context item; retire them in an append transaction instead: "
1000
1131
  + ";".join(sorted(dropped)),
1001
1132
  )
1133
+ kept = set(next_context["long_term_constraints"]) | set(next_context["continuity_risks"])
1134
+ still_listed = sorted(set(transaction["delta"]["retired_context_items"]) & kept)
1135
+ require(
1136
+ not still_listed,
1137
+ "delta.retired_context_items lists items that are still in context; remove them from context: "
1138
+ + ";".join(still_listed),
1139
+ )
1002
1140
  undeclared = sorted(dropped - set(transaction["delta"]["retired_context_items"]))
1003
1141
  require(
1004
1142
  not undeclared,
@@ -1126,6 +1264,11 @@ def _apply_transaction_locked(project: Path, document: object) -> dict[str, Any]
1126
1264
  state = load_state(project)
1127
1265
  transaction = normalize_transaction(project, state, document)
1128
1266
  next_state = merge_transaction(state, transaction)
1267
+ path = delta_path(tracking, transaction["chapter"])
1268
+ previous_record = parse_chapter_record(path) if transaction["mode"] == "revision" else None
1269
+ if previous_record is not None:
1270
+ # 退役只在 append 发生;修订重写旧章记录时把当初的退役登记原样带过来,不让它随修订消失。
1271
+ transaction["delta"]["retired_context_items"] = previous_record["retired"]
1129
1272
 
1130
1273
  delta_payload = render_delta(
1131
1274
  transaction["chapter"],
@@ -1136,7 +1279,6 @@ def _apply_transaction_locked(project: Path, document: object) -> dict[str, Any]
1136
1279
  )
1137
1280
  views = render_views(next_state)
1138
1281
  next_state_payload = json_payload(next_state)
1139
- path = delta_path(tracking, transaction["chapter"])
1140
1282
  if transaction["mode"] == "append" and path.exists():
1141
1283
  require(
1142
1284
  path.read_text(encoding="utf-8") == delta_payload,
@@ -1148,9 +1290,21 @@ def _apply_transaction_locked(project: Path, document: object) -> dict[str, Any]
1148
1290
  # 唯一权威文件最后落盘;在此之前失败可用同一事务直接重跑。
1149
1291
  atomic_write_text(state_path(project), next_state_payload)
1150
1292
  warn_sizes(views, delta_payload)
1293
+ if previous_record is not None:
1294
+ warn_emptied_record(transaction["chapter"], previous_record, transaction["delta"])
1151
1295
  return next_state
1152
1296
 
1153
1297
 
1298
+ def warn_emptied_record(chapter: int, previous: dict[str, Any], delta: dict[str, Any]) -> None:
1299
+ labels = {"character_changes": "角色变化", "foreshadow_changes": "伏笔变化", "timeline_events": "时间与揭示",
1300
+ "constraints": "连贯性约束", "next_chapter_commitments": "下一章承诺"}
1301
+ emptied = [f"{label}(原有 {len(previous[key])} 项)" for key, label in labels.items()
1302
+ if previous[key] and not delta[key]]
1303
+ if emptied:
1304
+ emit(f"WARNING: 第{chapter}章修订把逐章记录的 " + "、".join(emptied)
1305
+ + " 清空了;修订的 delta 是本章完整记录,不是只写改动。不是有意删除就用 draft 重新预填后再提交。", error=True)
1306
+
1307
+
1154
1308
  def apply_transaction(project: Path, document: object) -> dict[str, Any]:
1155
1309
  with project_write_lock(project):
1156
1310
  return _apply_transaction_locked(project, document)
@@ -1188,6 +1342,252 @@ def check_project(project: Path) -> dict[str, Any]:
1188
1342
  return state
1189
1343
 
1190
1344
 
1345
+ # 事务草稿里各文本字段的字数上限(按全中文估:UTF-8 每字 3 字节),写进提示,免得模型反复试错。
1346
+ DRAFT_LIMITS = {
1347
+ "delta.result": 160, "character_changes[].change": 120, "foreshadow_changes[].summary": 120,
1348
+ "timeline_events[].objective_fact / reader_knowledge": 160, "context.position.story_time / scene": 80,
1349
+ "character_snapshots.*.goal / state": 100, "逐章记录总量": 1024,
1350
+ }
1351
+
1352
+
1353
+ RECORD_SECTIONS = {"角色变化": "character_changes", "伏笔变化": "foreshadow_changes",
1354
+ "时间与揭示": "timeline_events", "连贯性约束": "constraints", "本章退役登记": "retired"}
1355
+
1356
+
1357
+ def parse_chapter_record(path: Path) -> dict[str, Any] | None:
1358
+ """读回本工具自己渲染的逐章记录(render_delta 的固定格式),供修订草稿预填与修订提交核对。
1359
+ 这是对自家确定性输出的逆运算,不是解析手写 Markdown;读不到返回 None。"""
1360
+ try:
1361
+ text = path.read_text(encoding="utf-8")
1362
+ except (OSError, UnicodeError):
1363
+ return None
1364
+ record: dict[str, Any] = {"title": "", "result": "", "next_chapter_commitments": [],
1365
+ **{key: [] for key in RECORD_SECTIONS.values()}}
1366
+ section = None
1367
+ in_commitments = False
1368
+ for line in text.splitlines():
1369
+ heading = re.match(r"^# 第\d+章 · (.+)$", line)
1370
+ if heading and not record["title"]:
1371
+ record["title"] = heading.group(1).strip()
1372
+ continue
1373
+ if line.startswith("## "):
1374
+ section = RECORD_SECTIONS.get(line[3:].strip())
1375
+ in_commitments = False
1376
+ continue
1377
+ if in_commitments and line.startswith(" - "):
1378
+ record["next_chapter_commitments"].append(line[4:])
1379
+ continue
1380
+ in_commitments = False
1381
+ if not line.startswith("- "):
1382
+ continue
1383
+ item = line[2:]
1384
+ if section is None:
1385
+ if item.startswith("结果:"):
1386
+ record["result"] = item[len("结果:"):]
1387
+ elif item.startswith("下一章承诺:"):
1388
+ value = item[len("下一章承诺:"):]
1389
+ # 空值 = 逐条子项格式(render_commitments),后面的缩进行逐条读回。
1390
+ in_commitments = value == ""
1391
+ record["next_chapter_commitments"] = [] if value in ("无", "") else value.split(";")
1392
+ continue
1393
+ if item == "无" and section != "retired":
1394
+ continue
1395
+ record[section].append(item)
1396
+ return record
1397
+
1398
+
1399
+ def record_prefill(state: dict[str, Any], record: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any], list[str]]:
1400
+ """把已提交的逐章记录还原成修订 delta:结果、承诺、约束、角色变化按记录原文;伏笔与时间线
1401
+ 取受影响 ID 的当前值(修订提交的就是截至最新章的当前值)。另返回记录里核心角色的当前快照。"""
1402
+ notes: list[str] = []
1403
+ changes = []
1404
+ for line in record["character_changes"]:
1405
+ parts = line.split("|", 2)
1406
+ if len(parts) == 3:
1407
+ changes.append({"name": parts[0], "change": parts[2]})
1408
+ snapshots = {item["name"]: copy.deepcopy(state["characters"][item["name"]])
1409
+ for item in changes if item["name"] in state["characters"]}
1410
+
1411
+ def rows(lines: list[str], current: dict[str, Any], label: str) -> list[dict[str, Any]]:
1412
+ prefilled = []
1413
+ for line in lines:
1414
+ parts = line.split("|")
1415
+ identifier = parts[0]
1416
+ if len(parts) > 1 and parts[1] == "删除当前登记":
1417
+ if identifier in current:
1418
+ notes.append(f"{label} {identifier} 本章删过、后来又登记了,没有预填删除,确需删除自行加回")
1419
+ else:
1420
+ prefilled.append({"action": "delete", "id": identifier})
1421
+ elif identifier in current:
1422
+ prefilled.append({"action": "upsert", **{key: copy.deepcopy(value) for key, value in current[identifier].items()
1423
+ if key not in ("updated_chapter", "first_recorded_chapter")}})
1424
+ else:
1425
+ notes.append(f"{label} {identifier} 已在后续章节删除,没有预填")
1426
+ return prefilled
1427
+
1428
+ delta = {
1429
+ "result": record["result"],
1430
+ "character_changes": changes,
1431
+ "foreshadow_changes": rows(record["foreshadow_changes"], state["foreshadow"], "伏笔"),
1432
+ "timeline_events": rows(record["timeline_events"], state["timeline"], "时间线事件"),
1433
+ "constraints": list(record["constraints"]),
1434
+ "next_chapter_commitments": list(record["next_chapter_commitments"]),
1435
+ }
1436
+ return delta, snapshots, notes
1437
+
1438
+
1439
+ def entry_shapes(chapter: int) -> dict[str, Any]:
1440
+ """draft 当场给出每类条目的形状:模型填 delta 时手里只有空数组,字段名、整数、枚举都靠猜,
1441
+ 首次提交几乎都因此被退回。取值从校验用的同一组常量生成,不另立一套。"""
1442
+ return {
1443
+ "delta.character_changes[]": {"name": "角色名", "change": "一句话:本章起了什么变化"},
1444
+ "delta.foreshadow_changes[]": {
1445
+ "action": "upsert(撤掉整条写 delete,只带 id)", "id": "F007", "summary": "伏笔一句话",
1446
+ "planted_chapter": chapter, "planned_resolution_chapter": "计划回收章的整数,未定写 null",
1447
+ "status": "|".join(FORESHADOW_STATUSES), "importance": "|".join(FORESHADOW_IMPORTANCE),
1448
+ },
1449
+ "delta.timeline_events[]": {
1450
+ "action": "upsert(撤掉整条写 delete,只带 id)", "id": "E012", "story_time": "故事内时间",
1451
+ "objective_fact": "客观发生了什么", "reader_knowledge": "读者此刻知道/以为什么",
1452
+ "reveal_status": "|".join(REVEAL_STATUSES),
1453
+ "reveal_chapter": f"已揭示或部分揭示时写揭示章的整数(不晚于 {chapter}),未揭示写 null",
1454
+ "characters": ["角色名"],
1455
+ },
1456
+ "character_snapshots.{角色名}": {
1457
+ **{key: "一句话" for key in SNAPSHOT_TEXT_FIELDS},
1458
+ **{key: ["一条一句"] for key in SNAPSHOT_LIST_FIELDS},
1459
+ },
1460
+ }
1461
+
1462
+
1463
+ def draft_transaction(project: Path, chapter: int) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]:
1464
+ """按当前 state 预填一份逐章事务:修订号、模式、章名和整份提交的上下文当前值都填好。
1465
+ 新章(append)调用方只写本章变化;另返回在场核心角色的当前快照,供有变化时整份改写后放进事务。
1466
+ 新章的故事时间与场景留空:它们必须是本章结束时的位置,沿用上一章的值能静默提交,
1467
+ 所以不预填,上一章的值另行返回作参考。
1468
+ 修订(revision)的 delta 是修订后本章的完整记录,所以按已提交的逐章记录预填,调用方在上面改;
1469
+ 第三项返回 {"notes": [...], "prefilled": bool}:没能预填的条目,以及逐章记录是否读到。"""
1470
+ state = load_state(project)
1471
+ last = state["last_committed_chapter"]
1472
+ require(1 <= chapter <= last + 1, f"draft chapter must be between 1 and {last + 1}")
1473
+ mode = "append" if chapter == last + 1 else "revision"
1474
+ title = ""
1475
+ for outline in sorted((project / "大纲").glob(f"细纲_第{chapter:03d}章*.md")):
1476
+ match = re.search(rf"^#{{1,4}}\s*第\s*0*{chapter}\s*章\s*[::]\s*(.+?)\s*$",
1477
+ outline.read_text(encoding="utf-8"), re.M)
1478
+ if match:
1479
+ title = match.group(1).strip()
1480
+ break
1481
+ context = json.loads(json.dumps(state["context"], ensure_ascii=False))
1482
+ previous_position = {key: context["position"][key] for key in ("story_time", "scene")}
1483
+ delta: dict[str, Any] = {
1484
+ "result": "", "character_changes": [], "foreshadow_changes": [], "timeline_events": [],
1485
+ "constraints": [], "next_chapter_commitments": [],
1486
+ }
1487
+ snapshots_prefill: dict[str, Any] = {}
1488
+ notes: list[str] = []
1489
+ prefilled = False
1490
+ if mode == "append":
1491
+ delta.update({"retired_context_items": [], "retired_characters": []})
1492
+ context["position"].update({"story_time": "", "scene": ""})
1493
+ else:
1494
+ record = parse_chapter_record(delta_path(tracking_root(project), chapter))
1495
+ if record is not None:
1496
+ prefilled = True
1497
+ delta, snapshots_prefill, notes = record_prefill(state, record)
1498
+ title = title or record["title"]
1499
+ if chapter == last:
1500
+ # 最新章的承诺原样在 state 里,比从记录行按「;」拆回更准。
1501
+ delta["next_chapter_commitments"] = list(state["context"]["next_chapter_commitments"])
1502
+ document = {
1503
+ "schema_version": INPUT_SCHEMA_VERSION,
1504
+ "mode": mode,
1505
+ "chapter": chapter,
1506
+ "chapter_title": title,
1507
+ "expected_state_revision": state["state_revision"],
1508
+ "delta": delta,
1509
+ "context": {key: context[key] for key in
1510
+ ("position", "long_term_constraints", "active_character_names", "continuity_risks")},
1511
+ "character_snapshots": snapshots_prefill,
1512
+ }
1513
+ snapshots = {name: state["characters"][name] for name in context["active_character_names"]
1514
+ if name in state["characters"]}
1515
+ if mode == "revision":
1516
+ return document, snapshots, {"notes": notes, "prefilled": prefilled}
1517
+ return document, snapshots, previous_position
1518
+
1519
+
1520
+ def rebuild_stale_draft(document: dict[str, Any], existing: dict[str, Any], append: bool,
1521
+ current_characters: Any = ()) -> str:
1522
+ """修订号变了:中间提交过别的事务。以当前状态为底,只自动合并确定安全的部分,其余列给调用方核对。
1523
+
1524
+ 新章草稿之间只可能插进修订事务,修订不能删约束与风险条目,所以当前约束与风险覆盖旧底稿:
1525
+ 旧草稿多出来的就是本章新增,delta 里声明退役的就是本章删除,可以精确合并。
1526
+ 修订草稿之间可能插进新章(新章能退役条目),在场角色名修订也能删,都无法判断谁新:
1527
+ 取当前状态,差异列出来;只有本章带了快照的新角色名自动加回。"""
1528
+ def items(value: object) -> list[str]:
1529
+ return [item for item in value if isinstance(item, str)] if isinstance(value, list) else []
1530
+
1531
+ def norm(item: str) -> str:
1532
+ return " ".join(item.replace("|", "|").split())
1533
+
1534
+ notes = ["修订号已变:context 按当前状态重建,本章已填的 delta" + ("、新增与退役的约束、结尾位置" if append else "")
1535
+ + "已合并"]
1536
+ for key in ("delta", "character_snapshots"):
1537
+ if isinstance(existing.get(key), dict):
1538
+ document[key] = existing[key]
1539
+ if existing.get("chapter_title"):
1540
+ document["chapter_title"] = existing["chapter_title"]
1541
+ old_context = existing.get("context") if isinstance(existing.get("context"), dict) else {}
1542
+ delta = document["delta"] if isinstance(document["delta"], dict) else {}
1543
+ context = document["context"]
1544
+ retired = {norm(item) for item in items(delta.get("retired_context_items"))}
1545
+ left_out: list[str] = []
1546
+ for key in ("long_term_constraints", "continuity_risks"):
1547
+ current = {norm(item) for item in context[key]}
1548
+ extra = []
1549
+ for item in items(old_context.get(key)):
1550
+ if norm(item) not in current:
1551
+ current.add(norm(item))
1552
+ extra.append(item)
1553
+ if append:
1554
+ context[key] = [item for item in context[key] if norm(item) not in retired] + extra
1555
+ else:
1556
+ left_out += extra
1557
+ snapshots = document["character_snapshots"] if isinstance(document["character_snapshots"], dict) else {}
1558
+ names = context["active_character_names"]
1559
+ for name in items(old_context.get("active_character_names")):
1560
+ if name in names:
1561
+ continue
1562
+ if append and name in snapshots:
1563
+ names.append(name)
1564
+ else:
1565
+ left_out.append(f"在场角色 {name}")
1566
+ names[:] = [name for name in names if name not in set(items(delta.get("retired_characters")))]
1567
+ known = set(current_characters)
1568
+ for name in list(snapshots):
1569
+ if name not in known and name not in names:
1570
+ snapshots.pop(name)
1571
+ left_out.append(f"角色快照 {name}")
1572
+ if left_out:
1573
+ notes.append("草稿里有、当前状态没有的条目没有自动带回(可能已被别的提交退役),本章确实需要就加回:"
1574
+ + ";".join(left_out))
1575
+ old_position = old_context.get("position") if isinstance(old_context.get("position"), dict) else {}
1576
+ position = context["position"]
1577
+ if append:
1578
+ for key in ("story_time", "scene"):
1579
+ if isinstance(old_position.get(key), str) and old_position[key].strip():
1580
+ position[key] = old_position[key]
1581
+ differs = [f"{key}:草稿写的是「{old_position[key]}」,当前状态是「{position[key]}」"
1582
+ for key in ("volume", "volume_start_chapter")
1583
+ if key in old_position and old_position[key] != position[key]]
1584
+ if differs:
1585
+ notes.append("卷信息与当前状态不同,已取当前状态,本章确实换卷就改回(" + ";".join(differs) + ")")
1586
+ if snapshots:
1587
+ notes.append("带过来的角色快照(" + "、".join(snapshots) + ")可能被中间的提交改过,逐个对照 current_snapshots 重核")
1588
+ return ";".join(notes)
1589
+
1590
+
1191
1591
  def build_parser() -> argparse.ArgumentParser:
1192
1592
  parser = argparse.ArgumentParser(description=__doc__)
1193
1593
  subparsers = parser.add_subparsers(dest="command", required=True)
@@ -1197,12 +1597,75 @@ def build_parser() -> argparse.ArgumentParser:
1197
1597
  subparser.add_argument("--input", type=Path, required=True, help="UTF-8 JSON input document")
1198
1598
  check_parser = subparsers.add_parser("check")
1199
1599
  check_parser.add_argument("--project", type=Path, required=True, help="book project root containing 追踪/")
1600
+ draft_parser = subparsers.add_parser("draft", help="write a pre-filled chapter transaction to fill in")
1601
+ draft_parser.add_argument("--project", type=Path, required=True, help="book project root containing 追踪/")
1602
+ draft_parser.add_argument("--chapter", type=int, required=True)
1603
+ draft_parser.add_argument("--out", type=Path, help="default: <project>/.story/work/第NNN章/tracking.json")
1604
+ draft_parser.add_argument("--force", action="store_true",
1605
+ help="discard an existing draft instead of only refreshing its revision")
1200
1606
  return parser
1201
1607
 
1202
1608
 
1203
1609
  def main() -> int:
1204
1610
  args = build_parser().parse_args()
1205
1611
  try:
1612
+ if args.command == "draft":
1613
+ document, snapshots, extra = draft_transaction(args.project, args.chapter)
1614
+ previous_position = extra if document["mode"] == "append" else {}
1615
+ prefill_notes = extra.get("notes", []) if document["mode"] == "revision" else []
1616
+ record_prefilled = bool(extra.get("prefilled")) if document["mode"] == "revision" else False
1617
+ out = args.out or args.project / ".story" / "work" / f"第{args.chapter:03d}章" / "tracking.json"
1618
+ out.parent.mkdir(parents=True, exist_ok=True)
1619
+ refreshed = ""
1620
+ if out.exists() and not args.force:
1621
+ # 重跑 draft 不冲掉已经填好的变化。修订号变了说明中间提交过别的事务:context 按当前
1622
+ # 状态重建(沿用旧 context 会把那次变更整份覆盖回去),只带过本章自己填的部分。
1623
+ existing = read_json(out)
1624
+ require(isinstance(existing, dict) and existing.get("chapter") == document["chapter"]
1625
+ and existing.get("mode") == document["mode"],
1626
+ f"{out} already holds a draft for a different chapter or mode; pass --force to replace it")
1627
+ if existing.get("expected_state_revision") == document["expected_state_revision"]:
1628
+ document, refreshed = existing, "已有草稿且修订号未变,原样保留"
1629
+ else:
1630
+ refreshed = rebuild_stale_draft(document, existing, bool(previous_position),
1631
+ load_state(args.project)["characters"])
1632
+ out.write_text(json.dumps(document, ensure_ascii=False, indent=1) + "\n", encoding="utf-8")
1633
+ if document["mode"] == "revision" and not record_prefilled:
1634
+ fill = (f"这是修订,但本章逐章记录缺失或读不了({delta_path(Path('追踪'), args.chapter).as_posix()}),delta 没有预填:"
1635
+ "按修订后正文把本章完整记录整份写上(结果、承诺、角色变化、伏笔、时间线、约束),没写的项不会进记录。"
1636
+ "角色快照、伏笔、时间线写截至最新已写章的当前值;context 原样保留全部当前条目(修订不能退役)。"
1637
+ "正文改过就用 story-long-write 的 storyctl.py chapter commit 提交(重新计数并清理本章工作目录)。"
1638
+ "不要从脚本源码或 state 文件里另找格式。")
1639
+ elif document["mode"] == "revision":
1640
+ fill = ("这是修订:delta 是修订后本章的完整记录,已按本章现有逐章记录预填(伏笔与时间线取当前值,"
1641
+ "记录里的核心角色快照已放进 character_snapshots)。对照修订后正文逐项改:仍成立的原样保留,"
1642
+ "被推翻的删掉或改写,新增的补上;不要清空后只写改动,没写的项会从本章记录里消失。"
1643
+ "角色快照、伏笔、时间线改成截至最新已写章的当前值;context 原样保留全部当前条目(修订不能退役)。"
1644
+ "正文改过就用 story-long-write 的 storyctl.py chapter commit 提交(重新计数并清理本章工作目录)。"
1645
+ "不要从脚本源码或 state 文件里另找格式。")
1646
+ if prefill_notes:
1647
+ fill += "没能预填:" + ";".join(prefill_notes) + "。"
1648
+ else:
1649
+ fill = ("只填 delta 里本章的变化;context 其余字段已是当前值,要撤下的长期约束或连贯性风险从 context 删掉并把原文放进 "
1650
+ "delta.retired_context_items(仅 append);本章有变化的核心角色把下面的当前快照整份改好放进 character_snapshots,"
1651
+ "并在 character_changes 写一句变化。每类条目照 shapes 的形状写,不要从脚本源码或 state 文件里另找格式。")
1652
+ blank = not (document["context"]["position"].get("story_time") or document["context"]["position"].get("scene"))
1653
+ if previous_position and blank:
1654
+ fill = ("context.position 的 story_time 与 scene 留空,填本章结束时的故事时间与场景(上一章结束时见 "
1655
+ "previous_position;换卷时连同 volume 与 volume_start_chapter 一起改)。") + fill
1656
+ payload = {
1657
+ "draft": str(out),
1658
+ "mode": document["mode"],
1659
+ "expected_state_revision": document["expected_state_revision"],
1660
+ "fill": (f"{refreshed};要从头生成加 --force。" if refreshed else "") + fill,
1661
+ "limits_chars": DRAFT_LIMITS,
1662
+ "shapes": entry_shapes(args.chapter),
1663
+ "current_snapshots": snapshots,
1664
+ }
1665
+ if previous_position:
1666
+ payload["previous_position"] = previous_position
1667
+ emit(json.dumps(payload, ensure_ascii=False))
1668
+ return 0
1206
1669
  if args.command == "init":
1207
1670
  result = initialize(args.project, read_json(args.input))
1208
1671
  elif args.command == "commit":