@oh-story/dsh 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (524) hide show
  1. package/README.md +58 -12
  2. package/lib/client.js +2970 -537
  3. package/lib/client.js.map +4 -4
  4. package/lib/drama/manifest.json +218 -178
  5. package/lib/drama/skills/short-drama/SKILL.md +110 -117
  6. package/lib/drama/skills/short-drama/references/audience-reveal.md +14 -67
  7. package/lib/drama/skills/short-drama/references/contract-and-ownership.md +26 -77
  8. package/lib/drama/skills/short-drama/references/creator-documents.md +204 -0
  9. package/lib/drama/skills/short-drama/references/creator-workflow.md +73 -63
  10. package/lib/drama/skills/short-drama/references/knowhow-index.md +5 -0
  11. package/lib/drama/skills/short-drama/references/look-development.md +2 -3
  12. package/lib/drama/skills/short-drama/references/pickup-and-alternate.md +14 -48
  13. package/lib/drama/skills/short-drama/references/production-form-profiles.md +1 -1
  14. package/lib/drama/skills/short-drama/references/reference-roles.md +32 -135
  15. package/lib/drama/skills/short-drama/references/runtime-preflight.md +16 -19
  16. package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +1002 -0
  17. package/lib/drama/skills/short-drama/scripts/project_tool.py +485 -5
  18. package/lib/drama/skills/short-drama-assets/SKILL.md +59 -170
  19. package/lib/drama/skills/short-drama-assets/references/asset-review-checklist.md +18 -25
  20. package/lib/drama/skills/short-drama-assets/references/character-and-look.md +13 -15
  21. package/lib/drama/skills/short-drama-assets/references/continuity-delta.md +11 -14
  22. package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +116 -0
  23. package/lib/drama/skills/short-drama-assets/references/identity-vs-variant.md +6 -12
  24. package/lib/drama/skills/short-drama-assets/references/occurrence-extraction.md +8 -11
  25. package/lib/drama/skills/short-drama-assets/references/prop-and-state.md +21 -2
  26. package/lib/drama/skills/short-drama-assets/references/stage-contract.md +12 -105
  27. package/lib/drama/skills/short-drama-assets/references/voice-direction.md +12 -32
  28. package/lib/drama/skills/short-drama-assets/scripts/asset_check.py +1 -1
  29. package/lib/drama/skills/short-drama-image-prompts/SKILL.md +45 -155
  30. package/lib/drama/skills/short-drama-image-prompts/references/character-and-look.md +12 -13
  31. package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +12 -13
  32. package/lib/drama/skills/short-drama-image-prompts/references/edit-and-revision.md +39 -51
  33. package/lib/drama/skills/short-drama-image-prompts/references/location-plate.md +3 -4
  34. package/lib/drama/skills/short-drama-image-prompts/references/look-and-state-variant.md +15 -14
  35. package/lib/drama/skills/short-drama-image-prompts/references/lookdev-frame.md +11 -15
  36. package/lib/drama/skills/short-drama-image-prompts/references/production-sheet-recipes.md +1 -1
  37. package/lib/drama/skills/short-drama-image-prompts/references/prop-plate.md +4 -7
  38. package/lib/drama/skills/short-drama-image-prompts/references/review-and-fixtures.md +23 -23
  39. package/lib/drama/skills/short-drama-image-prompts/references/stage-contract.md +8 -65
  40. package/lib/drama/skills/short-drama-image-prompts/scripts/image_prompt_check.py +8 -8
  41. package/lib/drama/skills/short-drama-image-prompts/scripts/selftest.py +4 -2
  42. package/lib/drama/skills/short-drama-produce/SKILL.md +67 -15
  43. package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +65 -8
  44. package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +85 -0
  45. package/lib/drama/skills/short-drama-produce/references/providers/seedance.md +27 -10
  46. package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +362 -3
  47. package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +627 -40
  48. package/lib/drama/skills/short-drama-produce/scripts/selftest.py +5 -4
  49. package/lib/drama/skills/short-drama-review/SKILL.md +61 -123
  50. package/lib/drama/skills/short-drama-review/references/anti-template-repair.md +10 -15
  51. package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +15 -16
  52. package/lib/drama/skills/short-drama-review/references/project-calibration.md +7 -9
  53. package/lib/drama/skills/short-drama-review/references/review-method.md +8 -9
  54. package/lib/drama/skills/short-drama-review/references/rubric-assets-prompts.md +9 -13
  55. package/lib/drama/skills/short-drama-review/references/rubric-story-script.md +104 -4
  56. package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +27 -19
  57. package/lib/drama/skills/short-drama-review/references/stage-contract.md +4 -63
  58. package/lib/drama/skills/short-drama-review/scripts/review_check.py +1 -1
  59. package/lib/drama/skills/short-drama-storyboard/SKILL.md +116 -238
  60. package/lib/drama/skills/short-drama-storyboard/assets/shot-template.jsonl +1 -1
  61. package/lib/drama/skills/short-drama-storyboard/references/blocking-playbooks.md +9 -34
  62. package/lib/drama/skills/short-drama-storyboard/references/comic-keyframe-lexicon.md +156 -0
  63. package/lib/drama/skills/short-drama-storyboard/references/coverage-audition.md +5 -10
  64. package/lib/drama/skills/short-drama-storyboard/references/keyframe-craft.md +12 -7
  65. package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +19 -4
  66. package/lib/drama/skills/short-drama-storyboard/references/review-and-fixtures.md +27 -62
  67. package/lib/drama/skills/short-drama-storyboard/references/scene-visual-plan.md +16 -25
  68. package/lib/drama/skills/short-drama-storyboard/references/screenplay-to-keyframe-example.md +21 -23
  69. package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +8 -10
  70. package/lib/drama/skills/short-drama-storyboard/references/shot-revision-identity.md +9 -13
  71. package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +16 -71
  72. package/lib/drama/skills/short-drama-storyboard/scripts/storyboard_check.py +1 -1
  73. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +123 -230
  74. package/lib/drama/skills/short-drama-video-prompts/assets/motion-spec.jsonl.md +6 -1
  75. package/lib/drama/skills/short-drama-video-prompts/assets/video-prompts.md +17 -1
  76. package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +10 -8
  77. package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +24 -6
  78. package/lib/drama/skills/short-drama-video-prompts/references/generability.md +2 -2
  79. package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +154 -0
  80. package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +35 -27
  81. package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +8 -11
  82. package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +13 -10
  83. package/lib/drama/skills/short-drama-video-prompts/references/review-and-fixtures.md +34 -63
  84. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +41 -0
  85. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +66 -0
  86. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +17 -73
  87. package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +115 -0
  88. package/lib/drama/skills/short-drama-video-prompts/scripts/container_check.py +1 -1
  89. package/lib/drama/skills/short-drama-video-prompts/scripts/music_spec_check.py +1 -1
  90. package/lib/drama/skills/short-drama-write/SKILL.md +74 -268
  91. package/lib/drama/skills/short-drama-write/references/dialogue-craft.md +7 -0
  92. package/lib/drama/skills/short-drama-write/references/scene-handoff-capsule.md +17 -43
  93. package/lib/drama/skills/short-drama-write/references/screenplay-format.md +15 -15
  94. package/lib/drama/skills/short-drama-write/references/script-craft.md +208 -21
  95. package/lib/drama/skills/short-drama-write/references/stage-contract.md +11 -70
  96. package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +1 -1
  97. package/lib/drama/skills/short-drama-write/scripts/screenplay_index.py +2 -2
  98. package/lib/drama/skills/short-drama-write/scripts/voice_sheet_check.py +1 -1
  99. package/lib/index.js +971 -46
  100. package/lib/index.js.map +4 -4
  101. package/lib/novel-to-game/LICENSE +21 -0
  102. package/lib/novel-to-game/README.md +180 -0
  103. package/lib/novel-to-game/README_ZH.md +178 -0
  104. package/lib/novel-to-game/VERSION +1 -0
  105. package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +217 -0
  106. package/lib/novel-to-game/examples/jin-ping-mei/build/app/.vercelignore +3 -0
  107. package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +130 -0
  108. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/compound_new_guofeng.webp +0 -0
  109. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/fate_coda.webp +0 -0
  110. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/meng.webp +0 -0
  111. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/pan.webp +0 -0
  112. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/pinger.webp +0 -0
  113. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/xuee.webp +0 -0
  114. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/yue.webp +0 -0
  115. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_accord_five.webp +0 -0
  116. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_afterglow_five.webp +0 -0
  117. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_alliance.webp +0 -0
  118. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day10.webp +0 -0
  119. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day15.webp +0 -0
  120. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day5.webp +0 -0
  121. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/title_new_guofeng.webp +0 -0
  122. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/pan_pinger.webp +0 -0
  123. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/pan_xuee.webp +0 -0
  124. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_meng.webp +0 -0
  125. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_pan.webp +0 -0
  126. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_pinger.webp +0 -0
  127. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/meng/explicit.webp +0 -0
  128. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/meng/prelude.webp +0 -0
  129. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/meng.webp +0 -0
  130. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/pan.webp +0 -0
  131. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/pinger.webp +0 -0
  132. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/xuee.webp +0 -0
  133. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/yue.webp +0 -0
  134. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pan/explicit.webp +0 -0
  135. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pan/prelude.webp +0 -0
  136. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pinger/explicit.webp +0 -0
  137. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pinger/prelude.webp +0 -0
  138. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/xuee/explicit.webp +0 -0
  139. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/xuee/prelude.webp +0 -0
  140. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/explicit.webp +0 -0
  141. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/prelude.webp +0 -0
  142. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/meng/night.webp +0 -0
  143. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/pan/night.webp +0 -0
  144. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/pinger/night.webp +0 -0
  145. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/xuee/night.webp +0 -0
  146. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/yue/night.webp +0 -0
  147. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/household/li_jiaoer.webp +0 -0
  148. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/scene/gate_collector.webp +0 -0
  149. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +1105 -0
  150. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +2539 -0
  151. package/lib/novel-to-game/examples/jin-ping-mei/build/app/index.html +18 -0
  152. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/assets.js +147 -0
  153. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/audio.js +332 -0
  154. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +7472 -0
  155. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +13077 -0
  156. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +3028 -0
  157. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +32 -0
  158. package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +10 -0
  159. package/lib/novel-to-game/examples/jin-ping-mei/build/app/vercel.json +23 -0
  160. package/lib/novel-to-game/examples/jin-ping-mei/build/art/generated-art.json +161 -0
  161. package/lib/novel-to-game/examples/jin-ping-mei/example.json +12 -0
  162. package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +31 -0
  163. package/lib/novel-to-game/examples/jin-ping-mei/qa/playable-model.json +99 -0
  164. package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +41 -0
  165. package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +129 -0
  166. package/lib/novel-to-game/manifest.json +508 -0
  167. package/lib/novel-to-game/skills/game-art-direction/SKILL.md +73 -0
  168. package/lib/novel-to-game/skills/game-art-direction/agents/openai.yaml +4 -0
  169. package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +119 -0
  170. package/lib/novel-to-game/skills/game-build/SKILL.md +84 -0
  171. package/lib/novel-to-game/skills/game-build/agents/openai.yaml +4 -0
  172. package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +109 -0
  173. package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +56 -0
  174. package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +81 -0
  175. package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +37 -0
  176. package/lib/novel-to-game/skills/game-concept/SKILL.md +72 -0
  177. package/lib/novel-to-game/skills/game-concept/agents/openai.yaml +4 -0
  178. package/lib/novel-to-game/skills/game-concept/references/concept-method.md +94 -0
  179. package/lib/novel-to-game/skills/game-qa/SKILL.md +50 -0
  180. package/lib/novel-to-game/skills/game-qa/agents/openai.yaml +4 -0
  181. package/lib/novel-to-game/skills/game-qa/references/qa-contract.md +66 -0
  182. package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +45 -0
  183. package/lib/novel-to-game/skills/game-world-design/SKILL.md +63 -0
  184. package/lib/novel-to-game/skills/game-world-design/agents/openai.yaml +4 -0
  185. package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +59 -0
  186. package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +53 -0
  187. package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +84 -0
  188. package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +56 -0
  189. package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +124 -0
  190. package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +77 -0
  191. package/lib/novel-to-game/skills/novel-game-analyze/agents/openai.yaml +4 -0
  192. package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +65 -0
  193. package/lib/novel-to-game/skills/novel-to-game/SKILL.md +71 -0
  194. package/lib/novel-to-game/skills/novel-to-game/agents/openai.yaml +4 -0
  195. package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +42 -0
  196. package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +51 -0
  197. package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +65 -0
  198. package/lib/oh-story/manifest.json +419 -309
  199. package/lib/oh-story/roles/character-designer.md +4 -4
  200. package/lib/oh-story/roles/consistency-checker.md +1 -1
  201. package/lib/oh-story/roles/narrative-writer.md +41 -42
  202. package/lib/oh-story/roles/story-architect.md +19 -23
  203. package/lib/oh-story/roles/story-explorer.md +8 -5
  204. package/lib/oh-story/skills/browser-cdp/SKILL.md +1 -1
  205. package/lib/oh-story/skills/story/SKILL.md +16 -9
  206. package/lib/oh-story/skills/story/VERSION +1 -1
  207. package/lib/oh-story/skills/story/references/author-memory.md +163 -0
  208. package/lib/oh-story/skills/story/scripts/author_memory_commit.py +871 -0
  209. package/lib/oh-story/skills/story-cover/SKILL.md +1 -1
  210. package/lib/oh-story/skills/story-deslop/SKILL.md +10 -5
  211. package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +2 -1
  212. package/lib/oh-story/skills/story-deslop/references/author-memory.md +163 -0
  213. package/lib/oh-story/skills/story-deslop/references/banned-words.md +6 -4
  214. package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +871 -0
  215. package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +64 -1
  216. package/lib/oh-story/skills/story-import/SKILL.md +21 -8
  217. package/lib/oh-story/skills/story-import/references/format-and-structure.md +9 -10
  218. package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +2 -2
  219. package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +1 -1
  220. package/lib/oh-story/skills/story-import/references/tracking-transaction.md +14 -6
  221. package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +251 -0
  222. package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +98 -9
  223. package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +246 -0
  224. package/lib/oh-story/skills/story-long-analyze/SKILL.md +4 -4
  225. package/lib/oh-story/skills/story-long-scan/SKILL.md +1 -1
  226. package/lib/oh-story/skills/story-long-write/SKILL.md +35 -21
  227. package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +2 -1
  228. package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +7 -3
  229. package/lib/oh-story/skills/story-long-write/references/author-memory.md +163 -0
  230. package/lib/oh-story/skills/story-long-write/references/banned-words.md +6 -4
  231. package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +6 -6
  232. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +1 -1
  233. package/lib/oh-story/skills/story-long-write/references/genre-readers.md +1 -1
  234. package/lib/oh-story/skills/{story-setup/references/agent-references/hooks-chapter.md → story-long-write/references/long-chapter-hooks.md} +5 -5
  235. package/lib/oh-story/skills/story-long-write/references/{quality-checklist.md → long-chapter-quality.md} +2 -1
  236. package/lib/oh-story/skills/story-long-write/references/long-format.md +42 -0
  237. package/lib/oh-story/skills/{story-setup/references/agent-references/genre-catalog.md → story-long-write/references/long-genre-catalog.md} +11 -97
  238. package/lib/oh-story/skills/story-long-write/references/{genre-core-mechanics.md → long-genre-mechanics.md} +1 -1
  239. package/lib/oh-story/skills/story-long-write/references/{reversal-toolkit.md → long-reversal.md} +18 -32
  240. package/lib/oh-story/skills/story-long-write/references/{hooks-suspense.md → long-suspense.md} +6 -6
  241. package/lib/oh-story/skills/story-long-write/references/opening-design.md +1 -1
  242. package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +1 -1
  243. package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
  244. package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +14 -6
  245. package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +32 -27
  246. package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +5 -4
  247. package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +2 -2
  248. package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +17 -11
  249. package/lib/oh-story/skills/story-long-write/references/writing-craft.md +54 -54
  250. package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +871 -0
  251. package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +64 -1
  252. package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +251 -0
  253. package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +303 -0
  254. package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +98 -9
  255. package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +246 -0
  256. package/lib/oh-story/skills/story-review/SKILL.md +26 -17
  257. package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +2 -1
  258. package/lib/oh-story/skills/story-review/references/author-memory.md +163 -0
  259. package/lib/oh-story/skills/story-review/references/banned-words.md +6 -4
  260. package/lib/oh-story/skills/story-review/references/{quality-checklist.md → review-quality.md} +22 -21
  261. package/lib/oh-story/skills/story-review/references/tracking-transaction.md +14 -6
  262. package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +871 -0
  263. package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +64 -1
  264. package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +98 -9
  265. package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +246 -0
  266. package/lib/oh-story/skills/story-setup/SKILL.md +85 -41
  267. package/lib/oh-story/skills/story-setup/references/agent-references/agent-quality.md +72 -0
  268. package/lib/oh-story/skills/story-setup/references/agent-references/agent-reference-profiles.md +55 -0
  269. package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +2 -1
  270. package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +6 -4
  271. package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +9 -10
  272. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards.md +1 -1
  273. package/lib/oh-story/skills/story-setup/references/agent-references/genre-readers.md +1 -1
  274. package/lib/oh-story/skills/{story-short-analyze/references/hooks-chapter.md → story-setup/references/agent-references/long-chapter-hooks.md} +5 -5
  275. package/lib/oh-story/skills/story-setup/references/agent-references/long-emotional-methods.md +209 -0
  276. package/lib/oh-story/skills/{story-long-write/references/genre-catalog.md → story-setup/references/agent-references/long-genre-catalog.md} +11 -97
  277. package/lib/oh-story/skills/story-setup/references/agent-references/{genre-core-mechanics.md → long-genre-mechanics.md} +1 -1
  278. package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +40 -0
  279. package/lib/oh-story/skills/{story-short-write/references/reversal-toolkit.md → story-setup/references/agent-references/long-reversal.md} +18 -32
  280. package/lib/oh-story/skills/{story-short-write/references/hooks-suspense.md → story-setup/references/agent-references/long-suspense.md} +6 -6
  281. package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +1 -1
  282. package/lib/oh-story/skills/story-setup/references/agent-references/{emotional-methods.md → short-emotional-methods.md} +4 -4
  283. package/lib/oh-story/skills/{story-long-write/references/genre-writing-formulas.md → story-setup/references/agent-references/short-genre-formulas.md} +32 -44
  284. package/lib/oh-story/skills/story-setup/references/agent-references/short-quality.md +39 -0
  285. package/lib/oh-story/skills/story-setup/references/agent-references/{reversal-toolkit.md → short-reversal.md} +29 -23
  286. package/lib/oh-story/skills/story-setup/references/agent-references/{hooks-suspense.md → short-suspense.md} +31 -31
  287. package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
  288. package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +55 -55
  289. package/lib/oh-story/skills/story-short-analyze/SKILL.md +28 -28
  290. package/lib/oh-story/skills/story-short-analyze/references/{character-basics.md → analysis-character-basics.md} +1 -1
  291. package/lib/oh-story/skills/story-short-analyze/references/{character-design-methods.md → analysis-character-design.md} +1 -1
  292. package/lib/oh-story/skills/story-short-analyze/references/{character-relations.md → analysis-character-relations.md} +1 -1
  293. package/lib/oh-story/skills/story-short-analyze/references/{hooks-paragraph.md → analysis-paragraph-hooks.md} +1 -1
  294. package/lib/oh-story/skills/story-short-analyze/references/{genre-readers.md → analysis-reader-profile.md} +2 -2
  295. package/lib/oh-story/skills/story-short-analyze/references/analysis-report-style.md +45 -0
  296. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-genres.md +65 -0
  297. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-hooks.md +77 -0
  298. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-mechanics.md +82 -0
  299. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-patterns.md +50 -0
  300. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-suspense.md +98 -0
  301. package/lib/oh-story/skills/story-short-analyze/references/{genre-writing-techniques.md → analysis-writing-techniques.md} +21 -19
  302. package/lib/oh-story/skills/story-short-analyze/references/deconstruction-examples.md +1 -1
  303. package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +21 -28
  304. package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +1 -1
  305. package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +17 -17
  306. package/lib/oh-story/skills/story-short-analyze/references/{quality-checklist.md → source-story-quality.md} +28 -49
  307. package/lib/oh-story/skills/story-short-analyze/references/zhihu-style.md +2 -2
  308. package/lib/oh-story/skills/story-short-scan/SKILL.md +1 -1
  309. package/lib/oh-story/skills/story-short-write/SKILL.md +78 -55
  310. package/lib/oh-story/skills/story-short-write/references/author-memory.md +163 -0
  311. package/lib/oh-story/skills/story-short-write/references/banned-words.md +6 -4
  312. package/lib/oh-story/skills/story-short-write/references/cross-book-recall.md +3 -3
  313. package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +4 -4
  314. 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
  315. package/lib/oh-story/skills/story-short-write/references/genre-styles//345/217/214/347/224/267/344/270/273.md +2 -2
  316. 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
  317. 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
  318. 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
  319. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/202/254/347/226/221.md +4 -4
  320. 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
  321. 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
  322. package/lib/oh-story/skills/story-short-write/references/genre-styles//347/224/234/345/256/240.md +4 -4
  323. 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
  324. package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +32 -44
  325. package/lib/oh-story/skills/story-short-write/references/genre-writing-techniques.md +20 -18
  326. package/lib/oh-story/skills/story-short-write/references/output-contract.md +1 -1
  327. package/lib/oh-story/skills/story-short-write/references/short-craft.md +18 -18
  328. package/lib/oh-story/skills/story-short-write/references/short-deslop.md +1 -1
  329. package/lib/oh-story/skills/story-short-write/references/short-format.md +38 -8
  330. package/lib/oh-story/skills/story-short-write/references/{quality-checklist.md → short-prose-quality.md} +22 -21
  331. package/lib/oh-story/skills/story-short-write/references/short-reversal.md +355 -0
  332. package/lib/oh-story/skills/{story-short-analyze/references/hooks-suspense.md → story-short-write/references/short-suspense.md} +31 -31
  333. package/lib/oh-story/skills/story-short-write/references/submission-craft.md +2 -2
  334. package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +31 -22
  335. package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +871 -0
  336. package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +64 -1
  337. package/lib/oh-story/skills/story-short-write/scripts/check-delivery-contract.js +212 -0
  338. package/lib/oh-story/skills/story-short-write/scripts/check-phase2-contract.js +363 -0
  339. package/lib/types/client/drama-production-view.d.ts +34 -0
  340. package/lib/types/client/drama-production-view.d.ts.map +1 -0
  341. package/lib/types/client/drama-production.d.ts +92 -0
  342. package/lib/types/client/drama-production.d.ts.map +1 -0
  343. package/lib/types/client/file-activity.d.ts +9 -2
  344. package/lib/types/client/file-activity.d.ts.map +1 -1
  345. package/lib/types/client/index.d.ts +1 -1
  346. package/lib/types/client/index.d.ts.map +1 -1
  347. package/lib/types/client/production-intents.d.ts +9 -0
  348. package/lib/types/client/production-intents.d.ts.map +1 -0
  349. package/lib/types/client/production-prompts.d.ts +9 -0
  350. package/lib/types/client/production-prompts.d.ts.map +1 -0
  351. package/lib/types/client/production-runtime.d.ts +58 -0
  352. package/lib/types/client/production-runtime.d.ts.map +1 -0
  353. package/lib/types/client/video-studio.d.ts +41 -0
  354. package/lib/types/client/video-studio.d.ts.map +1 -0
  355. package/lib/types/client/workbench-ui.d.ts +5 -0
  356. package/lib/types/client/workbench-ui.d.ts.map +1 -0
  357. package/lib/types/game-verification.d.ts +16 -0
  358. package/lib/types/game-verification.d.ts.map +1 -0
  359. package/lib/types/index.d.ts +5 -2
  360. package/lib/types/index.d.ts.map +1 -1
  361. package/lib/types/production-intent.d.ts +21 -0
  362. package/lib/types/production-intent.d.ts.map +1 -0
  363. package/lib/types/production-tool.d.ts +5 -0
  364. package/lib/types/production-tool.d.ts.map +1 -0
  365. package/lib/types/reference-tool.d.ts +6 -0
  366. package/lib/types/reference-tool.d.ts.map +1 -0
  367. package/lib/types/role-provider.d.ts.map +1 -1
  368. package/lib/types/role-tool.d.ts +3 -1
  369. package/lib/types/role-tool.d.ts.map +1 -1
  370. package/lib/types/skill-provider.d.ts +7 -0
  371. package/lib/types/skill-provider.d.ts.map +1 -1
  372. package/lib/types/video-project.d.ts +42 -0
  373. package/lib/types/video-project.d.ts.map +1 -0
  374. package/lib/types/workspace-request-trust.d.ts +6 -0
  375. package/lib/types/workspace-request-trust.d.ts.map +1 -1
  376. package/lib/types/workspace-route.d.ts +20 -0
  377. package/lib/types/workspace-route.d.ts.map +1 -1
  378. package/lib/video-recap/CHANGELOG.md +282 -0
  379. package/lib/video-recap/LICENSE +21 -0
  380. package/lib/video-recap/README.en.md +257 -0
  381. package/lib/video-recap/README.md +255 -0
  382. package/lib/video-recap/manifest.json +684 -0
  383. package/lib/video-recap/skills/video-assemble/SKILL.md +97 -0
  384. package/lib/video-recap/skills/video-assemble/references/jianying/LICENSE.duo-video +21 -0
  385. package/lib/video-recap/skills/video-assemble/references/jianying/SOURCE.md +15 -0
  386. package/lib/video-recap/skills/video-assemble/references/jianying/empty_draft_meta_info.json +69 -0
  387. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_segment.json +84 -0
  388. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_video_material.json +98 -0
  389. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_draft.json +204 -0
  390. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_material_video.json +83 -0
  391. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_meta_material_value.json +23 -0
  392. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_project_info.json +133 -0
  393. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_segment.json +70 -0
  394. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_text_styles.json +30 -0
  395. package/lib/video-recap/skills/video-assemble/references/jianying/empty_yj_material_audio.json +41 -0
  396. package/lib/video-recap/skills/video-assemble/references/jianying/empty_yj_material_text.json +109 -0
  397. package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +88 -0
  398. package/lib/video-recap/skills/video-assemble/scripts/assemble.py +345 -0
  399. package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +34 -0
  400. package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +305 -0
  401. package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +94 -0
  402. package/lib/video-recap/skills/video-assemble/scripts/audio_automation.py +205 -0
  403. package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +555 -0
  404. package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +261 -0
  405. package/lib/video-recap/skills/video-assemble/scripts/jianying_builders.py +767 -0
  406. package/lib/video-recap/skills/video-assemble/scripts/jianying_model.py +111 -0
  407. package/lib/video-recap/skills/video-assemble/scripts/jianying_optional.py +24 -0
  408. package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +109 -0
  409. package/lib/video-recap/skills/video-assemble/scripts/jianying_templates.py +37 -0
  410. package/lib/video-recap/skills/video-assemble/scripts/jianying_timeline_contract.py +191 -0
  411. package/lib/video-recap/skills/video-assemble/scripts/jianying_tracks.py +75 -0
  412. package/lib/video-recap/skills/video-assemble/scripts/jianying_writer.py +394 -0
  413. package/lib/video-recap/skills/video-assemble/scripts/lib.py +239 -0
  414. package/lib/video-recap/skills/video-assemble/scripts/media.py +243 -0
  415. package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +477 -0
  416. package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +46 -0
  417. package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +486 -0
  418. package/lib/video-recap/skills/video-assemble/scripts/subtitle_core.py +376 -0
  419. package/lib/video-recap/skills/video-assemble/scripts/subtitle_render.py +88 -0
  420. package/lib/video-recap/skills/video-assemble/scripts/timeline.py +248 -0
  421. package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +89 -0
  422. package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +512 -0
  423. package/lib/video-recap/skills/video-cut/SKILL.md +90 -0
  424. package/lib/video-recap/skills/video-cut/scripts/cut.py +67 -0
  425. package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +320 -0
  426. package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +545 -0
  427. package/lib/video-recap/skills/video-cut/scripts/cut_render.py +348 -0
  428. package/lib/video-recap/skills/video-cut/scripts/lib.py +77 -0
  429. package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +341 -0
  430. package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +249 -0
  431. package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +745 -0
  432. package/lib/video-recap/skills/video-recap/SKILL.md +216 -0
  433. package/lib/video-recap/skills/video-recap/references/config-playbook.md +61 -0
  434. package/lib/video-recap/skills/video-recap/references/creative-editing-playbook.md +225 -0
  435. package/lib/video-recap/skills/video-recap/references/data-schema.md +534 -0
  436. package/lib/video-recap/skills/video-recap/references/research-guide.md +74 -0
  437. package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +23 -0
  438. package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +251 -0
  439. package/lib/video-recap/skills/video-recap/scripts/doctor.py +483 -0
  440. package/lib/video-recap/skills/video-recap/scripts/final_qc.py +737 -0
  441. package/lib/video-recap/skills/video-recap/scripts/lib.py +242 -0
  442. package/lib/video-recap/skills/video-recap/scripts/materials.py +457 -0
  443. package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +95 -0
  444. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_client.py +18 -0
  445. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_contract.py +9 -0
  446. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_evidence.py +371 -0
  447. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_observations.py +201 -0
  448. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_payload.py +227 -0
  449. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_report.py +362 -0
  450. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_runner.py +129 -0
  451. package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +338 -0
  452. package/lib/video-recap/skills/video-recap/scripts/recap.py +9 -0
  453. package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +140 -0
  454. package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +550 -0
  455. package/lib/video-recap/skills/video-recap/scripts/recap_review.py +77 -0
  456. package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +654 -0
  457. package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +335 -0
  458. package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +217 -0
  459. package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +795 -0
  460. package/lib/video-recap/skills/video-script/SKILL.md +248 -0
  461. package/lib/video-recap/skills/video-script/references/creative-editing-playbook.md +225 -0
  462. package/lib/video-recap/skills/video-script/references/research-guide.md +75 -0
  463. package/lib/video-recap/skills/video-script/scripts/agent_brief.py +399 -0
  464. package/lib/video-recap/skills/video-script/scripts/agent_text.py +410 -0
  465. package/lib/video-recap/skills/video-script/scripts/brief_context.py +403 -0
  466. package/lib/video-recap/skills/video-script/scripts/brief_inputs.py +332 -0
  467. package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +442 -0
  468. package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +237 -0
  469. package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +492 -0
  470. package/lib/video-recap/skills/video-script/scripts/lib.py +327 -0
  471. package/lib/video-recap/skills/video-script/scripts/narration.py +12 -0
  472. package/lib/video-recap/skills/video-script/scripts/narration_lint.py +678 -0
  473. package/lib/video-recap/skills/video-script/scripts/review.py +42 -0
  474. package/lib/video-recap/skills/video-script/scripts/review_grounding.py +262 -0
  475. package/lib/video-recap/skills/video-script/scripts/review_response.py +761 -0
  476. package/lib/video-recap/skills/video-script/scripts/review_runner.py +200 -0
  477. package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +204 -0
  478. package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +199 -0
  479. package/lib/video-recap/skills/video-script/scripts/validate.py +164 -0
  480. package/lib/video-recap/skills/video-understanding/SKILL.md +75 -0
  481. package/lib/video-recap/skills/video-understanding/references/data-schema.md +299 -0
  482. package/lib/video-recap/skills/video-understanding/references/prompt-templates.md +37 -0
  483. package/lib/video-recap/skills/video-understanding/references/research-guide.md +74 -0
  484. package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +399 -0
  485. package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +410 -0
  486. package/lib/video-recap/skills/video-understanding/scripts/asr.py +260 -0
  487. package/lib/video-recap/skills/video-understanding/scripts/brief.py +12 -0
  488. package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +403 -0
  489. package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +332 -0
  490. package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +442 -0
  491. package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +647 -0
  492. package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +237 -0
  493. package/lib/video-recap/skills/video-understanding/scripts/detect.py +513 -0
  494. package/lib/video-recap/skills/video-understanding/scripts/extract.py +62 -0
  495. package/lib/video-recap/skills/video-understanding/scripts/lib.py +470 -0
  496. package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +678 -0
  497. package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +204 -0
  498. package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +507 -0
  499. package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +199 -0
  500. package/lib/video-recap/skills/video-understanding/scripts/understand.py +9 -0
  501. package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +216 -0
  502. package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +327 -0
  503. package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +367 -0
  504. package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +162 -0
  505. package/lib/video-recap/skills/video-understanding/scripts/vlm.py +705 -0
  506. package/lib/video-recap/skills/video-voiceover/SKILL.md +81 -0
  507. package/lib/video-recap/skills/video-voiceover/scripts/dub.py +637 -0
  508. package/lib/video-recap/skills/video-voiceover/scripts/fish_audio.py +109 -0
  509. package/lib/video-recap/skills/video-voiceover/scripts/lib.py +421 -0
  510. package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +113 -0
  511. package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +782 -0
  512. package/package.json +57 -41
  513. package/lib/drama/skills/short-drama/references/lifecycle-commands.md +0 -130
  514. package/lib/oh-story/skills/story-deslop/scripts/check-outline-copy.js +0 -241
  515. package/lib/oh-story/skills/story-long-write/references/format-and-structure.md +0 -169
  516. package/lib/oh-story/skills/story-setup/references/agent-references/genre-writing-formulas.md +0 -444
  517. package/lib/oh-story/skills/story-setup/references/agent-references/quality-checklist.md +0 -247
  518. package/lib/oh-story/skills/story-short-analyze/references/anti-ai-writing.md +0 -582
  519. package/lib/oh-story/skills/story-short-analyze/references/banned-words.md +0 -147
  520. package/lib/oh-story/skills/story-short-analyze/references/genre-catalog.md +0 -490
  521. package/lib/oh-story/skills/story-short-analyze/references/genre-core-mechanics.md +0 -425
  522. package/lib/oh-story/skills/story-short-analyze/references/genre-writing-formulas.md +0 -453
  523. /package/lib/oh-story/skills/{story-long-write/references/hooks-chapter.md → story-setup/references/agent-references/short-chapter-hooks.md} +0 -0
  524. /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,761 @@
