@oh-story/dsh 0.1.10 → 0.1.12

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 +11 -11
  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 +41 -41
  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
@@ -88,7 +88,9 @@ def coverage_policy_v1(
88
88
  "coverage_policy_version": COVERAGE_POLICY_VERSION,
89
89
  "selected_ranges": [],
90
90
  "dropped_ranges": [],
91
+ "dropped_range_count": 0,
91
92
  "duration": 0.0,
93
+ "baseline_range_count_before_narration": 0,
92
94
  }
93
95
  width = min(float(window_seconds), max(5.0, duration / 8.0))
94
96
  ranges = [
@@ -345,72 +347,21 @@ def filter_evidence_by_ranges(vlm_analysis, asr_result, ranges, *, timeline="sou
345
347
 
346
348
 
347
349
  def build_review_coverage_metadata(bundle):
348
- """Pure compatibility seam: summarize the evidence coverage contract."""
349
- items = bundle.get("items") or [] if isinstance(bundle, dict) else []
350
- coverage = bundle.get("coverage", {}) if isinstance(bundle, dict) else {}
351
- metadata = bundle.get("metadata", {}) if isinstance(bundle, dict) else {}
350
+ """Summarize a build_evidence_bundle() bundle's coverage contract."""
351
+ coverage = bundle["coverage"]
352
+ metadata = bundle["metadata"]
352
353
  return {
353
- "coverage_policy_version": coverage.get(
354
- "coverage_policy_version", COVERAGE_POLICY_VERSION
355
- ),
356
- "time_ranges": coverage.get("selected_ranges", []),
357
- "dropped_ranges": coverage.get("dropped_ranges", []),
358
- "dropped_range_count": coverage.get(
359
- "dropped_range_count", len(coverage.get("dropped_ranges", []))
360
- ),
361
- "scene_count": metadata.get(
362
- "reviewed_scene_count",
363
- sum(1 for item in items if item.get("source") == "visual"),
364
- ),
365
- "asr_count": metadata.get(
366
- "reviewed_asr_count",
367
- sum(1 for item in items if item.get("source") == "asr"),
368
- ),
369
- "dropped_scene_count": metadata.get("dropped_scene_count", 0),
370
- "dropped_asr_count": metadata.get("dropped_asr_count", 0),
354
+ "coverage_policy_version": coverage["coverage_policy_version"],
355
+ "time_ranges": coverage["selected_ranges"],
356
+ "dropped_ranges": coverage["dropped_ranges"],
357
+ "dropped_range_count": coverage["dropped_range_count"],
358
+ "scene_count": metadata["reviewed_scene_count"],
359
+ "asr_count": metadata["reviewed_asr_count"],
360
+ "dropped_scene_count": metadata["dropped_scene_count"],
361
+ "dropped_asr_count": metadata["dropped_asr_count"],
371
362
  }
372
363
 
373
364
 
374
- def validate_public_evidence_contract(bundle):
375
- """Pure compatibility seam: validate the public evidence bundle shape.
376
-
377
- Returns a non-throwing report so callers can use it in tests or advisory QC paths.
378
- """
379
- errors = []
380
- warnings = []
381
- if not isinstance(bundle, dict):
382
- return {"valid": False, "errors": ["bundle must be a dict"], "warnings": []}
383
- if bundle.get("schema_version") != EVIDENCE_CONTRACT_VERSION:
384
- errors.append("unsupported schema_version")
385
- if bundle.get("clock") not in ("source", "output"):
386
- errors.append("clock must be source or output")
387
- for idx, item in enumerate(bundle.get("items") or []):
388
- if not isinstance(item, dict):
389
- errors.append(f"items[{idx}] must be a dict")
390
- continue
391
- if item.get("source") not in ("visual", "asr"):
392
- errors.append(f"items[{idx}].source must be visual or asr")
393
- if item.get("clock") not in ("source", "output"):
394
- errors.append(f"items[{idx}].clock must be source or output")
395
- if item.get("support") != "direct":
396
- warnings.append(f"items[{idx}].support is not direct")
397
- start, end = item.get("start"), item.get("end")
398
- try:
399
- if float(end) <= float(start):
400
- errors.append(f"items[{idx}] has non-positive time range")
401
- except (TypeError, ValueError):
402
- errors.append(f"items[{idx}] has invalid time range")
403
- for idx, item in enumerate(bundle.get("context_items") or []):
404
- if not isinstance(item, dict):
405
- errors.append(f"context_items[{idx}] must be a dict")
406
- continue
407
- if item.get("clock") is not None:
408
- errors.append(f"context_items[{idx}].clock must be null")
409
- if item.get("support") != "context_only":
410
- errors.append(f"context_items[{idx}].support must be context_only")
411
- return {"valid": not errors, "errors": errors, "warnings": warnings}
412
-
413
-
414
365
  def build_evidence_bundle(
415
366
  vlm_analysis,
416
367
  asr_result,
@@ -448,30 +399,27 @@ def build_evidence_bundle(
448
399
 
449
400
 
450
401
  def render_evidence_bundle(bundle, *, limit_items=220):
451
- clock = str(bundle.get("clock") or "source").upper()
402
+ clock = bundle["clock"].upper()
452
403
  lines = [f"## Timeline evidence (clock={clock}; source=visual/asr)"]
453
- items = bundle.get("items") or []
404
+ items = bundle["items"]
454
405
  if not items:
455
406
  lines.append("(无 timeline evidence)")
456
407
  for item in items[:limit_items]:
457
- label = "画面" if item.get("source") == "visual" else "对白"
408
+ label = "画面" if item["source"] == "visual" else "对白"
458
409
  backref = ""
459
- if (
460
- item.get("clock") == "output"
461
- and "source_start" in item
462
- and "source_end" in item
463
- ):
464
- backref = f" ← SOURCE {float(item['source_start']):.1f}-{float(item['source_end']):.1f}s"
465
- if item.get("output_segment_index") is not None:
466
- backref += f" clip#{item.get('output_segment_index')}"
410
+ # Remapped cut_output evidence carries its source-time provenance.
411
+ if "source_start" in item:
412
+ backref = f" ← SOURCE {item['source_start']:.1f}-{item['source_end']:.1f}s"
413
+ if "output_segment_index" in item:
414
+ backref += f" clip#{item['output_segment_index']}"
467
415
  lines.append(
468
- f"[{clock} {float(item.get('start', 0)):.1f}-{float(item.get('end', 0)):.1f}s {label} id={item.get('id')}{backref}] {item.get('text', '')}"
416
+ f"[{clock} {item['start']:.1f}-{item['end']:.1f}s {label} id={item['id']}{backref}] {item['text']}"
469
417
  )
470
418
  if len(items) > limit_items:
471
419
  lines.append(
472
420
  f"... dropped from prompt: {len(items) - limit_items} items (artifact metadata keeps counts)"
473
421
  )
474
- context = bundle.get("context_items") or []
422
+ context = bundle["context_items"]
475
423
  lines.extend(
476
424
  [
477
425
  "",
@@ -482,7 +430,7 @@ def render_evidence_bundle(bundle, *, limit_items=220):
482
430
  lines.append("(无 context-only evidence)")
483
431
  for item in context[:40]:
484
432
  lines.append(
485
- f"[clock=null {item.get('source')} support=context_only id={item.get('id')}] {item.get('text', '')}"
433
+ f"[clock=null {item['source']} support=context_only id={item['id']}] {item['text']}"
486
434
  )
487
435
  return "\n".join(lines)
488
436
 
@@ -1,7 +1,6 @@
1
1
  """Self-contained config + utilities for this skill (no cross-skill imports).
2
2
  Merged from the shared core; reads the same env vars as the rest of the bundle."""
3
3
  import json
4
- import hashlib
5
4
  import math
6
5
  import os
7
6
  import re
@@ -22,8 +21,6 @@ MIMO_TOKEN_PLAN_API_URLS = {
22
21
  "ams": "https://token-plan-ams.xiaomimimo.com/v1",
23
22
  }
24
23
  DEFAULT_MIMO_MODEL = "mimo-v2.5" # VLM / chat (vision understanding)
25
- DEFAULT_MIMO_ASR_MODEL = "mimo-v2.5-asr" # speech-to-text
26
- DEFAULT_MIMO_TTS_MODEL = "mimo-v2.5-tts" # text-to-speech
27
24
 
28
25
 
29
26
  def normalize_api_url(raw_url):
@@ -99,32 +96,13 @@ def env_float(name, default, *, minimum=None):
99
96
  # are optional and fall back to MIMO_API_KEY / MIMO_API_URL. Token-Plan keys (tp-*) auto-
100
97
  # route to the Token-Plan cluster base URL; pay-as-you-go keys use api.xiaomimimo.com.
101
98
  _mimo_api_key = os.environ.get("MIMO_API_KEY", "")
102
- _mimo_video_api_key = os.environ.get("MIMO_VIDEO_API_KEY", "") or _mimo_api_key
103
99
  _raw_api_url = os.environ.get("MIMO_API_URL") or default_mimo_api_url(is_mimo_token_plan_key(_mimo_api_key))
104
- _raw_mimo_video_api_url = (
105
- os.environ.get("MIMO_VIDEO_API_URL")
106
- or os.environ.get("MIMO_API_URL")
107
- or default_mimo_api_url(is_mimo_token_plan_key(_mimo_video_api_key))
108
- )
109
100
 
110
101
  CONFIG = {
111
102
  "api_url": normalize_api_url(_raw_api_url),
112
103
  "api_key": _mimo_api_key,
113
- "api_key_source": "MIMO_API_KEY",
114
- "mimo_video_api_url": normalize_api_url(_raw_mimo_video_api_url),
115
- "mimo_video_model": os.environ.get("MIMO_VIDEO_MODEL") or os.environ.get("MIMO_MODEL", DEFAULT_MIMO_MODEL),
104
+ "api_env_var": "MIMO_API_KEY",
116
105
  "vlm_model": os.environ.get("MIMO_MODEL", DEFAULT_MIMO_MODEL),
117
- "mimo_media_resolution": os.environ.get("MIMO_MEDIA_RESOLUTION", "default"),
118
- "mimo_video_overview": env_bool("MIMO_VIDEO_OVERVIEW", False), # opt-in (--mimo-video-overview / =1); when on it becomes the PRIMARY per-scene description, frames stay the anchor/fallback
119
- "mimo_video_fps": env_float("MIMO_VIDEO_FPS", 3.0, minimum=0.1),
120
- "mimo_video_chunk_max_seconds": env_float("MIMO_VIDEO_CHUNK_MAX_SECONDS", 20.0, minimum=1.0),
121
- "mimo_video_chunk_min_seconds": env_float("MIMO_VIDEO_CHUNK_MIN_SECONDS", 1.0, minimum=0.2),
122
- "mimo_video_base64_max_mb": env_float("MIMO_VIDEO_BASE64_MAX_MB", 45.0, minimum=1.0),
123
- "mimo_video_prompt": os.environ.get(
124
- "MIMO_VIDEO_PROMPT",
125
- "请用中文分析这个视频分片的主要人物、场景变化、关键动作、情绪走向和剧情冲突,"
126
- "重点提取适合写短视频解说的故事线索。不要泛泛复述画面,要标出对后续写稿有用的信息。",
127
- ),
128
106
  "mimo_disable_thinking": env_bool("MIMO_DISABLE_THINKING", True),
129
107
  # TTS 语速(字符/秒)。实测 mimo-tts 冰糖音色中位 ~3.9 字/秒,可用 SPEECH_RATE 覆盖
130
108
  # 生成解说时使用 speech_rate * safety_margin 作为约束
@@ -134,7 +112,6 @@ CONFIG = {
134
112
  "narration_coverage_target": 0.7, # rough first-draft/diagnostic fallback; content-led audio decisions may differ (not a quota)
135
113
  "narration_coverage_max": 0.85, # above this coverage → no_original_blocks (narration is wall-to-wall)
136
114
  "narration_coverage_min": 0.5, # below this coverage → under_narrated
137
- "narration_block_seconds": 9.0, # block cadence used to derive target block count
138
115
  "original_block_min_seconds": 2.5, # a deliberate original-audio gap must be at least this long
139
116
  "narration_block_min_chars": 16, # below this avg block size → fragmented_beats
140
117
  "breath_ms": 250, # 段间呼吸空间(ms);block recap 块内连贯、块间留原声呼吸
@@ -145,73 +122,19 @@ CONFIG = {
145
122
  "visual_beat_max_facts": 3, # 单段解说最多建议覆盖的 frame_facts 锚点数量
146
123
  "asr_chunk_min_chars": env_int("ASR_CHUNK_MIN_CHARS", 500, minimum=1), # brief 中 ASR 写作分块最小字数/词数
147
124
  "asr_chunk_max_chars": env_int("ASR_CHUNK_MAX_CHARS", 800, minimum=1), # brief 中 ASR 写作分块最大字数/词数
148
- "context_info": "", # 额外上下文(节目名、角色名等)
149
125
  "edit_mode": os.environ.get("EDIT_MODE", "full"), # full | cut
150
- "target_duration": os.environ.get("TARGET_DURATION", ""), # cut 模式目标成片时长,如 10m
151
126
  }
152
127
 
153
- def narration_tempo_budget(tts_rate_offset=0.0, *, config=None):
154
- """Return the canonical tempo budget shared by voiceover and assemble."""
155
- cfg = config or CONFIG
156
- global_speed = max(0.01, float(cfg.get("narration_speed", 1.0) or 1.0))
157
- rate_factor = max(0.01, 1.0 + float(tts_rate_offset or 0.0))
158
- cumulative_max = max(1.0, float(cfg.get("narration_cumulative_tempo_max", 1.35) or 1.35))
159
- hard_max = max(cumulative_max, float(cfg.get("narration_cumulative_tempo_hard_max", 1.40) or 1.40))
160
- legacy_segment_cap = max(1.0, float(cfg.get("tts_segment_tempo_max", 1.20) or 1.20))
161
- segment_tempo_max = max(1.0, min(legacy_segment_cap, cumulative_max / (global_speed * rate_factor)))
162
- return {
163
- "global_narration_speed": global_speed,
164
- "tts_rate_factor": rate_factor,
165
- "cumulative_tempo_max": cumulative_max,
166
- "cumulative_tempo_hard_max": hard_max,
167
- "segment_tempo_max": segment_tempo_max,
168
- "max_raw_duration_factor": global_speed * segment_tempo_max,
169
- }
170
-
171
128
  def log(msg):
172
129
  print(f"[video-recap] {msg}", flush=True)
173
130
 
174
- def stable_json_dumps(value):
175
- """Serialize values deterministically for non-secret cache fingerprints."""
176
- return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"), default=str)
177
-
178
- def stable_hash(value):
179
- """Return an md5 digest for deterministic JSON-serializable values."""
180
- return hashlib.md5(stable_json_dumps(value).encode("utf-8")).hexdigest()
181
-
182
- _FILE_FINGERPRINT_MEMO = {}
183
-
184
-
185
- def _file_identity(path):
186
- """(device, inode, size, mtime_ns) — changes whenever the bytes could have changed."""
131
+ def file_identity(path):
132
+ """{size, mtime_ns} of an input file: the identity brief consumers compare against
133
+ the provenance a producer stored, instead of re-hashing bytes."""
187
134
  st = os.stat(os.fspath(path))
188
- return (st.st_dev, st.st_ino, st.st_size, st.st_mtime_ns)
189
-
135
+ return {"size": st.st_size, "mtime_ns": st.st_mtime_ns}
190
136
 
191
- def file_fingerprint(path, chunk_size=1024 * 1024):
192
- """Return a full-content fingerprint for cache-correct identity checks.
193
137
 
194
- The digest covers CONTENT only — never the path or mtime — so a copied video or
195
- artifact is still recognised as the same asset, while any byte change invalidates
196
- the cache even if timestamps, size, head, or tail bytes are misleading.
197
-
198
- Identity metadata is used ONLY to memoize within a single process. One understanding
199
- run fingerprints the same source video 8-10 times and the whole extracted frame set
200
- 2-3 times; on a 40-minute video at fps=1 that is gigabytes of redundant reads before
201
- any real work starts. A file rewritten in place gets a new (size, mtime_ns) and is
202
- re-hashed, so the memo can never serve a stale digest.
203
- """
204
- key = _file_identity(path)
205
- memoized = _FILE_FINGERPRINT_MEMO.get(key)
206
- if memoized is not None:
207
- return memoized
208
- h = hashlib.sha256()
209
- with open(os.fspath(path), "rb") as f:
210
- for chunk in iter(lambda: f.read(chunk_size), b""):
211
- h.update(chunk)
212
- digest = h.hexdigest()
213
- _FILE_FINGERPRINT_MEMO[key] = digest
214
- return digest
215
138
  def _retry_after_seconds(value, fallback):
216
139
  """Parse Retry-After seconds or HTTP-date; return fallback on malformed input."""
217
140
  if not value:
@@ -245,7 +168,7 @@ def _sanitize_api_error(value, limit=500):
245
168
  def _api_headers(api_provider=None, api_url=None, api_key=None):
246
169
  """Build MiMo auth headers (OpenAI-compatible chat/completions with an api-key header)."""
247
170
  del api_provider, api_url # MiMo is the only provider; signature kept for call sites
248
- key = CONFIG.get("api_key", "") if api_key is None else api_key
171
+ key = CONFIG["api_key"] if api_key is None else api_key
249
172
  return {
250
173
  "Content-Type": "application/json",
251
174
  "User-Agent": "video-recap/1.0",
@@ -258,9 +181,9 @@ def _prepare_api_payload(payload, api_provider=None, api_url=None):
258
181
  normalized = dict(payload)
259
182
  if "max_tokens" in normalized and "max_completion_tokens" not in normalized:
260
183
  normalized["max_completion_tokens"] = normalized.pop("max_tokens")
261
- model = str(normalized.get("model") or "")
184
+ model = normalized["model"]
262
185
  if (
263
- CONFIG.get("mimo_disable_thinking", True)
186
+ CONFIG["mimo_disable_thinking"]
264
187
  and not model.endswith(("-tts", "-asr"))
265
188
  and "thinking" not in normalized
266
189
  ):
@@ -269,7 +192,7 @@ def _prepare_api_payload(payload, api_provider=None, api_url=None):
269
192
  normalized["thinking"] = {"type": "disabled"}
270
193
  return normalized
271
194
 
272
- def api_call(payload, max_retries=8, *, api_provider=None, api_url=None, api_key=None, api_key_source=None):
195
+ def api_call(payload, max_retries=8, *, api_provider=None, api_url=None, api_key=None, api_env_var=None):
273
196
  """调用 OpenAI-compatible API,带重试。
274
197
 
275
198
  集群的 429 限流是常态而非错误,所以重试更耐心(更多次数 + 退避封顶 60s + 遵从 Retry-After),
@@ -292,7 +215,7 @@ def api_call(payload, max_retries=8, *, api_provider=None, api_url=None, api_key
292
215
  wait = _retry_after_seconds(retry_after, max(wait, 10))
293
216
  log(f"API 速率限制 (尝试 {attempt+1}/{max_retries}), 等待 {wait}s")
294
217
  elif e.code == 401:
295
- key_name = api_key_source or CONFIG.get("api_key_source", "MIMO_API_KEY")
218
+ key_name = api_env_var or CONFIG["api_env_var"]
296
219
  raise RuntimeError(f"API 认证失败 (401)。请检查 {key_name} 和 API URL 是否匹配。")
297
220
  elif e.code == 403:
298
221
  hint = "API 访问被拒绝 (403)。"
@@ -5,6 +5,7 @@ timing and text checks live here, and every later consumer trusts its result.
5
5
  """
6
6
 
7
7
  import json
8
+ import math
8
9
  from pathlib import Path
9
10
 
10
11
  from lib import CONFIG, log
@@ -32,7 +33,11 @@ def _lint_issue(level, index, code, message, **extra):
32
33
 
33
34
 
34
35
  def _is_number(value):
35
- return isinstance(value, (int, float)) and not isinstance(value, bool)
36
+ return (
37
+ isinstance(value, (int, float))
38
+ and not isinstance(value, bool)
39
+ and math.isfinite(value)
40
+ )
36
41
 
37
42
 
38
43
  def _visual_overlay_issues(index, seg):
@@ -115,17 +120,13 @@ def _clip_span(clip):
115
120
  return clip["start"], clip["end"]
116
121
 
117
122
 
118
- def _clip_matches_for_segment(seg, clip_plan):
123
+ def _clip_matches_for_segment(seg, clip_plan, requested_clip_id):
119
124
  if not clip_plan:
120
125
  return []
121
126
  clips = clip_plan["clips"]
122
127
  midpoint = (seg["start"] + seg["end"]) / 2
123
- if seg.get("source_clip_id") is not None:
124
- try:
125
- requested = int(seg["source_clip_id"])
126
- except (TypeError, ValueError):
127
- return []
128
- clips = [clip for clip in clips if clip.get("clip_id") == requested]
128
+ if requested_clip_id is not None:
129
+ clips = [clip for clip in clips if clip.get("clip_id") == requested_clip_id]
129
130
  return [
130
131
  clip
131
132
  for clip in clips
@@ -196,9 +197,13 @@ def _has_connected_predecessor(narration, idx, start):
196
197
 
197
198
 
198
199
  def lint_narration(
199
- narration, scenes_analysis=None, *, clip_plan=None, mode="full", work_dir=None
200
+ narration, scenes_analysis=None, *, clip_plan=None, mode="full", work_dir=None,
201
+ require_chronological=False,
200
202
  ):
201
- """Preflight-check agent narration before TTS; write narration_lint.json when work_dir is set."""
203
+ """Preflight-check agent narration before TTS; write narration_lint.json when work_dir is set.
204
+
205
+ Segments are sorted by start for the timing checks; ``require_chronological`` (the
206
+ --preserve-approved-text contract) additionally rejects input that is not already in order."""
202
207
  scenes_analysis = scenes_analysis or []
203
208
  errors = []
204
209
  warnings = []
@@ -215,6 +220,7 @@ def lint_narration(
215
220
  )
216
221
  )
217
222
  else:
223
+ previous_start = None
218
224
  for idx, seg in enumerate(narration):
219
225
  if not isinstance(seg, dict):
220
226
  errors.append(
@@ -230,33 +236,47 @@ def lint_narration(
230
236
  if not _is_number(start) or not _is_number(end):
231
237
  errors.append(
232
238
  _lint_issue(
233
- "error", idx, "invalid_time", "start/end must be numeric"
239
+ "error", idx, "invalid_time", "start/end must be finite numbers"
234
240
  )
235
241
  )
236
242
  continue
237
- text = str(seg.get("narration", "")).strip()
238
- try:
239
- pause = int(seg.get("pause_after_ms", CONFIG["breath_ms"]))
240
- except (TypeError, ValueError):
243
+ if require_chronological and previous_start is not None and start < previous_start:
241
244
  errors.append(
242
245
  _lint_issue(
243
246
  "error",
244
247
  idx,
245
- "invalid_pause",
246
- "pause_after_ms must be an integer number of milliseconds",
248
+ "out_of_order",
249
+ "Segments must be listed in chronological order",
250
+ start=start,
251
+ previous_start=previous_start,
252
+ )
253
+ )
254
+ previous_start = start
255
+ text = seg.get("narration")
256
+ if not isinstance(text, str):
257
+ errors.append(
258
+ _lint_issue(
259
+ "error",
260
+ idx,
261
+ "invalid_narration",
262
+ "narration must be a string",
263
+ start=start,
264
+ end=end,
247
265
  )
248
266
  )
249
267
  continue
250
- if pause < 0:
251
- warnings.append(
268
+ text = text.strip()
269
+ pause = seg.get("pause_after_ms", CONFIG["breath_ms"])
270
+ if not isinstance(pause, int) or isinstance(pause, bool) or pause < 0:
271
+ errors.append(
252
272
  _lint_issue(
253
- "warning",
273
+ "error",
254
274
  idx,
255
- "negative_pause",
256
- "pause_after_ms is negative; default should be used",
257
- pause_after_ms=pause,
275
+ "invalid_pause",
276
+ "pause_after_ms must be a non-negative integer number of milliseconds",
258
277
  )
259
278
  )
279
+ continue
260
280
  errors.extend(_visual_overlay_issues(idx, seg))
261
281
  if end <= start:
262
282
  errors.append(
@@ -388,7 +408,21 @@ def lint_narration(
388
408
  )
389
409
 
390
410
  if mode == "cut":
391
- matches = _clip_matches_for_segment(seg, clip_plan)
411
+ requested_clip_id = seg.get("source_clip_id")
412
+ if requested_clip_id is not None:
413
+ try:
414
+ requested_clip_id = int(requested_clip_id)
415
+ except (TypeError, ValueError):
416
+ errors.append(
417
+ _lint_issue(
418
+ "error",
419
+ idx,
420
+ "invalid_source_clip_id",
421
+ "source_clip_id must be an integer",
422
+ )
423
+ )
424
+ continue
425
+ matches = _clip_matches_for_segment(seg, clip_plan, requested_clip_id)
392
426
  if not matches:
393
427
  errors.append(
394
428
  _lint_issue(
@@ -400,7 +434,7 @@ def lint_narration(
400
434
  end=end,
401
435
  )
402
436
  )
403
- elif len(matches) > 1 and seg.get("source_clip_id") is None:
437
+ elif len(matches) > 1 and requested_clip_id is None:
404
438
  errors.append(
405
439
  _lint_issue(
406
440
  "error",
@@ -426,18 +460,6 @@ def lint_narration(
426
460
  clip_end=round(clip_end, 3),
427
461
  )
428
462
  )
429
- if seg.get("source_clip_id") is not None:
430
- try:
431
- int(seg["source_clip_id"])
432
- except (TypeError, ValueError):
433
- errors.append(
434
- _lint_issue(
435
- "error",
436
- idx,
437
- "invalid_source_clip_id",
438
- "source_clip_id must be an integer",
439
- )
440
- )
441
463
 
442
464
  normalized.append(
443
465
  {"index": idx, "start": start, "end": end, "char_count": char_count}
@@ -620,10 +642,12 @@ def lint_narration(
620
642
 
621
643
 
622
644
  def validate_narration_or_raise(
623
- narration, scenes_analysis=None, *, clip_plan=None, mode="full", work_dir=None
645
+ narration, scenes_analysis=None, *, clip_plan=None, mode="full", work_dir=None,
646
+ require_chronological=False,
624
647
  ):
625
648
  report = lint_narration(
626
- narration, scenes_analysis, clip_plan=clip_plan, mode=mode, work_dir=work_dir
649
+ narration, scenes_analysis, clip_plan=clip_plan, mode=mode, work_dir=work_dir,
650
+ require_chronological=require_chronological,
627
651
  )
628
652
  if report["errors"]:
629
653
  sample = "; ".join(
@@ -7,7 +7,6 @@ from evidence_bundle import (
7
7
  coverage_policy_v1,
8
8
  filter_evidence_by_ranges,
9
9
  render_evidence_bundle,
10
- validate_public_evidence_contract,
11
10
  )
12
11
  from review_grounding import remap_grounding_to_output_timeline
13
12
  from review_response import (
@@ -34,7 +33,6 @@ __all__ = [
34
33
  "remap_grounding_to_output_timeline",
35
34
  "render_evidence_bundle",
36
35
  "review_narration",
37
- "validate_public_evidence_contract",
38
36
  "write_grounding_qc",
39
37
  ]
40
38
 
@@ -1,22 +1,33 @@
1
1
  """Load and remap source evidence for narration review."""
2
2
 
3
3
  import json
4
+ from pathlib import Path
4
5
 
5
- import math
6
6
 
7
+ def _load(work_dir, name):
8
+ """Pipeline-written artifact: None when never written; corrupt JSON raises."""
9
+ path = Path(work_dir) / name
10
+ if not path.exists():
11
+ return None
12
+ return json.loads(path.read_text(encoding="utf-8"))
7
13
 
8
- from pathlib import Path
9
14
 
10
- from lib import stable_hash
15
+ def _load_required(work_dir, name, hint):
16
+ """Pipeline-written artifact the stage cannot run without; corrupt JSON raises."""
17
+ path = Path(work_dir) / name
18
+ if not path.exists():
19
+ raise SystemExit(f"缺少 {path};{hint}")
20
+ return json.loads(path.read_text(encoding="utf-8"))
11
21
 
12
22
 
13
- def _load(work_dir, name):
23
+ def _load_agent_optional(work_dir, name):
24
+ """Optional agent-authored artifact: missing or unparseable both fail open to None."""
14
25
  path = Path(work_dir) / name
15
26
  if not path.exists():
16
27
  return None
17
28
  try:
18
29
  return json.loads(path.read_text(encoding="utf-8"))
19
- except (ValueError, OSError):
30
+ except ValueError:
20
31
  return None
21
32
 
22
33
 
@@ -79,19 +90,6 @@ def _load_review_grounding(work_dir):
79
90
  )
80
91
 
81
92
 
82
- def _source_fingerprint(work_dir, name):
83
- path = Path(work_dir) / name
84
- if not path.exists():
85
- return ""
86
- try:
87
- return stable_hash(json.loads(path.read_text(encoding="utf-8")))
88
- except (ValueError, OSError):
89
- try:
90
- return stable_hash(path.read_text(encoding="utf-8"))
91
- except OSError:
92
- return ""
93
-
94
-
95
93
  def _load_cut_clip_spans(work_dir):
96
94
  """Load explicit source→output spans from the validated cut plan only.
97
95
 
@@ -102,50 +100,28 @@ def _load_cut_clip_spans(work_dir):
102
100
  """
103
101
  work_dir = Path(work_dir)
104
102
  plan = _load(work_dir, "clip_plan_validated.json")
105
- if not isinstance(plan, dict):
103
+ if plan is None:
106
104
  return None
107
- raw_plan = _load(work_dir, "clip_plan.json")
108
- if raw_plan is not None and plan.get("raw_plan_fingerprint") != stable_hash(
109
- raw_plan
105
+ raw_path = work_dir / "clip_plan.json"
106
+ if (
107
+ raw_path.exists()
108
+ and (work_dir / "clip_plan_validated.json").stat().st_mtime_ns
109
+ < raw_path.stat().st_mtime_ns
110
110
  ):
111
111
  return None
112
- clips = plan.get("clips")
113
- if not isinstance(clips, list):
114
- return None
115
- spans = []
116
- for clip in clips:
117
- if not isinstance(clip, dict):
118
- continue
119
- if not all(
120
- key in clip
121
- for key in ("source_start", "source_end", "output_start", "output_end")
122
- ):
123
- return None
124
- try:
125
- source_start = float(clip["source_start"])
126
- source_end = float(clip["source_end"])
127
- output_start = float(clip["output_start"])
128
- output_end = float(clip["output_end"])
129
- except (TypeError, ValueError):
130
- return None
131
- values = (source_start, source_end, output_start, output_end)
132
- if not all(math.isfinite(value) for value in values):
133
- return None
134
- if source_end <= source_start or output_end <= output_start:
135
- return None
136
- spans.append(
137
- {
138
- "source_start": source_start,
139
- "source_end": source_end,
140
- "output_start": output_start,
141
- "output_end": output_end,
142
- "source_id": str(clip.get("source_id", clip.get("source", "0"))),
143
- "source_clip_id": clip.get(
144
- "source_clip_id", clip.get("id", clip.get("clip_id"))
145
- ),
146
- "output_segment_index": len(spans),
147
- }
148
- )
112
+ spans = [
113
+ {
114
+ "source_start": clip["source_start"],
115
+ "source_end": clip["source_end"],
116
+ "output_start": clip["output_start"],
117
+ "output_end": clip["output_end"],
118
+ # video-cut labels source_id only for multi-source plans.
119
+ "source_id": str(clip.get("source_id", "0")),
120
+ "source_clip_id": clip["clip_id"],
121
+ "output_segment_index": index,
122
+ }
123
+ for index, clip in enumerate(plan["clips"])
124
+ ]
149
125
  return spans or None
150
126
 
151
127