@oh-story/dsh 0.1.10 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (444) hide show
  1. package/README.md +7 -7
  2. package/lib/client.js +7 -3
  3. package/lib/client.js.map +2 -2
  4. package/lib/drama/manifest.json +212 -142
  5. package/lib/drama/skills/short-drama/SKILL.md +14 -2
  6. package/lib/drama/skills/short-drama/assets/project-template/short-drama.json +3 -0
  7. package/lib/drama/skills/short-drama/references/creator-documents.md +3 -0
  8. package/lib/drama/skills/short-drama/references/knowhow-index.md +15 -0
  9. package/lib/drama/skills/short-drama/scripts/project_tool.py +131 -0
  10. package/lib/drama/skills/short-drama-assets/SKILL.md +2 -1
  11. package/lib/drama/skills/short-drama-assets/references/asset-review-checklist.md +2 -0
  12. package/lib/drama/skills/short-drama-assets/references/era-anchors.md +37 -0
  13. package/lib/drama/skills/short-drama-assets/references/stage-contract.md +2 -0
  14. package/lib/drama/skills/short-drama-develop/SKILL.md +10 -4
  15. package/lib/drama/skills/short-drama-develop/assets/adaptation-map.example.jsonl +1 -1
  16. package/lib/drama/skills/short-drama-develop/assets/creative-brief.md +8 -0
  17. package/lib/drama/skills/short-drama-develop/references/adaptation-craft.md +80 -13
  18. package/lib/drama/skills/short-drama-develop/references/episode-design.md +95 -12
  19. package/lib/drama/skills/short-drama-develop/references/genre-and-hook-playbook.md +9 -5
  20. package/lib/drama/skills/short-drama-develop/references/genre-cards//345/244/215/344/273/207/346/211/223/350/204/270.md +1 -1
  21. package/lib/drama/skills/short-drama-develop/references/genre-cards//347/263/273/347/273/237/346/265/201.md +51 -0
  22. package/lib/drama/skills/short-drama-develop/references/genre-cards//351/200/206/350/242/255.md +51 -0
  23. package/lib/drama/skills/short-drama-develop/references/genre-cards//351/207/215/347/224/237/347/251/277/350/266/212.md +51 -0
  24. package/lib/drama/skills/short-drama-develop/references/genre-cards.md +10 -7
  25. package/lib/drama/skills/short-drama-develop/references/premise-devices.md +15 -9
  26. package/lib/drama/skills/short-drama-develop/references/serial-character-and-memory.md +3 -2
  27. package/lib/drama/skills/short-drama-develop/references/stage-contract.md +14 -7
  28. package/lib/drama/skills/short-drama-develop/references/story-craft.md +7 -3
  29. package/lib/drama/skills/short-drama-edit/SKILL.md +43 -20
  30. package/lib/drama/skills/short-drama-edit/assets/remotion/README.md +49 -13
  31. package/lib/drama/skills/short-drama-edit/assets/remotion/package.json +4 -2
  32. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Overlay.tsx +31 -0
  33. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Root.tsx +5 -5
  34. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Subtitles.tsx +68 -69
  35. package/lib/drama/skills/short-drama-edit/assets/remotion/src/font.ts +39 -29
  36. package/lib/drama/skills/short-drama-edit/assets/remotion/src/rules.mjs +188 -0
  37. package/lib/drama/skills/short-drama-edit/assets/remotion/src/schema.ts +26 -13
  38. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/Card.tsx +72 -0
  39. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/CornerChip.tsx +39 -0
  40. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/ScreenText.tsx +36 -0
  41. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/SystemPanel.tsx +44 -0
  42. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/TaskPanel.tsx +100 -0
  43. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/chrome.tsx +197 -0
  44. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/tokens.ts +62 -0
  45. package/lib/drama/skills/short-drama-edit/assets/remotion/tsconfig.json +1 -0
  46. package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +4 -2
  47. package/lib/drama/skills/short-drama-edit/references/cut-craft.md +3 -1
  48. package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +6 -2
  49. package/lib/drama/skills/short-drama-edit/references/generated-footage.md +12 -6
  50. package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +13 -8
  51. package/lib/drama/skills/short-drama-edit/references/stage-contract.md +2 -0
  52. package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1153 -121
  53. package/lib/drama/skills/short-drama-edit/scripts/selftest.py +49 -9
  54. package/lib/drama/skills/short-drama-novel-analyze/SKILL.md +3 -3
  55. package/lib/drama/skills/short-drama-novel-analyze/assets/episode-candidate.example.jsonl +2 -2
  56. package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-triage.md +8 -4
  57. package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-value.md +10 -3
  58. package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +25 -1
  59. package/lib/drama/skills/short-drama-novel-analyze/references/stage-contract.md +1 -0
  60. package/lib/drama/skills/short-drama-review/SKILL.md +2 -2
  61. package/lib/drama/skills/short-drama-review/references/review-method.md +6 -1
  62. package/lib/drama/skills/short-drama-review/references/rubric-assets-prompts.md +11 -0
  63. package/lib/drama/skills/short-drama-review/references/rubric-story-script.md +77 -3
  64. package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +15 -0
  65. package/lib/drama/skills/short-drama-review/references/stage-contract.md +3 -0
  66. package/lib/drama/skills/short-drama-storyboard/SKILL.md +13 -5
  67. package/lib/drama/skills/short-drama-storyboard/references/blocking-playbooks.md +1 -1
  68. package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +2 -2
  69. package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +82 -14
  70. package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +4 -2
  71. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +5 -3
  72. package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +12 -0
  73. package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +4 -5
  74. package/lib/drama/skills/short-drama-video-prompts/references/generability.md +4 -2
  75. package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +40 -1
  76. package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +20 -14
  77. package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +1 -1
  78. package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +8 -7
  79. package/lib/drama/skills/short-drama-video-prompts/references/review-and-fixtures.md +1 -1
  80. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +1 -0
  81. package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +8 -1
  82. package/lib/drama/skills/short-drama-video-prompts/references/wan-3.0.md +103 -0
  83. package/lib/drama/skills/short-drama-video-prompts/scripts/motion_timing_check.py +8 -7
  84. package/lib/drama/skills/short-drama-write/SKILL.md +8 -3
  85. package/lib/drama/skills/short-drama-write/references/dialogue-craft.md +1 -1
  86. package/lib/drama/skills/short-drama-write/references/script-craft.md +56 -3
  87. package/lib/drama/skills/short-drama-write/references/stage-contract.md +4 -0
  88. package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +48 -2
  89. package/lib/index.js +71 -21
  90. package/lib/index.js.map +3 -3
  91. package/lib/oh-story/manifest.json +545 -310
  92. package/lib/oh-story/roles/chapter-extractor.md +11 -11
  93. package/lib/oh-story/roles/character-designer.md +1 -2
  94. package/lib/oh-story/roles/consistency-checker.md +11 -9
  95. package/lib/oh-story/roles/narrative-writer.md +24 -25
  96. package/lib/oh-story/roles/story-architect.md +9 -61
  97. package/lib/oh-story/roles/story-explorer.md +20 -125
  98. package/lib/oh-story/roles/story-researcher.md +0 -1
  99. package/lib/oh-story/skills/story/SKILL.md +6 -6
  100. package/lib/oh-story/skills/story/VERSION +1 -1
  101. package/lib/oh-story/skills/story/references/author-memory-maintenance.md +75 -0
  102. package/lib/oh-story/skills/story/references/author-memory.md +40 -118
  103. package/lib/oh-story/skills/story/scripts/author_memory_commit.py +3 -3
  104. package/lib/oh-story/skills/story-deslop/SKILL.md +24 -25
  105. package/lib/oh-story/skills/story-deslop/references/agent-calls.md +5 -0
  106. package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +1 -1
  107. package/lib/oh-story/skills/story-deslop/references/author-memory-maintenance.md +75 -0
  108. package/lib/oh-story/skills/story-deslop/references/author-memory.md +40 -118
  109. package/lib/oh-story/skills/story-deslop/references/banned-words.md +2 -2
  110. package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +21 -4
  111. package/lib/oh-story/skills/story-deslop/references/style-resolution.md +4 -4
  112. package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +3 -3
  113. package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +155 -8
  114. package/lib/oh-story/skills/story-import/SKILL.md +63 -579
  115. package/lib/oh-story/skills/story-import/references/deep-analysis.md +47 -0
  116. package/lib/oh-story/skills/story-import/references/format-and-structure.md +1 -1
  117. package/lib/oh-story/skills/story-import/references/import-report.md +64 -0
  118. package/lib/oh-story/skills/story-import/references/import-special-cases.md +34 -0
  119. package/lib/oh-story/skills/story-import/references/import-tracking.md +95 -0
  120. package/lib/oh-story/skills/story-import/references/length-routing.md +1 -1
  121. package/lib/oh-story/skills/story-import/references/outline-reverse-rules.md +79 -0
  122. package/lib/oh-story/skills/story-import/references/outline-reverse.md +24 -0
  123. package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +95 -91
  124. package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +24 -12
  125. package/lib/oh-story/skills/story-import/references/tracking-initialization.md +1 -1
  126. package/lib/oh-story/skills/story-import/references/tracking-transaction.md +11 -9
  127. package/lib/oh-story/skills/story-import/scripts/build_outline_brief.py +332 -0
  128. package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +101 -14
  129. package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +278 -23
  130. package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +179 -23
  131. package/lib/oh-story/skills/story-long-analyze/SKILL.md +28 -50
  132. package/lib/oh-story/skills/story-long-analyze/references/author-facing.md +12 -5
  133. package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +1 -1
  134. package/lib/oh-story/skills/story-long-analyze/references/final-checks.md +19 -0
  135. package/lib/oh-story/skills/story-long-analyze/references/index-rebuild.md +11 -0
  136. package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +21 -55
  137. package/lib/oh-story/skills/story-long-analyze/references/stage1-golden-chapters.md +128 -0
  138. package/lib/oh-story/skills/story-long-analyze/references/stage2-extraction.md +229 -0
  139. package/lib/oh-story/skills/story-long-analyze/references/stage3-plot-rhythm.md +480 -0
  140. package/lib/oh-story/skills/story-long-analyze/references/stage4-characters-settings.md +195 -0
  141. package/lib/oh-story/skills/story-long-analyze/references/stage5-report.md +33 -0
  142. package/lib/oh-story/skills/story-long-analyze/references/style-profile-generator.md +17 -0
  143. package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +1 -1
  144. package/lib/oh-story/skills/story-long-analyze/references/synthesis-inputs.md +39 -0
  145. package/lib/oh-story/skills/story-long-analyze/scripts/manage_analysis_run.py +47 -8
  146. package/lib/oh-story/skills/story-long-scan/SKILL.md +41 -193
  147. package/lib/oh-story/skills/story-long-scan/references/platform-ciweimao.md +19 -0
  148. package/lib/oh-story/skills/story-long-scan/references/platform-fanqie.md +45 -0
  149. package/lib/oh-story/skills/story-long-scan/references/platform-jjwxc.md +28 -0
  150. package/lib/oh-story/skills/story-long-scan/references/platform-qidian.md +35 -0
  151. package/lib/oh-story/skills/story-long-scan/references/platform-qimao.md +27 -0
  152. package/lib/oh-story/skills/story-long-scan/references/topic-decision.md +6 -2
  153. package/lib/oh-story/skills/story-long-write/SKILL.md +32 -73
  154. package/lib/oh-story/skills/story-long-write/references/agent-calls.md +47 -0
  155. package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +1 -1
  156. package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +18 -201
  157. package/lib/oh-story/skills/story-long-write/references/author-memory-maintenance.md +75 -0
  158. package/lib/oh-story/skills/story-long-write/references/author-memory.md +40 -118
  159. package/lib/oh-story/skills/story-long-write/references/banned-words.md +2 -2
  160. package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +6 -10
  161. package/lib/oh-story/skills/story-long-write/references/character-relations.md +49 -1
  162. package/lib/oh-story/skills/story-long-write/references/deslop-gates.md +171 -0
  163. package/lib/oh-story/skills/story-long-write/references/emotion-on-page.md +116 -0
  164. package/lib/oh-story/skills/story-long-write/references/emotional-arc-design.md +1 -114
  165. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +1 -1
  166. package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +3 -2
  167. package/lib/oh-story/skills/story-long-write/references/long-format.md +0 -8
  168. package/lib/oh-story/skills/story-long-write/references/long-genre-catalog.md +1 -1
  169. package/lib/oh-story/skills/story-long-write/references/long-genre-mechanics.md +3 -3
  170. package/lib/oh-story/skills/story-long-write/references/opening-design.md +2 -2
  171. package/lib/oh-story/skills/story-long-write/references/outline-conflict.md +4 -4
  172. package/lib/oh-story/skills/story-long-write/references/outline-methods.md +7 -7
  173. package/lib/oh-story/skills/story-long-write/references/outline-rhythm.md +3 -3
  174. package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +14 -69
  175. package/lib/oh-story/skills/story-long-write/references/plot-frameworks.md +1 -1
  176. package/lib/oh-story/skills/story-long-write/references/plot-special-topics.md +2 -2
  177. package/lib/oh-story/skills/story-long-write/references/project-files.md +10 -6
  178. package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +1 -1
  179. package/lib/oh-story/skills/story-long-write/references/reference-index.md +4 -4
  180. package/lib/oh-story/skills/story-long-write/references/stop-and-fix.md +9 -0
  181. package/lib/oh-story/skills/story-long-write/references/style-combat-face.md +1 -58
  182. package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
  183. package/lib/oh-story/skills/story-long-write/references/style-resolution.md +4 -4
  184. package/lib/oh-story/skills/story-long-write/references/tracking-initialization.md +1 -1
  185. package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +11 -9
  186. package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +33 -44
  187. package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +18 -51
  188. package/lib/oh-story/skills/story-long-write/references/workflow-outline.md +157 -0
  189. package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +53 -67
  190. package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +112 -251
  191. package/lib/oh-story/skills/story-long-write/references/workflow-volume.md +142 -0
  192. package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +5 -3
  193. package/lib/oh-story/skills/story-long-write/references/writing-craft.md +18 -56
  194. package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +3 -3
  195. package/lib/oh-story/skills/story-long-write/scripts/build_architect_brief.py +260 -0
  196. package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +96 -16
  197. package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +155 -8
  198. package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +101 -14
  199. package/lib/oh-story/skills/story-long-write/scripts/check-outline-copy.js +7 -2
  200. package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +2 -1
  201. package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +250 -50
  202. package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +278 -23
  203. package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +179 -23
  204. package/lib/oh-story/skills/story-review/SKILL.md +19 -273
  205. package/lib/oh-story/skills/story-review/references/agent-prompts.md +184 -0
  206. package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +1 -1
  207. package/lib/oh-story/skills/story-review/references/author-memory-maintenance.md +75 -0
  208. package/lib/oh-story/skills/story-review/references/author-memory.md +40 -118
  209. package/lib/oh-story/skills/story-review/references/banned-words.md +2 -2
  210. package/lib/oh-story/skills/story-review/references/batch-review.md +19 -0
  211. package/lib/oh-story/skills/story-review/references/character-relations.md +49 -1
  212. package/lib/oh-story/skills/story-review/references/review-quality.md +2 -1
  213. package/lib/oh-story/skills/story-review/references/review-tracking.md +10 -0
  214. package/lib/oh-story/skills/story-review/references/solo.md +52 -0
  215. package/lib/oh-story/skills/story-review/references/style-resolution.md +4 -4
  216. package/lib/oh-story/skills/story-review/references/tracking-initialization.md +1 -1
  217. package/lib/oh-story/skills/story-review/references/tracking-transaction.md +11 -9
  218. package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +3 -3
  219. package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +155 -8
  220. package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +278 -23
  221. package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +179 -23
  222. package/lib/oh-story/skills/story-setup/SKILL.md +69 -458
  223. package/lib/oh-story/skills/story-setup/references/agent-references/agent-quality.md +4 -4
  224. package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +1 -1
  225. package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +2 -2
  226. package/lib/oh-story/skills/story-setup/references/agent-references/benchmark-style-load.md +107 -0
  227. package/lib/oh-story/skills/story-setup/references/agent-references/character-relations.md +49 -1
  228. package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +21 -4
  229. package/lib/oh-story/skills/story-setup/references/agent-references/emotion-on-page.md +116 -0
  230. package/lib/oh-story/skills/story-setup/references/agent-references/emotional-arc-design.md +1 -114
  231. package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +1 -1
  232. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards.md +1 -1
  233. package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-catalog.md +1 -1
  234. package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-mechanics.md +3 -3
  235. package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +7 -5
  236. package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +2 -2
  237. package/lib/oh-story/skills/story-setup/references/agent-references/outline-conflict.md +4 -4
  238. package/lib/oh-story/skills/story-setup/references/agent-references/outline-methods.md +7 -7
  239. package/lib/oh-story/skills/story-setup/references/agent-references/outline-rhythm.md +3 -3
  240. package/lib/oh-story/skills/story-setup/references/agent-references/short-emotional-methods.md +1 -1
  241. package/lib/oh-story/skills/story-setup/references/agent-references/short-genre-formulas.md +0 -15
  242. package/lib/oh-story/skills/story-setup/references/agent-references/short-reversal.md +1 -1
  243. package/lib/oh-story/skills/story-setup/references/agent-references/style-combat-face.md +1 -58
  244. package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
  245. package/lib/oh-story/skills/story-setup/references/agent-references/style-resolution.md +4 -4
  246. package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +18 -56
  247. package/lib/oh-story/skills/story-setup/references/diagnostics.md +3 -3
  248. package/lib/oh-story/skills/story-short-analyze/SKILL.md +37 -110
  249. package/lib/oh-story/skills/story-short-analyze/references/analysis-character-relations.md +1 -1
  250. package/lib/oh-story/skills/story-short-analyze/references/analysis-method.md +42 -0
  251. package/lib/oh-story/skills/story-short-analyze/references/optional-modules.md +97 -0
  252. package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +9 -47
  253. package/lib/oh-story/skills/story-short-analyze/references/quality-checklist.md +80 -0
  254. package/lib/oh-story/skills/story-short-analyze/references/source-story-quality.md +4 -4
  255. package/lib/oh-story/skills/story-short-analyze/references/stage2-3-structure-emotion.md +308 -0
  256. package/lib/oh-story/skills/story-short-analyze/references/{material-decomposition.md → stage4-6-reversal-summary.md} +303 -374
  257. package/lib/oh-story/skills/story-short-scan/SKILL.md +30 -64
  258. package/lib/oh-story/skills/story-short-scan/references/manual-rank-input.md +36 -0
  259. package/lib/oh-story/skills/story-short-scan/references/platform-dianzhong.md +24 -0
  260. package/lib/oh-story/skills/story-short-scan/references/platform-heiyan.md +21 -0
  261. package/lib/oh-story/skills/story-short-scan/references/topic-match.md +38 -0
  262. package/lib/oh-story/skills/story-short-write/SKILL.md +23 -34
  263. package/lib/oh-story/skills/story-short-write/references/agent-calls.md +13 -0
  264. package/lib/oh-story/skills/story-short-write/references/author-memory-maintenance.md +75 -0
  265. package/lib/oh-story/skills/story-short-write/references/author-memory.md +40 -118
  266. package/lib/oh-story/skills/story-short-write/references/banned-words.md +2 -2
  267. package/lib/oh-story/skills/story-short-write/references/benchmark-recall.md +1 -21
  268. package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +1 -1
  269. package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +0 -15
  270. package/lib/oh-story/skills/story-short-write/references/output-contract.md +9 -47
  271. package/lib/oh-story/skills/story-short-write/references/reference-index.md +17 -49
  272. package/lib/oh-story/skills/story-short-write/references/scene-craft.md +40 -0
  273. package/lib/oh-story/skills/story-short-write/references/short-craft.md +7 -43
  274. package/lib/oh-story/skills/story-short-write/references/short-format.md +3 -3
  275. package/lib/oh-story/skills/story-short-write/references/short-prose-quality.md +2 -1
  276. package/lib/oh-story/skills/story-short-write/references/short-reversal.md +1 -1
  277. package/lib/oh-story/skills/story-short-write/references/style-resolution.md +4 -4
  278. package/lib/oh-story/skills/story-short-write/references/submission-craft.md +1 -1
  279. package/lib/oh-story/skills/story-short-write/references/villain-and-reveal.md +5 -10
  280. package/lib/oh-story/skills/story-short-write/references/workflow-design.md +19 -12
  281. package/lib/oh-story/skills/story-short-write/references/workflow-draft.md +22 -32
  282. package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +84 -17
  283. package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +7 -119
  284. package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +3 -3
  285. package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +155 -8
  286. package/lib/oh-story/skills/story-short-write/scripts/check-outline-copy.js +7 -2
  287. package/lib/types/client/video-studio.d.ts.map +1 -1
  288. package/lib/types/native-hooks.d.ts +2 -2
  289. package/lib/types/native-hooks.d.ts.map +1 -1
  290. package/lib/types/role-tool.d.ts.map +1 -1
  291. package/lib/types/skill-provider.d.ts.map +1 -1
  292. package/lib/types/video-project.d.ts.map +1 -1
  293. package/lib/types/workspace-route.d.ts.map +1 -1
  294. package/lib/video-recap/CHANGELOG.md +60 -1
  295. package/lib/video-recap/README.en.md +348 -137
  296. package/lib/video-recap/README.md +340 -136
  297. package/lib/video-recap/manifest.json +406 -251
  298. package/lib/video-recap/skills/video-assemble/SKILL.md +30 -15
  299. package/lib/video-recap/skills/video-assemble/references/audio-modes.md +63 -0
  300. package/lib/video-recap/skills/video-assemble/references/explicit-audio-mix.md +75 -0
  301. package/lib/video-recap/skills/video-assemble/references/foreground-compose.md +118 -0
  302. package/lib/video-recap/skills/video-assemble/references/narration-adoption.md +103 -0
  303. package/lib/video-recap/skills/video-assemble/references/packaging.md +45 -0
  304. package/lib/video-recap/skills/video-assemble/references/pair-media.md +74 -0
  305. package/lib/video-recap/skills/video-assemble/references/source-score.md +147 -0
  306. package/lib/video-recap/skills/video-assemble/references/subtitle-track.md +202 -0
  307. package/lib/video-recap/skills/video-assemble/scripts/adoption/__init__.py +1 -0
  308. package/lib/video-recap/skills/video-assemble/scripts/adoption/audio_mix_binding.py +279 -0
  309. package/lib/video-recap/skills/video-assemble/scripts/adoption/frozen_audio.py +187 -0
  310. package/lib/video-recap/skills/video-assemble/scripts/adoption/narration_binding.py +323 -0
  311. package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_inputs.py +109 -0
  312. package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_publish.py +130 -0
  313. package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +10 -46
  314. package/lib/video-recap/skills/video-assemble/scripts/assemble.py +378 -119
  315. package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +29 -12
  316. package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +95 -44
  317. package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +75 -32
  318. package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +11 -11
  319. package/lib/video-recap/skills/video-assemble/scripts/compose_foreground.py +293 -0
  320. package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +71 -78
  321. package/lib/video-recap/skills/video-assemble/scripts/jianying/__init__.py +1 -0
  322. package/lib/video-recap/skills/video-assemble/scripts/{jianying_builders.py → jianying/builders.py} +39 -67
  323. package/lib/video-recap/skills/video-assemble/scripts/{jianying_model.py → jianying/model.py} +3 -11
  324. package/lib/video-recap/skills/video-assemble/scripts/{jianying_optional.py → jianying/optional.py} +1 -1
  325. package/lib/video-recap/skills/video-assemble/scripts/jianying/schema.py +53 -0
  326. package/lib/video-recap/skills/video-assemble/scripts/{jianying_templates.py → jianying/templates.py} +1 -1
  327. package/lib/video-recap/skills/video-assemble/scripts/{jianying_timeline_contract.py → jianying/timeline_contract.py} +34 -13
  328. package/lib/video-recap/skills/video-assemble/scripts/{jianying_writer.py → jianying/writer.py} +7 -17
  329. package/lib/video-recap/skills/video-assemble/scripts/lib.py +37 -4
  330. package/lib/video-recap/skills/video-assemble/scripts/media.py +12 -21
  331. package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +64 -34
  332. package/lib/video-recap/skills/video-assemble/scripts/packaging.py +122 -0
  333. package/lib/video-recap/skills/video-assemble/scripts/pair_media.py +216 -0
  334. package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +1 -3
  335. package/lib/video-recap/skills/video-assemble/scripts/source_score.py +469 -0
  336. package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +25 -9
  337. package/lib/video-recap/skills/video-assemble/scripts/subtitles/__init__.py +1 -0
  338. package/lib/video-recap/skills/video-assemble/scripts/{subtitle_core.py → subtitles/core.py} +13 -6
  339. package/lib/video-recap/skills/video-assemble/scripts/{subtitle_render.py → subtitles/render.py} +5 -4
  340. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track.py +324 -0
  341. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track_binding.py +200 -0
  342. package/lib/video-recap/skills/video-assemble/scripts/timeline.py +30 -46
  343. package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +91 -7
  344. package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +22 -8
  345. package/lib/video-recap/skills/video-cut/SKILL.md +34 -16
  346. package/lib/video-recap/skills/video-cut/references/shot-review.md +50 -0
  347. package/lib/video-recap/skills/video-cut/scripts/cut.py +2 -28
  348. package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +58 -62
  349. package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +21 -106
  350. package/lib/video-recap/skills/video-cut/scripts/cut_render.py +14 -9
  351. package/lib/video-recap/skills/video-cut/scripts/lib.py +39 -0
  352. package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +2 -155
  353. package/lib/video-recap/skills/video-cut/scripts/narrative_selection.py +244 -0
  354. package/lib/video-recap/skills/video-cut/scripts/shot_review.py +357 -0
  355. package/lib/video-recap/skills/video-recap/SKILL.md +86 -42
  356. package/lib/video-recap/skills/video-recap/references/audio-routing.md +126 -0
  357. package/lib/video-recap/skills/video-recap/references/config-playbook.md +12 -8
  358. package/lib/video-recap/skills/video-recap/references/data-schema.md +44 -33
  359. package/lib/video-recap/skills/video-recap/references/resource-library.md +151 -0
  360. package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +1 -1
  361. package/lib/video-recap/skills/video-recap/scripts/doctor.py +123 -30
  362. package/lib/video-recap/skills/video-recap/scripts/final_qc.py +22 -75
  363. package/lib/video-recap/skills/video-recap/scripts/lib.py +3 -3
  364. package/lib/video-recap/skills/video-recap/scripts/library.py +509 -0
  365. package/lib/video-recap/skills/video-recap/scripts/materials.py +63 -91
  366. package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +8 -70
  367. package/lib/video-recap/skills/video-recap/scripts/project_binding.py +286 -0
  368. package/lib/video-recap/skills/video-recap/scripts/qc/__init__.py +1 -0
  369. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_client.py → qc/mimo_client.py} +1 -1
  370. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_evidence.py → qc/mimo_evidence.py} +33 -66
  371. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_observations.py → qc/mimo_observations.py} +3 -11
  372. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_payload.py → qc/mimo_payload.py} +4 -7
  373. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_report.py → qc/mimo_report.py} +17 -28
  374. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_runner.py → qc/mimo_runner.py} +2 -2
  375. package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +0 -19
  376. package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +119 -50
  377. package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +32 -114
  378. package/lib/video-recap/skills/video-recap/scripts/recap_review.py +9 -23
  379. package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +387 -334
  380. package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +23 -23
  381. package/lib/video-recap/skills/video-recap/scripts/recap_source.py +278 -0
  382. package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +25 -12
  383. package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +59 -52
  384. package/lib/video-recap/skills/video-recap/scripts/resource_lock.py +162 -0
  385. package/lib/video-recap/skills/video-script/SKILL.md +16 -13
  386. package/lib/video-recap/skills/video-script/references/creative-editing-playbook.md +9 -5
  387. package/lib/video-recap/skills/video-script/references/promotional-copy.md +68 -0
  388. package/lib/video-recap/skills/video-script/references/research-guide.md +1 -1
  389. package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +24 -76
  390. package/lib/video-recap/skills/video-script/scripts/lib.py +10 -87
  391. package/lib/video-recap/skills/video-script/scripts/narration_lint.py +63 -39
  392. package/lib/video-recap/skills/video-script/scripts/review.py +0 -2
  393. package/lib/video-recap/skills/video-script/scripts/review_grounding.py +35 -59
  394. package/lib/video-recap/skills/video-script/scripts/review_response.py +66 -128
  395. package/lib/video-recap/skills/video-script/scripts/review_runner.py +12 -20
  396. package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +4 -4
  397. package/lib/video-recap/skills/video-script/scripts/validate.py +53 -42
  398. package/lib/video-recap/skills/video-understanding/SKILL.md +6 -4
  399. package/lib/video-recap/skills/video-understanding/references/data-schema.md +50 -156
  400. package/lib/video-recap/skills/video-understanding/references/research-guide.md +4 -4
  401. package/lib/video-recap/skills/video-understanding/scripts/asr.py +85 -65
  402. package/lib/video-recap/skills/video-understanding/scripts/asr_timing_evidence.py +270 -0
  403. package/lib/video-recap/skills/video-understanding/scripts/brief.py +2 -5
  404. package/lib/video-recap/skills/video-understanding/scripts/briefing/__init__.py +1 -0
  405. package/lib/video-recap/skills/video-understanding/scripts/{agent_brief.py → briefing/builder.py} +38 -35
  406. package/lib/video-recap/skills/{video-script/scripts/brief_context.py → video-understanding/scripts/briefing/context.py} +4 -35
  407. package/lib/video-recap/skills/{video-script/scripts/brief_inputs.py → video-understanding/scripts/briefing/inputs.py} +9 -31
  408. package/lib/video-recap/skills/video-understanding/scripts/{brief_timeline.py → briefing/timeline.py} +5 -6
  409. package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +65 -116
  410. package/lib/video-recap/skills/video-understanding/scripts/detect.py +31 -40
  411. package/lib/video-recap/skills/video-understanding/scripts/lib.py +30 -86
  412. package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +63 -39
  413. package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +4 -4
  414. package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +17 -79
  415. package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +14 -40
  416. package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +64 -46
  417. package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +31 -11
  418. package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +19 -14
  419. package/lib/video-recap/skills/video-understanding/scripts/vlm.py +80 -108
  420. package/lib/video-recap/skills/video-voiceover/SKILL.md +29 -19
  421. package/lib/video-recap/skills/video-voiceover/references/index-tts.md +31 -0
  422. package/lib/video-recap/skills/video-voiceover/scripts/approved_text_policy.py +123 -0
  423. package/lib/video-recap/skills/video-voiceover/scripts/dub.py +51 -59
  424. package/lib/video-recap/skills/video-voiceover/scripts/lib.py +8 -44
  425. package/lib/video-recap/skills/video-voiceover/scripts/providers/__init__.py +1 -0
  426. package/lib/video-recap/skills/video-voiceover/scripts/providers/index_tts.py +184 -0
  427. package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +212 -134
  428. package/package.json +1 -1
  429. package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +0 -538
  430. package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +0 -603
  431. package/lib/oh-story/skills/story-long-scan/references/scan-output-format.md +0 -207
  432. package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +0 -561
  433. package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +0 -98
  434. package/lib/video-recap/skills/video-recap/references/creative-editing-playbook.md +0 -225
  435. package/lib/video-recap/skills/video-recap/references/research-guide.md +0 -74
  436. package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +0 -250
  437. package/lib/video-recap/skills/video-script/scripts/agent_brief.py +0 -399
  438. package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +0 -327
  439. package/lib/video-recap/skills/video-script/scripts/narration.py +0 -12
  440. package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +0 -348
  441. package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +0 -290
  442. /package/lib/video-recap/skills/video-assemble/scripts/{jianying_tracks.py → jianying/tracks.py} +0 -0
  443. /package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_contract.py → qc/mimo_contract.py} +0 -0
  444. /package/lib/video-recap/skills/video-voiceover/scripts/{fish_audio.py → providers/fish_audio.py} +0 -0
