@oh-story/dsh 0.1.4 → 0.1.5

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 (479) hide show
  1. package/README.md +56 -12
  2. package/lib/client.js +2692 -268
  3. package/lib/client.js.map +4 -4
  4. package/lib/drama/manifest.json +136 -101
  5. package/lib/drama/skills/short-drama/SKILL.md +61 -1
  6. package/lib/drama/skills/short-drama/references/contract-and-ownership.md +7 -4
  7. package/lib/drama/skills/short-drama/references/creator-documents.md +133 -8
  8. package/lib/drama/skills/short-drama/references/creator-workflow.md +38 -2
  9. package/lib/drama/skills/short-drama/references/knowhow-index.md +3 -0
  10. package/lib/drama/skills/short-drama/references/reference-roles.md +10 -7
  11. package/lib/drama/skills/short-drama/references/runtime-preflight.md +3 -0
  12. package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +1002 -0
  13. package/lib/drama/skills/short-drama/scripts/project_tool.py +485 -5
  14. package/lib/drama/skills/short-drama-assets/SKILL.md +13 -3
  15. package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +116 -0
  16. package/lib/drama/skills/short-drama-assets/references/identity-vs-variant.md +2 -1
  17. package/lib/drama/skills/short-drama-assets/references/prop-and-state.md +19 -0
  18. package/lib/drama/skills/short-drama-assets/references/stage-contract.md +2 -0
  19. package/lib/drama/skills/short-drama-assets/scripts/asset_check.py +1 -1
  20. package/lib/drama/skills/short-drama-image-prompts/SKILL.md +6 -2
  21. package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +4 -3
  22. package/lib/drama/skills/short-drama-image-prompts/references/lookdev-frame.md +1 -1
  23. package/lib/drama/skills/short-drama-image-prompts/references/review-and-fixtures.md +1 -1
  24. package/lib/drama/skills/short-drama-image-prompts/references/stage-contract.md +3 -1
  25. package/lib/drama/skills/short-drama-image-prompts/scripts/image_prompt_check.py +8 -8
  26. package/lib/drama/skills/short-drama-image-prompts/scripts/selftest.py +4 -2
  27. package/lib/drama/skills/short-drama-produce/SKILL.md +46 -10
  28. package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +65 -8
  29. package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +85 -0
  30. package/lib/drama/skills/short-drama-produce/references/providers/seedance.md +27 -10
  31. package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +362 -3
  32. package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +627 -40
  33. package/lib/drama/skills/short-drama-produce/scripts/selftest.py +5 -4
  34. package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +12 -0
  35. package/lib/drama/skills/short-drama-review/scripts/review_check.py +1 -1
  36. package/lib/drama/skills/short-drama-storyboard/SKILL.md +76 -7
  37. package/lib/drama/skills/short-drama-storyboard/assets/shot-template.jsonl +1 -1
  38. package/lib/drama/skills/short-drama-storyboard/references/keyframe-craft.md +6 -2
  39. package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +19 -4
  40. package/lib/drama/skills/short-drama-storyboard/references/review-and-fixtures.md +5 -2
  41. package/lib/drama/skills/short-drama-storyboard/references/screenplay-to-keyframe-example.md +16 -0
  42. package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +8 -3
  43. package/lib/drama/skills/short-drama-storyboard/scripts/storyboard_check.py +1 -1
  44. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +81 -13
  45. package/lib/drama/skills/short-drama-video-prompts/assets/motion-spec.jsonl.md +6 -1
  46. package/lib/drama/skills/short-drama-video-prompts/assets/video-prompts.md +17 -1
  47. package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +21 -1
  48. package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +154 -0
  49. package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +24 -6
  50. package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +11 -7
  51. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +41 -0
  52. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +66 -0
  53. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +5 -2
  54. package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +115 -0
  55. package/lib/drama/skills/short-drama-video-prompts/scripts/container_check.py +1 -1
  56. package/lib/drama/skills/short-drama-video-prompts/scripts/music_spec_check.py +1 -1
  57. package/lib/drama/skills/short-drama-write/SKILL.md +3 -2
  58. package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +1 -1
  59. package/lib/drama/skills/short-drama-write/scripts/screenplay_index.py +2 -2
  60. package/lib/drama/skills/short-drama-write/scripts/voice_sheet_check.py +1 -1
  61. package/lib/index.js +865 -33
  62. package/lib/index.js.map +4 -4
  63. package/lib/novel-to-game/LICENSE +21 -0
  64. package/lib/novel-to-game/README.md +180 -0
  65. package/lib/novel-to-game/README_ZH.md +178 -0
  66. package/lib/novel-to-game/VERSION +1 -0
  67. package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +217 -0
  68. package/lib/novel-to-game/examples/jin-ping-mei/build/app/.vercelignore +3 -0
  69. package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +130 -0
  70. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/compound_new_guofeng.webp +0 -0
  71. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/fate_coda.webp +0 -0
  72. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/meng.webp +0 -0
  73. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/pan.webp +0 -0
  74. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/pinger.webp +0 -0
  75. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/xuee.webp +0 -0
  76. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/yue.webp +0 -0
  77. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_accord_five.webp +0 -0
  78. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_afterglow_five.webp +0 -0
  79. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_alliance.webp +0 -0
  80. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day10.webp +0 -0
  81. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day15.webp +0 -0
  82. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day5.webp +0 -0
  83. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/title_new_guofeng.webp +0 -0
  84. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/pan_pinger.webp +0 -0
  85. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/pan_xuee.webp +0 -0
  86. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_meng.webp +0 -0
  87. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_pan.webp +0 -0
  88. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_pinger.webp +0 -0
  89. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/meng/explicit.webp +0 -0
  90. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/meng/prelude.webp +0 -0
  91. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/meng.webp +0 -0
  92. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/pan.webp +0 -0
  93. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/pinger.webp +0 -0
  94. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/xuee.webp +0 -0
  95. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/yue.webp +0 -0
  96. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pan/explicit.webp +0 -0
  97. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pan/prelude.webp +0 -0
  98. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pinger/explicit.webp +0 -0
  99. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pinger/prelude.webp +0 -0
  100. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/xuee/explicit.webp +0 -0
  101. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/xuee/prelude.webp +0 -0
  102. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/explicit.webp +0 -0
  103. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/prelude.webp +0 -0
  104. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/meng/night.webp +0 -0
  105. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/pan/night.webp +0 -0
  106. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/pinger/night.webp +0 -0
  107. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/xuee/night.webp +0 -0
  108. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/yue/night.webp +0 -0
  109. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/household/li_jiaoer.webp +0 -0
  110. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/scene/gate_collector.webp +0 -0
  111. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +1105 -0
  112. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +2539 -0
  113. package/lib/novel-to-game/examples/jin-ping-mei/build/app/index.html +18 -0
  114. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/assets.js +147 -0
  115. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/audio.js +332 -0
  116. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +7472 -0
  117. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +13077 -0
  118. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +3028 -0
  119. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +32 -0
  120. package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +10 -0
  121. package/lib/novel-to-game/examples/jin-ping-mei/build/app/vercel.json +23 -0
  122. package/lib/novel-to-game/examples/jin-ping-mei/build/art/generated-art.json +161 -0
  123. package/lib/novel-to-game/examples/jin-ping-mei/example.json +12 -0
  124. package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +31 -0
  125. package/lib/novel-to-game/examples/jin-ping-mei/qa/playable-model.json +99 -0
  126. package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +41 -0
  127. package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +129 -0
  128. package/lib/novel-to-game/manifest.json +508 -0
  129. package/lib/novel-to-game/skills/game-art-direction/SKILL.md +73 -0
  130. package/lib/novel-to-game/skills/game-art-direction/agents/openai.yaml +4 -0
  131. package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +119 -0
  132. package/lib/novel-to-game/skills/game-build/SKILL.md +84 -0
  133. package/lib/novel-to-game/skills/game-build/agents/openai.yaml +4 -0
  134. package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +109 -0
  135. package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +56 -0
  136. package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +81 -0
  137. package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +37 -0
  138. package/lib/novel-to-game/skills/game-concept/SKILL.md +72 -0
  139. package/lib/novel-to-game/skills/game-concept/agents/openai.yaml +4 -0
  140. package/lib/novel-to-game/skills/game-concept/references/concept-method.md +94 -0
  141. package/lib/novel-to-game/skills/game-qa/SKILL.md +50 -0
  142. package/lib/novel-to-game/skills/game-qa/agents/openai.yaml +4 -0
  143. package/lib/novel-to-game/skills/game-qa/references/qa-contract.md +66 -0
  144. package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +45 -0
  145. package/lib/novel-to-game/skills/game-world-design/SKILL.md +63 -0
  146. package/lib/novel-to-game/skills/game-world-design/agents/openai.yaml +4 -0
  147. package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +59 -0
  148. package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +53 -0
  149. package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +84 -0
  150. package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +56 -0
  151. package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +124 -0
  152. package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +77 -0
  153. package/lib/novel-to-game/skills/novel-game-analyze/agents/openai.yaml +4 -0
  154. package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +65 -0
  155. package/lib/novel-to-game/skills/novel-to-game/SKILL.md +71 -0
  156. package/lib/novel-to-game/skills/novel-to-game/agents/openai.yaml +4 -0
  157. package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +42 -0
  158. package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +51 -0
  159. package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +65 -0
  160. package/lib/oh-story/manifest.json +415 -305
  161. package/lib/oh-story/roles/consistency-checker.md +1 -1
  162. package/lib/oh-story/roles/narrative-writer.md +32 -33
  163. package/lib/oh-story/roles/story-architect.md +16 -26
  164. package/lib/oh-story/skills/browser-cdp/SKILL.md +1 -1
  165. package/lib/oh-story/skills/story/SKILL.md +16 -9
  166. package/lib/oh-story/skills/story/VERSION +1 -1
  167. package/lib/oh-story/skills/story/references/author-memory.md +163 -0
  168. package/lib/oh-story/skills/story/scripts/author_memory_commit.py +871 -0
  169. package/lib/oh-story/skills/story-cover/SKILL.md +1 -1
  170. package/lib/oh-story/skills/story-deslop/SKILL.md +10 -5
  171. package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +2 -1
  172. package/lib/oh-story/skills/story-deslop/references/author-memory.md +163 -0
  173. package/lib/oh-story/skills/story-deslop/references/banned-words.md +6 -4
  174. package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +871 -0
  175. package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +64 -1
  176. package/lib/oh-story/skills/story-import/SKILL.md +19 -10
  177. package/lib/oh-story/skills/story-import/references/format-and-structure.md +9 -10
  178. package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +2 -2
  179. package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +1 -1
  180. package/lib/oh-story/skills/story-import/references/tracking-transaction.md +14 -6
  181. package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +251 -0
  182. package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +98 -9
  183. package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +246 -0
  184. package/lib/oh-story/skills/story-long-analyze/SKILL.md +4 -4
  185. package/lib/oh-story/skills/story-long-scan/SKILL.md +1 -1
  186. package/lib/oh-story/skills/story-long-write/SKILL.md +35 -21
  187. package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +2 -1
  188. package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +7 -3
  189. package/lib/oh-story/skills/story-long-write/references/author-memory.md +163 -0
  190. package/lib/oh-story/skills/story-long-write/references/banned-words.md +6 -4
  191. package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +6 -6
  192. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +1 -1
  193. package/lib/oh-story/skills/story-long-write/references/genre-readers.md +1 -1
  194. package/lib/oh-story/skills/{story-setup/references/agent-references/hooks-chapter.md → story-long-write/references/long-chapter-hooks.md} +5 -5
  195. package/lib/oh-story/skills/story-long-write/references/{quality-checklist.md → long-chapter-quality.md} +2 -1
  196. package/lib/oh-story/skills/story-long-write/references/long-format.md +42 -0
  197. package/lib/oh-story/skills/{story-setup/references/agent-references/genre-catalog.md → story-long-write/references/long-genre-catalog.md} +11 -97
  198. package/lib/oh-story/skills/{story-setup/references/agent-references/genre-core-mechanics.md → story-long-write/references/long-genre-mechanics.md} +1 -1
  199. package/lib/oh-story/skills/{story-short-write/references/reversal-toolkit.md → story-long-write/references/long-reversal.md} +18 -32
  200. package/lib/oh-story/skills/story-long-write/references/{hooks-suspense.md → long-suspense.md} +6 -6
  201. package/lib/oh-story/skills/story-long-write/references/opening-design.md +1 -1
  202. package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +1 -1
  203. package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
  204. package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +14 -6
  205. package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +32 -27
  206. package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +4 -4
  207. package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +2 -2
  208. package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +14 -14
  209. package/lib/oh-story/skills/story-long-write/references/writing-craft.md +54 -54
  210. package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +871 -0
  211. package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +64 -1
  212. package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +251 -0
  213. package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +303 -0
  214. package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +98 -9
  215. package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +246 -0
  216. package/lib/oh-story/skills/story-review/SKILL.md +26 -17
  217. package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +2 -1
  218. package/lib/oh-story/skills/story-review/references/author-memory.md +163 -0
  219. package/lib/oh-story/skills/story-review/references/banned-words.md +6 -4
  220. package/lib/oh-story/skills/story-review/references/{quality-checklist.md → review-quality.md} +22 -21
  221. package/lib/oh-story/skills/story-review/references/tracking-transaction.md +14 -6
  222. package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +871 -0
  223. package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +64 -1
  224. package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +98 -9
  225. package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +246 -0
  226. package/lib/oh-story/skills/story-setup/SKILL.md +74 -33
  227. package/lib/oh-story/skills/story-setup/references/agent-references/agent-quality.md +72 -0
  228. package/lib/oh-story/skills/story-setup/references/agent-references/agent-reference-profiles.md +55 -0
  229. package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +2 -1
  230. package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +6 -4
  231. package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +9 -10
  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/genre-readers.md +1 -1
  234. package/lib/oh-story/skills/{story-short-analyze/references/hooks-chapter.md → story-setup/references/agent-references/long-chapter-hooks.md} +5 -5
  235. package/lib/oh-story/skills/story-setup/references/agent-references/long-emotional-methods.md +209 -0
  236. package/lib/oh-story/skills/{story-long-write/references/genre-catalog.md → story-setup/references/agent-references/long-genre-catalog.md} +11 -97
  237. package/lib/oh-story/skills/{story-long-write/references/genre-core-mechanics.md → story-setup/references/agent-references/long-genre-mechanics.md} +1 -1
  238. package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +40 -0
  239. package/lib/oh-story/skills/story-setup/references/agent-references/{reversal-toolkit.md → long-reversal.md} +18 -32
  240. package/lib/oh-story/skills/{story-short-write/references/hooks-suspense.md → story-setup/references/agent-references/long-suspense.md} +6 -6
  241. package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +1 -1
  242. package/lib/oh-story/skills/story-setup/references/agent-references/{emotional-methods.md → short-emotional-methods.md} +4 -4
  243. package/lib/oh-story/skills/{story-long-write/references/genre-writing-formulas.md → story-setup/references/agent-references/short-genre-formulas.md} +32 -44
  244. package/lib/oh-story/skills/story-setup/references/agent-references/short-quality.md +39 -0
  245. package/lib/oh-story/skills/{story-long-write/references/reversal-toolkit.md → story-setup/references/agent-references/short-reversal.md} +29 -23
  246. package/lib/oh-story/skills/story-setup/references/agent-references/{hooks-suspense.md → short-suspense.md} +31 -31
  247. package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
  248. package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +55 -55
  249. package/lib/oh-story/skills/story-short-analyze/SKILL.md +28 -28
  250. package/lib/oh-story/skills/story-short-analyze/references/{character-basics.md → analysis-character-basics.md} +1 -1
  251. package/lib/oh-story/skills/story-short-analyze/references/{character-design-methods.md → analysis-character-design.md} +1 -1
  252. package/lib/oh-story/skills/story-short-analyze/references/{character-relations.md → analysis-character-relations.md} +1 -1
  253. package/lib/oh-story/skills/story-short-analyze/references/{hooks-paragraph.md → analysis-paragraph-hooks.md} +1 -1
  254. package/lib/oh-story/skills/story-short-analyze/references/{genre-readers.md → analysis-reader-profile.md} +2 -2
  255. package/lib/oh-story/skills/story-short-analyze/references/analysis-report-style.md +45 -0
  256. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-genres.md +65 -0
  257. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-hooks.md +77 -0
  258. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-mechanics.md +82 -0
  259. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-patterns.md +50 -0
  260. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-suspense.md +98 -0
  261. package/lib/oh-story/skills/story-short-analyze/references/{genre-writing-techniques.md → analysis-writing-techniques.md} +21 -19
  262. package/lib/oh-story/skills/story-short-analyze/references/deconstruction-examples.md +1 -1
  263. package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +21 -28
  264. package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +1 -1
  265. package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +17 -17
  266. package/lib/oh-story/skills/story-short-analyze/references/{quality-checklist.md → source-story-quality.md} +28 -49
  267. package/lib/oh-story/skills/story-short-analyze/references/zhihu-style.md +2 -2
  268. package/lib/oh-story/skills/story-short-scan/SKILL.md +1 -1
  269. package/lib/oh-story/skills/story-short-write/SKILL.md +78 -55
  270. package/lib/oh-story/skills/story-short-write/references/author-memory.md +163 -0
  271. package/lib/oh-story/skills/story-short-write/references/banned-words.md +6 -4
  272. package/lib/oh-story/skills/story-short-write/references/cross-book-recall.md +3 -3
  273. package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +4 -4
  274. package/lib/oh-story/skills/story-short-write/references/genre-styles//344/270/226/346/203/205/346/211/223/350/204/270.md +3 -3
  275. package/lib/oh-story/skills/story-short-write/references/genre-styles//345/217/214/347/224/267/344/270/273.md +2 -2
  276. package/lib/oh-story/skills/story-short-write/references/genre-styles//345/244/215/344/273/207/346/211/223/350/204/270.md +3 -3
  277. package/lib/oh-story/skills/story-short-write/references/genre-styles//345/256/205/346/226/227/345/256/253/346/226/227.md +6 -6
  278. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/200/273/350/243/201/350/261/252/351/227/250.md +3 -3
  279. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/202/254/347/226/221.md +4 -4
  280. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/260/221/344/277/227/346/200/252/350/260/210.md +7 -7
  281. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/262/231/351/233/225/350/204/221/346/264/236.md +5 -5
  282. package/lib/oh-story/skills/story-short-write/references/genre-styles//347/224/234/345/256/240.md +4 -4
  283. package/lib/oh-story/skills/story-short-write/references/genre-styles//350/277/275/345/246/273/347/201/253/350/221/254/345/234/272.md +3 -3
  284. package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +32 -44
  285. package/lib/oh-story/skills/story-short-write/references/genre-writing-techniques.md +20 -18
  286. package/lib/oh-story/skills/story-short-write/references/output-contract.md +1 -1
  287. package/lib/oh-story/skills/story-short-write/references/short-craft.md +18 -18
  288. package/lib/oh-story/skills/story-short-write/references/short-deslop.md +1 -1
  289. package/lib/oh-story/skills/story-short-write/references/short-format.md +38 -8
  290. package/lib/oh-story/skills/story-short-write/references/{quality-checklist.md → short-prose-quality.md} +22 -21
  291. package/lib/oh-story/skills/story-short-write/references/short-reversal.md +355 -0
  292. package/lib/oh-story/skills/{story-short-analyze/references/hooks-suspense.md → story-short-write/references/short-suspense.md} +31 -31
  293. package/lib/oh-story/skills/story-short-write/references/submission-craft.md +2 -2
  294. package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +31 -22
  295. package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +871 -0
  296. package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +64 -1
  297. package/lib/oh-story/skills/story-short-write/scripts/check-delivery-contract.js +212 -0
  298. package/lib/oh-story/skills/story-short-write/scripts/check-phase2-contract.js +363 -0
  299. package/lib/types/client/drama-production-view.d.ts +34 -0
  300. package/lib/types/client/drama-production-view.d.ts.map +1 -0
  301. package/lib/types/client/drama-production.d.ts +92 -0
  302. package/lib/types/client/drama-production.d.ts.map +1 -0
  303. package/lib/types/client/file-activity.d.ts +4 -2
  304. package/lib/types/client/file-activity.d.ts.map +1 -1
  305. package/lib/types/client/index.d.ts +1 -1
  306. package/lib/types/client/index.d.ts.map +1 -1
  307. package/lib/types/client/production-intents.d.ts +9 -0
  308. package/lib/types/client/production-intents.d.ts.map +1 -0
  309. package/lib/types/client/production-prompts.d.ts +9 -0
  310. package/lib/types/client/production-prompts.d.ts.map +1 -0
  311. package/lib/types/client/production-runtime.d.ts +58 -0
  312. package/lib/types/client/production-runtime.d.ts.map +1 -0
  313. package/lib/types/client/video-studio.d.ts +41 -0
  314. package/lib/types/client/video-studio.d.ts.map +1 -0
  315. package/lib/types/client/workbench-ui.d.ts +5 -0
  316. package/lib/types/client/workbench-ui.d.ts.map +1 -0
  317. package/lib/types/game-verification.d.ts +16 -0
  318. package/lib/types/game-verification.d.ts.map +1 -0
  319. package/lib/types/index.d.ts +3 -1
  320. package/lib/types/index.d.ts.map +1 -1
  321. package/lib/types/production-intent.d.ts +21 -0
  322. package/lib/types/production-intent.d.ts.map +1 -0
  323. package/lib/types/production-tool.d.ts +5 -0
  324. package/lib/types/production-tool.d.ts.map +1 -0
  325. package/lib/types/role-tool.d.ts.map +1 -1
  326. package/lib/types/skill-provider.d.ts +7 -0
  327. package/lib/types/skill-provider.d.ts.map +1 -1
  328. package/lib/types/video-project.d.ts +42 -0
  329. package/lib/types/video-project.d.ts.map +1 -0
  330. package/lib/types/workspace-request-trust.d.ts +6 -0
  331. package/lib/types/workspace-request-trust.d.ts.map +1 -1
  332. package/lib/types/workspace-route.d.ts +20 -0
  333. package/lib/types/workspace-route.d.ts.map +1 -1
  334. package/lib/video-recap/CHANGELOG.md +282 -0
  335. package/lib/video-recap/LICENSE +21 -0
  336. package/lib/video-recap/README.en.md +257 -0
  337. package/lib/video-recap/README.md +255 -0
  338. package/lib/video-recap/manifest.json +684 -0
  339. package/lib/video-recap/skills/video-assemble/SKILL.md +97 -0
  340. package/lib/video-recap/skills/video-assemble/references/jianying/LICENSE.duo-video +21 -0
  341. package/lib/video-recap/skills/video-assemble/references/jianying/SOURCE.md +15 -0
  342. package/lib/video-recap/skills/video-assemble/references/jianying/empty_draft_meta_info.json +69 -0
  343. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_segment.json +84 -0
  344. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_video_material.json +98 -0
  345. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_draft.json +204 -0
  346. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_material_video.json +83 -0
  347. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_meta_material_value.json +23 -0
  348. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_project_info.json +133 -0
  349. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_segment.json +70 -0
  350. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_text_styles.json +30 -0
  351. package/lib/video-recap/skills/video-assemble/references/jianying/empty_yj_material_audio.json +41 -0
  352. package/lib/video-recap/skills/video-assemble/references/jianying/empty_yj_material_text.json +109 -0
  353. package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +88 -0
  354. package/lib/video-recap/skills/video-assemble/scripts/assemble.py +345 -0
  355. package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +34 -0
  356. package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +305 -0
  357. package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +94 -0
  358. package/lib/video-recap/skills/video-assemble/scripts/audio_automation.py +205 -0
  359. package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +555 -0
  360. package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +261 -0
  361. package/lib/video-recap/skills/video-assemble/scripts/jianying_builders.py +767 -0
  362. package/lib/video-recap/skills/video-assemble/scripts/jianying_model.py +111 -0
  363. package/lib/video-recap/skills/video-assemble/scripts/jianying_optional.py +24 -0
  364. package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +109 -0
  365. package/lib/video-recap/skills/video-assemble/scripts/jianying_templates.py +37 -0
  366. package/lib/video-recap/skills/video-assemble/scripts/jianying_timeline_contract.py +191 -0
  367. package/lib/video-recap/skills/video-assemble/scripts/jianying_tracks.py +75 -0
  368. package/lib/video-recap/skills/video-assemble/scripts/jianying_writer.py +394 -0
  369. package/lib/video-recap/skills/video-assemble/scripts/lib.py +239 -0
  370. package/lib/video-recap/skills/video-assemble/scripts/media.py +243 -0
  371. package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +477 -0
  372. package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +46 -0
  373. package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +486 -0
  374. package/lib/video-recap/skills/video-assemble/scripts/subtitle_core.py +376 -0
  375. package/lib/video-recap/skills/video-assemble/scripts/subtitle_render.py +88 -0
  376. package/lib/video-recap/skills/video-assemble/scripts/timeline.py +248 -0
  377. package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +89 -0
  378. package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +512 -0
  379. package/lib/video-recap/skills/video-cut/SKILL.md +90 -0
  380. package/lib/video-recap/skills/video-cut/scripts/cut.py +67 -0
  381. package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +320 -0
  382. package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +545 -0
  383. package/lib/video-recap/skills/video-cut/scripts/cut_render.py +348 -0
  384. package/lib/video-recap/skills/video-cut/scripts/lib.py +77 -0
  385. package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +341 -0
  386. package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +249 -0
  387. package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +745 -0
  388. package/lib/video-recap/skills/video-recap/SKILL.md +216 -0
  389. package/lib/video-recap/skills/video-recap/references/config-playbook.md +61 -0
  390. package/lib/video-recap/skills/video-recap/references/creative-editing-playbook.md +225 -0
  391. package/lib/video-recap/skills/video-recap/references/data-schema.md +534 -0
  392. package/lib/video-recap/skills/video-recap/references/research-guide.md +74 -0
  393. package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +23 -0
  394. package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +251 -0
  395. package/lib/video-recap/skills/video-recap/scripts/doctor.py +483 -0
  396. package/lib/video-recap/skills/video-recap/scripts/final_qc.py +737 -0
  397. package/lib/video-recap/skills/video-recap/scripts/lib.py +242 -0
  398. package/lib/video-recap/skills/video-recap/scripts/materials.py +457 -0
  399. package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +95 -0
  400. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_client.py +18 -0
  401. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_contract.py +9 -0
  402. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_evidence.py +371 -0
  403. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_observations.py +201 -0
  404. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_payload.py +227 -0
  405. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_report.py +362 -0
  406. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_runner.py +129 -0
  407. package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +338 -0
  408. package/lib/video-recap/skills/video-recap/scripts/recap.py +9 -0
  409. package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +140 -0
  410. package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +550 -0
  411. package/lib/video-recap/skills/video-recap/scripts/recap_review.py +77 -0
  412. package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +654 -0
  413. package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +335 -0
  414. package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +217 -0
  415. package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +795 -0
  416. package/lib/video-recap/skills/video-script/SKILL.md +248 -0
  417. package/lib/video-recap/skills/video-script/references/creative-editing-playbook.md +225 -0
  418. package/lib/video-recap/skills/video-script/references/research-guide.md +75 -0
  419. package/lib/video-recap/skills/video-script/scripts/agent_brief.py +399 -0
  420. package/lib/video-recap/skills/video-script/scripts/agent_text.py +410 -0
  421. package/lib/video-recap/skills/video-script/scripts/brief_context.py +403 -0
  422. package/lib/video-recap/skills/video-script/scripts/brief_inputs.py +332 -0
  423. package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +442 -0
  424. package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +237 -0
  425. package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +492 -0
  426. package/lib/video-recap/skills/video-script/scripts/lib.py +327 -0
  427. package/lib/video-recap/skills/video-script/scripts/narration.py +12 -0
  428. package/lib/video-recap/skills/video-script/scripts/narration_lint.py +678 -0
  429. package/lib/video-recap/skills/video-script/scripts/review.py +42 -0
  430. package/lib/video-recap/skills/video-script/scripts/review_grounding.py +262 -0
  431. package/lib/video-recap/skills/video-script/scripts/review_response.py +761 -0
  432. package/lib/video-recap/skills/video-script/scripts/review_runner.py +200 -0
  433. package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +204 -0
  434. package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +199 -0
  435. package/lib/video-recap/skills/video-script/scripts/validate.py +164 -0
  436. package/lib/video-recap/skills/video-understanding/SKILL.md +75 -0
  437. package/lib/video-recap/skills/video-understanding/references/data-schema.md +299 -0
  438. package/lib/video-recap/skills/video-understanding/references/prompt-templates.md +37 -0
  439. package/lib/video-recap/skills/video-understanding/references/research-guide.md +74 -0
  440. package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +399 -0
  441. package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +410 -0
  442. package/lib/video-recap/skills/video-understanding/scripts/asr.py +260 -0
  443. package/lib/video-recap/skills/video-understanding/scripts/brief.py +12 -0
  444. package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +403 -0
  445. package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +332 -0
  446. package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +442 -0
  447. package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +647 -0
  448. package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +237 -0
  449. package/lib/video-recap/skills/video-understanding/scripts/detect.py +513 -0
  450. package/lib/video-recap/skills/video-understanding/scripts/extract.py +62 -0
  451. package/lib/video-recap/skills/video-understanding/scripts/lib.py +470 -0
  452. package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +678 -0
  453. package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +204 -0
  454. package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +507 -0
  455. package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +199 -0
  456. package/lib/video-recap/skills/video-understanding/scripts/understand.py +9 -0
  457. package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +216 -0
  458. package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +327 -0
  459. package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +367 -0
  460. package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +162 -0
  461. package/lib/video-recap/skills/video-understanding/scripts/vlm.py +705 -0
  462. package/lib/video-recap/skills/video-voiceover/SKILL.md +81 -0
  463. package/lib/video-recap/skills/video-voiceover/scripts/dub.py +637 -0
  464. package/lib/video-recap/skills/video-voiceover/scripts/fish_audio.py +109 -0
  465. package/lib/video-recap/skills/video-voiceover/scripts/lib.py +421 -0
  466. package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +113 -0
  467. package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +782 -0
  468. package/package.json +55 -40
  469. package/lib/oh-story/skills/story-deslop/scripts/check-outline-copy.js +0 -241
  470. package/lib/oh-story/skills/story-long-write/references/format-and-structure.md +0 -169
  471. package/lib/oh-story/skills/story-setup/references/agent-references/genre-writing-formulas.md +0 -444
  472. package/lib/oh-story/skills/story-setup/references/agent-references/quality-checklist.md +0 -247
  473. package/lib/oh-story/skills/story-short-analyze/references/anti-ai-writing.md +0 -582
  474. package/lib/oh-story/skills/story-short-analyze/references/banned-words.md +0 -147
  475. package/lib/oh-story/skills/story-short-analyze/references/genre-catalog.md +0 -490
  476. package/lib/oh-story/skills/story-short-analyze/references/genre-core-mechanics.md +0 -425
  477. package/lib/oh-story/skills/story-short-analyze/references/genre-writing-formulas.md +0 -453
  478. /package/lib/oh-story/skills/{story-long-write/references/hooks-chapter.md → story-setup/references/agent-references/short-chapter-hooks.md} +0 -0
  479. /package/lib/oh-story/skills/{story-long-write/references/hooks-paragraph.md → story-setup/references/agent-references/short-paragraph-hooks.md} +0 -0