1
+ """Build review prompts and normalize model review responses."""
2
+
3
+ import json
4
+
5
+
6
+ import re
7
+
8
+ from pathlib import Path
9
+
10
+ from lib import stable_hash
11
+
12
+ from evidence_bundle import (
13
+ _clip_text,
14
+ _in_ranges,
15
+ _safe_time,
16
+ build_evidence_bundle,
17
+ build_review_coverage_metadata,
18
+ render_evidence_bundle,
19
+ validate_public_evidence_contract,
20
+ )
21
+ from review_grounding import _load, _source_fingerprint
22
+
23
+ CATEGORIES = [
24
+ "hallucination",
25
+ "weak_hook",
26
+ "no_throughline",
27
+ "narrating_picture",
28
+ "density",
29
+ "pacing",
30
+ "cliche",
31
+ "incomplete",
32
+ "disjoint_handoff",
33
+ "promise_mismatch",
34
+ "low_information_gain",
35
+ "not_write_for_ear",
36
+ "grounding_risk",
37
+ "original_audio_conflict",
38
+ "subtitle_readability",
39
+ "ai_flavor",
40
+ "weak_payoff",
41
+ "style_mismatch",
42
+ "packaging_mismatch",
43
+ "example_entity_leak",
44
+ "other",
45
+ ]
46
+
47
+ SCORECARD_KEYS = [
48
+ "promise_match",
49
+ "hook_3s",
50
+ "first_15s_delivery",
51
+ "spine_clarity",
52
+ "stakes_escalation",
53
+ "information_gain",
54
+ "spoken_language",
55
+ "sentence_brevity",
56
+ "tts_pacing",
57
+ "grounding",
58
+ "original_audio_use",
59
+ "subtitle_readability",
60
+ "ending_payoff",
61
+ "style_consistency",
62
+ "ai_flavor",
63
+ "packaging_consistency",
64
+ ]
65
+
66
+ FACTUAL_CATEGORIES = {"hallucination", "incomplete"}
67
+
68
+ COVERAGE_POLICY_VERSION = "coverage_policy_v1"
69
+
70
+ RUBRIC = """你是中文视频解说的创作复核编辑。依据素材证据和已有创作计划审阅草稿,只指出真实问题,宁缺毋滥:
71
+ 1. 反幻觉(最重要):解说里的人物、动作、因果、关系必须由带标签的 evidence 支撑。画面/对白是 timeline evidence(clock=SOURCE 或 OUTPUT);背景资料/user_context 只能作为 context-only(clock=null)辅助识别/消歧。research-only 不能升级成当前画面强事实;若与 research 一致但画面/对白里看不到,最多 severity=suggestion/category=grounding_risk,不要判 error;只有与全部可得证据矛盾才是 severity=error, category=hallucination,并指出冲突证据。
72
+ 2. 导演意图:若提供 recap_story_plan.json,检查草稿是否兑现 viewer promise、POV、dramatic question、情绪路径和 chosen_hypothesis;不要另起一条更“吸睛”但不属于该计划的故事。偏离主线 → no_throughline;承诺不兑现 → promise_mismatch/weak_payoff。
73
+ 3. change-based beats:每个 beat 应改变知识、权力、目标、关系、情绪或风险。若一段只重复上一段、删除后什么都不损失,可报 low_information_gain/pacing;不要用固定段数或秒数代替判断。
74
+ 4. 钩子:开头要提出正文真实兑现的戏剧问题/利害,不是交代场景,也不是无关的留存话术。弱钩子 → weak_hook。
75
+ 5. 给信息而非念画面:观众看得见动作表情;解说只增加上下文、因果、预期、证据支持的解释或跨越。复述画面 → narrating_picture。
76
+ 6. 视听分工:若提供 visual_audio_board.json,检查 narration_job=none 或 audio_owner=original_dialogue/action_sound/ambience/music/silence 的拍是否被旁白无故覆盖;必须听见的原声被盖住 → original_audio_conflict。沉默和低旁白覆盖本身不是问题。
77
+ 7. 人物与反应:不要用旁白解释掉素材中已经能成立的表演、停顿或反应。当前评审只能检查计划/稿件一致性,不能凭少量帧声称最终剪点一定好坏。
78
+ 8. 密度/节奏:7:3 不是配额。只在旁白没有任务、墙到墙压住原声、碎成一句一停,或无意长空档导致因果断裂时,报 density/pacing。
79
+ 9. 去废词:删空泛形容("危机四伏""震撼人心")→ cliche。
80
+ 10. 完整句子:半句话/未收尾 → incomplete。
81
+ 11. 段落衔接:解说块要为随后的原声留白铺垫,下一块要承接原声刚呈现的变化;若两块各说各的、原声进来接不上 → disjoint_handoff。
82
+ 12. 结尾回收:结尾要兑现开头承诺/主线情绪,不要突然停、只复述最后画面、没有情绪/信息回报;弱回收 → weak_payoff。
83
+ 13. 风格一致性:若提供 style_card.json,把它当作表达意图/语气/节奏边界;不符合意图 → style_mismatch。不要把 style_card 当标题/封面/首句包装计划。
84
+ 14. 包装一致性:只有提供 packaging_plan.json 时才评估标题/封面/首句/卖点承诺与正文兑现;缺失不扣分。不一致 → packaging_mismatch。不要让包装反过来改写故事判断。
85
+ 15. 去AI味:若出现模板化、空泛拔高、过度对仗、机械转折、明显 agent 示例残留,可报 ai_flavor;若出现示例人物/占位实体泄漏(如未替换示例名、模板角色)→ example_entity_leak。“不是 A,而是 B”本身不是错误,只有在先虚构旧判断再制造假洞察、或反复机械使用时才建议改写。deslop_qc.json 是 deterministic local report-only QC,不是 AIGC detector,不自动重写;只能作为证据参考,不能仅凭它判定。
86
+ 另外给一份内容效果 scorecard(1-5,advisory:除事实矛盾/残句外不要据此给 error;缺项可以省略,系统会保留为 null/未评分):promise_match/hook_3s/first_15s_delivery/spine_clarity/stakes_escalation/information_gain/spoken_language/sentence_brevity/tts_pacing/grounding/original_audio_use/subtitle_readability/ending_payoff/style_consistency/ai_flavor/packaging_consistency。`sentence_brevity` 衡量的是句子是否简洁而完整,不是越短越高;连续短句造成串珠式 TTS 应降低分数。ai_flavor 分数含义:5=自然、人味强,1=AI味明显。
87
+ 只返回 JSON(不要额外解释),格式:
88
+ {"verdict":"PASS|REVISE|FAIL","summary":"一两句总体判断","scorecard":{"promise_match":1-5,"hook_3s":1-5,"first_15s_delivery":1-5,"spine_clarity":1-5,"stakes_escalation":1-5,"information_gain":1-5,"spoken_language":1-5,"sentence_brevity":1-5,"tts_pacing":1-5,"grounding":1-5,"original_audio_use":1-5,"subtitle_readability":1-5,"ending_payoff":1-5,"style_consistency":1-5,"ai_flavor":1-5,"packaging_consistency":1-5},"hook_candidates_review":[{"candidate":"首句","type":"suspense|contrast|stakes","score":1-5,"keep":true}],"retention_risk_points":[{"time":"00:28","risk":"为什么可能掉人","fix":"怎么改"}],"highest_return_edits":["最值得改的动作"],"information_gain_notes":[{"segment":0,"label":"motive|relationship|stakes|foreshadowing|payoff|context|visual_restatement","note":"证据/改法"}],"spoken_language_rewrites":[{"segment":0,"original":"原句","rewrite":"口语改写","why":"为什么更适合听"}],"grounding_assertions":[{"segment":0,"assertion":"人物/关系/因果断言","source":"visual|asr|research|user_context|unsupported","risk":"谨慎说明"}],"findings":[{"segment":<草稿段号(从0起)或null表示整体>,"severity":"error|warning|suggestion","category":"<上面类别之一>","issue":"问题","fix":"具体改法"}]}"""
89
+
90
+
91
+ def merge_review_findings(chunks):
92
+ """Merge chunk review findings deterministically, keeping highest severity."""
93
+ severity_rank = {"error": 3, "warning": 2, "suggestion": 1}
94
+ by_key = {}
95
+ for chunk in chunks or []:
96
+ for f in (chunk or {}).get("findings", []) or []:
97
+ if not isinstance(f, dict):
98
+ continue
99
+ key = (f.get("segment"), f.get("category"), f.get("issue"))
100
+ old = by_key.get(key)
101
+ if old is None or severity_rank.get(
102
+ f.get("severity"), 0
103
+ ) > severity_rank.get(old.get("severity"), 0):
104
+ by_key[key] = dict(f)
105
+ return sorted(
106
+ by_key.values(),
107
+ key=lambda f: (
108
+ f.get("segment") is None,
109
+ f.get("segment") if f.get("segment") is not None else 10**9,
110
+ f.get("category") or "",
111
+ f.get("issue") or "",
112
+ ),
113
+ )
114
+
115
+
116
+ def _bundle_fingerprint(bundle):
117
+ try:
118
+ return stable_hash(
119
+ {
120
+ "schema_version": bundle.get("schema_version"),
121
+ "clock": bundle.get("clock"),
122
+ "coverage": bundle.get("coverage"),
123
+ "items": bundle.get("items"),
124
+ "context_items": bundle.get("context_items"),
125
+ }
126
+ )
127
+ except (TypeError, ValueError, RecursionError) as exc:
128
+ if isinstance(bundle, dict):
129
+ bundle.setdefault("metadata", {})["evidence_bundle_fingerprint_warning"] = (
130
+ f"evidence bundle fingerprint unavailable: {type(exc).__name__}"
131
+ )
132
+ return ""
133
+
134
+
135
+ def _bundle_fingerprint_warning(bundle):
136
+ if not isinstance(bundle, dict):
137
+ return "evidence bundle fingerprint unavailable: invalid bundle"
138
+ warning = (bundle.get("metadata") or {}).get("evidence_bundle_fingerprint_warning")
139
+ if warning:
140
+ return warning
141
+ return ""
142
+
143
+
144
+ def _append_warning_once(target, warning):
145
+ if warning and warning not in target:
146
+ target.append(warning)
147
+
148
+
149
+ def _bundle_prompt_size(bundle):
150
+ return len(render_evidence_bundle(bundle))
151
+
152
+
153
+ def _chunk_evidence_bundle(bundle, *, max_items=80, max_chars=12000):
154
+ """Split oversized evidence bundles for actual review calls.
155
+
156
+ Chunks are deterministic and range-oriented: selected coverage ranges remain the
157
+ contract, but each chunk carries only the timeline items whose spans overlap that
158
+ range. Context-only research remains advisory and is repeated in each chunk so the
159
+ judge can still use alias/background hints without upgrading them to timeline facts.
160
+ """
161
+ items = list(bundle.get("items") or [])
162
+ if len(items) <= max_items and _bundle_prompt_size(bundle) <= max_chars:
163
+ one = dict(bundle)
164
+ one["chunk_index"] = 0
165
+ one["chunk_count"] = 1
166
+ fp = _bundle_fingerprint(bundle)
167
+ fp_warning = _bundle_fingerprint_warning(bundle)
168
+ one.setdefault("metadata", {})["evidence_bundle_fingerprint"] = fp
169
+ if fp_warning:
170
+ one["metadata"]["evidence_bundle_fingerprint_warning"] = fp_warning
171
+ one.setdefault("warnings", list(bundle.get("warnings") or []))
172
+ _append_warning_once(one["warnings"], fp_warning)
173
+ return [one]
174
+ ranges = bundle.get("coverage", {}).get("selected_ranges") or []
175
+ chunks = []
176
+ used_ids = set()
177
+ for r in ranges:
178
+ r_items = [
179
+ item
180
+ for item in items
181
+ if _in_ranges(
182
+ _safe_time(item, "start"),
183
+ _safe_time(item, "end", _safe_time(item, "start")),
184
+ [r],
185
+ )
186
+ ]
187
+ if not r_items:
188
+ continue
189
+ for start in range(0, len(r_items), max_items):
190
+ part = r_items[start : start + max_items]
191
+ used_ids.update(id(item) for item in part)
192
+ chunk = dict(bundle)
193
+ chunk["items"] = part
194
+ chunk["coverage"] = dict(bundle.get("coverage") or {})
195
+ chunk["coverage"]["selected_ranges"] = [r]
196
+ chunk["chunk_index"] = len(chunks)
197
+ chunks.append(chunk)
198
+ leftovers = [item for item in items if id(item) not in used_ids]
199
+ for start in range(0, len(leftovers), max_items):
200
+ chunk = dict(bundle)
201
+ chunk["items"] = leftovers[start : start + max_items]
202
+ chunk["coverage"] = dict(bundle.get("coverage") or {})
203
+ chunk["coverage"]["selected_ranges"] = []
204
+ chunk["chunk_index"] = len(chunks)
205
+ chunks.append(chunk)
206
+ if not chunks:
207
+ chunk = dict(bundle)
208
+ chunk["items"] = []
209
+ chunk["chunk_index"] = 0
210
+ chunks = [chunk]
211
+ count = len(chunks)
212
+ fp = _bundle_fingerprint(bundle)
213
+ fp_warning = _bundle_fingerprint_warning(bundle)
214
+ for chunk in chunks:
215
+ chunk["chunk_count"] = count
216
+ chunk.setdefault("metadata", {})["chunked_review"] = count > 1
217
+ chunk["metadata"]["evidence_bundle_fingerprint"] = fp
218
+ if fp_warning:
219
+ chunk["metadata"]["evidence_bundle_fingerprint_warning"] = fp_warning
220
+ chunk.setdefault("warnings", list(bundle.get("warnings") or []))
221
+ _append_warning_once(chunk["warnings"], fp_warning)
222
+ return chunks
223
+
224
+
225
+ def _merge_chunk_reviews(chunk_reviews):
226
+ if not chunk_reviews:
227
+ return parse_review_response("")
228
+ if len(chunk_reviews) == 1:
229
+ return chunk_reviews[0]
230
+ verdict_rank = {"PASS": 0, "OK": 0, "REVISE": 1, "FAIL": 2}
231
+ best = max(chunk_reviews, key=lambda r: verdict_rank.get(r.get("verdict"), 1))
232
+ merged = dict(best)
233
+ merged["findings"] = merge_review_findings(chunk_reviews)
234
+ if any(f.get("severity") == "error" for f in merged["findings"]):
235
+ merged["verdict"] = "FAIL" if best.get("verdict") == "FAIL" else "REVISE"
236
+ else:
237
+ merged["verdict"] = best.get("verdict", "REVISE")
238
+ summaries = [
239
+ str(r.get("summary", "")).strip()
240
+ for r in chunk_reviews
241
+ if str(r.get("summary", "")).strip()
242
+ ]
243
+ merged["summary"] = summaries[0] if summaries else merged.get("summary", "")
244
+ merged["chunked_review"] = {
245
+ "chunk_count": len(chunk_reviews),
246
+ "findings_before_merge": sum(
247
+ len(r.get("findings") or []) for r in chunk_reviews
248
+ ),
249
+ "findings_after_merge": len(merged.get("findings") or []),
250
+ }
251
+ return merged
252
+
253
+
254
+ def _research_guardrail_qc(review, context_items):
255
+ assertions = [
256
+ a for a in (review.get("grounding_assertions") or []) if isinstance(a, dict)
257
+ ]
258
+ research_context_assertions = [
259
+ a
260
+ for a in assertions
261
+ if str(a.get("source", "")).lower() == "research"
262
+ or a.get("support") == "context_only"
263
+ or a.get("clock") is None
264
+ ]
265
+ risk_assertions = [
266
+ a
267
+ for a in research_context_assertions
268
+ if "spoiler" in str(a.get("risk", "")).lower()
269
+ or "research-only" in str(a.get("risk", "")).lower()
270
+ or "current-timeline" in str(a.get("risk", "")).lower()
271
+ ]
272
+ grounding_risk_findings = [
273
+ f
274
+ for f in (review.get("findings") or [])
275
+ if isinstance(f, dict) and f.get("category") == "grounding_risk"
276
+ ]
277
+ return {
278
+ "context_only_assertions": len(context_items or [])
279
+ + len(research_context_assertions),
280
+ "spoiler_risk_assertions": len(risk_assertions) + len(grounding_risk_findings),
281
+ "policy": "research evidence is context_only unless visual/asr-supported",
282
+ }
283
+
284
+
285
+ def _load_review_research_context(work_dir):
286
+ path = Path(work_dir) / "background_research.json"
287
+ if not path.exists():
288
+ return {}
289
+ try:
290
+ data = json.loads(path.read_text(encoding="utf-8"))
291
+ except (ValueError, OSError):
292
+ return {}
293
+ return data if isinstance(data, dict) else {}
294
+
295
+
296
+ def _format_review_research_context(research, limit=1200):
297
+ """Compact background_research.json for the quality reviewer.
298
+
299
+ Background research is rendered as context-only/advisory: it can help aliases and
300
+ disambiguation, but it is not timeline grounding for current visual/ASR facts.
301
+ """
302
+ if not isinstance(research, dict) or not research:
303
+ return ""
304
+ lines = []
305
+ for key, label in (
306
+ ("synopsis", "Synopsis"),
307
+ ("episode_context", "Episode context"),
308
+ ("worldbuilding", "Worldbuilding"),
309
+ ):
310
+ value = _clip_text(research.get(key), 500)
311
+ if value:
312
+ lines.append(f"- {label}: {value}")
313
+
314
+ characters = research.get("characters")
315
+ if isinstance(characters, dict) and characters:
316
+ lines.append("- Characters:")
317
+ for name, desc in list(characters.items())[:12]:
318
+ clean_name = _clip_text(name, 60)
319
+ clean_desc = _clip_text(desc, 160)
320
+ if clean_name:
321
+ lines.append(f" - {clean_name}:{clean_desc}")
322
+
323
+ details = research.get("character_details")
324
+ if isinstance(details, dict) and details:
325
+ lines.append("- Character details:")
326
+ for name, info in list(details.items())[:8]:
327
+ if not isinstance(info, dict):
328
+ continue
329
+ bits = []
330
+ aliases = info.get("aliases")
331
+ if isinstance(aliases, list) and aliases:
332
+ bits.append(
333
+ "别名 "
334
+ + "/".join(
335
+ _clip_text(alias, 40)
336
+ for alias in aliases[:4]
337
+ if _clip_text(alias, 40)
338
+ )
339
+ )
340
+ role = _clip_text(info.get("role"), 80)
341
+ if role:
342
+ bits.append(role)
343
+ rels = info.get("relationships")
344
+ if isinstance(rels, list) and rels:
345
+ bits.append(
346
+ ";".join(
347
+ _clip_text(rel, 80) for rel in rels[:4] if _clip_text(rel, 80)
348
+ )
349
+ )
350
+ clean_name = _clip_text(name, 60)
351
+ if clean_name and bits:
352
+ lines.append(f" - {clean_name}:{';'.join(bits)}")
353
+
354
+ arcs = research.get("plot_arcs")
355
+ if isinstance(arcs, list) and arcs:
356
+ lines.append("- Plot arcs:")
357
+ for arc in arcs[:8]:
358
+ if isinstance(arc, dict):
359
+ name = _clip_text(arc.get("name"), 80)
360
+ desc = _clip_text(arc.get("description"), 180)
361
+ status = _clip_text(arc.get("status"), 40)
362
+ if name or desc:
363
+ tail = f" [{status}]" if status else ""
364
+ lines.append(f" - {name}:{desc}{tail}")
365
+ else:
366
+ val = _clip_text(arc, 180)
367
+ if val:
368
+ lines.append(f" - {val}")
369
+
370
+ text = "\n".join(lines).strip()
371
+ return text[:limit]
372
+
373
+
374
+ def _load_optional_json(work_dir, name):
375
+ if work_dir is None:
376
+ return None
377
+ return _load(Path(work_dir), name)
378
+
379
+
380
+ def _format_json_context(title, value, limit=3000):
381
+ if value is None:
382
+ return f"## {title}\n(无)"
383
+ try:
384
+ text = json.dumps(value, ensure_ascii=False, indent=2)
385
+ except (TypeError, ValueError):
386
+ text = str(value)
387
+ return f"## {title}\n{text[:limit]}"
388
+
389
+
390
+ def _clamp_score(value, default=3):
391
+ try:
392
+ score = int(round(float(value)))
393
+ except (TypeError, ValueError):
394
+ score = default
395
+ return max(1, min(5, score))
396
+
397
+
398
+ def _normalise_scorecard(raw):
399
+ # Keep a stable scorecard schema, but do NOT fabricate a judge-looking score for a dimension
400
+ # the model omitted: those stay None ("未评分") rather than a neutral-looking 3.
401
+ source = raw if isinstance(raw, dict) else {}
402
+ return {
403
+ key: (_clamp_score(source[key]) if key in source else None)
404
+ for key in SCORECARD_KEYS
405
+ }
406
+
407
+
408
+ def _normalise_list_of_dicts(value, allowed_keys):
409
+ out = []
410
+ for item in value or []:
411
+ if isinstance(item, dict):
412
+ out.append({key: item.get(key) for key in allowed_keys if key in item})
413
+ return out
414
+
415
+
416
+ def _normalise_string_list(value, limit=12):
417
+ out = []
418
+ for item in value or []:
419
+ text = str(item).strip()
420
+ if text:
421
+ out.append(text)
422
+ if len(out) >= limit:
423
+ break
424
+ return out
425
+
426
+
427
+ def _format_draft(narration):
428
+ lines = []
429
+ for i, seg in enumerate(narration or []):
430
+ if not isinstance(seg, dict):
431
+ continue
432
+ start = seg.get("start", 0)
433
+ end = seg.get("end", 0)
434
+ text = str(seg.get("narration", "")).strip()
435
+ overlap = seg.get("overlaps_speech")
436
+ tag = "" if overlap is None else (" [盖原声]" if overlap else " [静音槽]")
437
+ lines.append(f"{i}. [{float(start):.1f}-{float(end):.1f}s]{tag} {text}")
438
+ return "\n".join(lines)
439
+
440
+
441
+ def build_review_messages(
442
+ narration,
443
+ vlm_analysis,
444
+ asr_result,
445
+ work_dir=None,
446
+ research_context=None,
447
+ evidence_bundle=None,
448
+ ):
449
+ """Pure: assemble the reviewer chat messages (testable without the API)."""
450
+ draft = _format_draft(narration)
451
+ research_obj = (
452
+ _load_review_research_context(work_dir) if work_dir is not None else {}
453
+ )
454
+ if research_context is None:
455
+ research_context = _format_review_research_context(research_obj)
456
+ bundle = evidence_bundle or build_evidence_bundle(
457
+ vlm_analysis,
458
+ asr_result,
459
+ narration,
460
+ timeline="cut_output"
461
+ if any("source_start" in x for x in (vlm_analysis or []) if isinstance(x, dict))
462
+ else "source",
463
+ research=research_obj,
464
+ )
465
+ evidence_text = render_evidence_bundle(bundle)
466
+ # Optional, agent-authored planning/QC artifacts. Bad JSON returns None via _load, matching
467
+ # existing fail-open optional artifact behavior; these only sharpen advisory scoring.
468
+ packaging = _load_optional_json(work_dir, "packaging_plan.json")
469
+ story_plan = _load_optional_json(work_dir, "recap_story_plan.json")
470
+ av_board = _load_optional_json(work_dir, "visual_audio_board.json")
471
+ style_card = _load_optional_json(work_dir, "style_card.json")
472
+ deslop_qc = _load_optional_json(work_dir, "deslop_qc.json")
473
+ user = (
474
+ f"{RUBRIC}\n\n"
475
+ "## Scorecard 评估提示\n"
476
+ "若 work_dir 提供了 packaging_plan/recap_story_plan/visual_audio_board/style_card/deslop_qc,则结合评估:"
477
+ "packaging_plan 只负责标题/封面/首句/卖点承诺与正文兑现;style_card 只负责表达意图、语气、节奏和禁忌;"
478
+ "recap_story_plan 是导演意图/备选假设/chosen POV/change-based beats 的基线;visual_audio_board 是画面/表演/原声/audio_owner/narration_job/剪辑锚点的基线;deslop_qc 是 deterministic local report-only QC,不是 AIGC detector,也不会自动重写,只能当证据参考。"
479
+ "若未提供,则基于解说本身与画面/对白证据评分,但不得因计划文件缺失给 error。统一评估:hook 是否真实兑现;每段是否产生变化/信息增量而非看图说话;"
480
+ "结尾是否兑现开头承诺/主线情绪;是否写给耳朵听(连续完整思路、自然口语、TTS可呼吸,而非一句一停);人物/关系/因果断言是否有 visual/ASR timeline evidence;research/user_context 仅可作为 context-only 辅助。\n"
481
+ "审美/风格/包装/去AI味项是 advisory:可 REVISE,但除事实矛盾/残句外不要给 error。\n\n"
482
+ f"{_format_json_context('packaging_plan.json(标题/封面/首句/卖点包装承诺,可能为空)', packaging)}\n\n"
483
+ f"{_format_json_context('recap_story_plan.json(主线/beats/original moments,可能为空)', story_plan)}\n\n"
484
+ f"{_format_json_context('visual_audio_board.json(画面/原声/字幕/剪辑锚点,可能为空)', av_board)}\n\n"
485
+ f"{_format_json_context('style_card.json(表达意图/语气/节奏/禁忌,不负责包装,可能为空)', style_card)}\n\n"
486
+ f"{_format_json_context('deslop_qc.json(deterministic report-only QC;非AIGC检测器;不自动重写,可能为空)', deslop_qc)}\n\n"
487
+ f"## 背景资料(context-only/advisory:只辅助识别/消歧/弱背景,不是当前画面强事实)\n"
488
+ f"Guardrail: clock=null/context_only;不得把未来剧情或 research-only 关系/因果升级为当前事实。\n"
489
+ f"{research_context or '(无)'}\n\n"
490
+ f"{evidence_text}\n\n"
491
+ f"## 解说草稿(共 {len([s for s in (narration or []) if isinstance(s, dict)])} 段)\n{draft or '(空)'}\n"
492
+ )
493
+ return [{"role": "user", "content": user}]
494
+
495
+
496
+ def _downgrade_context_assertions(assertions):
497
+ out = []
498
+ for item in assertions or []:
499
+ src = str(item.get("source", "")).strip().lower()
500
+ if src in {"research", "user_context"}:
501
+ item = dict(item)
502
+ item["support"] = "context_only"
503
+ item["clock"] = None
504
+ risk = str(item.get("risk", "")).strip()
505
+ label = "research-only" if src == "research" else "user_context-only"
506
+ item["risk"] = (
507
+ risk + "; " if risk else ""
508
+ ) + f"{label}: advisory/context_only, not a strong current-timeline fact"
509
+ out.append(item)
510
+ return out
511
+
512
+
513
+ def parse_review_response(text):
514
+ """Pure: robustly extract the reviewer JSON; fall back to a REVISE-unknown shell."""
515
+ raw = str(text or "")
516
+ candidate = raw
517
+ fence = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", raw, re.DOTALL)
518
+ if fence:
519
+ candidate = fence.group(1)
520
+ else:
521
+ first, last = raw.find("{"), raw.rfind("}")
522
+ if first != -1 and last > first:
523
+ candidate = raw[first : last + 1]
524
+ try:
525
+ data = json.loads(candidate)
526
+ except ValueError:
527
+ return {
528
+ "verdict": "REVISE",
529
+ "summary": "评审输出无法解析为 JSON,请人工检查。",
530
+ "findings": [],
531
+ "parse_error": True,
532
+ "raw": raw[:2000],
533
+ }
534
+ verdict = str(data.get("verdict", "REVISE")).upper()
535
+ # PASS/REVISE/FAIL is the new vocabulary; OK is kept as a backward-compatible alias.
536
+ if verdict not in ("PASS", "REVISE", "FAIL", "OK"):
537
+ verdict = "REVISE"
538
+ findings = []
539
+ for f in data.get("findings", []) or []:
540
+ if not isinstance(f, dict):
541
+ continue
542
+ sev = str(f.get("severity", "warning")).lower()
543
+ if sev not in ("error", "warning", "suggestion"):
544
+ sev = "warning"
545
+ cat = str(f.get("category", "other")).lower()
546
+ if cat not in CATEGORIES:
547
+ cat = "other"
548
+ # Only factual defects may gate strict mode (severity=error). Craft findings
549
+ # (weak_hook, narrating_picture, cliche, disjoint_handoff, ...) are advisory:
550
+ # clamp them to at most "warning" so they never block on subjective judgement.
551
+ if cat not in FACTUAL_CATEGORIES and sev == "error":
552
+ sev = "warning"
553
+ findings.append(
554
+ {
555
+ "segment": f.get("segment"),
556
+ "severity": sev,
557
+ "category": cat,
558
+ "issue": str(f.get("issue", "")).strip(),
559
+ "fix": str(f.get("fix", "")).strip(),
560
+ }
561
+ )
562
+ # The scorecard and the lists below are PURE ADVISORY enrichment: they never mutate the
563
+ # judge's verdict (deliberately not the colleague's auto-downgrade) and never gate. The
564
+ # hard pre-TTS gate stays exactly where it was — error findings counted in recap.py.
565
+ return {
566
+ "verdict": verdict,
567
+ "summary": str(data.get("summary", "")).strip(),
568
+ "scorecard": _normalise_scorecard(data.get("scorecard")),
569
+ "hook_candidates_review": _normalise_list_of_dicts(
570
+ data.get("hook_candidates_review"),
571
+ ["candidate", "type", "score", "keep", "reason"],
572
+ ),
573
+ "retention_risk_points": _normalise_list_of_dicts(
574
+ data.get("retention_risk_points"), ["time", "risk", "fix", "evidence"]
575
+ ),
576
+ "highest_return_edits": _normalise_string_list(
577
+ data.get("highest_return_edits")
578
+ ),
579
+ "information_gain_notes": _normalise_list_of_dicts(
580
+ data.get("information_gain_notes"), ["segment", "label", "note", "rewrite"]
581
+ ),
582
+ "spoken_language_rewrites": _normalise_list_of_dicts(
583
+ data.get("spoken_language_rewrites"),
584
+ ["segment", "original", "rewrite", "why"],
585
+ ),
586
+ "grounding_assertions": _downgrade_context_assertions(
587
+ _normalise_list_of_dicts(
588
+ data.get("grounding_assertions"),
589
+ ["segment", "assertion", "source", "risk", "support", "clock"],
590
+ )
591
+ ),
592
+ "findings": findings,
593
+ }
594
+
595
+
596
+ def format_review_md(review):
597
+ order = {"error": 0, "warning": 1, "suggestion": 2}
598
+ findings = sorted(
599
+ review.get("findings", []), key=lambda f: order.get(f["severity"], 3)
600
+ )
601
+ counts = {
602
+ s: sum(1 for f in findings if f["severity"] == s)
603
+ for s in ("error", "warning", "suggestion")
604
+ }
605
+ out = [
606
+ "# Narration review",
607
+ "",
608
+ f"Verdict: **{review.get('verdict', 'REVISE')}** "
609
+ f"(errors {counts['error']}, warnings {counts['warning']}, suggestions {counts['suggestion']})",
610
+ "",
611
+ review.get("summary", "") or "_(no summary)_",
612
+ "",
613
+ "## Scorecard",
614
+ ]
615
+ scorecard = review.get("scorecard") or {}
616
+ if scorecard:
617
+ for key in SCORECARD_KEYS:
618
+ v = scorecard.get(key)
619
+ out.append(f"- {key}: {v}/5" if v is not None else f"- {key}: 未评分")
620
+ else:
621
+ out.append("- (none)")
622
+ out.extend(["", "## Highest-return edits"])
623
+ edits = review.get("highest_return_edits") or []
624
+ out.extend([f"- {edit}" for edit in edits] or ["- (none)"])
625
+ out.extend(["", "## Retention risk points"])
626
+ risks = review.get("retention_risk_points") or []
627
+ if risks:
628
+ for item in risks:
629
+ out.append(
630
+ f"- {item.get('time', '?')}: {item.get('risk', '')} — {item.get('fix', '')}"
631
+ )
632
+ else:
633
+ out.append("- (none)")
634
+ out.extend(["", "## Hook candidates review"])
635
+ hooks = review.get("hook_candidates_review") or []
636
+ if hooks:
637
+ for item in hooks:
638
+ out.append(
639
+ f"- {item.get('type', '?')} {item.get('score', '-')}/5: {item.get('candidate', '')} ({'keep' if item.get('keep') else 'drop'}) {item.get('reason', '')}"
640
+ )
641
+ else:
642
+ out.append("- (none)")
643
+ out.extend(
644
+ ["", "## Information gain / write-for-ear / grounding", "### Information gain"]
645
+ )
646
+ notes = review.get("information_gain_notes") or []
647
+ out.extend(
648
+ [
649
+ f"- 段 {n.get('segment')}: {n.get('label')} — {n.get('note', '')} {n.get('rewrite', '')}"
650
+ for n in notes
651
+ ]
652
+ or ["- (none)"]
653
+ )
654
+ out.append("### Spoken rewrites")
655
+ rewrites = review.get("spoken_language_rewrites") or []
656
+ out.extend(
657
+ [
658
+ f"- 段 {r.get('segment')}: {r.get('original', '')} → {r.get('rewrite', '')}({r.get('why', '')})"
659
+ for r in rewrites
660
+ ]
661
+ or ["- (none)"]
662
+ )
663
+ out.append("### Grounding assertions")
664
+ assertions = review.get("grounding_assertions") or []
665
+ out.extend(
666
+ [
667
+ f"- 段 {a.get('segment')}: {a.get('assertion', '')} [{a.get('source', '')}] {a.get('risk', '')}"
668
+ for a in assertions
669
+ ]
670
+ or ["- (none)"]
671
+ )
672
+ out.extend(["", "## Findings"])
673
+ if not findings:
674
+ out.append("- (none)")
675
+ for f in findings:
676
+ seg = "整体" if f["segment"] is None else f"段 {f['segment']}"
677
+ out.append(f"- **[{f['severity']}/{f['category']}] {seg}** — {f['issue']}")
678
+ if f["fix"]:
679
+ out.append(f" - 改法: {f['fix']}")
680
+ return "\n".join(out) + "\n"
681
+
682
+
683
+ def build_grounding_qc(work_dir, review, bundle, *, timeline="source"):
684
+ """Pure-ish compatibility seam: build grounding QC payload without writing it.
685
+
686
+ It reads optional source fingerprints/QC from work_dir to preserve the existing artifact
687
+ contract, but has no side effects.
688
+ """
689
+ work_dir = Path(work_dir)
690
+ items = bundle.get("items") or []
691
+ context = bundle.get("context_items") or []
692
+ warnings = list(bundle.get("warnings") or []) + list(review.get("warnings") or [])
693
+ verdict = "warn" if warnings else "pass"
694
+ if any(
695
+ f.get("severity") == "error"
696
+ for f in (review.get("findings") or [])
697
+ if isinstance(f, dict)
698
+ ):
699
+ verdict = "fail"
700
+ if any(item.get("clock") not in ("source", "output") for item in items):
701
+ verdict = "warn" if verdict == "pass" else verdict
702
+ coverage_meta = build_review_coverage_metadata(bundle)
703
+ visual_items = [item for item in items if item.get("source") == "visual"]
704
+ asr_items = [item for item in items if item.get("source") == "asr"]
705
+ visual_fp = _source_fingerprint(work_dir, "vlm_analysis.json") or (
706
+ stable_hash(visual_items) if visual_items else ""
707
+ )
708
+ asr_fp = _source_fingerprint(work_dir, "asr_result.json") or (
709
+ stable_hash(asr_items) if asr_items else ""
710
+ )
711
+ return {
712
+ "schema_version": 1,
713
+ "owner": "video-script.review",
714
+ "timeline": timeline,
715
+ "coverage_policy_version": COVERAGE_POLICY_VERSION,
716
+ "source_fingerprints": {
717
+ "vlm": visual_fp,
718
+ "asr": asr_fp,
719
+ "research": _source_fingerprint(work_dir, "background_research.json"),
720
+ "clip_plan": _source_fingerprint(work_dir, "clip_plan_validated.json"),
721
+ },
722
+ "review_coverage": {
723
+ "time_ranges": coverage_meta["time_ranges"],
724
+ "scene_count": coverage_meta["scene_count"],
725
+ "asr_count": coverage_meta["asr_count"],
726
+ "dropped_ranges": coverage_meta["dropped_ranges"],
727
+ },
728
+ "evidence_contract": {
729
+ "source_items": sum(1 for item in items if item.get("clock") == "source"),
730
+ "output_items": sum(1 for item in items if item.get("clock") == "output"),
731
+ "unclocked_items": sum(
732
+ 1 for item in items if item.get("clock") not in ("source", "output")
733
+ ),
734
+ "context_only_items": len(context),
735
+ "validation": validate_public_evidence_contract(bundle),
736
+ },
737
+ "index_inputs": {
738
+ "vlm": bool(visual_items),
739
+ "asr": bool(asr_items),
740
+ "research": bool(_source_fingerprint(work_dir, "background_research.json")),
741
+ },
742
+ "speech_window_qc": _load_optional_json(work_dir, "silence_periods.qc.json")
743
+ or {"coarse_asr_windows": 0, "low_confidence_speech_flags": 0},
744
+ "research_guardrail": _research_guardrail_qc(review, context),
745
+ "warnings": warnings,
746
+ "verdict": verdict,
747
+ }
748
+
749
+
750
+ def write_grounding_qc(work_dir, qc):
751
+ """Compatibility seam: write a prebuilt grounding_qc.json payload."""
752
+ work_dir = Path(work_dir)
753
+ (work_dir / "grounding_qc.json").write_text(
754
+ json.dumps(qc, ensure_ascii=False, indent=2), encoding="utf-8"
755
+ )
756
+ return qc
757
+
758
+
759
+ def _write_grounding_qc(work_dir, review, bundle, *, timeline="source"):
760
+ qc = build_grounding_qc(work_dir, review, bundle, timeline=timeline)
761
+ return write_grounding_qc(work_dir, qc)