@@ -7,7 +7,6 @@ import re
7
7
 
8
8
  from pathlib import Path
9
9
 
10
- from lib import stable_hash
11
10
 
12
11
  from evidence_bundle import (
13
12
  _clip_text,
@@ -16,9 +15,8 @@ from evidence_bundle import (
16
15
  build_evidence_bundle,
17
16
  build_review_coverage_metadata,
18
17
  render_evidence_bundle,
19
- validate_public_evidence_contract,
20
18
  )
21
- from review_grounding import _load, _source_fingerprint
19
+ from review_grounding import _load_agent_optional
22
20
 
23
21
  CATEGORIES = [
24
22
  "hallucination",
@@ -70,17 +68,17 @@ COVERAGE_POLICY_VERSION = "coverage_policy_v1"
70
68
  RUBRIC = """你是中文视频解说的创作复核编辑。依据素材证据和已有创作计划审阅草稿,只指出真实问题,宁缺毋滥:
71
69
  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
70
  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;不要用固定段数或秒数代替判断。
71
+ 3. change-based beats:每个 beat 应改变知识、权力、目标、关系、情绪或风险。精简时不能只留下“发生了什么”,还要保住人物动机、接受条件及随后犹豫/行动的必要前提。若一段只重复上一段、删除后什么都不损失,可报 low_information_gain/pacing;不要用固定段数或秒数代替判断。
74
72
  4. 钩子:开头要提出正文真实兑现的戏剧问题/利害,不是交代场景,也不是无关的留存话术。弱钩子 → weak_hook。
75
73
  5. 给信息而非念画面:观众看得见动作表情;解说只增加上下文、因果、预期、证据支持的解释或跨越。复述画面 → narrating_picture。
76
74
  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。
75
+ 7. 人物、反应与动作兑现:不要用旁白解释掉素材中已经能成立的表演、停顿或反应。有情感回应或知情变化的反应镜不可机械删;反打是否保留取决于它是否提供新增信息,而非是否达到统一时长。以某个可见结果为看点时,只写 evidence 已呈现的结果,不推断更强的结果(例:证据只到受击,就不能写成倒地或胜负)。当前评审只能检查计划/稿件一致性,不能凭少量帧声称最终剪点一定好坏。
76
+ 8. 密度/节奏与因果边界:7:3 不是配额。只在旁白没有任务、墙到墙压住原声、碎成一句一停,或无意长空档导致因果断裂时,报 density/pacing。不得把跨场镜头拼成同场动作/反应的虚假因果,也不用花字替补源证据或提前宣布结果。
79
77
  9. 去废词:删空泛形容("危机四伏""震撼人心")→ cliche。
80
78
  10. 完整句子:半句话/未收尾 → incomplete。
81
79
  11. 段落衔接:解说块要为随后的原声留白铺垫,下一块要承接原声刚呈现的变化;若两块各说各的、原声进来接不上 → disjoint_handoff。
82
80
  12. 结尾回收:结尾要兑现开头承诺/主线情绪,不要突然停、只复述最后画面、没有情绪/信息回报;弱回收 → weak_payoff。
83
- 13. 风格一致性:若提供 style_card.json,把它当作表达意图/语气/节奏边界;不符合意图 → style_mismatch。不要把 style_card 当标题/封面/首句包装计划。
81
+ 13. 风格一致性与修改范围:若提供 style_card.json,把它当作表达意图/语气/节奏边界;不符合意图 → style_mismatch。不要把 style_card 当标题/封面/首句包装计划。只提能定位到具体段落、beat 或镜头边界的具体局部修法;REVISION 未点名层默认冻结,不借局部问题重做故事、声音或包装。
84
82
  14. 包装一致性:只有提供 packaging_plan.json 时才评估标题/封面/首句/卖点承诺与正文兑现;缺失不扣分。不一致 → packaging_mismatch。不要让包装反过来改写故事判断。
85
83
  15. 去AI味:若出现模板化、空泛拔高、过度对仗、机械转折、明显 agent 示例残留,可报 ai_flavor;若出现示例人物/占位实体泄漏(如未替换示例名、模板角色)→ example_entity_leak。“不是 A,而是 B”本身不是错误,只有在先虚构旧判断再制造假洞察、或反复机械使用时才建议改写。deslop_qc.json 是 deterministic local report-only QC,不是 AIGC detector,不自动重写;只能作为证据参考,不能仅凭它判定。
86
84
  另外给一份内容效果 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味明显。
@@ -92,39 +90,23 @@ def merge_review_findings(chunks):
92
90
  """Merge chunk review findings deterministically, keeping highest severity."""
93
91
  severity_rank = {"error": 3, "warning": 2, "suggestion": 1}
94
92
  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"))
93
+ for chunk in chunks:
94
+ for f in chunk["findings"]:
95
+ key = (f["segment"], f["category"], f["issue"])
100
96
  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):
97
+ if old is None or severity_rank[f["severity"]] > severity_rank[old["severity"]]:
104
98
  by_key[key] = dict(f)
105
99
  return sorted(
106
100
  by_key.values(),
107
101
  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 "",
102
+ f["segment"] is None,
103
+ f["segment"] if f["segment"] is not None else 10**9,
104
+ f["category"],
105
+ f["issue"],
112
106
  ),
113
107
  )
114
108
 
115
109
 
116
- def _bundle_fingerprint(bundle):
117
- return stable_hash(
118
- {
119
- "schema_version": bundle.get("schema_version"),
120
- "clock": bundle.get("clock"),
121
- "coverage": bundle.get("coverage"),
122
- "items": bundle.get("items"),
123
- "context_items": bundle.get("context_items"),
124
- }
125
- )
126
-
127
-
128
110
  def _bundle_prompt_size(bundle):
129
111
  return len(render_evidence_bundle(bundle))
130
112
 
@@ -137,16 +119,14 @@ def _chunk_evidence_bundle(bundle, *, max_items=80, max_chars=12000):
137
119
  range. Context-only research remains advisory and is repeated in each chunk so the
138
120
  judge can still use alias/background hints without upgrading them to timeline facts.
139
121
  """
140
- items = list(bundle.get("items") or [])
122
+ items = list(bundle["items"])
141
123
  if len(items) <= max_items and _bundle_prompt_size(bundle) <= max_chars:
142
124
  one = dict(bundle)
143
125
  one["chunk_index"] = 0
144
126
  one["chunk_count"] = 1
145
- one.setdefault("metadata", {})["evidence_bundle_fingerprint"] = (
146
- _bundle_fingerprint(bundle)
147
- )
127
+ one["metadata"] = dict(bundle["metadata"])
148
128
  return [one]
149
- ranges = bundle.get("coverage", {}).get("selected_ranges") or []
129
+ ranges = bundle["coverage"]["selected_ranges"]
150
130
  chunks = []
151
131
  used_ids = set()
152
132
  for r in ranges:
@@ -166,16 +146,14 @@ def _chunk_evidence_bundle(bundle, *, max_items=80, max_chars=12000):
166
146
  used_ids.update(id(item) for item in part)
167
147
  chunk = dict(bundle)
168
148
  chunk["items"] = part
169
- chunk["coverage"] = dict(bundle.get("coverage") or {})
170
- chunk["coverage"]["selected_ranges"] = [r]
149
+ chunk["coverage"] = {**bundle["coverage"], "selected_ranges": [r]}
171
150
  chunk["chunk_index"] = len(chunks)
172
151
  chunks.append(chunk)
173
152
  leftovers = [item for item in items if id(item) not in used_ids]
174
153
  for start in range(0, len(leftovers), max_items):
175
154
  chunk = dict(bundle)
176
155
  chunk["items"] = leftovers[start : start + max_items]
177
- chunk["coverage"] = dict(bundle.get("coverage") or {})
178
- chunk["coverage"]["selected_ranges"] = []
156
+ chunk["coverage"] = {**bundle["coverage"], "selected_ranges": []}
179
157
  chunk["chunk_index"] = len(chunks)
180
158
  chunks.append(chunk)
181
159
  if not chunks:
@@ -184,11 +162,9 @@ def _chunk_evidence_bundle(bundle, *, max_items=80, max_chars=12000):
184
162
  chunk["chunk_index"] = 0
185
163
  chunks = [chunk]
186
164
  count = len(chunks)
187
- fp = _bundle_fingerprint(bundle)
188
165
  for chunk in chunks:
189
166
  chunk["chunk_count"] = count
190
- chunk.setdefault("metadata", {})["chunked_review"] = count > 1
191
- chunk["metadata"]["evidence_bundle_fingerprint"] = fp
167
+ chunk["metadata"] = {**bundle["metadata"], "chunked_review": count > 1}
192
168
  return chunks
193
169
 
194
170
 
@@ -198,33 +174,24 @@ def _merge_chunk_reviews(chunk_reviews):
198
174
  if len(chunk_reviews) == 1:
199
175
  return chunk_reviews[0]
200
176
  verdict_rank = {"PASS": 0, "OK": 0, "REVISE": 1, "FAIL": 2}
201
- best = max(chunk_reviews, key=lambda r: verdict_rank.get(r.get("verdict"), 1))
177
+ best = max(chunk_reviews, key=lambda r: verdict_rank[r["verdict"]])
202
178
  merged = dict(best)
203
179
  merged["findings"] = merge_review_findings(chunk_reviews)
204
- if any(f.get("severity") == "error" for f in merged["findings"]):
205
- merged["verdict"] = "FAIL" if best.get("verdict") == "FAIL" else "REVISE"
206
- else:
207
- merged["verdict"] = best.get("verdict", "REVISE")
208
- summaries = [
209
- str(r.get("summary", "")).strip()
210
- for r in chunk_reviews
211
- if str(r.get("summary", "")).strip()
212
- ]
213
- merged["summary"] = summaries[0] if summaries else merged.get("summary", "")
180
+ if any(f["severity"] == "error" for f in merged["findings"]):
181
+ merged["verdict"] = "FAIL" if best["verdict"] == "FAIL" else "REVISE"
182
+ summaries = [r["summary"] for r in chunk_reviews if r["summary"]]
183
+ merged["summary"] = summaries[0] if summaries else best["summary"]
214
184
  merged["chunked_review"] = {
215
185
  "chunk_count": len(chunk_reviews),
216
- "findings_before_merge": sum(
217
- len(r.get("findings") or []) for r in chunk_reviews
218
- ),
219
- "findings_after_merge": len(merged.get("findings") or []),
186
+ "findings_before_merge": sum(len(r["findings"]) for r in chunk_reviews),
187
+ "findings_after_merge": len(merged["findings"]),
220
188
  }
221
189
  return merged
222
190
 
223
191
 
224
192
  def _research_guardrail_qc(review, context_items):
225
- assertions = [
226
- a for a in (review.get("grounding_assertions") or []) if isinstance(a, dict)
227
- ]
193
+ # Assertion keys are whatever the judge returned; only the list itself is normalized.
194
+ assertions = review["grounding_assertions"]
228
195
  research_context_assertions = [
229
196
  a
230
197
  for a in assertions
@@ -240,26 +207,22 @@ def _research_guardrail_qc(review, context_items):
240
207
  or "current-timeline" in str(a.get("risk", "")).lower()
241
208
  ]
242
209
  grounding_risk_findings = [
243
- f
244
- for f in (review.get("findings") or [])
245
- if isinstance(f, dict) and f.get("category") == "grounding_risk"
210
+ f for f in review["findings"] if f["category"] == "grounding_risk"
246
211
  ]
247
212
  return {
248
- "context_only_assertions": len(context_items or [])
249
- + len(research_context_assertions),
213
+ "context_only_assertions": len(context_items) + len(research_context_assertions),
250
214
  "spoiler_risk_assertions": len(risk_assertions) + len(grounding_risk_findings),
251
215
  "policy": "research evidence is context_only unless visual/asr-supported",
252
216
  }
253
217
 
254
218
 
255
219
  def _load_review_research_context(work_dir):
220
+ """Agent-authored background_research.json: {} when absent; corrupt JSON raises
221
+ (same policy as the brief's loader), a non-object document fails open to {}."""
256
222
  path = Path(work_dir) / "background_research.json"
257
223
  if not path.exists():
258
224
  return {}
259
- try:
260
- data = json.loads(path.read_text(encoding="utf-8"))
261
- except (ValueError, OSError):
262
- return {}
225
+ data = json.loads(path.read_text(encoding="utf-8"))
263
226
  return data if isinstance(data, dict) else {}
264
227
 
265
228
 
@@ -344,16 +307,13 @@ def _format_review_research_context(research, limit=1200):
344
307
  def _load_optional_json(work_dir, name):
345
308
  if work_dir is None:
346
309
  return None
347
- return _load(Path(work_dir), name)
310
+ return _load_agent_optional(Path(work_dir), name)
348
311
 
349
312
 
350
313
  def _format_json_context(title, value, limit=3000):
351
314
  if value is None:
352
315
  return f"## {title}\n(无)"
353
- try:
354
- text = json.dumps(value, ensure_ascii=False, indent=2)
355
- except (TypeError, ValueError):
356
- text = str(value)
316
+ text = json.dumps(value, ensure_ascii=False, indent=2)
357
317
  return f"## {title}\n{text[:limit]}"
358
318
 
359
319
 
@@ -494,13 +454,17 @@ def parse_review_response(text):
494
454
  try:
495
455
  data = json.loads(candidate)
496
456
  except ValueError:
457
+ # Same shape as a parsed review so every consumer reads one contract.
497
458
  return {
498
- "verdict": "REVISE",
459
+ **_normalise_review({}),
499
460
  "summary": "评审输出无法解析为 JSON,请人工检查。",
500
- "findings": [],
501
461
  "parse_error": True,
502
462
  "raw": raw[:2000],
503
463
  }
464
+ return _normalise_review(data)
465
+
466
+
467
+ def _normalise_review(data):
504
468
  verdict = str(data.get("verdict", "REVISE")).upper()
505
469
  # PASS/REVISE/FAIL is the new vocabulary; OK is kept as a backward-compatible alias.
506
470
  if verdict not in ("PASS", "REVISE", "FAIL", "OK"):
@@ -564,10 +528,9 @@ def parse_review_response(text):
564
528
 
565
529
 
566
530
  def format_review_md(review):
531
+ """Render a parse_review_response() review; list-item keys stay judge-optional."""
567
532
  order = {"error": 0, "warning": 1, "suggestion": 2}
568
- findings = sorted(
569
- review.get("findings", []), key=lambda f: order.get(f["severity"], 3)
570
- )
533
+ findings = sorted(review["findings"], key=lambda f: order[f["severity"]])
571
534
  counts = {
572
535
  s: sum(1 for f in findings if f["severity"] == s)
573
536
  for s in ("error", "warning", "suggestion")
@@ -575,25 +538,19 @@ def format_review_md(review):
575
538
  out = [
576
539
  "# Narration review",
577
540
  "",
578
- f"Verdict: **{review.get('verdict', 'REVISE')}** "
541
+ f"Verdict: **{review['verdict']}** "
579
542
  f"(errors {counts['error']}, warnings {counts['warning']}, suggestions {counts['suggestion']})",
580
543
  "",
581
- review.get("summary", "") or "_(no summary)_",
544
+ review["summary"] or "_(no summary)_",
582
545
  "",
583
546
  "## Scorecard",
584
547
  ]
585
- scorecard = review.get("scorecard") or {}
586
- if scorecard:
587
- for key in SCORECARD_KEYS:
588
- v = scorecard.get(key)
589
- out.append(f"- {key}: {v}/5" if v is not None else f"- {key}: 未评分")
590
- else:
591
- out.append("- (none)")
548
+ for key, v in review["scorecard"].items():
549
+ out.append(f"- {key}: {v}/5" if v is not None else f"- {key}: 未评分")
592
550
  out.extend(["", "## Highest-return edits"])
593
- edits = review.get("highest_return_edits") or []
594
- out.extend([f"- {edit}" for edit in edits] or ["- (none)"])
551
+ out.extend([f"- {edit}" for edit in review["highest_return_edits"]] or ["- (none)"])
595
552
  out.extend(["", "## Retention risk points"])
596
- risks = review.get("retention_risk_points") or []
553
+ risks = review["retention_risk_points"]
597
554
  if risks:
598
555
  for item in risks:
599
556
  out.append(
@@ -602,7 +559,7 @@ def format_review_md(review):
602
559
  else:
603
560
  out.append("- (none)")
604
561
  out.extend(["", "## Hook candidates review"])
605
- hooks = review.get("hook_candidates_review") or []
562
+ hooks = review["hook_candidates_review"]
606
563
  if hooks:
607
564
  for item in hooks:
608
565
  out.append(
@@ -613,7 +570,7 @@ def format_review_md(review):
613
570
  out.extend(
614
571
  ["", "## Information gain / write-for-ear / grounding", "### Information gain"]
615
572
  )
616
- notes = review.get("information_gain_notes") or []
573
+ notes = review["information_gain_notes"]
617
574
  out.extend(
618
575
  [
619
576
  f"- 段 {n.get('segment')}: {n.get('label')} — {n.get('note', '')} {n.get('rewrite', '')}"
@@ -622,7 +579,7 @@ def format_review_md(review):
622
579
  or ["- (none)"]
623
580
  )
624
581
  out.append("### Spoken rewrites")
625
- rewrites = review.get("spoken_language_rewrites") or []
582
+ rewrites = review["spoken_language_rewrites"]
626
583
  out.extend(
627
584
  [
628
585
  f"- 段 {r.get('segment')}: {r.get('original', '')} → {r.get('rewrite', '')}({r.get('why', '')})"
@@ -631,7 +588,7 @@ def format_review_md(review):
631
588
  or ["- (none)"]
632
589
  )
633
590
  out.append("### Grounding assertions")
634
- assertions = review.get("grounding_assertions") or []
591
+ assertions = review["grounding_assertions"]
635
592
  out.extend(
636
593
  [
637
594
  f"- 段 {a.get('segment')}: {a.get('assertion', '')} [{a.get('source', '')}] {a.get('risk', '')}"
@@ -653,42 +610,25 @@ def format_review_md(review):
653
610
  def build_grounding_qc(work_dir, review, bundle, *, timeline="source"):
654
611
  """Pure-ish compatibility seam: build grounding QC payload without writing it.
655
612
 
656
- It reads optional source fingerprints/QC from work_dir to preserve the existing artifact
613
+ It reads optional QC sidecars from work_dir to preserve the existing artifact
657
614
  contract, but has no side effects.
658
615
  """
659
616
  work_dir = Path(work_dir)
660
- items = bundle.get("items") or []
661
- context = bundle.get("context_items") or []
662
- warnings = list(bundle.get("warnings") or []) + list(review.get("warnings") or [])
617
+ items = bundle["items"]
618
+ context = bundle["context_items"]
619
+ # The runner hands the same pipeline warnings to the bundle and the review.
620
+ warnings = list(bundle["warnings"])
663
621
  verdict = "warn" if warnings else "pass"
664
- if any(
665
- f.get("severity") == "error"
666
- for f in (review.get("findings") or [])
667
- if isinstance(f, dict)
668
- ):
622
+ if any(f["severity"] == "error" for f in review["findings"]):
669
623
  verdict = "fail"
670
- if any(item.get("clock") not in ("source", "output") for item in items):
671
- verdict = "warn" if verdict == "pass" else verdict
672
624
  coverage_meta = build_review_coverage_metadata(bundle)
673
- visual_items = [item for item in items if item.get("source") == "visual"]
674
- asr_items = [item for item in items if item.get("source") == "asr"]
675
- visual_fp = _source_fingerprint(work_dir, "vlm_analysis.json") or (
676
- stable_hash(visual_items) if visual_items else ""
677
- )
678
- asr_fp = _source_fingerprint(work_dir, "asr_result.json") or (
679
- stable_hash(asr_items) if asr_items else ""
680
- )
625
+ visual_items = [item for item in items if item["source"] == "visual"]
626
+ asr_items = [item for item in items if item["source"] == "asr"]
681
627
  return {
682
628
  "schema_version": 1,
683
629
  "owner": "video-script.review",
684
630
  "timeline": timeline,
685
631
  "coverage_policy_version": COVERAGE_POLICY_VERSION,
686
- "source_fingerprints": {
687
- "vlm": visual_fp,
688
- "asr": asr_fp,
689
- "research": _source_fingerprint(work_dir, "background_research.json"),
690
- "clip_plan": _source_fingerprint(work_dir, "clip_plan_validated.json"),
691
- },
692
632
  "review_coverage": {
693
633
  "time_ranges": coverage_meta["time_ranges"],
694
634
  "scene_count": coverage_meta["scene_count"],
@@ -696,18 +636,16 @@ def build_grounding_qc(work_dir, review, bundle, *, timeline="source"):
696
636
  "dropped_ranges": coverage_meta["dropped_ranges"],
697
637
  },
698
638
  "evidence_contract": {
699
- "source_items": sum(1 for item in items if item.get("clock") == "source"),
700
- "output_items": sum(1 for item in items if item.get("clock") == "output"),
701
- "unclocked_items": sum(
702
- 1 for item in items if item.get("clock") not in ("source", "output")
703
- ),
639
+ # Every timeline item carries the bundle clock; context items are unclocked.
640
+ "source_items": len(items) if bundle["clock"] == "source" else 0,
641
+ "output_items": len(items) if bundle["clock"] == "output" else 0,
642
+ "unclocked_items": 0,
704
643
  "context_only_items": len(context),
705
- "validation": validate_public_evidence_contract(bundle),
706
644
  },
707
645
  "index_inputs": {
708
646
  "vlm": bool(visual_items),
709
647
  "asr": bool(asr_items),
710
- "research": bool(_source_fingerprint(work_dir, "background_research.json")),
648
+ "research": (work_dir / "background_research.json").exists(),
711
649
  },
712
650
  "speech_window_qc": _load_optional_json(work_dir, "silence_periods.qc.json")
713
651
  or {"coarse_asr_windows": 0, "low_confidence_speech_flags": 0},
@@ -11,13 +11,12 @@ from lib import CONFIG, log, api_call
11
11
 
12
12
  from evidence_bundle import build_evidence_bundle
13
13
  from review_grounding import (
14
- _load,
15
14
  _load_cut_clip_spans,
15
+ _load_required,
16
16
  _load_review_grounding,
17
17
  remap_grounding_to_output_timeline,
18
18
  )
19
19
  from review_response import (
20
- _bundle_fingerprint,
21
20
  _chunk_evidence_bundle,
22
21
  _load_review_research_context,
23
22
  _merge_chunk_reviews,
@@ -34,9 +33,7 @@ COVERAGE_POLICY_VERSION = "coverage_policy_v1"
34
33
 
35
34
  def review_narration(work_dir, *, timeline="source", strict_evidence=False):
36
35
  work_dir = Path(work_dir)
37
- narration = _load(work_dir, "narration.json")
38
- if narration is None:
39
- raise SystemExit(f"缺少 {work_dir / 'narration.json'};先写解说草稿再评审")
36
+ narration = _load_required(work_dir, "narration.json", "先写解说草稿再评审")
40
37
  vlm_analysis, asr_result = _load_review_grounding(work_dir)
41
38
  warnings = []
42
39
  if timeline == "cut_output":
@@ -62,7 +59,6 @@ def review_narration(work_dir, *, timeline="source", strict_evidence=False):
62
59
  research=_load_review_research_context(work_dir),
63
60
  warnings=warnings,
64
61
  )
65
- bundle_fp = _bundle_fingerprint(bundle)
66
62
  chunk_reviews = []
67
63
  chunks = _chunk_evidence_bundle(bundle)
68
64
  for chunk in chunks:
@@ -75,11 +71,11 @@ def review_narration(work_dir, *, timeline="source", strict_evidence=False):
75
71
  )
76
72
  resp = api_call(
77
73
  {
78
- "model": CONFIG.get("vlm_model", ""),
74
+ "model": CONFIG["vlm_model"],
79
75
  "messages": messages,
80
76
  "max_tokens": 2000 if len(chunks) == 1 else 1600,
81
77
  "temperature": 0,
82
- "seed": 7 + int(chunk.get("chunk_index", 0)),
78
+ "seed": 7 + chunk["chunk_index"],
83
79
  }
84
80
  )
85
81
  content = ""
@@ -88,8 +84,8 @@ def review_narration(work_dir, *, timeline="source", strict_evidence=False):
88
84
  except (KeyError, IndexError, TypeError):
89
85
  log("评审 API 返回结构异常")
90
86
  parsed = parse_review_response(content)
91
- parsed["chunk_index"] = chunk.get("chunk_index", 0)
92
- parsed["chunk_count"] = chunk.get("chunk_count", len(chunks))
87
+ parsed["chunk_index"] = chunk["chunk_index"]
88
+ parsed["chunk_count"] = chunk["chunk_count"]
93
89
  chunk_reviews.append(parsed)
94
90
  review = _merge_chunk_reviews(chunk_reviews)
95
91
  if warnings:
@@ -97,10 +93,9 @@ def review_narration(work_dir, *, timeline="source", strict_evidence=False):
97
93
  review["evidence_contract"] = {
98
94
  "schema_version": EVIDENCE_CONTRACT_VERSION,
99
95
  "timeline": timeline,
100
- "clock": bundle.get("clock"),
96
+ "clock": bundle["clock"],
101
97
  "coverage_policy_version": COVERAGE_POLICY_VERSION,
102
- "selected_ranges": bundle.get("coverage", {}).get("selected_ranges", []),
103
- "evidence_bundle_fingerprint": bundle_fp,
98
+ "selected_ranges": bundle["coverage"]["selected_ranges"],
104
99
  "chunk_count": len(chunks),
105
100
  "warnings": warnings,
106
101
  }
@@ -124,15 +119,14 @@ def _auto_timeline(work_dir):
124
119
  orchestrator does: cut_output when narration.json is in the cut OUTPUT timeline, else
125
120
  source. Without this, reviewing a cut narration on the default 'source' timeline compares
126
121
  OUTPUT-time narration against SOURCE-time evidence and floods false-positive 'hallucination'
127
- findings (and the inverse flood for a legacy source-time narration mis-read as cut_output).
122
+ findings.
128
123
 
129
124
  Detection is authoritative-first: the orchestrator records the run's edit_mode in
130
125
  recap_run_manifest.json. In orchestrated cut mode narration.json is OUTPUT time; in full
131
126
  mode it is SOURCE time. Trusting edit_mode is correct even when stale cut artifacts from a
132
127
  prior run linger in a reused work_dir. Only when no manifest is present (standalone review
133
- or a hand-built work_dir) do we fall back to artifact sniffing — and even then the legacy
134
- legacy direct single-pass path writes a SOURCE-time narration.json alongside a separate
135
- output-time narration_mapped.json, so its presence pins us back to source."""
128
+ or a hand-built work_dir) do we fall back to artifact sniffing: a rendered cut
129
+ (clip_plan_validated.json + edited_source.mp4) means narration.json is OUTPUT time."""
136
130
  work_dir = Path(work_dir)
137
131
  manifest = work_dir / "recap_run_manifest.json"
138
132
  if manifest.exists():
@@ -151,9 +145,7 @@ def _auto_timeline(work_dir):
151
145
  has_cut = (work_dir / "clip_plan_validated.json").exists() and (
152
146
  work_dir / "edited_source.mp4"
153
147
  ).exists()
154
- if has_cut and not (work_dir / "narration_mapped.json").exists():
155
- return "cut_output"
156
- return "source"
148
+ return "cut_output" if has_cut else "source"
157
149
 
158
150
 
159
151
  def main():
@@ -3,7 +3,7 @@
3
3
  import json
4
4
  from pathlib import Path
5
5
 
6
- from lib import CONFIG, stable_hash
6
+ from lib import CONFIG, file_identity
7
7
 
8
8
 
9
9
  def _empty_evidence(mode):
@@ -21,11 +21,11 @@ def _read_json(path):
21
21
 
22
22
 
23
23
  def _output_payload_is_current(payload, work_dir):
24
- plan = _read_json(Path(work_dir) / "clip_plan_validated.json")
24
+ plan_path = Path(work_dir) / "clip_plan_validated.json"
25
25
  return (
26
26
  payload is not None
27
- and plan is not None
28
- and payload["clip_plan_fingerprint"] == stable_hash(plan)
27
+ and plan_path.exists()
28
+ and payload["clip_plan_identity"] == file_identity(plan_path)
29
29
  )
30
30
 
31
31