@@ -0,0 +1,200 @@
1
+ """Run narration review and write its advisory artifacts."""
2
+
3
+ import argparse
4
+
5
+ import json
6
+
7
+
8
+ from pathlib import Path
9
+
10
+ from lib import CONFIG, log, api_call
11
+
12
+ from evidence_bundle import build_evidence_bundle
13
+ from review_grounding import (
14
+ _load,
15
+ _load_cut_clip_spans,
16
+ _load_review_grounding,
17
+ remap_grounding_to_output_timeline,
18
+ )
19
+ from review_response import (
20
+ _append_warning_once,
21
+ _bundle_fingerprint,
22
+ _bundle_fingerprint_warning,
23
+ _chunk_evidence_bundle,
24
+ _load_review_research_context,
25
+ _merge_chunk_reviews,
26
+ _write_grounding_qc,
27
+ build_review_messages,
28
+ format_review_md,
29
+ parse_review_response,
30
+ )
31
+
32
+ EVIDENCE_CONTRACT_VERSION = 1
33
+
34
+ COVERAGE_POLICY_VERSION = "coverage_policy_v1"
35
+
36
+
37
+ def review_narration(work_dir, *, timeline="source", strict_evidence=False):
38
+ work_dir = Path(work_dir)
39
+ narration = _load(work_dir, "narration.json")
40
+ if narration is None:
41
+ raise SystemExit(f"缺少 {work_dir / 'narration.json'};先写解说草稿再评审")
42
+ vlm_analysis, asr_result = _load_review_grounding(work_dir)
43
+ warnings = []
44
+ if timeline == "cut_output":
45
+ spans = _load_cut_clip_spans(work_dir)
46
+ if not spans:
47
+ msg = "cut_output review missing/stale clip_plan_validated.json; advisory fail-open, no strong OUTPUT-clock facts"
48
+ if strict_evidence:
49
+ raise SystemExit(msg)
50
+ warnings.append(msg)
51
+ vlm_analysis, asr_result = [], []
52
+ else:
53
+ vlm_analysis, asr_result = remap_grounding_to_output_timeline(
54
+ vlm_analysis, asr_result, spans
55
+ )
56
+ elif timeline != "source":
57
+ raise SystemExit(f"unknown review timeline: {timeline}")
58
+
59
+ bundle = build_evidence_bundle(
60
+ vlm_analysis,
61
+ asr_result,
62
+ narration,
63
+ timeline=timeline,
64
+ research=_load_review_research_context(work_dir),
65
+ warnings=warnings,
66
+ )
67
+ bundle_fp = _bundle_fingerprint(bundle)
68
+ fp_warning = _bundle_fingerprint_warning(bundle)
69
+ if fp_warning:
70
+ _append_warning_once(warnings, fp_warning)
71
+ _append_warning_once(bundle.setdefault("warnings", []), fp_warning)
72
+ chunk_reviews = []
73
+ chunks = _chunk_evidence_bundle(bundle)
74
+ for chunk in chunks:
75
+ messages = build_review_messages(
76
+ narration,
77
+ vlm_analysis,
78
+ asr_result,
79
+ work_dir=work_dir,
80
+ evidence_bundle=chunk,
81
+ )
82
+ resp = api_call(
83
+ {
84
+ "model": CONFIG.get("vlm_model", ""),
85
+ "messages": messages,
86
+ "max_tokens": 2000 if len(chunks) == 1 else 1600,
87
+ "temperature": 0,
88
+ "seed": 7 + int(chunk.get("chunk_index", 0)),
89
+ }
90
+ )
91
+ content = ""
92
+ try:
93
+ content = resp["choices"][0]["message"]["content"]
94
+ except (KeyError, IndexError, TypeError):
95
+ log("评审 API 返回结构异常")
96
+ parsed = parse_review_response(content)
97
+ parsed["chunk_index"] = chunk.get("chunk_index", 0)
98
+ parsed["chunk_count"] = chunk.get("chunk_count", len(chunks))
99
+ chunk_reviews.append(parsed)
100
+ review = _merge_chunk_reviews(chunk_reviews)
101
+ if warnings:
102
+ review["warnings"] = list(warnings)
103
+ review["evidence_contract"] = {
104
+ "schema_version": EVIDENCE_CONTRACT_VERSION,
105
+ "timeline": timeline,
106
+ "clock": bundle.get("clock"),
107
+ "coverage_policy_version": COVERAGE_POLICY_VERSION,
108
+ "selected_ranges": bundle.get("coverage", {}).get("selected_ranges", []),
109
+ "evidence_bundle_fingerprint": bundle_fp,
110
+ "chunk_count": len(chunks),
111
+ "warnings": warnings,
112
+ }
113
+ _write_grounding_qc(work_dir, review, bundle, timeline=timeline)
114
+
115
+ (work_dir / "narration_review.json").write_text(
116
+ json.dumps(review, ensure_ascii=False, indent=2), encoding="utf-8"
117
+ )
118
+ (work_dir / "narration_review.md").write_text(
119
+ format_review_md(review), encoding="utf-8"
120
+ )
121
+ n_err = sum(1 for f in review["findings"] if f["severity"] == "error")
122
+ log(
123
+ f"解说评审完成: {review['verdict']} | {len(review['findings'])} 条意见(error {n_err})"
124
+ )
125
+ return review
126
+
127
+
128
+ def _auto_timeline(work_dir):
129
+ """Default the grounding timeline so a manual `review.py --work-dir` matches what the
130
+ orchestrator does: cut_output when narration.json is in the cut OUTPUT timeline, else
131
+ source. Without this, reviewing a cut narration on the default 'source' timeline compares
132
+ OUTPUT-time narration against SOURCE-time evidence and floods false-positive 'hallucination'
133
+ findings (and the inverse flood for a legacy source-time narration mis-read as cut_output).
134
+
135
+ Detection is authoritative-first: the orchestrator records the run's edit_mode in
136
+ recap_run_manifest.json. In orchestrated cut mode narration.json is OUTPUT time; in full
137
+ mode it is SOURCE time. Trusting edit_mode is correct even when stale cut artifacts from a
138
+ prior run linger in a reused work_dir. Only when no manifest is present (standalone review
139
+ or a hand-built work_dir) do we fall back to artifact sniffing — and even then the legacy
140
+ legacy direct single-pass path writes a SOURCE-time narration.json alongside a separate
141
+ output-time narration_mapped.json, so its presence pins us back to source."""
142
+ work_dir = Path(work_dir)
143
+ manifest = work_dir / "recap_run_manifest.json"
144
+ if manifest.exists():
145
+ try:
146
+ mode = (
147
+ json.loads(manifest.read_text(encoding="utf-8"))
148
+ .get("settings", {})
149
+ .get("edit_mode")
150
+ )
151
+ except (ValueError, OSError):
152
+ mode = None
153
+ if mode == "cut":
154
+ return "cut_output"
155
+ if mode: # "full" or any non-cut mode → narration.json is source time
156
+ return "source"
157
+ has_cut = (work_dir / "clip_plan_validated.json").exists() and (
158
+ work_dir / "edited_source.mp4"
159
+ ).exists()
160
+ if has_cut and not (work_dir / "narration_mapped.json").exists():
161
+ return "cut_output"
162
+ return "source"
163
+
164
+
165
+ def main():
166
+ ap = argparse.ArgumentParser(
167
+ description="Review an agent-written narration.json for quality (LLM-as-judge)."
168
+ )
169
+ ap.add_argument("--work-dir", required=True)
170
+ ap.add_argument(
171
+ "--timeline",
172
+ choices=["source", "cut_output"],
173
+ default=None,
174
+ help="grounding timeline for narration.json; DEFAULT auto-detects cut_output when a "
175
+ "validated cut (clip_plan_validated.json + edited_source.mp4) is present, else source. "
176
+ "cut_output remaps source VLM/ASR to the cut output timeline via clip_plan_validated.json",
177
+ )
178
+ ap.add_argument(
179
+ "--strict-evidence",
180
+ action="store_true",
181
+ help="block instead of advisory fail-open when required cut-output evidence mapping is missing/stale",
182
+ )
183
+ args = ap.parse_args()
184
+ timeline = args.timeline or _auto_timeline(args.work_dir)
185
+ if args.timeline is None and timeline != "source":
186
+ log(f"评审 grounding 时间轴自动判定为 {timeline}(检测到已校验的剪辑产物)")
187
+ review = review_narration(
188
+ args.work_dir, timeline=timeline, strict_evidence=args.strict_evidence
189
+ )
190
+ print(
191
+ json.dumps(
192
+ {
193
+ "status": "reviewed",
194
+ "verdict": review["verdict"],
195
+ "findings": len(review["findings"]),
196
+ "review": str(Path(args.work_dir) / "narration_review.md"),
197
+ },
198
+ ensure_ascii=False,
199
+ )
200
+ )
@@ -0,0 +1,204 @@
1
+ """Load measured source-speech evidence and classify narration ownership."""
2
+
3
+ import json
4
+ import math
5
+ from pathlib import Path
6
+
7
+ from lib import CONFIG, stable_hash
8
+
9
+
10
+ def _empty_evidence(mode):
11
+ return {
12
+ "anchors": [],
13
+ "speech_spans": [],
14
+ "quiet_windows": [],
15
+ "require_measured": mode == "cut_output",
16
+ }
17
+
18
+
19
+ def _read_json(path):
20
+ try:
21
+ return json.loads(Path(path).read_text(encoding="utf-8"))
22
+ except (OSError, json.JSONDecodeError):
23
+ return None
24
+
25
+
26
+ def _timed_rows(payload, key):
27
+ rows = payload.get(key, []) if isinstance(payload, dict) else []
28
+ out = []
29
+ for row in rows if isinstance(rows, list) else []:
30
+ if not isinstance(row, dict):
31
+ continue
32
+ try:
33
+ start, end = float(row.get("start")), float(row.get("end"))
34
+ except (TypeError, ValueError):
35
+ continue
36
+ if math.isfinite(start) and math.isfinite(end) and end > start:
37
+ out.append({**row, "start": start, "end": end})
38
+ return out
39
+
40
+
41
+ def _output_payload_is_current(payload, work_dir):
42
+ if not isinstance(payload, dict):
43
+ return False
44
+ if payload.get("schema_version") != 2 or payload.get("timeline") != "cut_output":
45
+ return False
46
+ plan = _read_json(Path(work_dir) / "clip_plan_validated.json")
47
+ return bool(
48
+ isinstance(plan, dict)
49
+ and payload.get("clip_plan_fingerprint") == stable_hash(plan)
50
+ )
51
+
52
+
53
+ def load_source_sentence_evidence(work_dir, mode="full"):
54
+ """Load sentence boundaries plus speech/quiet spans on the narration clock."""
55
+ if work_dir is None:
56
+ return _empty_evidence(mode)
57
+ work_dir = Path(work_dir)
58
+ output_mode = mode in {"cut", "cut_output"}
59
+ path = work_dir / (
60
+ "speech_boundary_anchors_output.json"
61
+ if output_mode
62
+ else "speech_boundary_anchors.json"
63
+ )
64
+ payload = _read_json(path)
65
+ if mode == "cut_output" and not _output_payload_is_current(payload, work_dir):
66
+ return _empty_evidence(mode)
67
+ if not isinstance(payload, dict):
68
+ payload = {}
69
+
70
+ speech_spans = _timed_rows(payload, "speech_spans")
71
+ quiet_windows = _timed_rows(payload, "quiet_windows")
72
+ if not output_mode:
73
+ for name in ("asr_result.json", "asr_clean.json"):
74
+ raw = _read_json(work_dir / name)
75
+ if isinstance(raw, list):
76
+ candidate = {"speech_spans": raw}
77
+ elif isinstance(raw, dict):
78
+ candidate = {"speech_spans": raw.get("segments", [])}
79
+ else:
80
+ continue
81
+ speech_spans = _timed_rows(candidate, "speech_spans")
82
+ if speech_spans:
83
+ break
84
+ raw_quiet = _read_json(work_dir / "silence_periods.json")
85
+ candidate = {
86
+ "quiet_windows": [
87
+ row
88
+ for row in raw_quiet
89
+ if isinstance(row, dict) and not bool(row.get("has_speech", False))
90
+ ]
91
+ } if isinstance(raw_quiet, list) else {}
92
+ quiet_windows = _timed_rows(candidate, "quiet_windows")
93
+
94
+ anchors = []
95
+ for anchor in payload.get("sentence_anchors", []):
96
+ if not isinstance(anchor, dict) or anchor.get("confidence") not in {
97
+ "high",
98
+ "medium",
99
+ }:
100
+ continue
101
+ try:
102
+ when = float(anchor.get("time"))
103
+ except (TypeError, ValueError):
104
+ continue
105
+ if math.isfinite(when) and when >= 0:
106
+ anchors.append({**anchor, "time": round(when, 3)})
107
+ return {
108
+ "anchors": sorted(anchors, key=lambda item: item["time"]),
109
+ "speech_spans": speech_spans,
110
+ "quiet_windows": quiet_windows,
111
+ "require_measured": mode == "cut_output",
112
+ }
113
+
114
+
115
+ def _merged_intervals(start, end, rows):
116
+ intervals = sorted(
117
+ (max(start, row["start"]), min(end, row["end"]))
118
+ for row in rows
119
+ if row["end"] > start and row["start"] < end
120
+ )
121
+ merged = []
122
+ for left, right in intervals:
123
+ if right <= left:
124
+ continue
125
+ if merged and left <= merged[-1][1]:
126
+ merged[-1] = (merged[-1][0], max(merged[-1][1], right))
127
+ else:
128
+ merged.append((left, right))
129
+ return merged
130
+
131
+
132
+ def _interval_overlap(start, end, rows):
133
+ return sum(right - left for left, right in _merged_intervals(start, end, rows))
134
+
135
+
136
+ def _speech_overlap_excluding_quiet(start, end, speech, quiet):
137
+ speech_intervals = _merged_intervals(start, end, speech)
138
+ quiet_intervals = _merged_intervals(start, end, quiet)
139
+ overlap = sum(right - left for left, right in speech_intervals)
140
+ for speech_left, speech_right in speech_intervals:
141
+ overlap -= sum(
142
+ max(0.0, min(speech_right, quiet_right) - max(speech_left, quiet_left))
143
+ for quiet_left, quiet_right in quiet_intervals
144
+ )
145
+ return max(0.0, overlap)
146
+
147
+
148
+ def segment_overlaps_source_speech(seg, evidence):
149
+ """Classify aggregate mix ownership across the complete narration interval."""
150
+ try:
151
+ start, end = float(seg.get("start")), float(seg.get("end"))
152
+ except (AttributeError, TypeError, ValueError):
153
+ return bool(getattr(seg, "get", lambda *_: True)("overlaps_speech", True))
154
+ duration = max(0.0, end - start)
155
+ quiet = evidence["quiet_windows"]
156
+ quiet_min = max(
157
+ 0.3,
158
+ duration * float(CONFIG.get("quiet_overlap_min_ratio", 0.8) or 0.8),
159
+ )
160
+ speech = evidence["speech_spans"]
161
+ if speech:
162
+ return _speech_overlap_excluding_quiet(start, end, speech, quiet) > 0.05
163
+ if quiet and _interval_overlap(start, end, quiet) >= quiet_min:
164
+ return False
165
+ if evidence["anchors"] or evidence["require_measured"]:
166
+ return True
167
+ return bool(seg.get("overlaps_speech", True))
168
+
169
+
170
+ def entry_overlaps_source_speech(seg, evidence, tolerance=0.05):
171
+ """Classify the entry instant; later quiet time cannot erase an unsafe start."""
172
+ try:
173
+ start = float(seg.get("start"))
174
+ except (AttributeError, TypeError, ValueError):
175
+ return True
176
+ if any(
177
+ row["start"] - tolerance <= start <= row["end"] + tolerance
178
+ for row in evidence["quiet_windows"]
179
+ ):
180
+ return False
181
+ if any(
182
+ row["start"] - tolerance <= start < row["end"] - tolerance
183
+ for row in evidence["speech_spans"]
184
+ ):
185
+ return True
186
+ if evidence["speech_spans"]:
187
+ return False
188
+ if evidence["anchors"] or evidence["require_measured"]:
189
+ return True
190
+ return bool(seg.get("overlaps_speech", True))
191
+
192
+
193
+ def measure_narration_speech_ownership(narration, work_dir, mode="full"):
194
+ """Return narration copies with aggregate ownership derived from evidence."""
195
+ evidence = load_source_sentence_evidence(work_dir, mode=mode)
196
+ measured = []
197
+ for seg in narration if isinstance(narration, list) else []:
198
+ if not isinstance(seg, dict):
199
+ measured.append(seg)
200
+ continue
201
+ item = dict(seg)
202
+ item["overlaps_speech"] = segment_overlaps_source_speech(item, evidence)
203
+ measured.append(item)
204
+ return measured
@@ -0,0 +1,199 @@
1
+ """Fuse scenes, ASR, and quiet windows for narration planning."""
2
+
3
+ import importlib.util
4
+
5
+
6
+ from pathlib import Path
7
+
8
+ from lib import CONFIG
9
+
10
+
11
+ from agent_text import _overlap_seconds, _recommended_char_budget
12
+ from narration_lint import _validate_narration_budget
13
+
14
+ try:
15
+ from deslop_qc import analyze_deslop_qc
16
+ except ModuleNotFoundError:
17
+ _deslop_qc_path = Path(__file__).with_name("deslop_qc.py")
18
+ _deslop_qc_spec = importlib.util.spec_from_file_location(
19
+ "deslop_qc", _deslop_qc_path
20
+ )
21
+ if _deslop_qc_spec is None or _deslop_qc_spec.loader is None:
22
+ raise
23
+ _deslop_qc_module = importlib.util.module_from_spec(_deslop_qc_spec)
24
+ _deslop_qc_spec.loader.exec_module(_deslop_qc_module)
25
+ analyze_deslop_qc = _deslop_qc_module.analyze_deslop_qc
26
+
27
+
28
+ def _align_narration_to_quiet(narration, scenes_analysis, silence_periods):
29
+ """Recompute overlaps_speech from real quiet windows; keep the agent's timing.
30
+
31
+ The dense continuous-bed design places narration ON the pictured beat over a
32
+ ducked original bed, so we no longer relocate segments into silence gaps. The
33
+ old shift moved voiceover up to 3s off the moment it was written for and could
34
+ silently blank a squeezed segment; both fought the design intent. We now only
35
+ correct the overlaps_speech flag that the ducking stage consumes, leaving the
36
+ agent's start/end (and text) intact.
37
+ """
38
+ if not silence_periods:
39
+ for n in narration:
40
+ n["overlaps_speech"] = True
41
+ return _validate_narration_budget(narration, scenes_analysis)
42
+
43
+ quiet_windows = [
44
+ qp for qp in silence_periods if _silence_window_is_usable_quiet(qp)
45
+ ]
46
+ quiet_ratio_min = float(CONFIG.get("quiet_overlap_min_ratio", 0.8) or 0.8)
47
+
48
+ # Run budget/dedup FIRST, then set the flag on the final (possibly merged) spans,
49
+ # so a dedup-merged beat's overlaps_speech reflects its extended timing, not its
50
+ # original shorter span (which the ducking stage in assemble.py would mis-duck).
51
+ aligned = _validate_narration_budget(narration, scenes_analysis)
52
+ for n in aligned:
53
+ try:
54
+ seg_start = float(n["start"])
55
+ seg_end = float(n["end"])
56
+ except (KeyError, TypeError, ValueError):
57
+ n["overlaps_speech"] = True
58
+ continue
59
+ seg_dur = max(0.0, seg_end - seg_start)
60
+ quiet_overlap = _quiet_overlap_seconds(seg_start, seg_end, quiet_windows)
61
+ n["overlaps_speech"] = quiet_overlap < max(0.3, seg_dur * quiet_ratio_min)
62
+
63
+ return aligned
64
+
65
+
66
+ def _scene_asr_lines(asr_result, scene):
67
+ lines = []
68
+ for seg in asr_result or []:
69
+ try:
70
+ start = float(seg.get("start", 0))
71
+ end = float(seg.get("end", start))
72
+ except (TypeError, ValueError):
73
+ continue
74
+ if scene["start"] < end and scene["end"] > start:
75
+ text = str(seg.get("text", "")).strip()
76
+ if text:
77
+ lines.append(f" [{start:.1f}-{end:.1f}] {text}")
78
+ return lines
79
+
80
+
81
+ def _quiet_windows_for_scene(silence_periods, scene):
82
+ windows = []
83
+ for qp in silence_periods or []:
84
+ if not _silence_window_is_usable_quiet(qp):
85
+ continue
86
+ if qp["start"] < scene["end"] and qp["end"] > scene["start"]:
87
+ start = max(qp["start"], scene["start"])
88
+ end = min(qp["end"], scene["end"])
89
+ if end > start:
90
+ windows.append((start, end))
91
+ return windows
92
+
93
+
94
+ def _silence_window_is_usable_quiet(qp):
95
+ if not isinstance(qp, dict):
96
+ return False
97
+ reason = str(qp.get("has_speech_reason", "")).lower()
98
+ granularity = str(qp.get("asr_granularity", "")).lower()
99
+ if reason in {
100
+ "coarse_asr_overlap_ignored",
101
+ "asr_overlap_low_confidence_quiet",
102
+ "coarse_asr_no_overlap",
103
+ }:
104
+ return True
105
+ if granularity == "coarse_grid" and qp.get("has_speech") is True:
106
+ return True
107
+ return not qp.get("has_speech", False)
108
+
109
+
110
+ def _quiet_overlap_seconds(start, end, quiet_windows):
111
+ overlap_seconds = 0.0
112
+ for qw in quiet_windows:
113
+ try:
114
+ if isinstance(qw, dict):
115
+ q_start = float(qw.get("start", 0))
116
+ q_end = float(qw.get("end", q_start))
117
+ else:
118
+ q_start, q_end = qw
119
+ q_start = float(q_start)
120
+ q_end = float(q_end)
121
+ except (TypeError, ValueError):
122
+ continue
123
+ overlap_seconds += _overlap_seconds(start, end, q_start, q_end)
124
+ return overlap_seconds
125
+
126
+
127
+ def _build_timeline_fusion(scenes, asr_segments, silence_periods):
128
+ """Fuse VLM scenes, ASR dialogue and quiet narration slots on one timeline."""
129
+ fusion = []
130
+ quiet_windows = [
131
+ w for w in silence_periods or [] if _silence_window_is_usable_quiet(w)
132
+ ]
133
+ for scene in scenes or []:
134
+ try:
135
+ start = float(scene.get("start", 0))
136
+ end = float(scene.get("end", start))
137
+ except (TypeError, ValueError):
138
+ continue
139
+ dialogue_segments = []
140
+ dialogue_overlap = 0.0
141
+ for seg in asr_segments or []:
142
+ if not isinstance(seg, dict):
143
+ continue
144
+ try:
145
+ seg_start = float(seg.get("start", 0))
146
+ seg_end = float(seg.get("end", seg_start))
147
+ except (TypeError, ValueError):
148
+ continue
149
+ overlap = _overlap_seconds(start, end, seg_start, seg_end)
150
+ if overlap <= 0:
151
+ continue
152
+ text = str(seg.get("text", "")).strip()
153
+ dialogue_overlap += overlap
154
+ dialogue_segments.append(
155
+ {
156
+ "start": round(seg_start, 2),
157
+ "end": round(seg_end, 2),
158
+ "overlap_seconds": round(overlap, 2),
159
+ "text": text,
160
+ }
161
+ )
162
+
163
+ narration_slots = []
164
+ for window in quiet_windows:
165
+ try:
166
+ w_start = float(window.get("start", 0))
167
+ w_end = float(window.get("end", w_start))
168
+ except (TypeError, ValueError):
169
+ continue
170
+ overlap = _overlap_seconds(start, end, w_start, w_end)
171
+ if overlap <= 0:
172
+ continue
173
+ slot_start = max(start, w_start)
174
+ slot_end = min(end, w_end)
175
+ narration_slots.append(
176
+ {
177
+ "start": round(slot_start, 2),
178
+ "end": round(slot_end, 2),
179
+ "duration": round(slot_end - slot_start, 2),
180
+ "char_budget": _recommended_char_budget(slot_start, slot_end),
181
+ }
182
+ )
183
+
184
+ fusion.append(
185
+ {
186
+ "scene_id": scene.get("scene_id"),
187
+ "time_range": [round(start, 2), round(end, 2)],
188
+ "visual_description": scene.get("description", ""),
189
+ "depth_analysis": scene.get("depth_analysis", ""),
190
+ "frame_facts": scene.get("frame_facts", {}),
191
+ "dialogue_segments": dialogue_segments,
192
+ "dialogue_overlap_seconds": round(dialogue_overlap, 2),
193
+ "narration_slots": narration_slots,
194
+ "recommended_mode": "quiet-slot"
195
+ if narration_slots and dialogue_overlap < (end - start) * 0.4
196
+ else "ducked-bed",
197
+ }
198
+ )
199
+ return fusion