@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
@@ -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,9 +135,34 @@ 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))})")
118
166
 
119
167
 
120
168
  # 事务校验期间收集全部超长字段,一次报完;None 表示逐条立即报错(初始化、状态读取等路径)。
@@ -311,8 +359,22 @@ def validate_position(value: object, label: str = "context.position") -> dict[st
311
359
  }
312
360
 
313
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
+
314
374
  def normalize_snapshot(value: object, label: str) -> dict[str, Any]:
315
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()}
316
378
  require_known_keys(
317
379
  snapshot,
318
380
  {"identity", "location", "goal", "state", "abilities_resources", "relationships", "knowledge", "open_threads"},
@@ -379,23 +441,33 @@ def normalize_foreshadow_change(
379
441
  allow_delete: bool,
380
442
  through_chapter: int,
381
443
  ) -> dict[str, Any]:
382
- row = as_mapping(value, label)
444
+ row = with_aliases(as_mapping(value, label), FORESHADOW_KEY_ALIASES)
383
445
  require_known_keys(
384
446
  row,
385
447
  {"action", "id", "summary", "planted_chapter", "planned_resolution_chapter", "status", "importance"},
386
448
  label,
387
449
  )
388
- action = clean_text(row.get("action", "upsert"), f"{label}.action", max_bytes=24)
389
- 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)
390
452
  identifier = clean_text(row.get("id"), f"{label}.id", max_bytes=24)
391
453
  require(FORESHADOW_ID.fullmatch(identifier) is not None, f"{label}.id must look like F001")
392
454
  if action == "delete":
393
455
  return {"action": action, "id": identifier}
394
- 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")
395
467
  require(planted_chapter <= through_chapter, f"{label}.planted_chapter cannot be in the future")
396
468
  planned_raw = row.get("planned_resolution_chapter")
397
469
  planned_chapter = (
398
- 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")
399
471
  )
400
472
  require(
401
473
  planned_chapter is None or planned_chapter >= planted_chapter,
@@ -471,14 +543,13 @@ def normalize_timeline_change(
471
543
  allow_delete: bool,
472
544
  through_chapter: int,
473
545
  ) -> dict[str, Any]:
474
- event = as_mapping(value, label)
546
+ event = with_aliases(as_mapping(value, label), TIMELINE_KEY_ALIASES)
475
547
  require_known_keys(
476
548
  event,
477
549
  {"action", "id", "story_time", "objective_fact", "reader_knowledge", "reveal_status", "reveal_chapter", "characters"},
478
550
  label,
479
551
  )
480
- action = clean_text(event.get("action", "upsert"), f"{label}.action", max_bytes=24)
481
- 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)
482
553
  identifier = clean_text(event.get("id"), f"{label}.id", max_bytes=24)
483
554
  require(EVENT_ID.fullmatch(identifier) is not None, f"{label}.id must look like E001")
484
555
  if action == "delete":
