@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
@@ -9,18 +9,17 @@ Optional, synchronous, in-pipeline. Two independent LLM passes over the video's
9
9
  TEXT per segment index only; each segment's original start/end is re-attached here, so a
10
10
  cleanup pass can never shift the timing-bearing spans downstream chunking depends on.
11
11
 
12
- Both passes degrade gracefully (a chat-API failure logs and is skipped) and are idempotent
13
- (a fresh artifact is reused). Mirrors review.py's pure-seam + thin-driver shape so it is
12
+ Both passes are idempotent (a fresh artifact is reused); a failure raises and the runner
13
+ records it in consolidation.status.json. Mirrors review.py's pure-seam + thin-driver shape so it is
14
14
  unit-testable with a mocked api_call. NON-required: the pipeline runs unchanged without it.
15
15
  """
16
16
 
17
17
  import argparse
18
- import hashlib
19
18
  import json
20
19
  import re
21
20
  from pathlib import Path
22
21
 
23
- from lib import CONFIG, log, api_call
22
+ from lib import CONFIG, log, api_call, file_identity, load_background_research
24
23
  from understanding_cache import _fresh
25
24
 
26
25
  # Shared tolerance for the per-segment span check. The brief-side gate inlines the SAME
@@ -59,9 +58,7 @@ INDEX_PROMPT = """你在根据逐场景画面分析、ASR对白、background_res
59
58
  def build_clean_messages(asr_result):
60
59
  lines = []
61
60
  for i, seg in enumerate(asr_result or []):
62
- if not isinstance(seg, dict):
63
- continue
64
- lines.append(f"{i}. {str(seg.get('text', '')).strip()}")
61
+ lines.append(f"{i}. {seg['text'].strip()}")
65
62
  user = f"{CLEAN_PROMPT}\n\n## 逐段转写(共 {len(lines)} 段)\n" + "\n".join(lines)
66
63
  return [{"role": "user", "content": user}]
67
64
 
@@ -69,7 +66,7 @@ def build_clean_messages(asr_result):
69
66
  def parse_clean_response(text, asr_result):
70
67
  """Zip cleaned TEXT onto the original segments (start/end preserved by construction).
71
68
  Returns the original asr_result UNCHANGED on any parse / shape / count problem."""
72
- base = [s for s in (asr_result or []) if isinstance(s, dict)]
69
+ base = list(asr_result or [])
73
70
  if not base:
74
71
  return asr_result
75
72
  data = _extract_json(text)
@@ -87,8 +84,8 @@ def parse_clean_response(text, asr_result):
87
84
  out = []
88
85
  for i, seg in enumerate(base):
89
86
  cleaned = by_index.get(i, {})
90
- new_text = str(cleaned.get("text", "")).strip() or str(seg.get("text", ""))
91
- merged = {"start": seg.get("start"), "end": seg.get("end"), "text": new_text}
87
+ new_text = str(cleaned.get("text", "")).strip() or seg["text"]
88
+ merged = {"start": seg["start"], "end": seg["end"], "text": new_text}
92
89
  speaker = str(cleaned.get("speaker", "")).strip()
93
90
  if speaker:
94
91
  merged["speaker"] = speaker
@@ -96,17 +93,10 @@ def parse_clean_response(text, asr_result):
96
93
  return out
97
94
 
98
95
 
99
- def _json_md5_file(work_dir, name):
96
+ def _input_identity(work_dir, name):
97
+ """{size, mtime_ns} of one input artifact, or None when it is absent."""
100
98
  path = Path(work_dir) / name
101
- return hashlib.md5(path.read_bytes()).hexdigest() if path.exists() else ""
102
-
103
-
104
- def _asr_clean_source_md5(work_dir):
105
- return _json_md5_file(work_dir, "asr_clean.json")
106
-
107
-
108
- def _research_source_md5(work_dir):
109
- return _json_md5_file(work_dir, "background_research.json")
99
+ return file_identity(path) if path.exists() else None
110
100
 
111
101
 
112
102
  def _research_glossary_from_context(background_research):
@@ -148,23 +138,22 @@ def _research_glossary_from_context(background_research):
148
138
  return glossary
149
139
 
150
140
 
141
+ def _dialogue_source(asr_result, asr_clean):
142
+ """asr_clean.json segments when present (consolidate's own output), else raw asr_result rows."""
143
+ return asr_clean["segments"] if asr_clean else (asr_result or [])
144
+
145
+
151
146
  def _dialogue_segments_for_index(asr_result=None, asr_clean=None):
152
- clean_segments = (
153
- (asr_clean or {}).get("segments") if isinstance(asr_clean, dict) else None
154
- )
155
- source = clean_segments if isinstance(clean_segments, list) else (asr_result or [])
156
147
  out = []
157
- for i, seg in enumerate(source or []):
158
- if not isinstance(seg, dict):
159
- continue
160
- text = str(seg.get("text", "")).strip()
148
+ for i, seg in enumerate(_dialogue_source(asr_result, asr_clean)):
149
+ text = seg["text"].strip()
161
150
  if not text:
162
151
  continue
163
152
  out.append(
164
153
  {
165
154
  "id": f"asr:{i}",
166
- "start": seg.get("start"),
167
- "end": seg.get("end"),
155
+ "start": seg["start"],
156
+ "end": seg["end"],
168
157
  "text": text,
169
158
  }
170
159
  )
@@ -280,44 +269,34 @@ def build_index_messages(
280
269
  ):
281
270
  lines = []
282
271
  for i, scene in enumerate(vlm_analysis or []):
283
- if not isinstance(scene, dict):
284
- continue
285
- sid = scene.get("scene_id", i)
286
- start = float(scene.get("start", 0) or 0)
287
- end = float(scene.get("end", 0) or 0)
288
- desc = str(scene.get("description", "")).strip().replace("\n", " ")
289
- facts = scene.get("frame_facts")
272
+ sid = scene["scene_id"]
273
+ start = float(scene["start"])
274
+ end = float(scene["end"])
275
+ desc = scene["description"].strip().replace("\n", " ")
276
+ facts = scene.get("frame_facts") # vlm.py only writes the key when non-empty
290
277
  fact_txt = ""
291
- if isinstance(facts, dict) and facts:
278
+ if facts:
292
279
  actions = []
293
280
 
294
281
  def fact_key(x):
282
+ # timestamps are regex-captured from the VLM reply ("[\d.]+"), so "1.2.3" is possible
295
283
  try:
296
284
  return (0, float(x))
297
- except (TypeError, ValueError):
285
+ except ValueError:
298
286
  return (1, str(x))
299
287
 
300
288
  for ts in sorted(facts.keys(), key=fact_key):
301
- vals = facts[ts]
302
- actions.extend(vals if isinstance(vals, list) else [str(vals)])
289
+ actions.extend(facts[ts])
303
290
  if actions:
304
291
  fact_txt = " | 帧实: " + ";".join(a for a in actions[:6] if a)
305
292
  lines.append(f"[visual:{i} 场景{sid} {start:.0f}-{end:.0f}s] {desc}{fact_txt}")
306
293
  asr_lines = []
307
- clean_segments = (
308
- (asr_clean or {}).get("segments") if isinstance(asr_clean, dict) else None
309
- )
310
- source_asr = (
311
- clean_segments if isinstance(clean_segments, list) else (asr_result or [])
312
- )
313
- for i, seg in enumerate(source_asr or []):
314
- if not isinstance(seg, dict):
315
- continue
316
- text = str(seg.get("text", "")).strip()
294
+ for i, seg in enumerate(_dialogue_source(asr_result, asr_clean)):
295
+ text = seg["text"].strip()
317
296
  if not text:
318
297
  continue
319
298
  asr_lines.append(
320
- f"[asr:{i} {float(seg.get('start', 0) or 0):.0f}-{float(seg.get('end', 0) or 0):.0f}s] {text}"
299
+ f"[asr:{i} {float(seg['start']):.0f}-{float(seg['end']):.0f}s] {text}"
321
300
  )
322
301
  glossary = _research_glossary_from_context(background_research)
323
302
  glossary_lines = []
@@ -432,44 +411,31 @@ def _extract_json(text):
432
411
  return None
433
412
 
434
413
 
435
- def _asr_source_md5(work_dir):
436
- """Provenance: md5 of the on-disk asr_result.json BYTES (writer + reader hash the same thing)."""
437
- path = Path(work_dir) / "asr_result.json"
438
- return hashlib.md5(path.read_bytes()).hexdigest() if path.exists() else ""
439
-
440
-
441
- def _vlm_source_md5(work_dir):
442
- """Provenance: md5 of the on-disk vlm_analysis.json bytes."""
443
- path = Path(work_dir) / "vlm_analysis.json"
444
- return hashlib.md5(path.read_bytes()).hexdigest() if path.exists() else ""
445
-
446
-
447
414
  def _index_meta_path(work_dir):
448
415
  return Path(work_dir) / "understanding_index.json.meta.json"
449
416
 
450
417
 
451
- def _prompt_fingerprint(prompt):
452
- return hashlib.md5(str(prompt or "").encode("utf-8")).hexdigest()
418
+ def _index_meta(work_dir, vlm_analysis):
419
+ """Provenance of understanding_index.json: `source` (vlm_analysis.json identity) + `model`
420
+ are what brief consumers check; the other inputs and the prompt text are the producer's
421
+ own rebuild gate."""
422
+ return {
423
+ "schema_version": INDEX_SCHEMA_VERSION,
424
+ "source": _input_identity(work_dir, "vlm_analysis.json"),
425
+ "inputs": {
426
+ "asr_result": _input_identity(work_dir, "asr_result.json"),
427
+ "asr_clean": _input_identity(work_dir, "asr_clean.json"),
428
+ "background_research": _input_identity(work_dir, "background_research.json"),
429
+ },
430
+ "scene_count": len([s for s in (vlm_analysis or []) if isinstance(s, dict)]),
431
+ "model": CONFIG["vlm_model"],
432
+ "prompt": INDEX_PROMPT,
433
+ }
453
434
 
454
435
 
455
436
  def _write_index_meta(work_dir, vlm_analysis):
456
437
  _index_meta_path(work_dir).write_text(
457
- json.dumps(
458
- {
459
- "schema_version": INDEX_SCHEMA_VERSION,
460
- "source_md5": _vlm_source_md5(work_dir),
461
- "asr_md5": _asr_source_md5(work_dir),
462
- "asr_clean_md5": _asr_clean_source_md5(work_dir),
463
- "research_md5": _research_source_md5(work_dir),
464
- "scene_count": len(
465
- [s for s in (vlm_analysis or []) if isinstance(s, dict)]
466
- ),
467
- "model": CONFIG.get("vlm_model", ""),
468
- "prompt_md5": _prompt_fingerprint(INDEX_PROMPT),
469
- },
470
- ensure_ascii=False,
471
- indent=2,
472
- ),
438
+ json.dumps(_index_meta(work_dir, vlm_analysis), ensure_ascii=False, indent=2),
473
439
  encoding="utf-8",
474
440
  )
475
441
 
@@ -482,31 +448,18 @@ def _index_cache_matches(work_dir, vlm_analysis):
482
448
  meta = json.loads(meta_path.read_text(encoding="utf-8"))
483
449
  except (json.JSONDecodeError, OSError):
484
450
  return False
485
- return (
486
- isinstance(meta, dict)
487
- and meta.get("schema_version") == INDEX_SCHEMA_VERSION
488
- and meta.get("source_md5") == _vlm_source_md5(work_dir)
489
- and meta.get("asr_md5", "") == _asr_source_md5(work_dir)
490
- and meta.get("asr_clean_md5", "") == _asr_clean_source_md5(work_dir)
491
- and meta.get("research_md5", "") == _research_source_md5(work_dir)
492
- and meta.get("scene_count")
493
- == len([s for s in (vlm_analysis or []) if isinstance(s, dict)])
494
- and meta.get("model") == CONFIG.get("vlm_model", "")
495
- and meta.get("prompt_md5") == _prompt_fingerprint(INDEX_PROMPT)
496
- )
451
+ return meta == _index_meta(work_dir, vlm_analysis)
497
452
 
498
453
 
499
454
  # ── thin drivers (I/O + api_call) ─────────────────────────────────────────────
500
455
 
501
456
 
502
457
  def _load(work_dir, name):
458
+ """Read one of this skill's own JSON artifacts: absent → None, corrupt → raise."""
503
459
  path = Path(work_dir) / name
504
460
  if not path.exists():
505
461
  return None
506
- try:
507
- return json.loads(path.read_text(encoding="utf-8"))
508
- except (ValueError, OSError):
509
- return None
462
+ return json.loads(path.read_text(encoding="utf-8"))
510
463
 
511
464
 
512
465
  def consolidate_transcript(work_dir):
@@ -519,16 +472,16 @@ def consolidate_transcript(work_dir):
519
472
  if _fresh(out_path, work_dir / "asr_result.json"):
520
473
  existing = _load(work_dir, "asr_clean.json") or {}
521
474
  if (
522
- existing.get("source_md5") == _asr_source_md5(work_dir)
523
- and existing.get("model") == CONFIG.get("vlm_model", "")
524
- and existing.get("prompt_md5") == _prompt_fingerprint(CLEAN_PROMPT)
475
+ existing.get("source") == _input_identity(work_dir, "asr_result.json")
476
+ and existing.get("model") == CONFIG["vlm_model"]
477
+ and existing.get("prompt") == CLEAN_PROMPT
525
478
  and existing.get("postprocess_version") == ASR_CLEAN_POSTPROCESS_VERSION
526
479
  ):
527
480
  log("consolidate(asr): asr_clean.json 已最新,跳过")
528
481
  return existing
529
482
  resp = api_call(
530
483
  {
531
- "model": CONFIG.get("vlm_model", ""),
484
+ "model": CONFIG["vlm_model"],
532
485
  "messages": build_clean_messages(asr_result),
533
486
  "max_tokens": 4000,
534
487
  "temperature": 0.2,
@@ -537,9 +490,9 @@ def consolidate_transcript(work_dir):
537
490
  content = _response_text(resp)
538
491
  segments = parse_clean_response(content, asr_result)
539
492
  payload = {
540
- "source_md5": _asr_source_md5(work_dir),
541
- "model": CONFIG.get("vlm_model", ""),
542
- "prompt_md5": _prompt_fingerprint(CLEAN_PROMPT),
493
+ "source": _input_identity(work_dir, "asr_result.json"),
494
+ "model": CONFIG["vlm_model"],
495
+ "prompt": CLEAN_PROMPT,
543
496
  "postprocess_version": ASR_CLEAN_POSTPROCESS_VERSION,
544
497
  "segments": segments,
545
498
  }
@@ -564,10 +517,10 @@ def consolidate_index(work_dir):
564
517
  return _load(work_dir, "understanding_index.json")
565
518
  asr_result = _load(work_dir, "asr_result.json") or []
566
519
  asr_clean = _load(work_dir, "asr_clean.json") or {}
567
- background_research = _load(work_dir, "background_research.json") or {}
520
+ background_research = load_background_research(work_dir)
568
521
  resp = api_call(
569
522
  {
570
- "model": CONFIG.get("vlm_model", ""),
523
+ "model": CONFIG["vlm_model"],
571
524
  "messages": build_index_messages(
572
525
  vlm_analysis,
573
526
  asr_result=asr_result,
@@ -599,18 +552,14 @@ def consolidate_index(work_dir):
599
552
 
600
553
 
601
554
  def consolidate(work_dir, do_asr=False, do_index=True):
602
- """Default = index-only (Pass B, zero timing risk). Pass A (asr) is opt-in."""
555
+ """Default = index-only (Pass B, zero timing risk). Pass A (asr) is opt-in.
556
+
557
+ Failures propagate; understanding_runner catches them and writes the "failed" status."""
603
558
  result = {}
604
559
  if do_asr:
605
- try:
606
- result["asr_clean"] = consolidate_transcript(work_dir)
607
- except Exception as e:
608
- log(f"consolidate(asr) 跳过(忽略): {e}")
560
+ result["asr_clean"] = consolidate_transcript(work_dir)
609
561
  if do_index:
610
- try:
611
- result["index"] = consolidate_index(work_dir)
612
- except Exception as e:
613
- log(f"consolidate(index) 跳过(忽略): {e}")
562
+ result["index"] = consolidate_index(work_dir)
614
563
  return result
615
564
 
616
565
 
@@ -6,7 +6,7 @@ from concurrent.futures import ThreadPoolExecutor
6
6
  from pathlib import Path
7
7
 
8
8
  from lib import CONFIG
9
- from lib import log, run_cmd, get_video_duration, file_fingerprint
9
+ from lib import log, run_cmd, get_video_duration, file_identity
10
10
 
11
11
  # ── Step 2: 场景检测 ──────────────────────────────────────────────────
12
12
 
@@ -48,10 +48,10 @@ def detect_scenes(video_path, work_dir, threshold=None):
48
48
 
49
49
  # 先过滤黑/白帧过渡场景,再合并短场景
50
50
  # (合并会保留短场景的 start,若黑场被并入长场景,单点采样会误删整段,故顺序在前)
51
- if CONFIG.get("scene_junk_filter", True):
51
+ if CONFIG["scene_junk_filter"]:
52
52
  scenes = _filter_junk_scenes(scenes, video_path)
53
53
  # 合并短场景(< 3s 合并到相邻场景)
54
- scenes = _merge_short_scenes(scenes, min_duration=CONFIG.get("scene_merge_min", 4.0))
54
+ scenes = _merge_short_scenes(scenes, min_duration=CONFIG["scene_merge_min"])
55
55
 
56
56
  # 保存
57
57
  scenes_file = work_dir / "scenes.json"
@@ -115,9 +115,9 @@ def _sample_frame_luma(video_path, timestamp, sample_size=64):
115
115
 
116
116
  def _is_junk_scene(video_path, timestamp, threshold_dark=None, threshold_bright=None):
117
117
  """Return True for near-black or near-white scene-start frames."""
118
- threshold_dark = CONFIG.get("scene_junk_dark_luma", 8.0) if threshold_dark is None else threshold_dark
119
- threshold_bright = CONFIG.get("scene_junk_bright_luma", 245.0) if threshold_bright is None else threshold_bright
120
- pixel_ratio = min(1.0, max(0.0, float(CONFIG.get("scene_junk_pixel_ratio", 0.995))))
118
+ threshold_dark = CONFIG["scene_junk_dark_luma"] if threshold_dark is None else threshold_dark
119
+ threshold_bright = CONFIG["scene_junk_bright_luma"] if threshold_bright is None else threshold_bright
120
+ pixel_ratio = min(1.0, float(CONFIG["scene_junk_pixel_ratio"]))
121
121
  try:
122
122
  lumas = _sample_frame_luma(video_path, timestamp)
123
123
  except Exception as exc:
@@ -157,7 +157,7 @@ def _filter_junk_scenes(scenes, video_path):
157
157
  # 每个探点都是一次 ffmpeg 进程(seek + 解一帧),一部 30 分钟片有几百个场景,串行下来
158
158
  # 光进程启动就要几十秒。按场景并行:每个 worker 内部仍然短路,所以既保留了「多数场景只
159
159
  # 探一次」,又把进程启动开销摊平。判定逻辑与探点完全不变。
160
- workers = max(1, int(CONFIG.get("scene_junk_workers", 8) or 8))
160
+ workers = int(CONFIG["scene_junk_workers"])
161
161
  with ThreadPoolExecutor(max_workers=min(workers, len(scenes))) as executor:
162
162
  verdicts = list(executor.map(lambda s: _scene_is_junk(s, video_path), scenes))
163
163
  filtered = []
@@ -203,7 +203,7 @@ def annotate_quiet_windows_with_asr(periods, asr_result=None, *, video_duration=
203
203
  Coarse grid ASR (large synthetic windows from chunking) must not turn every quiet
204
204
  window into speech. The return value is (annotated_periods, qc). Inputs are copied.
205
205
  """
206
- out = [dict(p) for p in (periods or []) if isinstance(p, dict)]
206
+ out = [dict(p) for p in (periods or [])]
207
207
  qc = {"coarse_asr_windows": 0, "low_confidence_speech_flags": 0, "asr_granularity": "none"}
208
208
  for qp in out:
209
209
  qp.setdefault("speech_overlap_ratio", 0.0)
@@ -216,20 +216,15 @@ def annotate_quiet_windows_with_asr(periods, asr_result=None, *, video_duration=
216
216
 
217
217
  valid_segments = []
218
218
  for seg in asr_result:
219
- if not isinstance(seg, dict):
220
- continue
221
- try:
222
- ss, se = float(seg.get("start", 0)), float(seg.get("end", 0))
223
- except (TypeError, ValueError):
224
- continue
219
+ ss, se = float(seg["start"]), float(seg["end"])
225
220
  if se > ss:
226
221
  valid_segments.append((ss, se))
227
222
  asr_coverage = sum(se - ss for ss, se in valid_segments)
228
223
  avg_seg_dur = asr_coverage / len(valid_segments) if valid_segments else 0
229
224
  if video_duration is None:
230
- ends = [float(p.get("end", 0) or 0) for p in out] + [se for _, se in valid_segments]
225
+ ends = [float(p["end"]) for p in out] + [se for _, se in valid_segments]
231
226
  video_duration = max(ends or [0.0])
232
- configured = float(configured_segment_seconds if configured_segment_seconds is not None else (CONFIG.get("asr_segment_seconds", 30) or 30))
227
+ configured = float(configured_segment_seconds if configured_segment_seconds is not None else CONFIG["asr_segment_seconds"])
233
228
  coarse_asr = (
234
229
  (len(valid_segments) <= 5 and asr_coverage > float(video_duration) * 0.8) or
235
230
  asr_coverage > float(video_duration) * 1.5 or
@@ -243,7 +238,7 @@ def annotate_quiet_windows_with_asr(periods, asr_result=None, *, video_duration=
243
238
  overlap_seconds = 0.0
244
239
  for ss, se in valid_segments:
245
240
  overlap_seconds += max(0.0, min(float(qp["end"]), se) - max(float(qp["start"]), ss))
246
- ratio = overlap_seconds / max(0.001, float(qp.get("duration", 0.0) or 0.0))
241
+ ratio = overlap_seconds / max(0.001, float(qp["duration"]))
247
242
  qp["asr_overlap_seconds"] = round(overlap_seconds, 3)
248
243
  qp["speech_overlap_ratio"] = round(ratio, 4)
249
244
  qp["asr_granularity"] = granularity
@@ -333,7 +328,7 @@ def detect_silence_periods(video_path, work_dir, asr_result=None):
333
328
  "duration": round(total_dur - dur_start, 2)})
334
329
 
335
330
  # 合并相邻静音段(间隔 < merge_gap 的合并为一个大窗口)
336
- merge_gap = CONFIG.get("silence_merge_gap", 0.5)
331
+ merge_gap = CONFIG["silence_merge_gap"]
337
332
  merged = []
338
333
  for rp in sorted(raw_periods, key=lambda x: x["start"]):
339
334
  if merged and rp["start"] - merged[-1]["end"] < merge_gap:
@@ -352,7 +347,7 @@ def detect_silence_periods(video_path, work_dir, asr_result=None):
352
347
  periods,
353
348
  asr_result,
354
349
  video_duration=get_video_duration(str(audio_path)) if asr_result else None,
355
- configured_segment_seconds=CONFIG.get("asr_segment_seconds", 30),
350
+ configured_segment_seconds=CONFIG["asr_segment_seconds"],
356
351
  )
357
352
 
358
353
  (work_dir / "silence_periods.qc.json").write_text(
@@ -368,12 +363,6 @@ def detect_silence_periods(video_path, work_dir, asr_result=None):
368
363
  return periods
369
364
 
370
365
 
371
- def _asr_segments(asr_result):
372
- if isinstance(asr_result, dict):
373
- asr_result = asr_result.get("segments", [])
374
- return [item for item in (asr_result or []) if isinstance(item, dict)]
375
-
376
-
377
366
  def detect_speech_boundary_anchors(work_dir, asr_result):
378
367
  """Write sentence-end entry anchors by aligning ASR punctuation to short pauses.
379
368
 
@@ -385,14 +374,14 @@ def detect_speech_boundary_anchors(work_dir, asr_result):
385
374
  work_dir = Path(work_dir)
386
375
  audio_path = work_dir / "audio.wav"
387
376
  out_path = work_dir / "speech_boundary_anchors.json"
388
- segments = _asr_segments(asr_result)
377
+ segments = list(asr_result or [])
389
378
  report = {
390
379
  "schema_version": 1,
391
380
  "artifact": "speech_boundary_anchors.json",
392
381
  "status": "completed",
393
382
  "detector": {
394
- "noise_threshold": CONFIG.get("source_boundary_noise_threshold", "-18dB"),
395
- "min_pause_seconds": float(CONFIG.get("source_boundary_min_pause", 0.12)),
383
+ "noise_threshold": CONFIG["source_boundary_noise_threshold"],
384
+ "min_pause_seconds": float(CONFIG["source_boundary_min_pause"]),
396
385
  "alignment": "terminal_punctuation_to_nearest_acoustic_pause",
397
386
  },
398
387
  "sentence_anchors": [],
@@ -408,8 +397,8 @@ def detect_speech_boundary_anchors(work_dir, asr_result):
408
397
  "ffmpeg", "-hide_banner", "-nostats", "-i", str(audio_path),
409
398
  "-af", (
410
399
  "silencedetect="
411
- f"noise={CONFIG.get('source_boundary_noise_threshold', '-18dB')}:"
412
- f"d={float(CONFIG.get('source_boundary_min_pause', 0.12))}"
400
+ f"noise={CONFIG['source_boundary_noise_threshold']}:"
401
+ f"d={float(CONFIG['source_boundary_min_pause'])}"
413
402
  ),
414
403
  "-f", "null", "-",
415
404
  ], timeout=120)
@@ -434,16 +423,13 @@ def detect_speech_boundary_anchors(work_dir, asr_result):
434
423
  })
435
424
  report["acoustic_pauses"] = pauses
436
425
 
437
- max_error = float(CONFIG.get("source_boundary_max_alignment_error", 2.1))
426
+ max_error = float(CONFIG["source_boundary_max_alignment_error"])
438
427
  used = set()
439
428
  anchors = []
440
429
  for asr_index, segment in enumerate(segments):
441
- try:
442
- seg_start = float(segment.get("start"))
443
- seg_end = float(segment.get("end"))
444
- except (TypeError, ValueError):
445
- continue
446
- text = str(segment.get("text") or "").strip()
430
+ seg_start = float(segment["start"])
431
+ seg_end = float(segment["end"])
432
+ text = segment["text"].strip()
447
433
  if seg_end <= seg_start or not text:
448
434
  continue
449
435
  last_midpoint = seg_start - 1e-6
@@ -496,17 +482,22 @@ def _audio_cache_matches(audio_path, video_path):
496
482
  except (OSError, ValueError, TypeError):
497
483
  return False
498
484
  try:
499
- expected = file_fingerprint(video_path)
485
+ expected = file_identity(video_path)
500
486
  except OSError:
501
487
  return False
502
- return meta.get("source_video_fingerprint") == expected
488
+ return (
489
+ isinstance(meta, dict)
490
+ and meta.get("source_video") == str(Path(video_path).resolve())
491
+ and meta.get("source_video_identity") == expected
492
+ )
503
493
 
504
494
 
505
495
  def _write_audio_meta(work_dir, video_path):
506
496
  _audio_meta_path(work_dir).write_text(
507
497
  json.dumps({
508
498
  "schema_version": 1,
509
- "source_video_fingerprint": file_fingerprint(video_path),
499
+ "source_video": str(Path(video_path).resolve()),
500
+ "source_video_identity": file_identity(video_path),
510
501
  "audio": "audio.wav",
511
502
  }, ensure_ascii=False, indent=2),
512
503
  encoding="utf-8",