@oh-story/dsh 0.1.9 → 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 (529) hide show
  1. package/README.md +14 -14
  2. package/lib/client.js +209 -73
  3. package/lib/client.js.map +3 -3
  4. package/lib/drama/manifest.json +248 -168
  5. package/lib/drama/skills/short-drama/SKILL.md +16 -4
  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/creator_markdown_check.py +14 -0
  10. package/lib/drama/skills/short-drama/scripts/project_tool.py +131 -0
  11. package/lib/drama/skills/short-drama-assets/SKILL.md +2 -1
  12. package/lib/drama/skills/short-drama-assets/references/asset-review-checklist.md +2 -0
  13. package/lib/drama/skills/short-drama-assets/references/character-and-look.md +10 -0
  14. package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +6 -9
  15. package/lib/drama/skills/short-drama-assets/references/era-anchors.md +37 -0
  16. package/lib/drama/skills/short-drama-assets/references/stage-contract.md +2 -0
  17. package/lib/drama/skills/short-drama-assets/references/voice-direction.md +28 -0
  18. package/lib/drama/skills/short-drama-develop/SKILL.md +10 -4
  19. package/lib/drama/skills/short-drama-develop/assets/adaptation-map.example.jsonl +1 -1
  20. package/lib/drama/skills/short-drama-develop/assets/creative-brief.md +8 -0
  21. package/lib/drama/skills/short-drama-develop/references/adaptation-craft.md +80 -13
  22. package/lib/drama/skills/short-drama-develop/references/episode-design.md +95 -12
  23. package/lib/drama/skills/short-drama-develop/references/genre-and-hook-playbook.md +9 -5
  24. 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
  25. package/lib/drama/skills/short-drama-develop/references/genre-cards//347/263/273/347/273/237/346/265/201.md +51 -0
  26. package/lib/drama/skills/short-drama-develop/references/genre-cards//351/200/206/350/242/255.md +51 -0
  27. 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
  28. package/lib/drama/skills/short-drama-develop/references/genre-cards.md +10 -7
  29. package/lib/drama/skills/short-drama-develop/references/premise-devices.md +15 -9
  30. package/lib/drama/skills/short-drama-develop/references/serial-character-and-memory.md +3 -2
  31. package/lib/drama/skills/short-drama-develop/references/stage-contract.md +14 -7
  32. package/lib/drama/skills/short-drama-develop/references/story-craft.md +7 -3
  33. package/lib/drama/skills/short-drama-edit/SKILL.md +65 -44
  34. package/lib/drama/skills/short-drama-edit/assets/remotion/README.md +49 -13
  35. package/lib/drama/skills/short-drama-edit/assets/remotion/package.json +4 -2
  36. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Overlay.tsx +31 -0
  37. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Root.tsx +5 -5
  38. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Subtitles.tsx +68 -69
  39. package/lib/drama/skills/short-drama-edit/assets/remotion/src/font.ts +39 -29
  40. package/lib/drama/skills/short-drama-edit/assets/remotion/src/rules.mjs +188 -0
  41. package/lib/drama/skills/short-drama-edit/assets/remotion/src/schema.ts +26 -13
  42. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/Card.tsx +72 -0
  43. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/CornerChip.tsx +39 -0
  44. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/ScreenText.tsx +36 -0
  45. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/SystemPanel.tsx +44 -0
  46. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/TaskPanel.tsx +100 -0
  47. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/chrome.tsx +197 -0
  48. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/tokens.ts +62 -0
  49. package/lib/drama/skills/short-drama-edit/assets/remotion/tsconfig.json +1 -0
  50. package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +6 -4
  51. package/lib/drama/skills/short-drama-edit/references/cut-craft.md +26 -67
  52. package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +31 -56
  53. package/lib/drama/skills/short-drama-edit/references/generated-footage.md +42 -78
  54. package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +44 -92
  55. package/lib/drama/skills/short-drama-edit/references/stage-contract.md +14 -16
  56. package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1272 -125
  57. package/lib/drama/skills/short-drama-edit/scripts/selftest.py +113 -9
  58. package/lib/drama/skills/short-drama-image-prompts/SKILL.md +2 -0
  59. package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +11 -0
  60. package/lib/drama/skills/short-drama-novel-analyze/SKILL.md +3 -3
  61. package/lib/drama/skills/short-drama-novel-analyze/assets/episode-candidate.example.jsonl +2 -2
  62. package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-triage.md +8 -4
  63. package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-value.md +10 -3
  64. package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +25 -1
  65. package/lib/drama/skills/short-drama-novel-analyze/references/stage-contract.md +1 -0
  66. package/lib/drama/skills/short-drama-produce/SKILL.md +5 -1
  67. package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +14 -20
  68. package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +8 -26
  69. package/lib/drama/skills/short-drama-produce/references/providers/minimax-speech.md +41 -0
  70. package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +142 -1
  71. package/lib/drama/skills/short-drama-review/SKILL.md +4 -5
  72. package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +6 -19
  73. package/lib/drama/skills/short-drama-review/references/review-method.md +6 -1
  74. package/lib/drama/skills/short-drama-review/references/rubric-assets-prompts.md +11 -0
  75. package/lib/drama/skills/short-drama-review/references/rubric-story-script.md +77 -3
  76. package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +24 -35
  77. package/lib/drama/skills/short-drama-review/references/stage-contract.md +3 -0
  78. package/lib/drama/skills/short-drama-storyboard/SKILL.md +23 -11
  79. package/lib/drama/skills/short-drama-storyboard/references/blocking-playbooks.md +1 -1
  80. package/lib/drama/skills/short-drama-storyboard/references/lighting-craft.md +39 -0
  81. package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +2 -2
  82. package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +139 -34
  83. package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +5 -3
  84. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +15 -12
  85. package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +29 -0
  86. package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +4 -5
  87. package/lib/drama/skills/short-drama-video-prompts/references/generability.md +4 -2
  88. package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +52 -39
  89. package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +24 -21
  90. package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +9 -23
  91. package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +14 -26
  92. package/lib/drama/skills/short-drama-video-prompts/references/review-and-fixtures.md +1 -1
  93. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +2 -3
  94. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +34 -9
  95. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +3 -2
  96. package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +8 -1
  97. package/lib/drama/skills/short-drama-video-prompts/references/wan-3.0.md +103 -0
  98. package/lib/drama/skills/short-drama-video-prompts/scripts/motion_timing_check.py +8 -7
  99. package/lib/drama/skills/short-drama-write/SKILL.md +10 -7
  100. package/lib/drama/skills/short-drama-write/references/dialogue-craft.md +1 -1
  101. package/lib/drama/skills/short-drama-write/references/script-craft.md +56 -3
  102. package/lib/drama/skills/short-drama-write/references/stage-contract.md +4 -0
  103. package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +48 -2
  104. package/lib/index.js +346 -98
  105. package/lib/index.js.map +4 -4
  106. package/lib/novel-to-game/README.md +299 -59
  107. package/lib/novel-to-game/README_ZH.md +299 -56
  108. package/lib/novel-to-game/VERSION +1 -1
  109. package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +3 -1
  110. package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +11 -3
  111. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +397 -8
  112. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +0 -31
  113. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +71 -73
  114. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +78 -89
  115. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +37 -14
  116. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +5 -5
  117. package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +2 -1
  118. package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +11 -16
  119. package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +11 -11
  120. package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +23 -0
  121. package/lib/novel-to-game/manifest.json +77 -87
  122. package/lib/novel-to-game/skills/game-art-direction/SKILL.md +7 -12
  123. package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +27 -106
  124. package/lib/novel-to-game/skills/game-build/SKILL.md +13 -14
  125. package/lib/novel-to-game/skills/game-build/references/blender-asset-workflow.md +24 -0
  126. package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +45 -82
  127. package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +14 -45
  128. package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +30 -65
  129. package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +13 -27
  130. package/lib/novel-to-game/skills/game-concept/SKILL.md +13 -15
  131. package/lib/novel-to-game/skills/game-concept/references/concept-method.md +28 -75
  132. package/lib/novel-to-game/skills/game-qa/SKILL.md +6 -7
  133. package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +9 -5
  134. package/lib/novel-to-game/skills/game-world-design/SKILL.md +12 -16
  135. package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +23 -72
  136. package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +14 -48
  137. package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +36 -103
  138. package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +4 -10
  139. package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +4 -4
  140. package/lib/novel-to-game/skills/novel-to-game/SKILL.md +9 -21
  141. package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +5 -4
  142. package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +5 -8
  143. package/lib/oh-story/manifest.json +607 -327
  144. package/lib/oh-story/roles/chapter-extractor.md +177 -237
  145. package/lib/oh-story/roles/character-designer.md +1 -2
  146. package/lib/oh-story/roles/consistency-checker.md +15 -13
  147. package/lib/oh-story/roles/narrative-writer.md +36 -38
  148. package/lib/oh-story/roles/story-architect.md +9 -61
  149. package/lib/oh-story/roles/story-explorer.md +20 -125
  150. package/lib/oh-story/roles/story-researcher.md +0 -1
  151. package/lib/oh-story/skills/story/SKILL.md +15 -9
  152. package/lib/oh-story/skills/story/VERSION +1 -1
  153. package/lib/oh-story/skills/story/references/author-memory-maintenance.md +75 -0
  154. package/lib/oh-story/skills/story/references/author-memory.md +46 -94
  155. package/lib/oh-story/skills/story/scripts/author_memory_commit.py +934 -170
  156. package/lib/oh-story/skills/story-cover/SKILL.md +10 -1
  157. package/lib/oh-story/skills/story-deslop/SKILL.md +59 -73
  158. package/lib/oh-story/skills/story-deslop/references/agent-calls.md +5 -0
  159. package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +12 -11
  160. package/lib/oh-story/skills/story-deslop/references/author-memory-maintenance.md +75 -0
  161. package/lib/oh-story/skills/story-deslop/references/author-memory.md +46 -94
  162. package/lib/oh-story/skills/story-deslop/references/banned-words.md +2 -2
  163. package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +22 -5
  164. package/lib/oh-story/skills/story-deslop/references/style-resolution.md +4 -4
  165. package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +934 -170
  166. package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +155 -8
  167. package/lib/oh-story/skills/story-import/SKILL.md +64 -584
  168. package/lib/oh-story/skills/story-import/references/character-state-reverse.md +1 -1
  169. package/lib/oh-story/skills/story-import/references/deep-analysis.md +47 -0
  170. package/lib/oh-story/skills/story-import/references/format-and-structure.md +1 -1
  171. package/lib/oh-story/skills/story-import/references/import-report.md +64 -0
  172. package/lib/oh-story/skills/story-import/references/import-special-cases.md +34 -0
  173. package/lib/oh-story/skills/story-import/references/import-tracking.md +95 -0
  174. package/lib/oh-story/skills/story-import/references/length-routing.md +1 -1
  175. package/lib/oh-story/skills/story-import/references/outline-reverse-rules.md +79 -0
  176. package/lib/oh-story/skills/story-import/references/outline-reverse.md +24 -0
  177. package/lib/oh-story/skills/story-import/references/state-tracking.md +4 -69
  178. package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +98 -92
  179. package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +24 -12
  180. package/lib/oh-story/skills/story-import/references/tracking-initialization.md +1 -1
  181. package/lib/oh-story/skills/story-import/references/tracking-transaction.md +15 -11
  182. package/lib/oh-story/skills/story-import/scripts/build_outline_brief.py +332 -0
  183. package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +132 -27
  184. package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +478 -15
  185. package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +182 -23
  186. package/lib/oh-story/skills/story-long-analyze/SKILL.md +81 -270
  187. package/lib/oh-story/skills/story-long-analyze/references/author-facing.md +252 -0
  188. package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +3 -3
  189. package/lib/oh-story/skills/story-long-analyze/references/final-checks.md +19 -0
  190. package/lib/oh-story/skills/story-long-analyze/references/index-rebuild.md +11 -0
  191. package/lib/oh-story/skills/story-long-analyze/references/inspiration-library.md +105 -0
  192. package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +138 -64
  193. package/lib/oh-story/skills/story-long-analyze/references/semantic-acceptance-fixtures.md +84 -0
  194. package/lib/oh-story/skills/story-long-analyze/references/stage1-golden-chapters.md +128 -0
  195. package/lib/oh-story/skills/story-long-analyze/references/stage2-extraction.md +229 -0
  196. package/lib/oh-story/skills/story-long-analyze/references/stage3-plot-rhythm.md +480 -0
  197. package/lib/oh-story/skills/story-long-analyze/references/stage4-characters-settings.md +195 -0
  198. package/lib/oh-story/skills/story-long-analyze/references/stage5-report.md +33 -0
  199. package/lib/oh-story/skills/story-long-analyze/references/style-profile-generator.md +55 -128
  200. package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +6 -6
  201. package/lib/oh-story/skills/story-long-analyze/references/synthesis-inputs.md +39 -0
  202. package/lib/oh-story/skills/story-long-analyze/scripts/build_chapter_index.py +466 -0
  203. package/lib/oh-story/skills/story-long-analyze/scripts/inspect_existing_assets.py +702 -0
  204. package/lib/oh-story/skills/story-long-analyze/scripts/inspiration_index.py +864 -0
  205. package/lib/oh-story/skills/story-long-analyze/scripts/manage_analysis_run.py +1145 -0
  206. package/lib/oh-story/skills/story-long-analyze/scripts/render_relation_chart.py +425 -0
  207. package/lib/oh-story/skills/story-long-scan/SKILL.md +45 -194
  208. package/lib/oh-story/skills/story-long-scan/references/platform-ciweimao.md +19 -0
  209. package/lib/oh-story/skills/story-long-scan/references/platform-fanqie.md +45 -0
  210. package/lib/oh-story/skills/story-long-scan/references/platform-jjwxc.md +28 -0
  211. package/lib/oh-story/skills/story-long-scan/references/platform-qidian.md +35 -0
  212. package/lib/oh-story/skills/story-long-scan/references/platform-qimao.md +27 -0
  213. package/lib/oh-story/skills/story-long-scan/references/topic-decision.md +6 -2
  214. package/lib/oh-story/skills/story-long-write/SKILL.md +49 -81
  215. package/lib/oh-story/skills/story-long-write/references/agent-calls.md +47 -0
  216. package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +12 -11
  217. package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +24 -202
  218. package/lib/oh-story/skills/story-long-write/references/author-memory-maintenance.md +75 -0
  219. package/lib/oh-story/skills/story-long-write/references/author-memory.md +46 -94
  220. package/lib/oh-story/skills/story-long-write/references/banned-words.md +2 -2
  221. package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +7 -11
  222. package/lib/oh-story/skills/story-long-write/references/character-relations.md +49 -1
  223. package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +26 -0
  224. package/lib/oh-story/skills/story-long-write/references/deslop-gates.md +171 -0
  225. package/lib/oh-story/skills/story-long-write/references/dialogue-mastery.md +4 -0
  226. package/lib/oh-story/skills/story-long-write/references/emotion-on-page.md +116 -0
  227. package/lib/oh-story/skills/story-long-write/references/emotional-arc-design.md +1 -114
  228. package/lib/oh-story/skills/story-long-write/references/emotional-methods.md +2 -2
  229. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +1 -1
  230. package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +7 -15
  231. package/lib/oh-story/skills/story-long-write/references/long-format.md +0 -8
  232. package/lib/oh-story/skills/story-long-write/references/long-genre-catalog.md +1 -1
  233. package/lib/oh-story/skills/story-long-write/references/long-genre-mechanics.md +3 -3
  234. package/lib/oh-story/skills/story-long-write/references/opening-design.md +2 -2
  235. package/lib/oh-story/skills/story-long-write/references/outline-conflict.md +4 -4
  236. package/lib/oh-story/skills/story-long-write/references/outline-methods.md +7 -7
  237. package/lib/oh-story/skills/story-long-write/references/outline-rhythm.md +3 -3
  238. package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +14 -69
  239. package/lib/oh-story/skills/story-long-write/references/plot-frameworks.md +1 -1
  240. package/lib/oh-story/skills/story-long-write/references/plot-special-topics.md +2 -2
  241. package/lib/oh-story/skills/story-long-write/references/project-files.md +19 -13
  242. package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +13 -5
  243. package/lib/oh-story/skills/story-long-write/references/reference-index.md +5 -6
  244. package/lib/oh-story/skills/story-long-write/references/stop-and-fix.md +9 -0
  245. package/lib/oh-story/skills/story-long-write/references/style-combat-face.md +1 -58
  246. package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
  247. package/lib/oh-story/skills/story-long-write/references/style-resolution.md +4 -4
  248. package/lib/oh-story/skills/story-long-write/references/tracking-initialization.md +1 -1
  249. package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +15 -11
  250. package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +81 -121
  251. package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +41 -139
  252. package/lib/oh-story/skills/story-long-write/references/workflow-outline.md +157 -0
  253. package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +57 -67
  254. package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +112 -253
  255. package/lib/oh-story/skills/story-long-write/references/workflow-volume.md +142 -0
  256. package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +6 -4
  257. package/lib/oh-story/skills/story-long-write/references/writing-craft.md +19 -65
  258. package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +934 -170
  259. package/lib/oh-story/skills/story-long-write/scripts/build_architect_brief.py +260 -0
  260. package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +226 -25
  261. package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +155 -8
  262. package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +132 -27
  263. package/lib/oh-story/skills/story-long-write/scripts/check-outline-copy.js +7 -2
  264. package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +7 -4
  265. package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +304 -46
  266. package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +478 -15
  267. package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +182 -23
  268. package/lib/oh-story/skills/story-review/SKILL.md +31 -302
  269. package/lib/oh-story/skills/story-review/references/agent-prompts.md +184 -0
  270. package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +12 -11
  271. package/lib/oh-story/skills/story-review/references/author-memory-maintenance.md +75 -0
  272. package/lib/oh-story/skills/story-review/references/author-memory.md +46 -94
  273. package/lib/oh-story/skills/story-review/references/banned-words.md +2 -2
  274. package/lib/oh-story/skills/story-review/references/batch-review.md +19 -0
  275. package/lib/oh-story/skills/story-review/references/character-relations.md +49 -1
  276. package/lib/oh-story/skills/story-review/references/dialogue-mastery.md +4 -0
  277. package/lib/oh-story/skills/story-review/references/review-quality.md +6 -11
  278. package/lib/oh-story/skills/story-review/references/review-tracking.md +10 -0
  279. package/lib/oh-story/skills/story-review/references/solo.md +52 -0
  280. package/lib/oh-story/skills/story-review/references/style-resolution.md +4 -4
  281. package/lib/oh-story/skills/story-review/references/tracking-initialization.md +1 -1
  282. package/lib/oh-story/skills/story-review/references/tracking-transaction.md +15 -11
  283. package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +934 -170
  284. package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +155 -8
  285. package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +478 -15
  286. package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +182 -23
  287. package/lib/oh-story/skills/story-setup/SKILL.md +71 -452
  288. package/lib/oh-story/skills/story-setup/references/agent-references/agent-quality.md +4 -4
  289. package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +12 -11
  290. package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +2 -2
  291. package/lib/oh-story/skills/story-setup/references/agent-references/benchmark-style-load.md +107 -0
  292. package/lib/oh-story/skills/story-setup/references/agent-references/character-relations.md +49 -1
  293. package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +22 -5
  294. package/lib/oh-story/skills/story-setup/references/agent-references/dialogue-mastery.md +4 -0
  295. package/lib/oh-story/skills/story-setup/references/agent-references/emotion-on-page.md +116 -0
  296. package/lib/oh-story/skills/story-setup/references/agent-references/emotional-arc-design.md +1 -114
  297. package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +1 -1
  298. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards.md +1 -1
  299. package/lib/oh-story/skills/story-setup/references/agent-references/long-emotional-methods.md +2 -2
  300. package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-catalog.md +1 -1
  301. package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-mechanics.md +3 -3
  302. package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +7 -5
  303. package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +2 -2
  304. package/lib/oh-story/skills/story-setup/references/agent-references/outline-conflict.md +4 -4
  305. package/lib/oh-story/skills/story-setup/references/agent-references/outline-methods.md +7 -7
  306. package/lib/oh-story/skills/story-setup/references/agent-references/outline-rhythm.md +3 -3
  307. package/lib/oh-story/skills/story-setup/references/agent-references/short-emotional-methods.md +1 -1
  308. package/lib/oh-story/skills/story-setup/references/agent-references/short-genre-formulas.md +0 -15
  309. package/lib/oh-story/skills/story-setup/references/agent-references/short-reversal.md +1 -1
  310. package/lib/oh-story/skills/story-setup/references/agent-references/style-combat-face.md +1 -58
  311. package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
  312. package/lib/oh-story/skills/story-setup/references/agent-references/style-resolution.md +4 -4
  313. package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +19 -65
  314. package/lib/oh-story/skills/story-setup/references/diagnostics.md +4 -4
  315. package/lib/oh-story/skills/story-short-analyze/SKILL.md +56 -110
  316. package/lib/oh-story/skills/story-short-analyze/references/analysis-character-relations.md +1 -1
  317. package/lib/oh-story/skills/story-short-analyze/references/analysis-method.md +42 -0
  318. package/lib/oh-story/skills/story-short-analyze/references/optional-modules.md +97 -0
  319. package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +9 -47
  320. package/lib/oh-story/skills/story-short-analyze/references/quality-checklist.md +80 -0
  321. package/lib/oh-story/skills/story-short-analyze/references/source-story-quality.md +4 -4
  322. package/lib/oh-story/skills/story-short-analyze/references/stage2-3-structure-emotion.md +308 -0
  323. package/lib/oh-story/skills/story-short-analyze/references/stage4-6-reversal-summary.md +611 -0
  324. package/lib/oh-story/skills/story-short-scan/SKILL.md +35 -65
  325. package/lib/oh-story/skills/story-short-scan/references/manual-rank-input.md +36 -0
  326. package/lib/oh-story/skills/story-short-scan/references/platform-dianzhong.md +24 -0
  327. package/lib/oh-story/skills/story-short-scan/references/platform-heiyan.md +21 -0
  328. package/lib/oh-story/skills/story-short-scan/references/topic-match.md +38 -0
  329. package/lib/oh-story/skills/story-short-write/SKILL.md +23 -175
  330. package/lib/oh-story/skills/story-short-write/references/agent-calls.md +13 -0
  331. package/lib/oh-story/skills/story-short-write/references/author-memory-maintenance.md +75 -0
  332. package/lib/oh-story/skills/story-short-write/references/author-memory.md +46 -94
  333. package/lib/oh-story/skills/story-short-write/references/banned-words.md +2 -2
  334. package/lib/oh-story/skills/story-short-write/references/benchmark-recall.md +1 -21
  335. package/lib/oh-story/skills/story-short-write/references/dialogue-mastery.md +4 -0
  336. package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +1 -1
  337. package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +0 -15
  338. package/lib/oh-story/skills/story-short-write/references/output-contract.md +9 -47
  339. package/lib/oh-story/skills/story-short-write/references/reference-index.md +17 -47
  340. package/lib/oh-story/skills/story-short-write/references/scene-craft.md +40 -0
  341. package/lib/oh-story/skills/story-short-write/references/short-craft.md +7 -43
  342. package/lib/oh-story/skills/story-short-write/references/short-format.md +3 -3
  343. package/lib/oh-story/skills/story-short-write/references/short-prose-quality.md +2 -1
  344. package/lib/oh-story/skills/story-short-write/references/short-reversal.md +1 -1
  345. package/lib/oh-story/skills/story-short-write/references/style-resolution.md +4 -4
  346. package/lib/oh-story/skills/story-short-write/references/submission-craft.md +1 -1
  347. package/lib/oh-story/skills/story-short-write/references/villain-and-reveal.md +5 -10
  348. package/lib/oh-story/skills/story-short-write/references/workflow-design.md +21 -13
  349. package/lib/oh-story/skills/story-short-write/references/workflow-draft.md +116 -0
  350. package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +99 -0
  351. package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +7 -119
  352. package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +934 -170
  353. package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +155 -8
  354. package/lib/oh-story/skills/story-short-write/scripts/check-outline-copy.js +7 -2
  355. package/lib/types/client/drama-production-view.d.ts.map +1 -1
  356. package/lib/types/client/file-activity.d.ts +19 -1
  357. package/lib/types/client/file-activity.d.ts.map +1 -1
  358. package/lib/types/client/index.d.ts.map +1 -1
  359. package/lib/types/client/production-prompts.d.ts +7 -0
  360. package/lib/types/client/production-prompts.d.ts.map +1 -1
  361. package/lib/types/client/production-runtime.d.ts +16 -0
  362. package/lib/types/client/production-runtime.d.ts.map +1 -1
  363. package/lib/types/client/video-studio.d.ts.map +1 -1
  364. package/lib/types/drama-adapters.d.ts +5 -3
  365. package/lib/types/drama-adapters.d.ts.map +1 -1
  366. package/lib/types/native-hooks.d.ts +53 -1
  367. package/lib/types/native-hooks.d.ts.map +1 -1
  368. package/lib/types/role-identity.d.ts +17 -0
  369. package/lib/types/role-identity.d.ts.map +1 -0
  370. package/lib/types/role-tool.d.ts.map +1 -1
  371. package/lib/types/skill-provider.d.ts.map +1 -1
  372. package/lib/types/video-project.d.ts.map +1 -1
  373. package/lib/types/workspace-route.d.ts.map +1 -1
  374. package/lib/video-recap/CHANGELOG.md +60 -1
  375. package/lib/video-recap/README.en.md +348 -137
  376. package/lib/video-recap/README.md +340 -136
  377. package/lib/video-recap/manifest.json +406 -251
  378. package/lib/video-recap/skills/video-assemble/SKILL.md +30 -15
  379. package/lib/video-recap/skills/video-assemble/references/audio-modes.md +63 -0
  380. package/lib/video-recap/skills/video-assemble/references/explicit-audio-mix.md +75 -0
  381. package/lib/video-recap/skills/video-assemble/references/foreground-compose.md +118 -0
  382. package/lib/video-recap/skills/video-assemble/references/narration-adoption.md +103 -0
  383. package/lib/video-recap/skills/video-assemble/references/packaging.md +45 -0
  384. package/lib/video-recap/skills/video-assemble/references/pair-media.md +74 -0
  385. package/lib/video-recap/skills/video-assemble/references/source-score.md +147 -0
  386. package/lib/video-recap/skills/video-assemble/references/subtitle-track.md +202 -0
  387. package/lib/video-recap/skills/video-assemble/scripts/adoption/__init__.py +1 -0
  388. package/lib/video-recap/skills/video-assemble/scripts/adoption/audio_mix_binding.py +279 -0
  389. package/lib/video-recap/skills/video-assemble/scripts/adoption/frozen_audio.py +187 -0
  390. package/lib/video-recap/skills/video-assemble/scripts/adoption/narration_binding.py +323 -0
  391. package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_inputs.py +109 -0
  392. package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_publish.py +130 -0
  393. package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +10 -46
  394. package/lib/video-recap/skills/video-assemble/scripts/assemble.py +378 -119
  395. package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +29 -12
  396. package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +95 -44
  397. package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +75 -32
  398. package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +11 -11
  399. package/lib/video-recap/skills/video-assemble/scripts/compose_foreground.py +293 -0
  400. package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +71 -78
  401. package/lib/video-recap/skills/video-assemble/scripts/jianying/__init__.py +1 -0
  402. package/lib/video-recap/skills/video-assemble/scripts/{jianying_builders.py → jianying/builders.py} +39 -67
  403. package/lib/video-recap/skills/video-assemble/scripts/{jianying_model.py → jianying/model.py} +3 -11
  404. package/lib/video-recap/skills/video-assemble/scripts/{jianying_optional.py → jianying/optional.py} +1 -1
  405. package/lib/video-recap/skills/video-assemble/scripts/jianying/schema.py +53 -0
  406. package/lib/video-recap/skills/video-assemble/scripts/{jianying_templates.py → jianying/templates.py} +1 -1
  407. package/lib/video-recap/skills/video-assemble/scripts/{jianying_timeline_contract.py → jianying/timeline_contract.py} +34 -13
  408. package/lib/video-recap/skills/video-assemble/scripts/{jianying_writer.py → jianying/writer.py} +7 -17
  409. package/lib/video-recap/skills/video-assemble/scripts/lib.py +37 -4
  410. package/lib/video-recap/skills/video-assemble/scripts/media.py +12 -21
  411. package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +64 -34
  412. package/lib/video-recap/skills/video-assemble/scripts/packaging.py +122 -0
  413. package/lib/video-recap/skills/video-assemble/scripts/pair_media.py +216 -0
  414. package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +1 -3
  415. package/lib/video-recap/skills/video-assemble/scripts/source_score.py +469 -0
  416. package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +25 -9
  417. package/lib/video-recap/skills/video-assemble/scripts/subtitles/__init__.py +1 -0
  418. package/lib/video-recap/skills/video-assemble/scripts/{subtitle_core.py → subtitles/core.py} +13 -6
  419. package/lib/video-recap/skills/video-assemble/scripts/{subtitle_render.py → subtitles/render.py} +5 -4
  420. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track.py +324 -0
  421. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track_binding.py +200 -0
  422. package/lib/video-recap/skills/video-assemble/scripts/timeline.py +30 -46
  423. package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +91 -7
  424. package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +22 -8
  425. package/lib/video-recap/skills/video-cut/SKILL.md +34 -16
  426. package/lib/video-recap/skills/video-cut/references/shot-review.md +50 -0
  427. package/lib/video-recap/skills/video-cut/scripts/cut.py +2 -28
  428. package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +58 -62
  429. package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +21 -106
  430. package/lib/video-recap/skills/video-cut/scripts/cut_render.py +14 -9
  431. package/lib/video-recap/skills/video-cut/scripts/lib.py +39 -0
  432. package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +2 -155
  433. package/lib/video-recap/skills/video-cut/scripts/narrative_selection.py +244 -0
  434. package/lib/video-recap/skills/video-cut/scripts/shot_review.py +357 -0
  435. package/lib/video-recap/skills/video-recap/SKILL.md +86 -42
  436. package/lib/video-recap/skills/video-recap/references/audio-routing.md +126 -0
  437. package/lib/video-recap/skills/video-recap/references/config-playbook.md +12 -8
  438. package/lib/video-recap/skills/video-recap/references/data-schema.md +44 -33
  439. package/lib/video-recap/skills/video-recap/references/resource-library.md +151 -0
  440. package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +1 -1
  441. package/lib/video-recap/skills/video-recap/scripts/doctor.py +123 -30
  442. package/lib/video-recap/skills/video-recap/scripts/final_qc.py +22 -75
  443. package/lib/video-recap/skills/video-recap/scripts/lib.py +3 -3
  444. package/lib/video-recap/skills/video-recap/scripts/library.py +509 -0
  445. package/lib/video-recap/skills/video-recap/scripts/materials.py +63 -91
  446. package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +8 -70
  447. package/lib/video-recap/skills/video-recap/scripts/project_binding.py +286 -0
  448. package/lib/video-recap/skills/video-recap/scripts/qc/__init__.py +1 -0
  449. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_client.py → qc/mimo_client.py} +1 -1
  450. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_evidence.py → qc/mimo_evidence.py} +33 -66
  451. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_observations.py → qc/mimo_observations.py} +3 -11
  452. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_payload.py → qc/mimo_payload.py} +4 -7
  453. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_report.py → qc/mimo_report.py} +17 -28
  454. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_runner.py → qc/mimo_runner.py} +2 -2
  455. package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +0 -19
  456. package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +119 -50
  457. package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +32 -114
  458. package/lib/video-recap/skills/video-recap/scripts/recap_review.py +9 -23
  459. package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +387 -334
  460. package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +23 -23
  461. package/lib/video-recap/skills/video-recap/scripts/recap_source.py +278 -0
  462. package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +25 -12
  463. package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +59 -52
  464. package/lib/video-recap/skills/video-recap/scripts/resource_lock.py +162 -0
  465. package/lib/video-recap/skills/video-script/SKILL.md +16 -13
  466. package/lib/video-recap/skills/video-script/references/creative-editing-playbook.md +9 -5
  467. package/lib/video-recap/skills/video-script/references/promotional-copy.md +68 -0
  468. package/lib/video-recap/skills/video-script/references/research-guide.md +1 -1
  469. package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +24 -76
  470. package/lib/video-recap/skills/video-script/scripts/lib.py +10 -87
  471. package/lib/video-recap/skills/video-script/scripts/narration_lint.py +63 -39
  472. package/lib/video-recap/skills/video-script/scripts/review.py +0 -2
  473. package/lib/video-recap/skills/video-script/scripts/review_grounding.py +35 -59
  474. package/lib/video-recap/skills/video-script/scripts/review_response.py +66 -128
  475. package/lib/video-recap/skills/video-script/scripts/review_runner.py +12 -20
  476. package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +4 -4
  477. package/lib/video-recap/skills/video-script/scripts/validate.py +53 -42
  478. package/lib/video-recap/skills/video-understanding/SKILL.md +6 -4
  479. package/lib/video-recap/skills/video-understanding/references/data-schema.md +50 -156
  480. package/lib/video-recap/skills/video-understanding/references/research-guide.md +4 -4
  481. package/lib/video-recap/skills/video-understanding/scripts/asr.py +85 -65
  482. package/lib/video-recap/skills/video-understanding/scripts/asr_timing_evidence.py +270 -0
  483. package/lib/video-recap/skills/video-understanding/scripts/brief.py +2 -5
  484. package/lib/video-recap/skills/video-understanding/scripts/briefing/__init__.py +1 -0
  485. package/lib/video-recap/skills/{video-script/scripts/agent_brief.py → video-understanding/scripts/briefing/builder.py} +38 -35
  486. package/lib/video-recap/skills/video-understanding/scripts/{brief_context.py → briefing/context.py} +4 -35
  487. package/lib/video-recap/skills/{video-script/scripts/brief_inputs.py → video-understanding/scripts/briefing/inputs.py} +9 -31
  488. package/lib/video-recap/skills/{video-script/scripts/brief_timeline.py → video-understanding/scripts/briefing/timeline.py} +5 -6
  489. package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +65 -116
  490. package/lib/video-recap/skills/video-understanding/scripts/detect.py +31 -40
  491. package/lib/video-recap/skills/video-understanding/scripts/lib.py +30 -86
  492. package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +63 -39
  493. package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +4 -4
  494. package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +17 -79
  495. package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +14 -40
  496. package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +64 -46
  497. package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +31 -11
  498. package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +19 -14
  499. package/lib/video-recap/skills/video-understanding/scripts/vlm.py +80 -108
  500. package/lib/video-recap/skills/video-voiceover/SKILL.md +29 -19
  501. package/lib/video-recap/skills/video-voiceover/references/index-tts.md +31 -0
  502. package/lib/video-recap/skills/video-voiceover/scripts/approved_text_policy.py +123 -0
  503. package/lib/video-recap/skills/video-voiceover/scripts/dub.py +51 -59
  504. package/lib/video-recap/skills/video-voiceover/scripts/lib.py +8 -44
  505. package/lib/video-recap/skills/video-voiceover/scripts/providers/__init__.py +1 -0
  506. package/lib/video-recap/skills/video-voiceover/scripts/providers/index_tts.py +184 -0
  507. package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +212 -134
  508. package/package.json +45 -45
  509. package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +0 -58
  510. package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +0 -53
  511. package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +0 -38
  512. package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +0 -530
  513. package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +0 -559
  514. package/lib/oh-story/skills/story-long-scan/references/scan-output-format.md +0 -207
  515. package/lib/oh-story/skills/story-long-write/references/state-tracking.md +0 -108
  516. package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +0 -678
  517. package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +0 -561
  518. package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +0 -98
  519. package/lib/video-recap/skills/video-recap/references/creative-editing-playbook.md +0 -225
  520. package/lib/video-recap/skills/video-recap/references/research-guide.md +0 -74
  521. package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +0 -250
  522. package/lib/video-recap/skills/video-script/scripts/brief_context.py +0 -348
  523. package/lib/video-recap/skills/video-script/scripts/narration.py +0 -12
  524. package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +0 -399
  525. package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +0 -290
  526. package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +0 -327
  527. /package/lib/video-recap/skills/video-assemble/scripts/{jianying_tracks.py → jianying/tracks.py} +0 -0
  528. /package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_contract.py → qc/mimo_contract.py} +0 -0
  529. /package/lib/video-recap/skills/video-voiceover/scripts/{fish_audio.py → providers/fish_audio.py} +0 -0