@@ -486,7 +557,7 @@ def normalize_timeline_change(
486
557
  reveal_status = clean_text(event.get("reveal_status"), f"{label}.reveal_status", max_bytes=24)
487
558
  require(reveal_status in REVEAL_STATUSES, f"{label}.reveal_status must be one of {REVEAL_STATUSES}")
488
559
  reveal_raw = event.get("reveal_chapter")
489
- 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")
490
561
  if reveal_status == "未揭示":
491
562
  require(reveal_chapter is None, f"{label} must not put a future reveal chapter in established timeline facts")
492
563
  else:
@@ -708,7 +779,8 @@ def normalize_delta(
708
779
  # 本章退役的角色记录最后一次变化即可,不必再交一份马上要删的快照。
709
780
  require(
710
781
  not is_core or name in snapshots or portable_name_key(name) in retiring,
711
- 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",
712
784
  )
713
785
  character_changes.append(
714
786
  {"name": name, "change": clean_text(change.get("change"), f"delta.character_changes[{index}].change", max_bytes=360)}
@@ -755,11 +827,18 @@ def normalize_delta(
755
827
  }
756
828
 
757
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
+
758
837
  def render_delta(chapter: int, title: str, delta: dict[str, Any], core_names: set[str]) -> str:
759
838
  lines = [
760
839
  f"# 第{chapter:03d}章 · {title}",
761
840
  f"- 结果:{delta['result']}",
762
- "- 下一章承诺:" + (";".join(delta["next_chapter_commitments"]) or "无"),
841
+ *render_commitments(delta["next_chapter_commitments"]),
763
842
  "",
764
843
  "## 角色变化",
765
844
  ]
@@ -938,6 +1017,11 @@ def normalize_transaction(project: Path, state: dict[str, Any], document: object
938
1017
  return transaction
939
1018
 
940
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
+
941
1025
  def _normalize_transaction(project: Path, state: dict[str, Any], document: object) -> dict[str, Any]:
942
1026
  root = as_mapping(document, "transaction")
943
1027
  require_known_keys(
@@ -978,6 +1062,15 @@ def _normalize_transaction(project: Path, state: dict[str, Any], document: objec
978
1062
  wordcount = wordcount_value(
979
1063
  wordcount_core.validate_current_wordcount_record, project, chapter, wordcount_input
980
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}")
981
1074
  return {
982
1075
  "mode": mode,
983
1076
  "chapter": chapter,
@@ -1171,6 +1264,11 @@ def _apply_transaction_locked(project: Path, document: object) -> dict[str, Any]
1171
1264
  state = load_state(project)
1172
1265
  transaction = normalize_transaction(project, state, document)
1173
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"]
1174
1272
 
1175
1273
  delta_payload = render_delta(
1176
1274
  transaction["chapter"],
@@ -1181,7 +1279,6 @@ def _apply_transaction_locked(project: Path, document: object) -> dict[str, Any]
1181
1279
  )
1182
1280
  views = render_views(next_state)
1183
1281
  next_state_payload = json_payload(next_state)
1184
- path = delta_path(tracking, transaction["chapter"])
1185
1282
  if transaction["mode"] == "append" and path.exists():
1186
1283
  require(
1187
1284
  path.read_text(encoding="utf-8") == delta_payload,
@@ -1193,9 +1290,21 @@ def _apply_transaction_locked(project: Path, document: object) -> dict[str, Any]
1193
1290
  # 唯一权威文件最后落盘;在此之前失败可用同一事务直接重跑。
1194
1291
  atomic_write_text(state_path(project), next_state_payload)
1195
1292
  warn_sizes(views, delta_payload)
1293
+ if previous_record is not None:
1294
+ warn_emptied_record(transaction["chapter"], previous_record, transaction["delta"])
1196
1295
  return next_state
1197
1296
 
1198
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
+
1199
1308
  def apply_transaction(project: Path, document: object) -> dict[str, Any]:
1200
1309
  with project_write_lock(project):
1201
1310
  return _apply_transaction_locked(project, document)
@@ -1241,11 +1350,123 @@ DRAFT_LIMITS = {
1241
1350
  }
1242
1351
 
1243
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
+
1244
1463
  def draft_transaction(project: Path, chapter: int) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]:
1245
- """按当前 state 预填一份逐章事务:修订号、模式、章名和整份提交的上下文当前值都填好,
1246
- 调用方只写本章变化。另返回在场核心角色的当前快照,供有变化时整份改写后放进事务。
1247
- 新章(append)的故事时间与场景留空:它们必须是本章结束时的位置,沿用上一章的值能静默提交,
1248
- 所以不预填,上一章的值另行返回作参考。"""
1464
+ """按当前 state 预填一份逐章事务:修订号、模式、章名和整份提交的上下文当前值都填好。
1465
+ 新章(append)调用方只写本章变化;另返回在场核心角色的当前快照,供有变化时整份改写后放进事务。
1466
+ 新章的故事时间与场景留空:它们必须是本章结束时的位置,沿用上一章的值能静默提交,
1467
+ 所以不预填,上一章的值另行返回作参考。
1468
+ 修订(revision)的 delta 是修订后本章的完整记录,所以按已提交的逐章记录预填,调用方在上面改;
1469
+ 第三项返回 {"notes": [...], "prefilled": bool}:没能预填的条目,以及逐章记录是否读到。"""
1249
1470
  state = load_state(project)
1250
1471
  last = state["last_committed_chapter"]
1251
1472
  require(1 <= chapter <= last + 1, f"draft chapter must be between 1 and {last + 1}")
@@ -1263,9 +1484,21 @@ def draft_transaction(project: Path, chapter: int) -> tuple[dict[str, Any], dict
1263
1484
  "result": "", "character_changes": [], "foreshadow_changes": [], "timeline_events": [],
1264
1485
  "constraints": [], "next_chapter_commitments": [],
1265
1486
  }
1487
+ snapshots_prefill: dict[str, Any] = {}
1488
+ notes: list[str] = []
1489
+ prefilled = False
1266
1490
  if mode == "append":
1267
1491
  delta.update({"retired_context_items": [], "retired_characters": []})
1268
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"])
1269
1502
  document = {
1270
1503
  "schema_version": INPUT_SCHEMA_VERSION,
1271
1504
  "mode": mode,
@@ -1275,11 +1508,13 @@ def draft_transaction(project: Path, chapter: int) -> tuple[dict[str, Any], dict
1275
1508
  "delta": delta,
1276
1509
  "context": {key: context[key] for key in
1277
1510
  ("position", "long_term_constraints", "active_character_names", "continuity_risks")},
1278
- "character_snapshots": {},
1511
+ "character_snapshots": snapshots_prefill,
1279
1512
  }
1280
1513
  snapshots = {name: state["characters"][name] for name in context["active_character_names"]
1281
1514
  if name in state["characters"]}
1282
- return document, snapshots, previous_position if mode == "append" else {}
1515
+ if mode == "revision":
1516
+ return document, snapshots, {"notes": notes, "prefilled": prefilled}
1517
+ return document, snapshots, previous_position
1283
1518
 
1284
1519
 
1285
1520
  def rebuild_stale_draft(document: dict[str, Any], existing: dict[str, Any], append: bool,
@@ -1375,7 +1610,10 @@ def main() -> int:
1375
1610
  args = build_parser().parse_args()
1376
1611
  try:
1377
1612
  if args.command == "draft":
1378
- document, snapshots, previous_position = draft_transaction(args.project, args.chapter)
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
1379
1617
  out = args.out or args.project / ".story" / "work" / f"第{args.chapter:03d}章" / "tracking.json"
1380
1618
  out.parent.mkdir(parents=True, exist_ok=True)
1381
1619
  refreshed = ""
@@ -1392,9 +1630,25 @@ def main() -> int:
1392
1630
  refreshed = rebuild_stale_draft(document, existing, bool(previous_position),
1393
1631
  load_state(args.project)["characters"])
1394
1632
  out.write_text(json.dumps(document, ensure_ascii=False, indent=1) + "\n", encoding="utf-8")
1395
- fill = ("只填 delta 里本章的变化;context 其余字段已是当前值,要撤下的长期约束或连贯性风险从 context 删掉并把原文放进 "
1396
- "delta.retired_context_items(仅 append);本章有变化的核心角色把下面的当前快照整份改好放进 character_snapshots,"
1397
- "并在 character_changes 写一句变化。不要从脚本源码或 state 文件里另找格式。")
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 文件里另找格式。")
1398
1652
  blank = not (document["context"]["position"].get("story_time") or document["context"]["position"].get("scene"))
1399
1653
  if previous_position and blank:
1400
1654
  fill = ("context.position 的 story_time 与 scene 留空,填本章结束时的故事时间与场景(上一章结束时见 "
@@ -1405,6 +1659,7 @@ def main() -> int:
1405
1659
  "expected_state_revision": document["expected_state_revision"],
1406
1660
  "fill": (f"{refreshed};要从头生成加 --force。" if refreshed else "") + fill,
1407
1661
  "limits_chars": DRAFT_LIMITS,
1662
+ "shapes": entry_shapes(args.chapter),
1408
1663
  "current_snapshots": snapshots,
1409
1664
  }
1410
1665
  if previous_position: