@oh-story/dsh 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (479) hide show
  1. package/README.md +56 -12
  2. package/lib/client.js +2707 -268
  3. package/lib/client.js.map +4 -4
  4. package/lib/drama/manifest.json +136 -101
  5. package/lib/drama/skills/short-drama/SKILL.md +61 -1
  6. package/lib/drama/skills/short-drama/references/contract-and-ownership.md +7 -4
  7. package/lib/drama/skills/short-drama/references/creator-documents.md +133 -8
  8. package/lib/drama/skills/short-drama/references/creator-workflow.md +38 -2
  9. package/lib/drama/skills/short-drama/references/knowhow-index.md +3 -0
  10. package/lib/drama/skills/short-drama/references/reference-roles.md +10 -7
  11. package/lib/drama/skills/short-drama/references/runtime-preflight.md +3 -0
  12. package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +1002 -0
  13. package/lib/drama/skills/short-drama/scripts/project_tool.py +485 -5
  14. package/lib/drama/skills/short-drama-assets/SKILL.md +13 -3
  15. package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +116 -0
  16. package/lib/drama/skills/short-drama-assets/references/identity-vs-variant.md +2 -1
  17. package/lib/drama/skills/short-drama-assets/references/prop-and-state.md +19 -0
  18. package/lib/drama/skills/short-drama-assets/references/stage-contract.md +2 -0
  19. package/lib/drama/skills/short-drama-assets/scripts/asset_check.py +1 -1
  20. package/lib/drama/skills/short-drama-image-prompts/SKILL.md +6 -2
  21. package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +4 -3
  22. package/lib/drama/skills/short-drama-image-prompts/references/lookdev-frame.md +1 -1
  23. package/lib/drama/skills/short-drama-image-prompts/references/review-and-fixtures.md +1 -1
  24. package/lib/drama/skills/short-drama-image-prompts/references/stage-contract.md +3 -1
  25. package/lib/drama/skills/short-drama-image-prompts/scripts/image_prompt_check.py +8 -8
  26. package/lib/drama/skills/short-drama-image-prompts/scripts/selftest.py +4 -2
  27. package/lib/drama/skills/short-drama-produce/SKILL.md +46 -10
  28. package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +65 -8
  29. package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +85 -0
  30. package/lib/drama/skills/short-drama-produce/references/providers/seedance.md +27 -10
  31. package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +362 -3
  32. package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +627 -40
  33. package/lib/drama/skills/short-drama-produce/scripts/selftest.py +5 -4
  34. package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +12 -0
  35. package/lib/drama/skills/short-drama-review/scripts/review_check.py +1 -1
  36. package/lib/drama/skills/short-drama-storyboard/SKILL.md +76 -7
  37. package/lib/drama/skills/short-drama-storyboard/assets/shot-template.jsonl +1 -1
  38. package/lib/drama/skills/short-drama-storyboard/references/keyframe-craft.md +6 -2
  39. package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +19 -4
  40. package/lib/drama/skills/short-drama-storyboard/references/review-and-fixtures.md +5 -2
  41. package/lib/drama/skills/short-drama-storyboard/references/screenplay-to-keyframe-example.md +16 -0
  42. package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +8 -3
  43. package/lib/drama/skills/short-drama-storyboard/scripts/storyboard_check.py +1 -1
  44. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +81 -13
  45. package/lib/drama/skills/short-drama-video-prompts/assets/motion-spec.jsonl.md +6 -1
  46. package/lib/drama/skills/short-drama-video-prompts/assets/video-prompts.md +17 -1
  47. package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +21 -1
  48. package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +154 -0
  49. package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +24 -6
  50. package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +11 -7
  51. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +41 -0
  52. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +66 -0
  53. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +5 -2
  54. package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +115 -0
  55. package/lib/drama/skills/short-drama-video-prompts/scripts/container_check.py +1 -1
  56. package/lib/drama/skills/short-drama-video-prompts/scripts/music_spec_check.py +1 -1
  57. package/lib/drama/skills/short-drama-write/SKILL.md +3 -2
  58. package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +1 -1
  59. package/lib/drama/skills/short-drama-write/scripts/screenplay_index.py +2 -2
  60. package/lib/drama/skills/short-drama-write/scripts/voice_sheet_check.py +1 -1
  61. package/lib/index.js +865 -33
  62. package/lib/index.js.map +4 -4
  63. package/lib/novel-to-game/LICENSE +21 -0
  64. package/lib/novel-to-game/README.md +180 -0
  65. package/lib/novel-to-game/README_ZH.md +178 -0
  66. package/lib/novel-to-game/VERSION +1 -0
  67. package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +217 -0
  68. package/lib/novel-to-game/examples/jin-ping-mei/build/app/.vercelignore +3 -0
  69. package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +130 -0
  70. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/compound_new_guofeng.webp +0 -0
  71. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/fate_coda.webp +0 -0
  72. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/meng.webp +0 -0
  73. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/pan.webp +0 -0
  74. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/pinger.webp +0 -0
  75. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/xuee.webp +0 -0
  76. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/yue.webp +0 -0
  77. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_accord_five.webp +0 -0
  78. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_afterglow_five.webp +0 -0
  79. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_alliance.webp +0 -0
  80. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day10.webp +0 -0
  81. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day15.webp +0 -0
  82. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day5.webp +0 -0
  83. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/title_new_guofeng.webp +0 -0
  84. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/pan_pinger.webp +0 -0
  85. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/pan_xuee.webp +0 -0
  86. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_meng.webp +0 -0
  87. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_pan.webp +0 -0
  88. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_pinger.webp +0 -0
  89. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/meng/explicit.webp +0 -0
  90. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/meng/prelude.webp +0 -0
  91. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/meng.webp +0 -0
  92. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/pan.webp +0 -0
  93. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/pinger.webp +0 -0
  94. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/xuee.webp +0 -0
  95. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/yue.webp +0 -0
  96. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pan/explicit.webp +0 -0
  97. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pan/prelude.webp +0 -0
  98. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pinger/explicit.webp +0 -0
  99. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pinger/prelude.webp +0 -0
  100. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/xuee/explicit.webp +0 -0
  101. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/xuee/prelude.webp +0 -0
  102. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/explicit.webp +0 -0
  103. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/prelude.webp +0 -0
  104. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/meng/night.webp +0 -0
  105. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/pan/night.webp +0 -0
  106. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/pinger/night.webp +0 -0
  107. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/xuee/night.webp +0 -0
  108. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/yue/night.webp +0 -0
  109. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/household/li_jiaoer.webp +0 -0
  110. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/scene/gate_collector.webp +0 -0
  111. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +1105 -0
  112. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +2539 -0
  113. package/lib/novel-to-game/examples/jin-ping-mei/build/app/index.html +18 -0
  114. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/assets.js +147 -0
  115. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/audio.js +332 -0
  116. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +7472 -0
  117. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +13077 -0
  118. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +3028 -0
  119. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +32 -0
  120. package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +10 -0
  121. package/lib/novel-to-game/examples/jin-ping-mei/build/app/vercel.json +23 -0
  122. package/lib/novel-to-game/examples/jin-ping-mei/build/art/generated-art.json +161 -0
  123. package/lib/novel-to-game/examples/jin-ping-mei/example.json +12 -0
  124. package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +31 -0
  125. package/lib/novel-to-game/examples/jin-ping-mei/qa/playable-model.json +99 -0
  126. package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +41 -0
  127. package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +129 -0
  128. package/lib/novel-to-game/manifest.json +508 -0
  129. package/lib/novel-to-game/skills/game-art-direction/SKILL.md +73 -0
  130. package/lib/novel-to-game/skills/game-art-direction/agents/openai.yaml +4 -0
  131. package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +119 -0
  132. package/lib/novel-to-game/skills/game-build/SKILL.md +84 -0
  133. package/lib/novel-to-game/skills/game-build/agents/openai.yaml +4 -0
  134. package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +109 -0
  135. package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +56 -0
  136. package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +81 -0
  137. package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +37 -0
  138. package/lib/novel-to-game/skills/game-concept/SKILL.md +72 -0
  139. package/lib/novel-to-game/skills/game-concept/agents/openai.yaml +4 -0
  140. package/lib/novel-to-game/skills/game-concept/references/concept-method.md +94 -0
  141. package/lib/novel-to-game/skills/game-qa/SKILL.md +50 -0
  142. package/lib/novel-to-game/skills/game-qa/agents/openai.yaml +4 -0
  143. package/lib/novel-to-game/skills/game-qa/references/qa-contract.md +66 -0
  144. package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +45 -0
  145. package/lib/novel-to-game/skills/game-world-design/SKILL.md +63 -0
  146. package/lib/novel-to-game/skills/game-world-design/agents/openai.yaml +4 -0
  147. package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +59 -0
  148. package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +53 -0
  149. package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +84 -0
  150. package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +56 -0
  151. package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +124 -0
  152. package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +77 -0
  153. package/lib/novel-to-game/skills/novel-game-analyze/agents/openai.yaml +4 -0
  154. package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +65 -0
  155. package/lib/novel-to-game/skills/novel-to-game/SKILL.md +71 -0
  156. package/lib/novel-to-game/skills/novel-to-game/agents/openai.yaml +4 -0
  157. package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +42 -0
  158. package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +51 -0
  159. package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +65 -0
  160. package/lib/oh-story/manifest.json +415 -305
  161. package/lib/oh-story/roles/consistency-checker.md +1 -1
  162. package/lib/oh-story/roles/narrative-writer.md +32 -33
  163. package/lib/oh-story/roles/story-architect.md +16 -26
  164. package/lib/oh-story/skills/browser-cdp/SKILL.md +1 -1
  165. package/lib/oh-story/skills/story/SKILL.md +16 -9
  166. package/lib/oh-story/skills/story/VERSION +1 -1
  167. package/lib/oh-story/skills/story/references/author-memory.md +163 -0
  168. package/lib/oh-story/skills/story/scripts/author_memory_commit.py +871 -0
  169. package/lib/oh-story/skills/story-cover/SKILL.md +1 -1
  170. package/lib/oh-story/skills/story-deslop/SKILL.md +10 -5
  171. package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +2 -1
  172. package/lib/oh-story/skills/story-deslop/references/author-memory.md +163 -0
  173. package/lib/oh-story/skills/story-deslop/references/banned-words.md +6 -4
  174. package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +871 -0
  175. package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +64 -1
  176. package/lib/oh-story/skills/story-import/SKILL.md +19 -10
  177. package/lib/oh-story/skills/story-import/references/format-and-structure.md +9 -10
  178. package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +2 -2
  179. package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +1 -1
  180. package/lib/oh-story/skills/story-import/references/tracking-transaction.md +14 -6
  181. package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +251 -0
  182. package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +98 -9
  183. package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +246 -0
  184. package/lib/oh-story/skills/story-long-analyze/SKILL.md +4 -4
  185. package/lib/oh-story/skills/story-long-scan/SKILL.md +1 -1
  186. package/lib/oh-story/skills/story-long-write/SKILL.md +35 -21
  187. package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +2 -1
  188. package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +7 -3
  189. package/lib/oh-story/skills/story-long-write/references/author-memory.md +163 -0
  190. package/lib/oh-story/skills/story-long-write/references/banned-words.md +6 -4
  191. package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +6 -6
  192. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +1 -1
  193. package/lib/oh-story/skills/story-long-write/references/genre-readers.md +1 -1
  194. package/lib/oh-story/skills/{story-setup/references/agent-references/hooks-chapter.md → story-long-write/references/long-chapter-hooks.md} +5 -5
  195. package/lib/oh-story/skills/story-long-write/references/{quality-checklist.md → long-chapter-quality.md} +2 -1
  196. package/lib/oh-story/skills/story-long-write/references/long-format.md +42 -0
  197. package/lib/oh-story/skills/{story-setup/references/agent-references/genre-catalog.md → story-long-write/references/long-genre-catalog.md} +11 -97
  198. package/lib/oh-story/skills/{story-setup/references/agent-references/genre-core-mechanics.md → story-long-write/references/long-genre-mechanics.md} +1 -1
  199. package/lib/oh-story/skills/{story-short-write/references/reversal-toolkit.md → story-long-write/references/long-reversal.md} +18 -32
  200. package/lib/oh-story/skills/story-long-write/references/{hooks-suspense.md → long-suspense.md} +6 -6
  201. package/lib/oh-story/skills/story-long-write/references/opening-design.md +1 -1
  202. package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +1 -1
  203. package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
  204. package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +14 -6
  205. package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +32 -27
  206. package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +4 -4
  207. package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +2 -2
  208. package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +14 -14
  209. package/lib/oh-story/skills/story-long-write/references/writing-craft.md +54 -54
  210. package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +871 -0
  211. package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +64 -1
  212. package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +251 -0
  213. package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +303 -0
  214. package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +98 -9
  215. package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +246 -0
  216. package/lib/oh-story/skills/story-review/SKILL.md +26 -17
  217. package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +2 -1
  218. package/lib/oh-story/skills/story-review/references/author-memory.md +163 -0
  219. package/lib/oh-story/skills/story-review/references/banned-words.md +6 -4
  220. package/lib/oh-story/skills/story-review/references/{quality-checklist.md → review-quality.md} +22 -21
  221. package/lib/oh-story/skills/story-review/references/tracking-transaction.md +14 -6
  222. package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +871 -0
  223. package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +64 -1
  224. package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +98 -9
  225. package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +246 -0
  226. package/lib/oh-story/skills/story-setup/SKILL.md +74 -33
  227. package/lib/oh-story/skills/story-setup/references/agent-references/agent-quality.md +72 -0
  228. package/lib/oh-story/skills/story-setup/references/agent-references/agent-reference-profiles.md +55 -0
  229. package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +2 -1
  230. package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +6 -4
  231. package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +9 -10
  232. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards.md +1 -1
  233. package/lib/oh-story/skills/story-setup/references/agent-references/genre-readers.md +1 -1
  234. package/lib/oh-story/skills/{story-short-analyze/references/hooks-chapter.md → story-setup/references/agent-references/long-chapter-hooks.md} +5 -5
  235. package/lib/oh-story/skills/story-setup/references/agent-references/long-emotional-methods.md +209 -0
  236. package/lib/oh-story/skills/{story-long-write/references/genre-catalog.md → story-setup/references/agent-references/long-genre-catalog.md} +11 -97
  237. package/lib/oh-story/skills/{story-long-write/references/genre-core-mechanics.md → story-setup/references/agent-references/long-genre-mechanics.md} +1 -1
  238. package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +40 -0
  239. package/lib/oh-story/skills/story-setup/references/agent-references/{reversal-toolkit.md → long-reversal.md} +18 -32
  240. package/lib/oh-story/skills/{story-short-write/references/hooks-suspense.md → story-setup/references/agent-references/long-suspense.md} +6 -6
  241. package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +1 -1
  242. package/lib/oh-story/skills/story-setup/references/agent-references/{emotional-methods.md → short-emotional-methods.md} +4 -4
  243. package/lib/oh-story/skills/{story-long-write/references/genre-writing-formulas.md → story-setup/references/agent-references/short-genre-formulas.md} +32 -44
  244. package/lib/oh-story/skills/story-setup/references/agent-references/short-quality.md +39 -0
  245. package/lib/oh-story/skills/{story-long-write/references/reversal-toolkit.md → story-setup/references/agent-references/short-reversal.md} +29 -23
  246. package/lib/oh-story/skills/story-setup/references/agent-references/{hooks-suspense.md → short-suspense.md} +31 -31
  247. package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
  248. package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +55 -55
  249. package/lib/oh-story/skills/story-short-analyze/SKILL.md +28 -28
  250. package/lib/oh-story/skills/story-short-analyze/references/{character-basics.md → analysis-character-basics.md} +1 -1
  251. package/lib/oh-story/skills/story-short-analyze/references/{character-design-methods.md → analysis-character-design.md} +1 -1
  252. package/lib/oh-story/skills/story-short-analyze/references/{character-relations.md → analysis-character-relations.md} +1 -1
  253. package/lib/oh-story/skills/story-short-analyze/references/{hooks-paragraph.md → analysis-paragraph-hooks.md} +1 -1
  254. package/lib/oh-story/skills/story-short-analyze/references/{genre-readers.md → analysis-reader-profile.md} +2 -2
  255. package/lib/oh-story/skills/story-short-analyze/references/analysis-report-style.md +45 -0
  256. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-genres.md +65 -0
  257. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-hooks.md +77 -0
  258. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-mechanics.md +82 -0
  259. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-patterns.md +50 -0
  260. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-suspense.md +98 -0
  261. package/lib/oh-story/skills/story-short-analyze/references/{genre-writing-techniques.md → analysis-writing-techniques.md} +21 -19
  262. package/lib/oh-story/skills/story-short-analyze/references/deconstruction-examples.md +1 -1
  263. package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +21 -28
  264. package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +1 -1
  265. package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +17 -17
  266. package/lib/oh-story/skills/story-short-analyze/references/{quality-checklist.md → source-story-quality.md} +28 -49
  267. package/lib/oh-story/skills/story-short-analyze/references/zhihu-style.md +2 -2
  268. package/lib/oh-story/skills/story-short-scan/SKILL.md +1 -1
  269. package/lib/oh-story/skills/story-short-write/SKILL.md +78 -55
  270. package/lib/oh-story/skills/story-short-write/references/author-memory.md +163 -0
  271. package/lib/oh-story/skills/story-short-write/references/banned-words.md +6 -4
  272. package/lib/oh-story/skills/story-short-write/references/cross-book-recall.md +3 -3
  273. package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +4 -4
  274. package/lib/oh-story/skills/story-short-write/references/genre-styles//344/270/226/346/203/205/346/211/223/350/204/270.md +3 -3
  275. package/lib/oh-story/skills/story-short-write/references/genre-styles//345/217/214/347/224/267/344/270/273.md +2 -2
  276. package/lib/oh-story/skills/story-short-write/references/genre-styles//345/244/215/344/273/207/346/211/223/350/204/270.md +3 -3
  277. package/lib/oh-story/skills/story-short-write/references/genre-styles//345/256/205/346/226/227/345/256/253/346/226/227.md +6 -6
  278. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/200/273/350/243/201/350/261/252/351/227/250.md +3 -3
  279. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/202/254/347/226/221.md +4 -4
  280. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/260/221/344/277/227/346/200/252/350/260/210.md +7 -7
  281. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/262/231/351/233/225/350/204/221/346/264/236.md +5 -5
  282. package/lib/oh-story/skills/story-short-write/references/genre-styles//347/224/234/345/256/240.md +4 -4
  283. package/lib/oh-story/skills/story-short-write/references/genre-styles//350/277/275/345/246/273/347/201/253/350/221/254/345/234/272.md +3 -3
  284. package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +32 -44
  285. package/lib/oh-story/skills/story-short-write/references/genre-writing-techniques.md +20 -18
  286. package/lib/oh-story/skills/story-short-write/references/output-contract.md +1 -1
  287. package/lib/oh-story/skills/story-short-write/references/short-craft.md +18 -18
  288. package/lib/oh-story/skills/story-short-write/references/short-deslop.md +1 -1
  289. package/lib/oh-story/skills/story-short-write/references/short-format.md +38 -8
  290. package/lib/oh-story/skills/story-short-write/references/{quality-checklist.md → short-prose-quality.md} +22 -21
  291. package/lib/oh-story/skills/story-short-write/references/short-reversal.md +355 -0
  292. package/lib/oh-story/skills/{story-short-analyze/references/hooks-suspense.md → story-short-write/references/short-suspense.md} +31 -31
  293. package/lib/oh-story/skills/story-short-write/references/submission-craft.md +2 -2
  294. package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +31 -22
  295. package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +871 -0
  296. package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +64 -1
  297. package/lib/oh-story/skills/story-short-write/scripts/check-delivery-contract.js +212 -0
  298. package/lib/oh-story/skills/story-short-write/scripts/check-phase2-contract.js +363 -0
  299. package/lib/types/client/drama-production-view.d.ts +34 -0
  300. package/lib/types/client/drama-production-view.d.ts.map +1 -0
  301. package/lib/types/client/drama-production.d.ts +92 -0
  302. package/lib/types/client/drama-production.d.ts.map +1 -0
  303. package/lib/types/client/file-activity.d.ts +4 -2
  304. package/lib/types/client/file-activity.d.ts.map +1 -1
  305. package/lib/types/client/index.d.ts +1 -1
  306. package/lib/types/client/index.d.ts.map +1 -1
  307. package/lib/types/client/production-intents.d.ts +9 -0
  308. package/lib/types/client/production-intents.d.ts.map +1 -0
  309. package/lib/types/client/production-prompts.d.ts +9 -0
  310. package/lib/types/client/production-prompts.d.ts.map +1 -0
  311. package/lib/types/client/production-runtime.d.ts +58 -0
  312. package/lib/types/client/production-runtime.d.ts.map +1 -0
  313. package/lib/types/client/video-studio.d.ts +41 -0
  314. package/lib/types/client/video-studio.d.ts.map +1 -0
  315. package/lib/types/client/workbench-ui.d.ts +5 -0
  316. package/lib/types/client/workbench-ui.d.ts.map +1 -0
  317. package/lib/types/game-verification.d.ts +16 -0
  318. package/lib/types/game-verification.d.ts.map +1 -0
  319. package/lib/types/index.d.ts +3 -1
  320. package/lib/types/index.d.ts.map +1 -1
  321. package/lib/types/production-intent.d.ts +21 -0
  322. package/lib/types/production-intent.d.ts.map +1 -0
  323. package/lib/types/production-tool.d.ts +5 -0
  324. package/lib/types/production-tool.d.ts.map +1 -0
  325. package/lib/types/role-tool.d.ts.map +1 -1
  326. package/lib/types/skill-provider.d.ts +7 -0
  327. package/lib/types/skill-provider.d.ts.map +1 -1
  328. package/lib/types/video-project.d.ts +42 -0
  329. package/lib/types/video-project.d.ts.map +1 -0
  330. package/lib/types/workspace-request-trust.d.ts +6 -0
  331. package/lib/types/workspace-request-trust.d.ts.map +1 -1
  332. package/lib/types/workspace-route.d.ts +20 -0
  333. package/lib/types/workspace-route.d.ts.map +1 -1
  334. package/lib/video-recap/CHANGELOG.md +282 -0
  335. package/lib/video-recap/LICENSE +21 -0
  336. package/lib/video-recap/README.en.md +257 -0
  337. package/lib/video-recap/README.md +255 -0
  338. package/lib/video-recap/manifest.json +684 -0
  339. package/lib/video-recap/skills/video-assemble/SKILL.md +97 -0
  340. package/lib/video-recap/skills/video-assemble/references/jianying/LICENSE.duo-video +21 -0
  341. package/lib/video-recap/skills/video-assemble/references/jianying/SOURCE.md +15 -0
  342. package/lib/video-recap/skills/video-assemble/references/jianying/empty_draft_meta_info.json +69 -0
  343. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_segment.json +84 -0
  344. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_video_material.json +98 -0
  345. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_draft.json +204 -0
  346. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_material_video.json +83 -0
  347. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_meta_material_value.json +23 -0
  348. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_project_info.json +133 -0
  349. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_segment.json +70 -0
  350. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_text_styles.json +30 -0
  351. package/lib/video-recap/skills/video-assemble/references/jianying/empty_yj_material_audio.json +41 -0
  352. package/lib/video-recap/skills/video-assemble/references/jianying/empty_yj_material_text.json +109 -0
  353. package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +88 -0
  354. package/lib/video-recap/skills/video-assemble/scripts/assemble.py +345 -0
  355. package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +34 -0
  356. package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +305 -0
  357. package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +94 -0
  358. package/lib/video-recap/skills/video-assemble/scripts/audio_automation.py +205 -0
  359. package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +555 -0
  360. package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +261 -0
  361. package/lib/video-recap/skills/video-assemble/scripts/jianying_builders.py +767 -0
  362. package/lib/video-recap/skills/video-assemble/scripts/jianying_model.py +111 -0
  363. package/lib/video-recap/skills/video-assemble/scripts/jianying_optional.py +24 -0
  364. package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +109 -0
  365. package/lib/video-recap/skills/video-assemble/scripts/jianying_templates.py +37 -0
  366. package/lib/video-recap/skills/video-assemble/scripts/jianying_timeline_contract.py +191 -0
  367. package/lib/video-recap/skills/video-assemble/scripts/jianying_tracks.py +75 -0
  368. package/lib/video-recap/skills/video-assemble/scripts/jianying_writer.py +394 -0
  369. package/lib/video-recap/skills/video-assemble/scripts/lib.py +239 -0
  370. package/lib/video-recap/skills/video-assemble/scripts/media.py +243 -0
  371. package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +477 -0
  372. package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +46 -0
  373. package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +486 -0
  374. package/lib/video-recap/skills/video-assemble/scripts/subtitle_core.py +376 -0
  375. package/lib/video-recap/skills/video-assemble/scripts/subtitle_render.py +88 -0
  376. package/lib/video-recap/skills/video-assemble/scripts/timeline.py +248 -0
  377. package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +89 -0
  378. package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +512 -0
  379. package/lib/video-recap/skills/video-cut/SKILL.md +90 -0
  380. package/lib/video-recap/skills/video-cut/scripts/cut.py +67 -0
  381. package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +320 -0
  382. package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +545 -0
  383. package/lib/video-recap/skills/video-cut/scripts/cut_render.py +348 -0
  384. package/lib/video-recap/skills/video-cut/scripts/lib.py +77 -0
  385. package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +341 -0
  386. package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +249 -0
  387. package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +745 -0
  388. package/lib/video-recap/skills/video-recap/SKILL.md +216 -0
  389. package/lib/video-recap/skills/video-recap/references/config-playbook.md +61 -0
  390. package/lib/video-recap/skills/video-recap/references/creative-editing-playbook.md +225 -0
  391. package/lib/video-recap/skills/video-recap/references/data-schema.md +534 -0
  392. package/lib/video-recap/skills/video-recap/references/research-guide.md +74 -0
  393. package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +23 -0
  394. package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +251 -0
  395. package/lib/video-recap/skills/video-recap/scripts/doctor.py +483 -0
  396. package/lib/video-recap/skills/video-recap/scripts/final_qc.py +737 -0
  397. package/lib/video-recap/skills/video-recap/scripts/lib.py +242 -0
  398. package/lib/video-recap/skills/video-recap/scripts/materials.py +457 -0
  399. package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +95 -0
  400. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_client.py +18 -0
  401. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_contract.py +9 -0
  402. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_evidence.py +371 -0
  403. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_observations.py +201 -0
  404. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_payload.py +227 -0
  405. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_report.py +362 -0
  406. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_runner.py +129 -0
  407. package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +338 -0
  408. package/lib/video-recap/skills/video-recap/scripts/recap.py +9 -0
  409. package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +140 -0
  410. package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +550 -0
  411. package/lib/video-recap/skills/video-recap/scripts/recap_review.py +77 -0
  412. package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +654 -0
  413. package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +335 -0
  414. package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +217 -0
  415. package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +795 -0
  416. package/lib/video-recap/skills/video-script/SKILL.md +248 -0
  417. package/lib/video-recap/skills/video-script/references/creative-editing-playbook.md +225 -0
  418. package/lib/video-recap/skills/video-script/references/research-guide.md +75 -0
  419. package/lib/video-recap/skills/video-script/scripts/agent_brief.py +399 -0
  420. package/lib/video-recap/skills/video-script/scripts/agent_text.py +410 -0
  421. package/lib/video-recap/skills/video-script/scripts/brief_context.py +403 -0
  422. package/lib/video-recap/skills/video-script/scripts/brief_inputs.py +332 -0
  423. package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +442 -0
  424. package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +237 -0
  425. package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +492 -0
  426. package/lib/video-recap/skills/video-script/scripts/lib.py +327 -0
  427. package/lib/video-recap/skills/video-script/scripts/narration.py +12 -0
  428. package/lib/video-recap/skills/video-script/scripts/narration_lint.py +678 -0
  429. package/lib/video-recap/skills/video-script/scripts/review.py +42 -0
  430. package/lib/video-recap/skills/video-script/scripts/review_grounding.py +262 -0
  431. package/lib/video-recap/skills/video-script/scripts/review_response.py +761 -0
  432. package/lib/video-recap/skills/video-script/scripts/review_runner.py +200 -0
  433. package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +204 -0
  434. package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +199 -0
  435. package/lib/video-recap/skills/video-script/scripts/validate.py +164 -0
  436. package/lib/video-recap/skills/video-understanding/SKILL.md +75 -0
  437. package/lib/video-recap/skills/video-understanding/references/data-schema.md +299 -0
  438. package/lib/video-recap/skills/video-understanding/references/prompt-templates.md +37 -0
  439. package/lib/video-recap/skills/video-understanding/references/research-guide.md +74 -0
  440. package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +399 -0
  441. package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +410 -0
  442. package/lib/video-recap/skills/video-understanding/scripts/asr.py +260 -0
  443. package/lib/video-recap/skills/video-understanding/scripts/brief.py +12 -0
  444. package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +403 -0
  445. package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +332 -0
  446. package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +442 -0
  447. package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +647 -0
  448. package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +237 -0
  449. package/lib/video-recap/skills/video-understanding/scripts/detect.py +513 -0
  450. package/lib/video-recap/skills/video-understanding/scripts/extract.py +62 -0
  451. package/lib/video-recap/skills/video-understanding/scripts/lib.py +470 -0
  452. package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +678 -0
  453. package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +204 -0
  454. package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +507 -0
  455. package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +199 -0
  456. package/lib/video-recap/skills/video-understanding/scripts/understand.py +9 -0
  457. package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +216 -0
  458. package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +327 -0
  459. package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +367 -0
  460. package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +162 -0
  461. package/lib/video-recap/skills/video-understanding/scripts/vlm.py +705 -0
  462. package/lib/video-recap/skills/video-voiceover/SKILL.md +81 -0
  463. package/lib/video-recap/skills/video-voiceover/scripts/dub.py +637 -0
  464. package/lib/video-recap/skills/video-voiceover/scripts/fish_audio.py +109 -0
  465. package/lib/video-recap/skills/video-voiceover/scripts/lib.py +421 -0
  466. package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +113 -0
  467. package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +782 -0
  468. package/package.json +55 -40
  469. package/lib/oh-story/skills/story-deslop/scripts/check-outline-copy.js +0 -241
  470. package/lib/oh-story/skills/story-long-write/references/format-and-structure.md +0 -169
  471. package/lib/oh-story/skills/story-setup/references/agent-references/genre-writing-formulas.md +0 -444
  472. package/lib/oh-story/skills/story-setup/references/agent-references/quality-checklist.md +0 -247
  473. package/lib/oh-story/skills/story-short-analyze/references/anti-ai-writing.md +0 -582
  474. package/lib/oh-story/skills/story-short-analyze/references/banned-words.md +0 -147
  475. package/lib/oh-story/skills/story-short-analyze/references/genre-catalog.md +0 -490
  476. package/lib/oh-story/skills/story-short-analyze/references/genre-core-mechanics.md +0 -425
  477. package/lib/oh-story/skills/story-short-analyze/references/genre-writing-formulas.md +0 -453
  478. /package/lib/oh-story/skills/{story-long-write/references/hooks-chapter.md → story-setup/references/agent-references/short-chapter-hooks.md} +0 -0
  479. /package/lib/oh-story/skills/{story-long-write/references/hooks-paragraph.md → story-setup/references/agent-references/short-paragraph-hooks.md} +0 -0
@@ -0,0 +1,457 @@
1
+ """Filesystem material library helpers for video-recap.
2
+
3
+ The library is intentionally grep-friendly: JSON/MD/JSONL files on disk, no DB,
4
+ no embeddings, no raw-media copies. Current metadata lives in each material
5
+ folder; the root ``materials_index.jsonl`` is an append-only journal for grep and
6
+ history.
7
+
8
+ Secret handling is BEST-EFFORT defense-in-depth, NOT a guarantee. ``_redact_text``
9
+ masks common credential value shapes (``tp-``/``sk-``/``gh*_``/``AKIA``/JWT and
10
+ ``KEY=VALUE`` assignments) and ``_redact_json`` drops the value of exact
11
+ credential-named keys, but a secret in an unrecognized format can still slip
12
+ through. Keep secrets (API keys, tokens) out of the analysis artifacts in the
13
+ first place — the key is read from the environment/``.env`` and never needs to be
14
+ written into scenes/ASR/VLM/summary JSON.
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import hashlib
19
+ import json
20
+ import os
21
+ import re
22
+ import tempfile
23
+ from datetime import datetime, timezone
24
+ from pathlib import Path
25
+
26
+ ALLOWED_ARTIFACTS = {
27
+ "scenes.json",
28
+ "asr_result.json",
29
+ "asr_clean.json",
30
+ "vlm_analysis.json",
31
+ "silence_periods.json",
32
+ "speech_boundary_anchors.json",
33
+ "speech_boundary_anchors_output.json",
34
+ "timeline_fusion.json",
35
+ "understanding_index.json",
36
+ "understanding_index.md",
37
+ "agent_narration_brief.md",
38
+ "background_research.json",
39
+ "reference_profile.json",
40
+ "reference_match_report.json",
41
+ "recap_run_manifest.json",
42
+ }
43
+ # Redaction targets credential VALUE shapes, not English/Chinese dictionary words — the
44
+ # library must stay a faithful copy of the analysis. Bare words like "secret"/"token"
45
+ # legitimately appear in transcripts/summaries and must NOT be touched.
46
+ SECRET_VALUE_RES = (
47
+ re.compile(r"\btp-[A-Za-z0-9_-]{8,}\b"), # MiMo Token Plan keys
48
+ re.compile(r"\bsk-[A-Za-z0-9_-]{16,}\b"), # OpenAI-style keys
49
+ re.compile(r"\bgh[pousr]_[A-Za-z0-9]{20,}\b"), # GitHub tokens
50
+ re.compile(r"\bAKIA[0-9A-Z]{16}\b"), # AWS access key id
51
+ re.compile(r"\beyJ[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\b"), # JWT
52
+ )
53
+ # `KEY=VALUE` / `"key": "value"` assignments whose key denotes a credential -> mask the VALUE.
54
+ SECRET_ASSIGN_RE = re.compile(
55
+ r"(?i)(\b(?:mimo(?:_\w+)?_api_key|api_key|secret_key|access_token|refresh_token|"
56
+ r"authorization|password|passwd|bearer)\b\s*[:=]\s*)(\"?)([^\s\"',;]+)(\"?)"
57
+ )
58
+ # Exact JSON/dict key names whose VALUE is a credential and must be dropped (key name kept).
59
+ SECRET_KEY_NAMES = frozenset({
60
+ "api_key", "mimo_api_key", "mimo_asr_api_key", "mimo_tts_api_key", "mimo_video_api_key",
61
+ "secret_key", "access_token", "refresh_token", "authorization", "password", "passwd",
62
+ })
63
+
64
+
65
+ def utc_now_iso() -> str:
66
+ return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
67
+
68
+
69
+ _FILE_FINGERPRINT_MEMO = {}
70
+
71
+
72
+ def _file_identity(path):
73
+ """(device, inode, size, mtime_ns) — changes whenever the bytes could have changed."""
74
+ st = os.stat(os.fspath(path))
75
+ return (st.st_dev, st.st_ino, st.st_size, st.st_mtime_ns)
76
+
77
+
78
+ def file_fingerprint(path: str | Path, chunk_size: int = 1024 * 1024) -> str:
79
+ """Return a full-content fingerprint for cache-correct identity checks.
80
+
81
+ The digest covers CONTENT only — never the path or mtime — so a copied video or
82
+ artifact is still recognised as the same asset, while any byte change invalidates
83
+ the cache even if timestamps, size, head, or tail bytes are misleading.
84
+
85
+ Identity metadata is used ONLY to memoize within a single process. One understanding
86
+ run fingerprints the same source video 8-10 times and the whole extracted frame set
87
+ 2-3 times; on a 40-minute video at fps=1 that is gigabytes of redundant reads before
88
+ any real work starts. A file rewritten in place gets a new (size, mtime_ns) and is
89
+ re-hashed, so the memo can never serve a stale digest.
90
+ """
91
+ key = _file_identity(path)
92
+ memoized = _FILE_FINGERPRINT_MEMO.get(key)
93
+ if memoized is not None:
94
+ return memoized
95
+ h = hashlib.sha256()
96
+ with open(os.fspath(path), "rb") as f:
97
+ for chunk in iter(lambda: f.read(chunk_size), b""):
98
+ h.update(chunk)
99
+ digest = h.hexdigest()
100
+ _FILE_FINGERPRINT_MEMO[key] = digest
101
+ return digest
102
+
103
+
104
+ def stable_json_dumps(value) -> str:
105
+ return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"), default=str)
106
+
107
+
108
+ def settings_fingerprint(settings) -> str:
109
+ return hashlib.sha256(stable_json_dumps(settings or {}).encode("utf-8")).hexdigest()
110
+
111
+
112
+ def source_id_from_fingerprint(fingerprint: str) -> str:
113
+ return f"src_{str(fingerprint)[:12]}"
114
+
115
+
116
+ def assign_source_ids(sources: list[dict]) -> list[dict]:
117
+ """Assign deterministic source_id values to manifest source records.
118
+
119
+ Base id is ``src_<sha256[:12]>``. When the same fingerprint appears more than
120
+ once in one project, the first keeps the base id and later distinct paths get
121
+ ``_<pathhash6>`` suffixes. Input order does not affect the id for unique
122
+ fingerprints, and duplicate suffixes are derived from resolved path text.
123
+ """
124
+ seen: dict[str, set[str]] = {}
125
+ assigned = []
126
+ for raw in sources:
127
+ item = dict(raw)
128
+ fp = str(item.get("source_video_fingerprint") or item.get("fingerprint") or "")
129
+ if not fp:
130
+ raise ValueError("source fingerprint is required")
131
+ base = source_id_from_fingerprint(fp)
132
+ path = str(Path(item.get("source_path") or item.get("path") or "").resolve())
133
+ used = seen.setdefault(fp, set())
134
+ if not used:
135
+ sid = base
136
+ else:
137
+ suffix = hashlib.sha256(path.encode("utf-8")).hexdigest()[:6]
138
+ sid = f"{base}_{suffix}"
139
+ # Avoid accidental path-hash collisions within one manifest.
140
+ while sid in used:
141
+ suffix = hashlib.sha256((path + sid).encode("utf-8")).hexdigest()[:6]
142
+ sid = f"{base}_{suffix}"
143
+ used.add(sid)
144
+ item["source_id"] = sid
145
+ item["source_path"] = path
146
+ assigned.append(item)
147
+ return assigned
148
+
149
+
150
+ def _slug(text: str, max_len: int = 48) -> str:
151
+ raw = Path(str(text or "material")).stem.lower()
152
+ raw = re.sub(r"[^a-z0-9\u4e00-\u9fff._-]+", "-", raw).strip("-._")
153
+ return (raw or "material")[:max_len].strip("-._") or "material"
154
+
155
+
156
+ def material_id_for(source_path: str | Path, source_fingerprint: str) -> str:
157
+ return f"{_slug(str(source_path))}-{str(source_fingerprint)[:12]}"
158
+
159
+
160
+ def material_dir(library_dir: str | Path, material_id: str) -> Path:
161
+ return Path(library_dir) / "materials" / material_id
162
+
163
+
164
+ def _load_json(path: Path):
165
+ try:
166
+ return json.loads(path.read_text(encoding="utf-8"))
167
+ except (OSError, ValueError, TypeError):
168
+ return None
169
+
170
+
171
+ def _safe_text(value, limit: int = 600) -> str:
172
+ text = str(value or "").replace("\x00", " ").strip()
173
+ return _redact_text(text)[:limit]
174
+
175
+
176
+ def _redact_text(text: str) -> str:
177
+ """Redact credential value shapes only; leave ordinary words (secret/token/…) intact."""
178
+ text = str(text or "")
179
+ for rx in SECRET_VALUE_RES:
180
+ text = rx.sub("[redacted-token]", text)
181
+ return SECRET_ASSIGN_RE.sub(lambda m: f"{m.group(1)}{m.group(2)}[redacted-key]{m.group(4)}", text)
182
+
183
+
184
+ def _redact_json(value):
185
+ if isinstance(value, dict):
186
+ out = {}
187
+ for key, item in value.items():
188
+ key_text = str(key)
189
+ # Drop only the value of an exact credential-named key; keep the key name and
190
+ # never coalesce distinct keys (so benign fields like token_economy survive).
191
+ if key_text.strip().lower() in SECRET_KEY_NAMES:
192
+ out[key_text] = "[redacted]"
193
+ else:
194
+ out[key_text] = _redact_json(item)
195
+ return out
196
+ if isinstance(value, list):
197
+ return [_redact_json(item) for item in value]
198
+ if isinstance(value, str):
199
+ return _redact_text(value)
200
+ return value
201
+
202
+
203
+ def copy_artifact_redacted(src: Path, dst: Path) -> None:
204
+ """Copy an allowed JSON/MD artifact without persisting obvious secret markers."""
205
+ try:
206
+ text = src.read_text(encoding="utf-8")
207
+ except UnicodeDecodeError as exc:
208
+ raise ValueError(f"refusing to persist non-text material artifact: {src.name}") from exc
209
+ if src.suffix.lower() == ".json":
210
+ try:
211
+ data = json.loads(text)
212
+ except ValueError:
213
+ dst.write_text(_redact_text(text), encoding="utf-8")
214
+ else:
215
+ dst.write_text(json.dumps(_redact_json(data), ensure_ascii=False, indent=2), encoding="utf-8")
216
+ else:
217
+ dst.write_text(_redact_text(text), encoding="utf-8")
218
+
219
+
220
+ def summarize_work_dir(work_dir: str | Path, *, source_name: str = "") -> dict:
221
+ """Best-effort small summary for material.md/index grep."""
222
+ work = Path(work_dir)
223
+ summary_parts = []
224
+ tags = []
225
+ idx = _load_json(work / "understanding_index.json")
226
+ if isinstance(idx, dict):
227
+ for key in ("summary", "story_summary", "overall_summary", "one_sentence"):
228
+ if idx.get(key):
229
+ summary_parts.append(_safe_text(idx.get(key)))
230
+ break
231
+ for key in ("characters", "entities", "keywords", "tags"):
232
+ vals = idx.get(key)
233
+ if isinstance(vals, list):
234
+ tags.extend(_safe_text(v, 80) for v in vals[:12])
235
+ vlm = _load_json(work / "vlm_analysis.json")
236
+ if isinstance(vlm, dict):
237
+ for key in ("summary", "overall_summary", "video_summary"):
238
+ if vlm.get(key):
239
+ summary_parts.append(_safe_text(vlm.get(key)))
240
+ break
241
+ scenes = _load_json(work / "scenes.json")
242
+ if isinstance(scenes, list):
243
+ tags.append(f"scenes:{len(scenes)}")
244
+ asr = _load_json(work / "asr_result.json")
245
+ if isinstance(asr, list):
246
+ tags.append(f"asr:{len(asr)}")
247
+ summary = " | ".join(p for p in summary_parts if p) or f"Analyzed video material: {source_name or work.name}"
248
+ dedup_tags = []
249
+ for tag in tags:
250
+ if tag and tag not in dedup_tags:
251
+ dedup_tags.append(tag)
252
+ return {"summary": summary, "tags": dedup_tags[:20]}
253
+
254
+
255
+ def allowed_artifact_paths(work_dir: str | Path) -> list[Path]:
256
+ work = Path(work_dir)
257
+ return [work / name for name in sorted(ALLOWED_ARTIFACTS) if (work / name).is_file()]
258
+
259
+
260
+ def write_material_md(path: Path, metadata: dict, summary: str, tags: list[str]) -> None:
261
+ artifact_lines = "\n".join(f"- `{a.get('name')}` → `{a.get('path')}`" for a in metadata.get("artifacts", []))
262
+ tags_text = ", ".join(tags) if tags else "(none)"
263
+ text = f"""# Material: {metadata.get('source_name') or metadata.get('material_id')}
264
+
265
+ - material_id: `{metadata.get('material_id')}`
266
+ - source: `{metadata.get('source_path')}`
267
+ - source_fingerprint: `{metadata.get('source_video_fingerprint')}`
268
+ - settings_fingerprint: `{metadata.get('settings_fingerprint')}`
269
+ - updated_at: `{metadata.get('updated_at')}`
270
+ - tags: {tags_text}
271
+
272
+ ## Summary
273
+ {_safe_text(summary, 2000)}
274
+
275
+ ## Artifacts
276
+ {artifact_lines or '- (none)'}
277
+ """
278
+ path.write_text(text, encoding="utf-8")
279
+
280
+
281
+ def save_material(
282
+ library_dir: str | Path,
283
+ work_dir: str | Path,
284
+ source_path: str | Path,
285
+ source_fingerprint: str,
286
+ settings_fp: str,
287
+ *,
288
+ duration: float | None = None,
289
+ source_id: str | None = None,
290
+ material_id: str | None = None,
291
+ now: str | None = None,
292
+ ) -> dict:
293
+ """Persist small reusable analysis artifacts into the filesystem library."""
294
+ lib = Path(library_dir)
295
+ mid = material_id or material_id_for(source_path, source_fingerprint)
296
+ dest = material_dir(lib, mid)
297
+ artifacts_dir = dest / "artifacts"
298
+ artifacts_dir.mkdir(parents=True, exist_ok=True)
299
+ now = now or utc_now_iso()
300
+
301
+ sources = allowed_artifact_paths(work_dir)
302
+ fresh_names = {src.name for src in sources}
303
+ # Reconcile: drop allowed artifacts left from a previous (larger) save so the on-disk
304
+ # artifacts/ dir always matches material.json — a stale orphan must not surface in greps.
305
+ for stale in artifacts_dir.iterdir() if artifacts_dir.exists() else []:
306
+ if stale.is_file() and stale.name in ALLOWED_ARTIFACTS and stale.name not in fresh_names:
307
+ stale.unlink()
308
+ copied = []
309
+ for src in sources:
310
+ dst = artifacts_dir / src.name
311
+ copy_artifact_redacted(src, dst)
312
+ copied.append({"name": src.name, "path": f"artifacts/{src.name}", "sha256": file_fingerprint(dst)})
313
+
314
+ existing = _load_json(dest / "material.json")
315
+ created_at = existing.get("created_at") if isinstance(existing, dict) and existing.get("created_at") else now
316
+ source_path = Path(source_path).resolve()
317
+ summary_info = summarize_work_dir(work_dir, source_name=source_path.name)
318
+ metadata = {
319
+ "schema_version": 1,
320
+ "material_id": mid,
321
+ "source_id": source_id,
322
+ "source_name": source_path.name,
323
+ "source_path": str(source_path),
324
+ "source_video_fingerprint": source_fingerprint,
325
+ "duration": duration,
326
+ "settings_fingerprint": settings_fp,
327
+ "artifacts": copied,
328
+ "created_at": created_at,
329
+ "updated_at": now,
330
+ }
331
+ (dest / "material.json").write_text(json.dumps(metadata, ensure_ascii=False, indent=2), encoding="utf-8")
332
+ write_material_md(dest / "material.md", metadata, summary_info["summary"], summary_info["tags"])
333
+
334
+ index_record = {
335
+ "schema_version": 1,
336
+ "event": "saved",
337
+ "material_id": mid,
338
+ "source_name": metadata["source_name"],
339
+ "source_path": metadata["source_path"],
340
+ "source_video_fingerprint": source_fingerprint,
341
+ "settings_fingerprint": settings_fp,
342
+ "summary": summary_info["summary"],
343
+ "tags": summary_info["tags"],
344
+ "material_dir": str(dest),
345
+ "updated_at": now,
346
+ }
347
+ lib.mkdir(parents=True, exist_ok=True)
348
+ with (lib / "materials_index.jsonl").open("a", encoding="utf-8") as f:
349
+ f.write(json.dumps(index_record, ensure_ascii=False, separators=(",", ":")) + "\n")
350
+ return metadata
351
+
352
+
353
+ def find_material_by_fingerprint(library_dir: str | Path, source_fingerprint: str) -> dict | None:
354
+ root = Path(library_dir) / "materials"
355
+ if not root.exists():
356
+ return None
357
+ candidates = []
358
+ for meta_path in root.glob("*/material.json"):
359
+ data = _load_json(meta_path)
360
+ if isinstance(data, dict) and data.get("source_video_fingerprint") == source_fingerprint:
361
+ data["material_dir"] = str(meta_path.parent)
362
+ candidates.append(data)
363
+ if not candidates:
364
+ return None
365
+ # Deterministic fallback policy for legacy/manual callers that do not know
366
+ # the expected material_id: newest wins, then material_id for stable ties.
367
+ candidates.sort(key=lambda d: (str(d.get("updated_at") or ""), str(d.get("material_id") or "")), reverse=True)
368
+ return candidates[0]
369
+
370
+
371
+ def restore_material(
372
+ library_dir: str | Path,
373
+ work_dir: str | Path,
374
+ *,
375
+ source_fingerprint: str,
376
+ settings_fp: str,
377
+ material_id: str | None = None,
378
+ overwrite: bool = True,
379
+ prune_stale_allowed: bool = True,
380
+ ) -> dict:
381
+ """Restore allowed artifacts when fingerprint/settings match.
382
+
383
+ Returns a status dict and never partially restores on mismatch.
384
+
385
+ By default the restored material is treated as the authoritative analysis
386
+ snapshot for ``work_dir``: allowed analysis artifacts are staged first, then
387
+ stale allowed artifacts in the destination are pruned before replacement.
388
+ This prevents reused work dirs from mixing old scenes/ASR/VLM files with a
389
+ newly restored material. Non-allowed files (for example narration.json or
390
+ clip_plan.json) are never removed here.
391
+ """
392
+ lib = Path(library_dir)
393
+ if material_id:
394
+ meta = _load_json(material_dir(lib, material_id) / "material.json")
395
+ if isinstance(meta, dict):
396
+ meta["material_dir"] = str(material_dir(lib, material_id))
397
+ else:
398
+ meta = find_material_by_fingerprint(lib, source_fingerprint)
399
+ if not isinstance(meta, dict):
400
+ return {"restored": False, "reason": "material not found"}
401
+ if meta.get("source_video_fingerprint") != source_fingerprint:
402
+ return {"restored": False, "reason": "source fingerprint mismatch", "material_id": meta.get("material_id")}
403
+ if meta.get("settings_fingerprint") != settings_fp:
404
+ return {"restored": False, "reason": "settings fingerprint mismatch", "material_id": meta.get("material_id")}
405
+
406
+ src_dir = Path(meta.get("material_dir") or material_dir(lib, meta["material_id"])) / "artifacts"
407
+ if not src_dir.exists():
408
+ return {"restored": False, "reason": "material artifacts missing", "material_id": meta.get("material_id")}
409
+ dest = Path(work_dir)
410
+ dest.mkdir(parents=True, exist_ok=True)
411
+ staged = []
412
+ with tempfile.TemporaryDirectory(prefix=".material_restore_", dir=str(dest)) as tmp_name:
413
+ tmp = Path(tmp_name)
414
+ for artifact in meta.get("artifacts") or []:
415
+ name = artifact.get("name") if isinstance(artifact, dict) else None
416
+ if name not in ALLOWED_ARTIFACTS:
417
+ continue
418
+ src = src_dir / name
419
+ if not src.exists():
420
+ continue
421
+ copy_artifact_redacted(src, tmp / name)
422
+ staged.append(name)
423
+ if not staged:
424
+ return {
425
+ "restored": False,
426
+ "reason": "material artifacts empty",
427
+ "material_id": meta.get("material_id"),
428
+ }
429
+
430
+ pruned = []
431
+ if prune_stale_allowed:
432
+ staged_set = set(staged)
433
+ for name in sorted(ALLOWED_ARTIFACTS):
434
+ # Never prune an artifact we are about to restore: the restore loop below
435
+ # honors `overwrite`, so pruning a staged name would (with overwrite=False)
436
+ # delete it and then skip the copy, losing the file.
437
+ if name in staged_set:
438
+ continue
439
+ out = dest / name
440
+ if out.exists():
441
+ out.unlink()
442
+ pruned.append(name)
443
+
444
+ restored = []
445
+ for name in staged:
446
+ out = dest / name
447
+ if out.exists() and not overwrite:
448
+ continue
449
+ (tmp / name).replace(out)
450
+ restored.append(name)
451
+ return {
452
+ "restored": bool(restored),
453
+ "material_id": meta.get("material_id"),
454
+ "artifacts": restored,
455
+ "pruned_artifacts": pruned,
456
+ "material": meta,
457
+ }
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env python3
2
+ """Public API and CLI entrypoint for advisory MiMo multimodal QC."""
3
+
4
+ import mimo_qc_report
5
+ import mimo_qc_runner
6
+ from mimo_qc_evidence import collect_evidence, safe_mimo_config
7
+ from mimo_qc_observations import normalize_observations
8
+ from mimo_qc_payload import build_payload
9
+ from mimo_qc_client import mimo_qc_api_call
10
+ from mimo_qc_contract import DEFAULT_STAGE
11
+
12
+ sample_video_frames = mimo_qc_report.sample_video_frames
13
+ write_report = mimo_qc_report.write_report
14
+ clear_report = mimo_qc_runner.clear_report
15
+
16
+
17
+ def build_report(
18
+ work_dir,
19
+ *,
20
+ stage=DEFAULT_STAGE,
21
+ fixture=None,
22
+ dry_run=False,
23
+ judge=None,
24
+ config=None,
25
+ final_output=None,
26
+ live=False,
27
+ refresh=False,
28
+ frame_sampler=None,
29
+ existing=None,
30
+ ):
31
+ return mimo_qc_report.build_report(
32
+ work_dir,
33
+ stage=stage,
34
+ fixture=fixture,
35
+ dry_run=dry_run,
36
+ judge=judge,
37
+ config=config,
38
+ final_output=final_output,
39
+ live=live,
40
+ refresh=refresh,
41
+ frame_sampler=frame_sampler,
42
+ existing=existing,
43
+ api_call=mimo_qc_api_call,
44
+ )
45
+
46
+
47
+ def run(
48
+ work_dir,
49
+ *,
50
+ stage=DEFAULT_STAGE,
51
+ fixture=None,
52
+ dry_run=False,
53
+ judge=None,
54
+ config=None,
55
+ final_output=None,
56
+ output=None,
57
+ live=False,
58
+ refresh=False,
59
+ frame_sampler=None,
60
+ ):
61
+ return mimo_qc_runner.run(
62
+ work_dir,
63
+ stage=stage,
64
+ fixture=fixture,
65
+ dry_run=dry_run,
66
+ judge=judge,
67
+ config=config,
68
+ final_output=final_output,
69
+ output=output,
70
+ live=live,
71
+ refresh=refresh,
72
+ frame_sampler=frame_sampler,
73
+ api_call=mimo_qc_api_call,
74
+ )
75
+
76
+
77
+ def main(argv=None):
78
+ return mimo_qc_runner.main(argv, run_callable=run)
79
+
80
+ __all__ = [
81
+ "build_payload",
82
+ "build_report",
83
+ "clear_report",
84
+ "collect_evidence",
85
+ "main",
86
+ "mimo_qc_api_call",
87
+ "normalize_observations",
88
+ "run",
89
+ "sample_video_frames",
90
+ "safe_mimo_config",
91
+ "write_report",
92
+ ]
93
+
94
+ if __name__ == "__main__":
95
+ raise SystemExit(main())
@@ -0,0 +1,18 @@
1
+ """Load this skill's local MiMo client exactly once under an unambiguous name."""
2
+
3
+ import importlib.util
4
+ from pathlib import Path
5
+
6
+ _LOCAL_LIB_PATH = Path(__file__).with_name("lib.py")
7
+ _LOCAL_LIB_SPEC = importlib.util.spec_from_file_location(
8
+ "video_recap_mimo_qc_lib", _LOCAL_LIB_PATH
9
+ )
10
+ if (
11
+ _LOCAL_LIB_SPEC is None or _LOCAL_LIB_SPEC.loader is None
12
+ ): # pragma: no cover - import invariant
13
+ raise ImportError(f"cannot load local MiMo QC client: {_LOCAL_LIB_PATH}")
14
+ _LOCAL_LIB = importlib.util.module_from_spec(_LOCAL_LIB_SPEC)
15
+ _LOCAL_LIB_SPEC.loader.exec_module(_LOCAL_LIB)
16
+
17
+ DEFAULT_CONFIG = _LOCAL_LIB.CONFIG
18
+ mimo_qc_api_call = _LOCAL_LIB.mimo_qc_api_call
@@ -0,0 +1,9 @@
1
+ """Shared constants for the self-contained MiMo QC adapter."""
2
+
3
+ ARTIFACT_NAME = "mimo_qc.json"
4
+ DEFAULT_STAGE = "pre_assemble"
5
+ MAX_OBSERVATIONS = 12
6
+ MAX_MESSAGE_CHARS = 800
7
+ MAX_FRAMES = 6
8
+ MAX_FRAME_DIMENSION = 768
9
+ FRAME_SAMPLER_VERSION = 2