@@ -0,0 +1,702 @@
1
+ #!/usr/bin/env python3
2
+ """Inspect reusable long-analysis assets without changing user files.
3
+
4
+ Only the upstream directory contract is recognized: ``章节/第N章_摘要.md``,
5
+ golden-three-chapter analyses, and the aggregate files. Missing chapters are
6
+ reported exactly so a run only fills gaps.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import csv
13
+ import json
14
+ import re
15
+ import sys
16
+ import unicodedata
17
+ from pathlib import Path
18
+ from typing import Iterable
19
+
20
+
21
+ SUMMARY_RE = re.compile(r"^第0*(\d+)章_摘要\.md$")
22
+ GOLDEN_RE = re.compile(r"^第0*(\d+)章_深度拆解\.md$")
23
+ SCHEMA_RE = re.compile(r"schema_version\s*[::]\s*v?(\d+)", re.IGNORECASE)
24
+ TOTAL_RE = re.compile(r"总章数\s*(?:[::]|\|)\s*(\d+)")
25
+ TOTAL_FALLBACK_RE = re.compile(r"总章数\s+(\d+)\s*章?")
26
+ TABLE_CHAPTER_TOTAL_RE = re.compile(r"\|\s*章节数\s*\|\s*(\d+)\s*(?:章)?\s*\|")
27
+ COVERAGE_TOTAL_RE = re.compile(
28
+ r"(?:输入覆盖\s*[::]\s*全文|精读覆盖\s*[::]\s*第\s*1\s*[—–-]\s*)(\d+)\s*章"
29
+ )
30
+ FINAL_RE = re.compile(r"(?:最终状态|当前状态)\s*[::]\s*([a-z0-9_]+)", re.IGNORECASE)
31
+ SOURCE_HASH_RE = re.compile(
32
+ r"(?:输入版本|SHA-256|source_sha256)\s*(?:[::]|\|)\s*([0-9a-f]{64})", re.IGNORECASE
33
+ )
34
+ PROJECTION_RE = re.compile(
35
+ r"<!--\s*story-long-analyze:projection\s+runtime=(?P<runtime>[^\s]+)\s+"
36
+ r"source=(?P<source>[^\s]+)(?:\s+[^>]*?)?\s*-->"
37
+ )
38
+ STATE_START = "<!-- story-long-analyze:runtime-state:start -->"
39
+ STATE_END = "<!-- story-long-analyze:runtime-state:end -->"
40
+ # v0.7.x wrote a 「章节边界」 table (章号 | 标题 | 起始行 | 字数) into _progress.md.
41
+ BOUNDARY_HEADING_RE = re.compile(r"^#{1,6}\s*章节边界")
42
+ TITLE_LABEL_RE = re.compile(
43
+ r"^\s*(?:第(?P<number>[〇零一二三四五六七八九十百千万两0-9]+)章|第[〇零一二三四五六七八九十百千万两0-9]+[卷回节]"
44
+ r"|卷[〇零一二三四五六七八九十百千万两0-9]+|Chapter\s*(?P<english>[0-9]+)|(?P<numeric>[0-9]+)[.、](?![0-9])"
45
+ r"|(?P<special>楔子|序章|引子|前言|后记|尾声|番外[〇零一二三四五六七八九十百千万两0-9]*))",
46
+ re.IGNORECASE,
47
+ )
48
+ # Author notes appended to a heading, e.g. 「(求收藏)」「【二合一】」.
49
+ TITLE_NOTE_RE = re.compile(r"[(\[【〔〖][^()\[\]【】〔〕〖〗]*[)\]】〕〗]\s*$")
50
+ TITLE_NOISE_RE = re.compile(r"[\s\-—::、.,;!?\"“”'‘’《》「」『』()\[\]【】〔〕〖〗]+")
51
+
52
+
53
+ def nonempty(path: Path) -> bool:
54
+ try:
55
+ return path.is_file() and path.stat().st_size > 0
56
+ except OSError:
57
+ return False
58
+
59
+
60
+ def any_nonempty(paths: Iterable[Path]) -> bool:
61
+ return any(nonempty(path) for path in paths)
62
+
63
+
64
+ def read_text(path: Path) -> str | None:
65
+ if not nonempty(path):
66
+ return None
67
+ try:
68
+ return path.read_text(encoding="utf-8-sig")
69
+ except (OSError, UnicodeError):
70
+ return None
71
+
72
+
73
+ def read_progress(path: Path) -> tuple[int | None, int | None, str | None]:
74
+ text = read_text(path)
75
+ if text is None:
76
+ return None, None, "unreadable" if path.exists() else None
77
+ schema_match = SCHEMA_RE.search(text)
78
+ total_match = TOTAL_RE.search(text) or COVERAGE_TOTAL_RE.search(text)
79
+ final_match = FINAL_RE.search(text)
80
+ return (
81
+ int(schema_match.group(1)) if schema_match else None,
82
+ int(total_match.group(1)) if total_match else None,
83
+ final_match.group(1).lower() if final_match else None,
84
+ )
85
+
86
+
87
+ def read_declared_total(paths: Iterable[Path]) -> tuple[int | None, str | None]:
88
+ for path in paths:
89
+ text = read_text(path)
90
+ if text is None:
91
+ continue
92
+ match = TOTAL_RE.search(text) or TOTAL_FALLBACK_RE.search(text) or TABLE_CHAPTER_TOTAL_RE.search(text)
93
+ if match and int(match.group(1)) > 0:
94
+ return int(match.group(1)), path.as_posix()
95
+ return None, None
96
+
97
+
98
+ def read_source_hash(paths: Iterable[Path]) -> tuple[str | None, str | None]:
99
+ for path in paths:
100
+ text = read_text(path)
101
+ if text is None:
102
+ continue
103
+ match = SOURCE_HASH_RE.search(text)
104
+ if match:
105
+ return match.group(1).lower(), path.as_posix()
106
+ return None, None
107
+
108
+
109
+ def read_index_chapters(path: Path) -> tuple[list[int], list[str]]:
110
+ if not nonempty(path):
111
+ return [], []
112
+ errors: list[str] = []
113
+ chapters: list[int] = []
114
+ try:
115
+ with path.open("r", encoding="utf-8-sig", newline="") as handle:
116
+ reader = csv.DictReader(handle)
117
+ required = {"chapter", "char_count", "source_locator", "status", "source_sha256"}
118
+ missing = required - set(reader.fieldnames or [])
119
+ if missing:
120
+ return [], ["chapter_index.csv 缺列:" + ", ".join(sorted(missing))]
121
+ for row_number, row in enumerate(reader, start=2):
122
+ try:
123
+ chapters.append(int(row["chapter"]))
124
+ except (TypeError, ValueError):
125
+ errors.append(f"chapter_index.csv 第 {row_number} 行章号无效")
126
+ except (OSError, UnicodeError, csv.Error) as exc:
127
+ return [], [f"chapter_index.csv 不可读:{exc}"]
128
+ if len(chapters) != len(set(chapters)):
129
+ errors.append("chapter_index.csv 存在重复章号")
130
+ unique = sorted(set(chapters))
131
+ if unique and unique != list(range(1, max(unique) + 1)):
132
+ errors.append("chapter_index.csv 章号不连续")
133
+ return unique, errors
134
+
135
+
136
+ def read_index_identities(path: Path) -> list[dict[str, str]]:
137
+ if not nonempty(path):
138
+ return []
139
+ try:
140
+ with path.open("r", encoding="utf-8-sig", newline="") as handle:
141
+ return [dict(row) for row in csv.DictReader(handle)]
142
+ except (OSError, UnicodeError, csv.Error):
143
+ return []
144
+
145
+
146
+ def read_managed_stages(text: str | None) -> tuple[bool, dict[str, dict[str, str]]]:
147
+ if not text or text.count(STATE_START) != 1 or text.count(STATE_END) != 1:
148
+ return False, {}
149
+ match = re.search(re.escape(STATE_START) + r"(.*?)" + re.escape(STATE_END), text, re.DOTALL)
150
+ if not match:
151
+ return False, {}
152
+ stages: dict[str, dict[str, str]] = {}
153
+ section = None
154
+ for line in match.group(1).splitlines():
155
+ if line.strip() == "### 阶段状态":
156
+ section = "stages"
157
+ continue
158
+ if line.startswith("### "):
159
+ section = None
160
+ continue
161
+ if section != "stages" or not line.lstrip().startswith("|"):
162
+ continue
163
+ cells = [cell.strip() for cell in line.strip().strip("|").split("|")]
164
+ if len(cells) >= 3 and cells[0] not in {"阶段", "---"}:
165
+ stages[cells[0].lower()] = {"status": cells[1].lower(), "output": cells[2]}
166
+ return True, stages
167
+
168
+
169
+ def collect_numbered_files(
170
+ directory: Path, pattern: re.Pattern[str]
171
+ ) -> tuple[list[int], dict[int, Path], list[str]]:
172
+ chapters: list[int] = []
173
+ sources: dict[int, Path] = {}
174
+ duplicates: list[str] = []
175
+ if not directory.is_dir():
176
+ return chapters, sources, duplicates
177
+ for path in sorted(directory.iterdir()):
178
+ match = pattern.match(path.name)
179
+ if not match or not nonempty(path):
180
+ continue
181
+ chapter = int(match.group(1))
182
+ if chapter in sources:
183
+ duplicates.append(f"第{chapter}章:{sources[chapter].as_posix()} / {path.as_posix()}")
184
+ continue
185
+ chapters.append(chapter)
186
+ sources[chapter] = path
187
+ return sorted(chapters), sources, duplicates
188
+
189
+
190
+ def compact_ranges(chapters: Iterable[int]) -> list[str]:
191
+ values = sorted(set(chapters))
192
+ if not values:
193
+ return []
194
+ ranges: list[str] = []
195
+ start = previous = values[0]
196
+ for chapter in values[1:]:
197
+ if chapter == previous + 1:
198
+ previous = chapter
199
+ continue
200
+ ranges.append(str(start) if start == previous else f"{start}-{previous}")
201
+ start = previous = chapter
202
+ ranges.append(str(start) if start == previous else f"{start}-{previous}")
203
+ return ranges
204
+
205
+
206
+ def relative(root: Path, path: Path) -> str:
207
+ try:
208
+ return path.resolve().relative_to(root.resolve()).as_posix()
209
+ except (OSError, ValueError):
210
+ return path.as_posix()
211
+
212
+
213
+ def summary_kind(path: Path) -> str:
214
+ text = read_text(path) or ""
215
+ return "three_script_projection" if PROJECTION_RE.search(text[:1000]) else "upstream_summary"
216
+
217
+
218
+ def read_legacy_boundaries(text: str | None) -> list[dict[str, object]]:
219
+ """Rows of the v0.7.x 「章节边界」 table: old chapter number, title, start line."""
220
+ if not text:
221
+ return []
222
+ rows: list[dict[str, object]] = []
223
+ in_section = False
224
+ header: list[str] | None = None
225
+ for line in text.splitlines():
226
+ stripped = line.strip()
227
+ if stripped.startswith("#"):
228
+ if in_section and rows:
229
+ break
230
+ in_section = bool(BOUNDARY_HEADING_RE.match(stripped))
231
+ header = None
232
+ continue
233
+ if not in_section or not stripped.startswith("|"):
234
+ continue
235
+ cells = [cell.strip() for cell in stripped.strip("|").split("|")]
236
+ if header is None:
237
+ if "章号" in cells:
238
+ header = cells
239
+ continue
240
+ if not "".join(cells).strip("-: "):
241
+ continue
242
+ record = dict(zip(header, cells))
243
+ number = re.search(r"\d+", record.get("章号", ""))
244
+ start = re.search(r"\d+", record.get("起始行", ""))
245
+ if not number:
246
+ continue
247
+ rows.append({
248
+ "chapter": int(number.group(0)),
249
+ "title": record.get("标题", ""),
250
+ "start_line": int(start.group(0)) if start else None,
251
+ })
252
+ return rows
253
+
254
+
255
+ def title_parts(title: str) -> tuple[str, str, str]:
256
+ """(label, strict key, loose key) of a heading; loose drops trailing author notes."""
257
+ text = unicodedata.normalize("NFKC", str(title or "")).strip()
258
+ label = ""
259
+ for _ in range(3):
260
+ match = TITLE_LABEL_RE.match(text)
261
+ if not match or not match.group(0).strip():
262
+ break
263
+ for group in ("number", "english", "numeric", "special"):
264
+ if match.group(group):
265
+ label = match.group(group)
266
+ text = text[match.end():]
267
+ strict = TITLE_NOISE_RE.sub("", text)
268
+ loose = text
269
+ while TITLE_NOTE_RE.search(loose):
270
+ loose = TITLE_NOTE_RE.sub("", loose)
271
+ return label, strict, TITLE_NOISE_RE.sub("", loose) or strict
272
+
273
+
274
+ def label_matches(label: str, row: dict[str, object]) -> bool:
275
+ source = str(row.get("source_chapter", "")).strip()
276
+ if not label or not source:
277
+ return False
278
+ if label == source:
279
+ return True
280
+ try:
281
+ from build_chapter_index import parse_number
282
+ return source.isdigit() and parse_number(label) == int(source)
283
+ except ValueError:
284
+ return False
285
+
286
+
287
+ def title_candidates(title: str, index_rows: list[dict[str, object]]) -> list[dict[str, object]]:
288
+ """Index rows whose title agrees with an old table title, best tier first."""
289
+ label, strict, loose = title_parts(title)
290
+ if strict:
291
+ keys = [(row, title_parts(str(row.get("title", "")))) for row in index_rows]
292
+ for tier in (
293
+ [row for row, (_, row_strict, _) in keys if row_strict == strict],
294
+ [row for row, (_, _, row_loose) in keys
295
+ if row_loose and (row_loose == loose or row_loose in loose or loose in row_loose)],
296
+ ):
297
+ if tier:
298
+ return tier
299
+ # Retitled or title-less headings still carry their chapter number.
300
+ return [row for row in index_rows if label_matches(label, row)]
301
+
302
+
303
+ def map_legacy_row(old: dict[str, object], index_rows: list[dict[str, object]]) -> int | None:
304
+ """Index chapter an old boundary row points at.
305
+
306
+ A start line that is exactly a chapter's heading line decides on its own.
307
+ Otherwise the title picks the chapter (nearest to the old start line when
308
+ several match); a title-less row falls back to the chapter holding the line.
309
+ """
310
+ start = old.get("start_line")
311
+ if isinstance(start, int):
312
+ for row in index_rows:
313
+ if int(row["start_line"]) == start:
314
+ return int(row["chapter"])
315
+ matches = title_candidates(str(old.get("title", "")), index_rows)
316
+ if not isinstance(start, int):
317
+ return int(matches[0]["chapter"]) if len(matches) == 1 else None
318
+ if not matches and not any(title_parts(str(old.get("title", "")))[:2]):
319
+ matches = [row for row in index_rows if int(row["start_line"]) <= start <= int(row["end_line"])]
320
+
321
+ def distance(row: dict[str, object]) -> int:
322
+ first, last = int(row["start_line"]), int(row["end_line"])
323
+ return 0 if first <= start <= last else min(abs(start - first), abs(start - last))
324
+
325
+ ranked = sorted(matches, key=distance)
326
+ if not ranked or (len(ranked) > 1 and distance(ranked[0]) == distance(ranked[1])):
327
+ return None
328
+ return int(ranked[0]["chapter"])
329
+
330
+
331
+ def leading_special_labels(index_rows: list[dict[str, object]]) -> list[str]:
332
+ """Prologue-like chapters (楔子、序章、第0章…) before the first numbered chapter."""
333
+ labels: list[str] = []
334
+ for row in index_rows:
335
+ source = str(row.get("source_chapter", "")).strip()
336
+ if source.isdigit() and int(source) >= 1:
337
+ break
338
+ labels.append("第0章" if source == "0" else source)
339
+ return labels
340
+
341
+
342
+ def mapping_author_message(detail: str, labels: list[str], done: str) -> str:
343
+ """One plain-language stop message with the author's choices (no field names)."""
344
+ if labels:
345
+ prologue = "、".join("「%s」" % label for label in labels)
346
+ return (
347
+ "先停一下:%s。这次重新识别章节时,开头的%s被算成了单独一章,"
348
+ "照这样续拆,旧文件会整体错开一章——%s没人拆、有的章被拆两遍。请选一种:"
349
+ "① 按旧章号继续(推荐,旧拆文当时没把%s算作一章时选这个):%s并进第一章,"
350
+ "已拆好的%s原样复用,%s不单独拆;"
351
+ "② %s单独算一章:这本书已有的拆文结果全部挪进备份目录(不删除),按新章号重拆;"
352
+ "③ 换一个新目录,整本重新拆。"
353
+ % (detail, prologue, prologue, prologue, prologue, done, prologue, prologue)
354
+ )
355
+ return (
356
+ "先停一下:%s,没法确认已拆好的%s各对应哪一章。请选一种:"
357
+ "① 换一个新目录,整本重新拆(推荐);② 如果原文换过版本或被改过,换回拆文时用的那份原文再续拆。"
358
+ % (detail, done)
359
+ )
360
+
361
+
362
+ def legacy_mapping_check(root: Path, index_rows: list[dict[str, object]]) -> dict[str, object] | None:
363
+ """Stop when files numbered by an older run no longer match the chapter index.
364
+
365
+ Old summaries always predate the index. Golden-chapter analyses count as old
366
+ when ``_progress.md`` comes from v0.7.x (a 「章节边界」 table or no runtime
367
+ block); this run's Stage 1 writes them against the index. The old boundary
368
+ table, when present, decides; otherwise a source that does not start at
369
+ chapter one is ambiguous.
370
+ """
371
+ if not index_rows:
372
+ return None
373
+ progress_text = read_text(root / "_progress.md")
374
+ boundaries = read_legacy_boundaries(progress_text)
375
+ legacy_progress = progress_text is not None and (bool(boundaries) or STATE_START not in progress_text)
376
+ _, summary_sources, _ = collect_numbered_files(root / "章节", SUMMARY_RE)
377
+ old_chapters = {chapter for chapter, path in summary_sources.items() if summary_kind(path) == "upstream_summary"}
378
+ old_kinds = ["逐章摘要"] if old_chapters else []
379
+ if legacy_progress:
380
+ golden, _, _ = collect_numbered_files(root / "章节", GOLDEN_RE)
381
+ if golden:
382
+ old_chapters.update(golden)
383
+ old_kinds.insert(0, "开头三章拆解")
384
+ if not old_chapters:
385
+ return None
386
+ rows = sorted(index_rows, key=lambda row: int(row["chapter"]))
387
+ labels = leading_special_labels(rows)
388
+ done = "、".join(old_kinds)
389
+ if boundaries:
390
+ by_old = {int(row["chapter"]): row for row in boundaries}
391
+ # Chapters the old run appended after its table stay on the same numbering
392
+ # as long as every chapter the table does cover lines up.
393
+ checked = sorted(chapter for chapter in old_chapters if chapter <= max(by_old)) or sorted(old_chapters)
394
+ shifted = []
395
+ unknown = []
396
+ for chapter in checked:
397
+ old = by_old.get(chapter)
398
+ mapped = map_legacy_row(old, rows) if old else None
399
+ if mapped is None:
400
+ unknown.append(chapter)
401
+ elif mapped != chapter:
402
+ shifted.append((chapter, mapped))
403
+ if not shifted and not unknown:
404
+ return None
405
+ if shifted:
406
+ old_number, new_number = shifted[0]
407
+ title = next((str(row.get("title", "")) for row in rows if int(row["chapter"]) == new_number), "")
408
+ detail = "旧拆文的第%s章「%s」,在这次的章节表里是第%s章" % (old_number, title, new_number)
409
+ else:
410
+ detail = "旧进度里的章节表和原文对不上(第%s章找不到)" % compact_ranges(unknown)[0]
411
+ code = "legacy_boundary_shift" if shifted else "legacy_boundary_unmatched"
412
+ else:
413
+ first_source = str(rows[0].get("source_chapter", "")).strip()
414
+ if first_source.isdigit() and int(first_source) == 1:
415
+ return None
416
+ detail = "原文开头是「%s」,而旧拆文没有留下能核对章号的章节表" % (first_source or "未知")
417
+ code = "legacy_identity_unverifiable"
418
+ return {
419
+ "code": code,
420
+ "conflict": "旧拆文章号与当前索引不一致:" + detail,
421
+ "author_message": mapping_author_message(detail, labels, done),
422
+ "fold_prologue_available": bool(labels),
423
+ "legacy_chapters": sorted(old_chapters),
424
+ }
425
+
426
+
427
+ def inspect(root: Path, expected_override: int | None) -> dict[str, object]:
428
+ root = root.resolve()
429
+ schema, progress_total, final_state = read_progress(root / "_progress.md")
430
+ progress_text = read_text(root / "_progress.md")
431
+ index_chapters, index_errors = read_index_chapters(root / "chapter_index.csv")
432
+ index_identities = read_index_identities(root / "chapter_index.csv")
433
+ managed_state_present, managed_stages = read_managed_stages(progress_text)
434
+
435
+ summaries, summary_sources, duplicate_summaries = collect_numbered_files(root / "章节", SUMMARY_RE)
436
+ golden, golden_sources, duplicate_golden = collect_numbered_files(root / "章节", GOLDEN_RE)
437
+
438
+ preferred_paths: dict[int, str] = {}
439
+ preferred_kinds: dict[int, str] = {}
440
+ for chapter, path in summary_sources.items():
441
+ preferred_paths[chapter] = relative(root, path)
442
+ preferred_kinds[chapter] = summary_kind(path)
443
+ for chapter, path in golden_sources.items():
444
+ if chapter not in preferred_paths:
445
+ preferred_paths[chapter] = relative(root, path)
446
+ preferred_kinds[chapter] = "golden_analysis"
447
+
448
+ expected = expected_override
449
+ expected_source = "argument" if expected is not None else None
450
+ if expected is None and index_chapters and not index_errors:
451
+ expected = max(index_chapters)
452
+ expected_source = "chapter_index.csv"
453
+ if expected is None and progress_total is not None and final_state in {"completed", "completed_with_errors"}:
454
+ expected = progress_total
455
+ expected_source = "_progress.md"
456
+ if expected is None:
457
+ expected, source = read_declared_total((root / "拆文报告.md", root / "概要.md", root / "快速预览.md"))
458
+ expected_source = Path(source).name if source else None
459
+ if expected is None and progress_total is not None:
460
+ expected = progress_total
461
+ expected_source = "_progress.md"
462
+
463
+ expected_set = set(range(1, expected + 1)) if expected else set()
464
+ union_semantic_set = set(preferred_paths)
465
+ missing_semantic = sorted(expected_set - union_semantic_set)
466
+ out_of_range_semantic = sorted(union_semantic_set - expected_set) if expected else []
467
+ missing_summaries = sorted(expected_set - set(summaries))
468
+ out_of_range_summaries = sorted(set(summaries) - expected_set) if expected else []
469
+
470
+ primary = {
471
+ "emotion_module": nonempty(root / "剧情" / "情绪模块.md"),
472
+ "rhythm": nonempty(root / "剧情" / "节奏.md"),
473
+ }
474
+ plot_dir = root / "剧情"
475
+ assets = {
476
+ "report": nonempty(root / "拆文报告.md"),
477
+ "style": nonempty(root / "文风.md"),
478
+ "storyline": nonempty(plot_dir / "故事线.md"),
479
+ "plot_units": any_nonempty(
480
+ path
481
+ for path in plot_dir.glob("*.md")
482
+ if path.name not in {"README.md", "故事线.md", "节奏.md", "情绪模块.md", "散落情节.md"}
483
+ )
484
+ if plot_dir.is_dir()
485
+ else False,
486
+ "characters": any_nonempty((root / "角色").glob("*.md")) if (root / "角色").is_dir() else False,
487
+ "settings": any_nonempty((root / "设定").rglob("*.md")) if (root / "设定").is_dir() else False,
488
+ "batch_cache": any_nonempty((root / "_analysis_cache").glob("批次-*.md"))
489
+ if (root / "_analysis_cache").is_dir()
490
+ else False,
491
+ "chapter_index": nonempty(root / "chapter_index.csv"),
492
+ }
493
+
494
+ has_any = bool(union_semantic_set) or any(assets.values()) or any(primary.values()) or nonempty(root / "_progress.md")
495
+ semantic_coverage_complete = bool(union_semantic_set) and (
496
+ (expected is not None and not missing_semantic and not out_of_range_semantic)
497
+ or (expected is None and final_state in {"completed", "completed_with_errors"})
498
+ )
499
+ summary_coverage_complete = (
500
+ bool(summaries) and expected is not None and not missing_summaries and not out_of_range_summaries
501
+ )
502
+ standard_coverage_complete = bool(union_semantic_set) and expected is not None and union_semantic_set == expected_set
503
+ legacy_core = assets["report"] and summary_coverage_complete and (assets["storyline"] or assets["plot_units"])
504
+ managed_pipeline_complete = all(
505
+ managed_stages.get(stage, {}).get("status") == "completed"
506
+ for stage in ("stage1", "stage2", "stage3", "stage4", "stage5", "stage6")
507
+ )
508
+ usable_upstream_complete = (
509
+ all(primary.values())
510
+ and assets["report"]
511
+ and standard_coverage_complete
512
+ and (
513
+ managed_pipeline_complete
514
+ or (
515
+ final_state in {"completed", "completed_with_errors"}
516
+ and (assets["storyline"] or assets["plot_units"])
517
+ )
518
+ )
519
+ )
520
+
521
+ if expected:
522
+ preferred_paths = {chapter: path for chapter, path in preferred_paths.items() if chapter in expected_set}
523
+ preferred_kinds = {chapter: kind for chapter, kind in preferred_kinds.items() if chapter in expected_set}
524
+ semantic_set = set(preferred_paths)
525
+
526
+ full_result_available = usable_upstream_complete or legacy_core
527
+ mixed_sources = len(set(preferred_kinds.values())) > 1
528
+
529
+ # Files numbered by an older run can drift from the index; this run's golden
530
+ # analyses and projections are written against it.
531
+ mapping_conflicts: list[str] = []
532
+ mapping_blocked_chapters: list[int] = []
533
+ mapping = legacy_mapping_check(root, index_identities) if index_identities else None
534
+ if mapping:
535
+ mapping_blocked_chapters = sorted(expected_set or set(index_chapters))
536
+ mapping_conflicts.append(str(mapping["conflict"]))
537
+
538
+ stage_repairs: list[str] = []
539
+ if semantic_coverage_complete:
540
+ legacy_completed = final_state in {"completed", "completed_with_errors"}
541
+ if not primary["emotion_module"]:
542
+ stage_repairs.append("stage3_emotion")
543
+ if not primary["rhythm"]:
544
+ stage_repairs.append("stage3_rhythm")
545
+ if not legacy_completed and (not assets["characters"] or not assets["settings"]):
546
+ stage_repairs.append("stage4")
547
+ if not assets["report"]:
548
+ stage_repairs.append("stage5")
549
+ if not assets["style"]:
550
+ stage_repairs.append("stage6_style")
551
+ for stage in ("stage3", "stage4", "stage5", "stage6"):
552
+ row = managed_stages.get(stage)
553
+ if row and row.get("status") != "completed" and stage not in stage_repairs:
554
+ stage_repairs.append(stage)
555
+ elif managed_state_present and not legacy_completed and row is None:
556
+ # New three-script runs require a completed stage row even when
557
+ # a file was manually placed before the interruption.
558
+ if stage not in stage_repairs and not any(item.startswith(stage + "_") for item in stage_repairs):
559
+ stage_repairs.append(stage)
560
+
561
+ if not has_any:
562
+ classification = "empty"
563
+ recommended_path = "new_analysis"
564
+ elif usable_upstream_complete:
565
+ classification = "current_complete"
566
+ recommended_path = "repair_stages" if stage_repairs else "direct_use"
567
+ elif legacy_core:
568
+ classification = "legacy_complete"
569
+ recommended_path = "repair_stages" if stage_repairs else "direct_use"
570
+ elif semantic_coverage_complete:
571
+ classification = "partial_or_mixed"
572
+ recommended_path = "repair_stages" if stage_repairs else "enhance_existing"
573
+ else:
574
+ classification = "partial_or_mixed"
575
+ recommended_path = "continue_partial" if expected and missing_semantic else "enhance_existing"
576
+
577
+ conflicts = list(index_errors)
578
+ conflicts.extend(f"重复摘要:{item}" for item in duplicate_summaries)
579
+ conflicts.extend(f"重复黄金三章:{item}" for item in duplicate_golden)
580
+ conflicts.extend(mapping_conflicts)
581
+ if final_state == "completed" and not usable_upstream_complete:
582
+ conflicts.append("进度标记 completed,但当前主产物或上游逐章覆盖不完整")
583
+ if final_state == "completed_with_errors" and not semantic_coverage_complete:
584
+ conflicts.append("进度标记 completed_with_errors,需按失败与待核记录确认可用范围")
585
+ if index_chapters and expected and max(index_chapters) != expected:
586
+ conflicts.append("机械索引章数与期望章数不一致")
587
+
588
+ source_hash, source_hash_path = read_source_hash((root / "_progress.md",))
589
+
590
+ return {
591
+ "root": str(root),
592
+ "classification": classification,
593
+ "recommended_path": recommended_path,
594
+ "schema_version": schema,
595
+ "final_state": final_state,
596
+ "expected_chapters": expected,
597
+ "expected_chapters_source": expected_source,
598
+ # Original keys remain for callers that need the upstream interface itself.
599
+ "completed_summary_chapters": summaries,
600
+ "missing_summary_chapters": missing_summaries,
601
+ "out_of_range_summary_chapters": out_of_range_summaries,
602
+ # Runtime routing must use the selected semantic family below, never summary filenames alone.
603
+ "completed_semantic_chapters": sorted(semantic_set),
604
+ "missing_semantic_chapters": missing_semantic,
605
+ "out_of_range_semantic_chapters": out_of_range_semantic,
606
+ "semantic_coverage_complete": semantic_coverage_complete,
607
+ "full_result_available": full_result_available,
608
+ "chapter_sources": {
609
+ "priority": ["upstream_summary", "three_script_projection", "golden_analysis"],
610
+ "upstream_summary": {"chapters": summaries, "ranges": compact_ranges(summaries)},
611
+ "golden_analysis": {"chapters": golden, "ranges": compact_ranges(golden)},
612
+ "preferred_by_chapter": {str(chapter): preferred_kinds[chapter] for chapter in sorted(preferred_kinds)},
613
+ "preferred_paths": {str(chapter): preferred_paths[chapter] for chapter in sorted(preferred_paths)},
614
+ },
615
+ "source_hash": source_hash,
616
+ "source_hash_source": source_hash_path,
617
+ "primary_artifacts": primary,
618
+ "reusable_assets": assets,
619
+ "legacy_capabilities": {
620
+ "benchmark_reference": full_result_available or assets["report"] or assets["plot_units"] or bool(semantic_set),
621
+ "import_facts": bool(semantic_set),
622
+ "writing_style_reference": assets["style"],
623
+ },
624
+ "current_capabilities": {
625
+ "emotion_module_recall": primary["emotion_module"],
626
+ "rhythm_reference_recall": primary["rhythm"],
627
+ "source_location": assets["chapter_index"],
628
+ },
629
+ "mixed_sources": mixed_sources,
630
+ "stage_repairs": stage_repairs,
631
+ "managed_stage_status": managed_stages,
632
+ "chapter_mapping_conflicts": mapping_conflicts,
633
+ "chapter_mapping_blocked_chapters": mapping_blocked_chapters,
634
+ "chapter_mapping_author_message": mapping["author_message"] if mapping else None,
635
+ "provenance_requires_review": mixed_sources or (has_any and schema is None),
636
+ "conflicts": conflicts,
637
+ "notes": [
638
+ "本检查只扫描传入书目目录中的上游标准路径,不扫描其他项目或磁盘。",
639
+ "运行路由必须使用 completed_semantic_chapters;缺少逐章摘要文件不等于缺少语义成果。",
640
+ "direct_use 表示默认直接复用;只有用户明确要求增强时才二次提取已有成果。",
641
+ "整本重拆换一个新目录;本检查不提供就地重拆入口。",
642
+ ],
643
+ }
644
+
645
+
646
+ def compact_payload(payload: dict[str, object]) -> dict[str, object]:
647
+ """Remove per-chapter arrays from the human/model-facing inspection view."""
648
+ result = dict(payload)
649
+ semantic = result.pop("completed_semantic_chapters")
650
+ missing = result.pop("missing_semantic_chapters")
651
+ result["semantic_coverage"] = {
652
+ "completed_count": len(semantic),
653
+ "completed_ranges": compact_ranges(semantic),
654
+ "missing_count": len(missing),
655
+ "missing_ranges": compact_ranges(missing),
656
+ }
657
+ result.pop("completed_summary_chapters", None)
658
+ result.pop("missing_summary_chapters", None)
659
+ result.pop("out_of_range_summary_chapters", None)
660
+ result.pop("out_of_range_semantic_chapters", None)
661
+ source_info = dict(result["chapter_sources"])
662
+ source_info.pop("preferred_by_chapter", None)
663
+ source_info.pop("preferred_paths", None)
664
+ for key in ("upstream_summary", "golden_analysis"):
665
+ entry = dict(source_info[key])
666
+ entry["count"] = len(entry.pop("chapters"))
667
+ source_info[key] = entry
668
+ result["chapter_sources"] = source_info
669
+ return result
670
+
671
+
672
+ def main() -> int:
673
+ for stream in (sys.stdout, sys.stderr):
674
+ if hasattr(stream, "reconfigure"):
675
+ stream.reconfigure(encoding="utf-8")
676
+ parser = argparse.ArgumentParser(description=__doc__)
677
+ parser.add_argument("--root", type=Path, required=True, help="拆文库/{书名} 目录")
678
+ parser.add_argument("--expected-chapters", type=int, help="已知总章数;省略时按标准路径优先推断")
679
+ parser.add_argument("--compact", action="store_true", help="省略逐章数组,输出适合运行路由读取的范围摘要")
680
+ args = parser.parse_args()
681
+ if args.expected_chapters is not None and args.expected_chapters < 1:
682
+ parser.error("--expected-chapters 必须大于 0")
683
+ try:
684
+ if not args.root.exists():
685
+ raise ValueError("root_not_found:%s" % args.root)
686
+ if not args.root.is_dir():
687
+ raise ValueError("root_is_not_directory:%s" % args.root)
688
+ # Force a directory read here so an unreadable path fails before it can
689
+ # be mistaken for an empty new-analysis project.
690
+ next(args.root.iterdir(), None)
691
+ payload = inspect(args.root, args.expected_chapters)
692
+ if args.compact:
693
+ payload = compact_payload(payload)
694
+ print(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True))
695
+ return 0
696
+ except (OSError, UnicodeError, ValueError) as exc:
697
+ print(json.dumps({"ok": False, "error": str(exc)}, ensure_ascii=False))
698
+ return 2
699
+
700
+
701
+ if __name__ == "__main__":
702
+ raise SystemExit(main())