@oh-story/dsh 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (479) hide show
  1. package/README.md +56 -12
  2. package/lib/client.js +2692 -268
  3. package/lib/client.js.map +4 -4
  4. package/lib/drama/manifest.json +136 -101
  5. package/lib/drama/skills/short-drama/SKILL.md +61 -1
  6. package/lib/drama/skills/short-drama/references/contract-and-ownership.md +7 -4
  7. package/lib/drama/skills/short-drama/references/creator-documents.md +133 -8
  8. package/lib/drama/skills/short-drama/references/creator-workflow.md +38 -2
  9. package/lib/drama/skills/short-drama/references/knowhow-index.md +3 -0
  10. package/lib/drama/skills/short-drama/references/reference-roles.md +10 -7
  11. package/lib/drama/skills/short-drama/references/runtime-preflight.md +3 -0
  12. package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +1002 -0
  13. package/lib/drama/skills/short-drama/scripts/project_tool.py +485 -5
  14. package/lib/drama/skills/short-drama-assets/SKILL.md +13 -3
  15. package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +116 -0
  16. package/lib/drama/skills/short-drama-assets/references/identity-vs-variant.md +2 -1
  17. package/lib/drama/skills/short-drama-assets/references/prop-and-state.md +19 -0
  18. package/lib/drama/skills/short-drama-assets/references/stage-contract.md +2 -0
  19. package/lib/drama/skills/short-drama-assets/scripts/asset_check.py +1 -1
  20. package/lib/drama/skills/short-drama-image-prompts/SKILL.md +6 -2
  21. package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +4 -3
  22. package/lib/drama/skills/short-drama-image-prompts/references/lookdev-frame.md +1 -1
  23. package/lib/drama/skills/short-drama-image-prompts/references/review-and-fixtures.md +1 -1
  24. package/lib/drama/skills/short-drama-image-prompts/references/stage-contract.md +3 -1
  25. package/lib/drama/skills/short-drama-image-prompts/scripts/image_prompt_check.py +8 -8
  26. package/lib/drama/skills/short-drama-image-prompts/scripts/selftest.py +4 -2
  27. package/lib/drama/skills/short-drama-produce/SKILL.md +46 -10
  28. package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +65 -8
  29. package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +85 -0
  30. package/lib/drama/skills/short-drama-produce/references/providers/seedance.md +27 -10
  31. package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +362 -3
  32. package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +627 -40
  33. package/lib/drama/skills/short-drama-produce/scripts/selftest.py +5 -4
  34. package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +12 -0
  35. package/lib/drama/skills/short-drama-review/scripts/review_check.py +1 -1
  36. package/lib/drama/skills/short-drama-storyboard/SKILL.md +76 -7
  37. package/lib/drama/skills/short-drama-storyboard/assets/shot-template.jsonl +1 -1
  38. package/lib/drama/skills/short-drama-storyboard/references/keyframe-craft.md +6 -2
  39. package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +19 -4
  40. package/lib/drama/skills/short-drama-storyboard/references/review-and-fixtures.md +5 -2
  41. package/lib/drama/skills/short-drama-storyboard/references/screenplay-to-keyframe-example.md +16 -0
  42. package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +8 -3
  43. package/lib/drama/skills/short-drama-storyboard/scripts/storyboard_check.py +1 -1
  44. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +81 -13
  45. package/lib/drama/skills/short-drama-video-prompts/assets/motion-spec.jsonl.md +6 -1
  46. package/lib/drama/skills/short-drama-video-prompts/assets/video-prompts.md +17 -1
  47. package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +21 -1
  48. package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +154 -0
  49. package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +24 -6
  50. package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +11 -7
  51. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +41 -0
  52. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +66 -0
  53. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +5 -2
  54. package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +115 -0
  55. package/lib/drama/skills/short-drama-video-prompts/scripts/container_check.py +1 -1
  56. package/lib/drama/skills/short-drama-video-prompts/scripts/music_spec_check.py +1 -1
  57. package/lib/drama/skills/short-drama-write/SKILL.md +3 -2
  58. package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +1 -1
  59. package/lib/drama/skills/short-drama-write/scripts/screenplay_index.py +2 -2
  60. package/lib/drama/skills/short-drama-write/scripts/voice_sheet_check.py +1 -1
  61. package/lib/index.js +865 -33
  62. package/lib/index.js.map +4 -4
  63. package/lib/novel-to-game/LICENSE +21 -0
  64. package/lib/novel-to-game/README.md +180 -0
  65. package/lib/novel-to-game/README_ZH.md +178 -0
  66. package/lib/novel-to-game/VERSION +1 -0
  67. package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +217 -0
  68. package/lib/novel-to-game/examples/jin-ping-mei/build/app/.vercelignore +3 -0
  69. package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +130 -0
  70. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/bg/compound_new_guofeng.webp +0 -0
  71. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/fate_coda.webp +0 -0
  72. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/meng.webp +0 -0
  73. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/pan.webp +0 -0
  74. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/pinger.webp +0 -0
  75. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/xuee.webp +0 -0
  76. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/finale/yue.webp +0 -0
  77. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_accord_five.webp +0 -0
  78. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_afterglow_five.webp +0 -0
  79. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_alliance.webp +0 -0
  80. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day10.webp +0 -0
  81. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day15.webp +0 -0
  82. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/public_day5.webp +0 -0
  83. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/group/title_new_guofeng.webp +0 -0
  84. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/pan_pinger.webp +0 -0
  85. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/pan_xuee.webp +0 -0
  86. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_meng.webp +0 -0
  87. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_pan.webp +0 -0
  88. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/joint/yue_pinger.webp +0 -0
  89. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/meng/explicit.webp +0 -0
  90. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/meng/prelude.webp +0 -0
  91. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/meng.webp +0 -0
  92. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/pan.webp +0 -0
  93. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/pinger.webp +0 -0
  94. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/xuee.webp +0 -0
  95. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/milestone/yue.webp +0 -0
  96. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pan/explicit.webp +0 -0
  97. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pan/prelude.webp +0 -0
  98. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pinger/explicit.webp +0 -0
  99. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/pinger/prelude.webp +0 -0
  100. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/xuee/explicit.webp +0 -0
  101. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/xuee/prelude.webp +0 -0
  102. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/explicit.webp +0 -0
  103. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/cg/yue/prelude.webp +0 -0
  104. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/meng/night.webp +0 -0
  105. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/pan/night.webp +0 -0
  106. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/pinger/night.webp +0 -0
  107. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/xuee/night.webp +0 -0
  108. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/heroine/yue/night.webp +0 -0
  109. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/household/li_jiaoer.webp +0 -0
  110. package/lib/novel-to-game/examples/jin-ping-mei/build/app/assets/scene/gate_collector.webp +0 -0
  111. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +1105 -0
  112. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +2539 -0
  113. package/lib/novel-to-game/examples/jin-ping-mei/build/app/index.html +18 -0
  114. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/assets.js +147 -0
  115. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/audio.js +332 -0
  116. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +7472 -0
  117. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +13077 -0
  118. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +3028 -0
  119. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +32 -0
  120. package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +10 -0
  121. package/lib/novel-to-game/examples/jin-ping-mei/build/app/vercel.json +23 -0
  122. package/lib/novel-to-game/examples/jin-ping-mei/build/art/generated-art.json +161 -0
  123. package/lib/novel-to-game/examples/jin-ping-mei/example.json +12 -0
  124. package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +31 -0
  125. package/lib/novel-to-game/examples/jin-ping-mei/qa/playable-model.json +99 -0
  126. package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +41 -0
  127. package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +129 -0
  128. package/lib/novel-to-game/manifest.json +508 -0
  129. package/lib/novel-to-game/skills/game-art-direction/SKILL.md +73 -0
  130. package/lib/novel-to-game/skills/game-art-direction/agents/openai.yaml +4 -0
  131. package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +119 -0
  132. package/lib/novel-to-game/skills/game-build/SKILL.md +84 -0
  133. package/lib/novel-to-game/skills/game-build/agents/openai.yaml +4 -0
  134. package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +109 -0
  135. package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +56 -0
  136. package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +81 -0
  137. package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +37 -0
  138. package/lib/novel-to-game/skills/game-concept/SKILL.md +72 -0
  139. package/lib/novel-to-game/skills/game-concept/agents/openai.yaml +4 -0
  140. package/lib/novel-to-game/skills/game-concept/references/concept-method.md +94 -0
  141. package/lib/novel-to-game/skills/game-qa/SKILL.md +50 -0
  142. package/lib/novel-to-game/skills/game-qa/agents/openai.yaml +4 -0
  143. package/lib/novel-to-game/skills/game-qa/references/qa-contract.md +66 -0
  144. package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +45 -0
  145. package/lib/novel-to-game/skills/game-world-design/SKILL.md +63 -0
  146. package/lib/novel-to-game/skills/game-world-design/agents/openai.yaml +4 -0
  147. package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +59 -0
  148. package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +53 -0
  149. package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +84 -0
  150. package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +56 -0
  151. package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +124 -0
  152. package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +77 -0
  153. package/lib/novel-to-game/skills/novel-game-analyze/agents/openai.yaml +4 -0
  154. package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +65 -0
  155. package/lib/novel-to-game/skills/novel-to-game/SKILL.md +71 -0
  156. package/lib/novel-to-game/skills/novel-to-game/agents/openai.yaml +4 -0
  157. package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +42 -0
  158. package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +51 -0
  159. package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +65 -0
  160. package/lib/oh-story/manifest.json +415 -305
  161. package/lib/oh-story/roles/consistency-checker.md +1 -1
  162. package/lib/oh-story/roles/narrative-writer.md +32 -33
  163. package/lib/oh-story/roles/story-architect.md +16 -26
  164. package/lib/oh-story/skills/browser-cdp/SKILL.md +1 -1
  165. package/lib/oh-story/skills/story/SKILL.md +16 -9
  166. package/lib/oh-story/skills/story/VERSION +1 -1
  167. package/lib/oh-story/skills/story/references/author-memory.md +163 -0
  168. package/lib/oh-story/skills/story/scripts/author_memory_commit.py +871 -0
  169. package/lib/oh-story/skills/story-cover/SKILL.md +1 -1
  170. package/lib/oh-story/skills/story-deslop/SKILL.md +10 -5
  171. package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +2 -1
  172. package/lib/oh-story/skills/story-deslop/references/author-memory.md +163 -0
  173. package/lib/oh-story/skills/story-deslop/references/banned-words.md +6 -4
  174. package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +871 -0
  175. package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +64 -1
  176. package/lib/oh-story/skills/story-import/SKILL.md +19 -10
  177. package/lib/oh-story/skills/story-import/references/format-and-structure.md +9 -10
  178. package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +2 -2
  179. package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +1 -1
  180. package/lib/oh-story/skills/story-import/references/tracking-transaction.md +14 -6
  181. package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +251 -0
  182. package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +98 -9
  183. package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +246 -0
  184. package/lib/oh-story/skills/story-long-analyze/SKILL.md +4 -4
  185. package/lib/oh-story/skills/story-long-scan/SKILL.md +1 -1
  186. package/lib/oh-story/skills/story-long-write/SKILL.md +35 -21
  187. package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +2 -1
  188. package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +7 -3
  189. package/lib/oh-story/skills/story-long-write/references/author-memory.md +163 -0
  190. package/lib/oh-story/skills/story-long-write/references/banned-words.md +6 -4
  191. package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +6 -6
  192. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +1 -1
  193. package/lib/oh-story/skills/story-long-write/references/genre-readers.md +1 -1
  194. package/lib/oh-story/skills/{story-setup/references/agent-references/hooks-chapter.md → story-long-write/references/long-chapter-hooks.md} +5 -5
  195. package/lib/oh-story/skills/story-long-write/references/{quality-checklist.md → long-chapter-quality.md} +2 -1
  196. package/lib/oh-story/skills/story-long-write/references/long-format.md +42 -0
  197. package/lib/oh-story/skills/{story-setup/references/agent-references/genre-catalog.md → story-long-write/references/long-genre-catalog.md} +11 -97
  198. package/lib/oh-story/skills/{story-setup/references/agent-references/genre-core-mechanics.md → story-long-write/references/long-genre-mechanics.md} +1 -1
  199. package/lib/oh-story/skills/{story-short-write/references/reversal-toolkit.md → story-long-write/references/long-reversal.md} +18 -32
  200. package/lib/oh-story/skills/story-long-write/references/{hooks-suspense.md → long-suspense.md} +6 -6
  201. package/lib/oh-story/skills/story-long-write/references/opening-design.md +1 -1
  202. package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +1 -1
  203. package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
  204. package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +14 -6
  205. package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +32 -27
  206. package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +4 -4
  207. package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +2 -2
  208. package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +14 -14
  209. package/lib/oh-story/skills/story-long-write/references/writing-craft.md +54 -54
  210. package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +871 -0
  211. package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +64 -1
  212. package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +251 -0
  213. package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +303 -0
  214. package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +98 -9
  215. package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +246 -0
  216. package/lib/oh-story/skills/story-review/SKILL.md +26 -17
  217. package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +2 -1
  218. package/lib/oh-story/skills/story-review/references/author-memory.md +163 -0
  219. package/lib/oh-story/skills/story-review/references/banned-words.md +6 -4
  220. package/lib/oh-story/skills/story-review/references/{quality-checklist.md → review-quality.md} +22 -21
  221. package/lib/oh-story/skills/story-review/references/tracking-transaction.md +14 -6
  222. package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +871 -0
  223. package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +64 -1
  224. package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +98 -9
  225. package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +246 -0
  226. package/lib/oh-story/skills/story-setup/SKILL.md +74 -33
  227. package/lib/oh-story/skills/story-setup/references/agent-references/agent-quality.md +72 -0
  228. package/lib/oh-story/skills/story-setup/references/agent-references/agent-reference-profiles.md +55 -0
  229. package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +2 -1
  230. package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +6 -4
  231. package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +9 -10
  232. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards.md +1 -1
  233. package/lib/oh-story/skills/story-setup/references/agent-references/genre-readers.md +1 -1
  234. package/lib/oh-story/skills/{story-short-analyze/references/hooks-chapter.md → story-setup/references/agent-references/long-chapter-hooks.md} +5 -5
  235. package/lib/oh-story/skills/story-setup/references/agent-references/long-emotional-methods.md +209 -0
  236. package/lib/oh-story/skills/{story-long-write/references/genre-catalog.md → story-setup/references/agent-references/long-genre-catalog.md} +11 -97
  237. package/lib/oh-story/skills/{story-long-write/references/genre-core-mechanics.md → story-setup/references/agent-references/long-genre-mechanics.md} +1 -1
  238. package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +40 -0
  239. package/lib/oh-story/skills/story-setup/references/agent-references/{reversal-toolkit.md → long-reversal.md} +18 -32
  240. package/lib/oh-story/skills/{story-short-write/references/hooks-suspense.md → story-setup/references/agent-references/long-suspense.md} +6 -6
  241. package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +1 -1
  242. package/lib/oh-story/skills/story-setup/references/agent-references/{emotional-methods.md → short-emotional-methods.md} +4 -4
  243. package/lib/oh-story/skills/{story-long-write/references/genre-writing-formulas.md → story-setup/references/agent-references/short-genre-formulas.md} +32 -44
  244. package/lib/oh-story/skills/story-setup/references/agent-references/short-quality.md +39 -0
  245. package/lib/oh-story/skills/{story-long-write/references/reversal-toolkit.md → story-setup/references/agent-references/short-reversal.md} +29 -23
  246. package/lib/oh-story/skills/story-setup/references/agent-references/{hooks-suspense.md → short-suspense.md} +31 -31
  247. package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
  248. package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +55 -55
  249. package/lib/oh-story/skills/story-short-analyze/SKILL.md +28 -28
  250. package/lib/oh-story/skills/story-short-analyze/references/{character-basics.md → analysis-character-basics.md} +1 -1
  251. package/lib/oh-story/skills/story-short-analyze/references/{character-design-methods.md → analysis-character-design.md} +1 -1
  252. package/lib/oh-story/skills/story-short-analyze/references/{character-relations.md → analysis-character-relations.md} +1 -1
  253. package/lib/oh-story/skills/story-short-analyze/references/{hooks-paragraph.md → analysis-paragraph-hooks.md} +1 -1
  254. package/lib/oh-story/skills/story-short-analyze/references/{genre-readers.md → analysis-reader-profile.md} +2 -2
  255. package/lib/oh-story/skills/story-short-analyze/references/analysis-report-style.md +45 -0
  256. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-genres.md +65 -0
  257. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-hooks.md +77 -0
  258. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-mechanics.md +82 -0
  259. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-patterns.md +50 -0
  260. package/lib/oh-story/skills/story-short-analyze/references/analysis-short-suspense.md +98 -0
  261. package/lib/oh-story/skills/story-short-analyze/references/{genre-writing-techniques.md → analysis-writing-techniques.md} +21 -19
  262. package/lib/oh-story/skills/story-short-analyze/references/deconstruction-examples.md +1 -1
  263. package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +21 -28
  264. package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +1 -1
  265. package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +17 -17
  266. package/lib/oh-story/skills/story-short-analyze/references/{quality-checklist.md → source-story-quality.md} +28 -49
  267. package/lib/oh-story/skills/story-short-analyze/references/zhihu-style.md +2 -2
  268. package/lib/oh-story/skills/story-short-scan/SKILL.md +1 -1
  269. package/lib/oh-story/skills/story-short-write/SKILL.md +78 -55
  270. package/lib/oh-story/skills/story-short-write/references/author-memory.md +163 -0
  271. package/lib/oh-story/skills/story-short-write/references/banned-words.md +6 -4
  272. package/lib/oh-story/skills/story-short-write/references/cross-book-recall.md +3 -3
  273. package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +4 -4
  274. package/lib/oh-story/skills/story-short-write/references/genre-styles//344/270/226/346/203/205/346/211/223/350/204/270.md +3 -3
  275. package/lib/oh-story/skills/story-short-write/references/genre-styles//345/217/214/347/224/267/344/270/273.md +2 -2
  276. package/lib/oh-story/skills/story-short-write/references/genre-styles//345/244/215/344/273/207/346/211/223/350/204/270.md +3 -3
  277. package/lib/oh-story/skills/story-short-write/references/genre-styles//345/256/205/346/226/227/345/256/253/346/226/227.md +6 -6
  278. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/200/273/350/243/201/350/261/252/351/227/250.md +3 -3
  279. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/202/254/347/226/221.md +4 -4
  280. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/260/221/344/277/227/346/200/252/350/260/210.md +7 -7
  281. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/262/231/351/233/225/350/204/221/346/264/236.md +5 -5
  282. package/lib/oh-story/skills/story-short-write/references/genre-styles//347/224/234/345/256/240.md +4 -4
  283. package/lib/oh-story/skills/story-short-write/references/genre-styles//350/277/275/345/246/273/347/201/253/350/221/254/345/234/272.md +3 -3
  284. package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +32 -44
  285. package/lib/oh-story/skills/story-short-write/references/genre-writing-techniques.md +20 -18
  286. package/lib/oh-story/skills/story-short-write/references/output-contract.md +1 -1
  287. package/lib/oh-story/skills/story-short-write/references/short-craft.md +18 -18
  288. package/lib/oh-story/skills/story-short-write/references/short-deslop.md +1 -1
  289. package/lib/oh-story/skills/story-short-write/references/short-format.md +38 -8
  290. package/lib/oh-story/skills/story-short-write/references/{quality-checklist.md → short-prose-quality.md} +22 -21
  291. package/lib/oh-story/skills/story-short-write/references/short-reversal.md +355 -0
  292. package/lib/oh-story/skills/{story-short-analyze/references/hooks-suspense.md → story-short-write/references/short-suspense.md} +31 -31
  293. package/lib/oh-story/skills/story-short-write/references/submission-craft.md +2 -2
  294. package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +31 -22
  295. package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +871 -0
  296. package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +64 -1
  297. package/lib/oh-story/skills/story-short-write/scripts/check-delivery-contract.js +212 -0
  298. package/lib/oh-story/skills/story-short-write/scripts/check-phase2-contract.js +363 -0
  299. package/lib/types/client/drama-production-view.d.ts +34 -0
  300. package/lib/types/client/drama-production-view.d.ts.map +1 -0
  301. package/lib/types/client/drama-production.d.ts +92 -0
  302. package/lib/types/client/drama-production.d.ts.map +1 -0
  303. package/lib/types/client/file-activity.d.ts +4 -2
  304. package/lib/types/client/file-activity.d.ts.map +1 -1
  305. package/lib/types/client/index.d.ts +1 -1
  306. package/lib/types/client/index.d.ts.map +1 -1
  307. package/lib/types/client/production-intents.d.ts +9 -0
  308. package/lib/types/client/production-intents.d.ts.map +1 -0
  309. package/lib/types/client/production-prompts.d.ts +9 -0
  310. package/lib/types/client/production-prompts.d.ts.map +1 -0
  311. package/lib/types/client/production-runtime.d.ts +58 -0
  312. package/lib/types/client/production-runtime.d.ts.map +1 -0
  313. package/lib/types/client/video-studio.d.ts +41 -0
  314. package/lib/types/client/video-studio.d.ts.map +1 -0
  315. package/lib/types/client/workbench-ui.d.ts +5 -0
  316. package/lib/types/client/workbench-ui.d.ts.map +1 -0
  317. package/lib/types/game-verification.d.ts +16 -0
  318. package/lib/types/game-verification.d.ts.map +1 -0
  319. package/lib/types/index.d.ts +3 -1
  320. package/lib/types/index.d.ts.map +1 -1
  321. package/lib/types/production-intent.d.ts +21 -0
  322. package/lib/types/production-intent.d.ts.map +1 -0
  323. package/lib/types/production-tool.d.ts +5 -0
  324. package/lib/types/production-tool.d.ts.map +1 -0
  325. package/lib/types/role-tool.d.ts.map +1 -1
  326. package/lib/types/skill-provider.d.ts +7 -0
  327. package/lib/types/skill-provider.d.ts.map +1 -1
  328. package/lib/types/video-project.d.ts +42 -0
  329. package/lib/types/video-project.d.ts.map +1 -0
  330. package/lib/types/workspace-request-trust.d.ts +6 -0
  331. package/lib/types/workspace-request-trust.d.ts.map +1 -1
  332. package/lib/types/workspace-route.d.ts +20 -0
  333. package/lib/types/workspace-route.d.ts.map +1 -1
  334. package/lib/video-recap/CHANGELOG.md +282 -0
  335. package/lib/video-recap/LICENSE +21 -0
  336. package/lib/video-recap/README.en.md +257 -0
  337. package/lib/video-recap/README.md +255 -0
  338. package/lib/video-recap/manifest.json +684 -0
  339. package/lib/video-recap/skills/video-assemble/SKILL.md +97 -0
  340. package/lib/video-recap/skills/video-assemble/references/jianying/LICENSE.duo-video +21 -0
  341. package/lib/video-recap/skills/video-assemble/references/jianying/SOURCE.md +15 -0
  342. package/lib/video-recap/skills/video-assemble/references/jianying/empty_draft_meta_info.json +69 -0
  343. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_segment.json +84 -0
  344. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_combination_video_material.json +98 -0
  345. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_draft.json +204 -0
  346. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_material_video.json +83 -0
  347. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_meta_material_value.json +23 -0
  348. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_project_info.json +133 -0
  349. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_segment.json +70 -0
  350. package/lib/video-recap/skills/video-assemble/references/jianying/empty_jy_text_styles.json +30 -0
  351. package/lib/video-recap/skills/video-assemble/references/jianying/empty_yj_material_audio.json +41 -0
  352. package/lib/video-recap/skills/video-assemble/references/jianying/empty_yj_material_text.json +109 -0
  353. package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +88 -0
  354. package/lib/video-recap/skills/video-assemble/scripts/assemble.py +345 -0
  355. package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +34 -0
  356. package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +305 -0
  357. package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +94 -0
  358. package/lib/video-recap/skills/video-assemble/scripts/audio_automation.py +205 -0
  359. package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +555 -0
  360. package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +261 -0
  361. package/lib/video-recap/skills/video-assemble/scripts/jianying_builders.py +767 -0
  362. package/lib/video-recap/skills/video-assemble/scripts/jianying_model.py +111 -0
  363. package/lib/video-recap/skills/video-assemble/scripts/jianying_optional.py +24 -0
  364. package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +109 -0
  365. package/lib/video-recap/skills/video-assemble/scripts/jianying_templates.py +37 -0
  366. package/lib/video-recap/skills/video-assemble/scripts/jianying_timeline_contract.py +191 -0
  367. package/lib/video-recap/skills/video-assemble/scripts/jianying_tracks.py +75 -0
  368. package/lib/video-recap/skills/video-assemble/scripts/jianying_writer.py +394 -0
  369. package/lib/video-recap/skills/video-assemble/scripts/lib.py +239 -0
  370. package/lib/video-recap/skills/video-assemble/scripts/media.py +243 -0
  371. package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +477 -0
  372. package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +46 -0
  373. package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +486 -0
  374. package/lib/video-recap/skills/video-assemble/scripts/subtitle_core.py +376 -0
  375. package/lib/video-recap/skills/video-assemble/scripts/subtitle_render.py +88 -0
  376. package/lib/video-recap/skills/video-assemble/scripts/timeline.py +248 -0
  377. package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +89 -0
  378. package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +512 -0
  379. package/lib/video-recap/skills/video-cut/SKILL.md +90 -0
  380. package/lib/video-recap/skills/video-cut/scripts/cut.py +67 -0
  381. package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +320 -0
  382. package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +545 -0
  383. package/lib/video-recap/skills/video-cut/scripts/cut_render.py +348 -0
  384. package/lib/video-recap/skills/video-cut/scripts/lib.py +77 -0
  385. package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +341 -0
  386. package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +249 -0
  387. package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +745 -0
  388. package/lib/video-recap/skills/video-recap/SKILL.md +216 -0
  389. package/lib/video-recap/skills/video-recap/references/config-playbook.md +61 -0
  390. package/lib/video-recap/skills/video-recap/references/creative-editing-playbook.md +225 -0
  391. package/lib/video-recap/skills/video-recap/references/data-schema.md +534 -0
  392. package/lib/video-recap/skills/video-recap/references/research-guide.md +74 -0
  393. package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +23 -0
  394. package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +251 -0
  395. package/lib/video-recap/skills/video-recap/scripts/doctor.py +483 -0
  396. package/lib/video-recap/skills/video-recap/scripts/final_qc.py +737 -0
  397. package/lib/video-recap/skills/video-recap/scripts/lib.py +242 -0
  398. package/lib/video-recap/skills/video-recap/scripts/materials.py +457 -0
  399. package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +95 -0
  400. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_client.py +18 -0
  401. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_contract.py +9 -0
  402. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_evidence.py +371 -0
  403. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_observations.py +201 -0
  404. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_payload.py +227 -0
  405. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_report.py +362 -0
  406. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_runner.py +129 -0
  407. package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +338 -0
  408. package/lib/video-recap/skills/video-recap/scripts/recap.py +9 -0
  409. package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +140 -0
  410. package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +550 -0
  411. package/lib/video-recap/skills/video-recap/scripts/recap_review.py +77 -0
  412. package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +654 -0
  413. package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +335 -0
  414. package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +217 -0
  415. package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +795 -0
  416. package/lib/video-recap/skills/video-script/SKILL.md +248 -0
  417. package/lib/video-recap/skills/video-script/references/creative-editing-playbook.md +225 -0
  418. package/lib/video-recap/skills/video-script/references/research-guide.md +75 -0
  419. package/lib/video-recap/skills/video-script/scripts/agent_brief.py +399 -0
  420. package/lib/video-recap/skills/video-script/scripts/agent_text.py +410 -0
  421. package/lib/video-recap/skills/video-script/scripts/brief_context.py +403 -0
  422. package/lib/video-recap/skills/video-script/scripts/brief_inputs.py +332 -0
  423. package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +442 -0
  424. package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +237 -0
  425. package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +492 -0
  426. package/lib/video-recap/skills/video-script/scripts/lib.py +327 -0
  427. package/lib/video-recap/skills/video-script/scripts/narration.py +12 -0
  428. package/lib/video-recap/skills/video-script/scripts/narration_lint.py +678 -0
  429. package/lib/video-recap/skills/video-script/scripts/review.py +42 -0
  430. package/lib/video-recap/skills/video-script/scripts/review_grounding.py +262 -0
  431. package/lib/video-recap/skills/video-script/scripts/review_response.py +761 -0
  432. package/lib/video-recap/skills/video-script/scripts/review_runner.py +200 -0
  433. package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +204 -0
  434. package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +199 -0
  435. package/lib/video-recap/skills/video-script/scripts/validate.py +164 -0
  436. package/lib/video-recap/skills/video-understanding/SKILL.md +75 -0
  437. package/lib/video-recap/skills/video-understanding/references/data-schema.md +299 -0
  438. package/lib/video-recap/skills/video-understanding/references/prompt-templates.md +37 -0
  439. package/lib/video-recap/skills/video-understanding/references/research-guide.md +74 -0
  440. package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +399 -0
  441. package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +410 -0
  442. package/lib/video-recap/skills/video-understanding/scripts/asr.py +260 -0
  443. package/lib/video-recap/skills/video-understanding/scripts/brief.py +12 -0
  444. package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +403 -0
  445. package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +332 -0
  446. package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +442 -0
  447. package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +647 -0
  448. package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +237 -0
  449. package/lib/video-recap/skills/video-understanding/scripts/detect.py +513 -0
  450. package/lib/video-recap/skills/video-understanding/scripts/extract.py +62 -0
  451. package/lib/video-recap/skills/video-understanding/scripts/lib.py +470 -0
  452. package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +678 -0
  453. package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +204 -0
  454. package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +507 -0
  455. package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +199 -0
  456. package/lib/video-recap/skills/video-understanding/scripts/understand.py +9 -0
  457. package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +216 -0
  458. package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +327 -0
  459. package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +367 -0
  460. package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +162 -0
  461. package/lib/video-recap/skills/video-understanding/scripts/vlm.py +705 -0
  462. package/lib/video-recap/skills/video-voiceover/SKILL.md +81 -0
  463. package/lib/video-recap/skills/video-voiceover/scripts/dub.py +637 -0
  464. package/lib/video-recap/skills/video-voiceover/scripts/fish_audio.py +109 -0
  465. package/lib/video-recap/skills/video-voiceover/scripts/lib.py +421 -0
  466. package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +113 -0
  467. package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +782 -0
  468. package/package.json +55 -40
  469. package/lib/oh-story/skills/story-deslop/scripts/check-outline-copy.js +0 -241
  470. package/lib/oh-story/skills/story-long-write/references/format-and-structure.md +0 -169
  471. package/lib/oh-story/skills/story-setup/references/agent-references/genre-writing-formulas.md +0 -444
  472. package/lib/oh-story/skills/story-setup/references/agent-references/quality-checklist.md +0 -247
  473. package/lib/oh-story/skills/story-short-analyze/references/anti-ai-writing.md +0 -582
  474. package/lib/oh-story/skills/story-short-analyze/references/banned-words.md +0 -147
  475. package/lib/oh-story/skills/story-short-analyze/references/genre-catalog.md +0 -490
  476. package/lib/oh-story/skills/story-short-analyze/references/genre-core-mechanics.md +0 -425
  477. package/lib/oh-story/skills/story-short-analyze/references/genre-writing-formulas.md +0 -453
  478. /package/lib/oh-story/skills/{story-long-write/references/hooks-chapter.md → story-setup/references/agent-references/short-chapter-hooks.md} +0 -0
  479. /package/lib/oh-story/skills/{story-long-write/references/hooks-paragraph.md → story-setup/references/agent-references/short-paragraph-hooks.md} +0 -0
@@ -13,6 +13,7 @@ import argparse
13
13
  import contextlib
14
14
  import hashlib
15
15
  import json
16
+ import math
16
17
  import os
17
18
  import re
18
19
  import shutil
@@ -20,7 +21,7 @@ import stat
20
21
  import sys
21
22
  import unicodedata
22
23
  import uuid
23
- from collections.abc import Callable, Iterable, Mapping
24
+ from collections.abc import Callable, Iterable, Mapping, Sequence
24
25
  from datetime import datetime, timezone
25
26
  from pathlib import Path, PurePosixPath
26
27
  from typing import Any, Iterator
@@ -40,6 +41,15 @@ STATE_SCHEMA = "2.0"
40
41
  DEFAULT_PROMPT_LANGUAGE = "en"
41
42
  LANGUAGE_TAG_RE = re.compile(r"[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*")
42
43
  DELIVERY_SUFFIXES = {".md", ".json", ".jsonl"}
44
+ CREATOR_DOCUMENTS = (
45
+ "剧本.md",
46
+ "视觉设定.md",
47
+ "分镜.md",
48
+ "图片提示词.md",
49
+ "视频提示词.md",
50
+ )
51
+ EXPORT_MEDIA_DIRECTORY = "制作成果"
52
+ EXPORT_SCHEMA = "1.0"
43
53
  EPISODE_ID_RE = re.compile(r"EP(?:[0-9]{3}|[1-9][0-9]{3,})")
44
54
  WINDOWS_FORBIDDEN_PATH_CHARACTERS = frozenset('<>:"|?*')
45
55
  WINDOWS_RESERVED_PATH_STEMS = frozenset(
@@ -193,12 +203,56 @@ def project_languages(project: Mapping[str, Any]) -> dict[str, str]:
193
203
  if isinstance(format_block, Mapping)
194
204
  else None
195
205
  )
206
+ authority = project.get("creator_authority")
207
+ production_profile = (
208
+ authority.get("production_profile")
209
+ if isinstance(authority, Mapping)
210
+ else None
211
+ )
212
+ choices = (
213
+ production_profile.get("choices")
214
+ if isinstance(production_profile, Mapping)
215
+ else None
216
+ )
217
+ video_prompt_language = (
218
+ choices.get("video_prompt_language") if isinstance(choices, Mapping) else None
219
+ )
196
220
  return {
197
221
  "language": str(project.get("language") or "zh-CN"),
198
222
  "prompt_language": str(prompt_language or DEFAULT_PROMPT_LANGUAGE),
223
+ "video_prompt_language": str(
224
+ video_prompt_language or prompt_language or DEFAULT_PROMPT_LANGUAGE
225
+ ),
199
226
  }
200
227
 
201
228
 
229
+ def project_video_model_profile(project: Mapping[str, Any]) -> dict[str, Any]:
230
+ """Expose the accepted prompt-facing model choices without guessing defaults."""
231
+ authority = project.get("creator_authority")
232
+ production_profile = (
233
+ authority.get("production_profile")
234
+ if isinstance(authority, Mapping)
235
+ else None
236
+ )
237
+ if (
238
+ not isinstance(production_profile, Mapping)
239
+ or production_profile.get("status") != "accepted"
240
+ ):
241
+ return {}
242
+ choices = production_profile.get("choices")
243
+ if not isinstance(choices, Mapping):
244
+ return {}
245
+ fields = (
246
+ "target_video_model",
247
+ "video_prompt_dialect",
248
+ "video_prompt_language",
249
+ "native_duration_seconds",
250
+ "supported_generation_modes",
251
+ "audio_generation",
252
+ )
253
+ return {field: choices[field] for field in fields if field in choices}
254
+
255
+
202
256
  def initialize_project(
203
257
  path: Path,
204
258
  *,
@@ -206,10 +260,25 @@ def initialize_project(
206
260
  language: str,
207
261
  aspect_ratio: str,
208
262
  prompt_language: str = DEFAULT_PROMPT_LANGUAGE,
263
+ episode_count: int | None = None,
264
+ target_seconds_per_episode: float | None = None,
209
265
  suite_root: Path | None = None,
210
266
  ) -> dict[str, Any]:
211
267
  language = normalize_language_tag(language, field="language")
212
268
  prompt_language = normalize_language_tag(prompt_language, field="prompt_language")
269
+ if episode_count is not None and (
270
+ isinstance(episode_count, bool)
271
+ or not isinstance(episode_count, int)
272
+ or episode_count <= 0
273
+ ):
274
+ raise ValueError("episode_count must be a positive integer")
275
+ if target_seconds_per_episode is not None and (
276
+ isinstance(target_seconds_per_episode, bool)
277
+ or not isinstance(target_seconds_per_episode, (int, float))
278
+ or not math.isfinite(target_seconds_per_episode)
279
+ or target_seconds_per_episode <= 0
280
+ ):
281
+ raise ValueError("target_seconds_per_episode must be a positive number of seconds")
213
282
  root = path.expanduser().resolve()
214
283
  project_path = root / PROJECT_FILE
215
284
  if project_path.exists():
@@ -233,6 +302,10 @@ def initialize_project(
233
302
  )
234
303
  project["format"]["aspect_ratio"] = aspect_ratio
235
304
  project["format"]["prompt_language"] = prompt_language
305
+ if episode_count is not None:
306
+ project["format"]["episode_count"] = episode_count
307
+ if target_seconds_per_episode is not None:
308
+ project["format"]["target_seconds_per_episode"] = target_seconds_per_episode
236
309
  state = {
237
310
  "schema_version": STATE_SCHEMA,
238
311
  "project_id": project["project_id"],
@@ -620,13 +693,26 @@ def _project_path(root: Path, relative: str, *, create_parent: bool = False) ->
620
693
  current = root
621
694
  for part in PurePosixPath(relative).parts[:-1]:
622
695
  current /= part
623
- if current.exists() and current.is_symlink():
696
+ # ``is_symlink`` misses the reparse points that are not name
697
+ # surrogates, and a Windows creator can make those without any extra
698
+ # privilege. Ask about the attribute directly instead.
699
+ try:
700
+ details = os.lstat(current)
701
+ except FileNotFoundError:
702
+ details = None
703
+ if details is not None and _is_link_or_reparse(details):
624
704
  raise ProjectConflictError(f"project parent cannot be a symlink: {part}")
625
- if current.exists() and not current.is_dir():
705
+ if details is not None and not stat.S_ISDIR(details.st_mode):
626
706
  raise ProjectConflictError(f"project parent is not a directory: {part}")
627
- if create_parent and not current.exists():
707
+ if create_parent and details is None:
628
708
  current.mkdir()
629
- if target.exists() and (target.is_symlink() or not target.is_file()):
709
+ try:
710
+ target_details = os.lstat(target)
711
+ except (FileNotFoundError, NotADirectoryError):
712
+ target_details = None
713
+ if target_details is not None and (
714
+ _is_link_or_reparse(target_details) or not stat.S_ISREG(target_details.st_mode)
715
+ ):
630
716
  raise ProjectConflictError(f"project target is not a regular file: {relative}")
631
717
  if not target.parent.resolve().is_relative_to(root):
632
718
  raise ValueError(f"path escapes project root: {relative}")
@@ -819,11 +905,14 @@ def _build_status(
819
905
  if isinstance(output, str) and output:
820
906
  ownership[output] = owner
821
907
  languages = project_languages(project)
908
+ video_model_profile = project_video_model_profile(project)
822
909
  return {
823
910
  "project_id": project.get("project_id"),
824
911
  "title": project.get("title"),
825
912
  "language": languages["language"],
826
913
  "prompt_language": languages["prompt_language"],
914
+ "video_prompt_language": languages["video_prompt_language"],
915
+ "video_model_profile": video_model_profile,
827
916
  "project_root": project_root,
828
917
  "last_action": state.get("last_action"),
829
918
  "layout": dict(layout),
@@ -1007,6 +1096,113 @@ def project_path_lifecycle_at(
1007
1096
  return None
1008
1097
 
1009
1098
 
1099
+ # The three functions below are the path-based half of the dashboard contract.
1100
+ # Windows has no ``openat``, so the dashboard pins a project root by verified
1101
+ # path there instead of by directory descriptor and calls these. They compose
1102
+ # the same rules the descriptor twins do -- ``_build_status``,
1103
+ # ``_artifact_state_from``, ``_normalize_state`` -- so the two halves cannot
1104
+ # drift on what a status or a lifecycle state means; they differ only in how a
1105
+ # file is reached.
1106
+
1107
+
1108
+ def _read_regular(root: Path, relative: str) -> bytes:
1109
+ """Read one project file, refusing every link and reparse point on the way.
1110
+
1111
+ ``os.lstat`` never follows, so a component swapped for a symlink or a
1112
+ junction is rejected rather than traversed. This is the Windows stand-in
1113
+ for opening each component with ``O_NOFOLLOW``.
1114
+ """
1115
+
1116
+ pure = PurePosixPath(relative)
1117
+ current = root
1118
+ for part in pure.parts[:-1]:
1119
+ current = current / part
1120
+ details = os.lstat(current)
1121
+ if _is_link_or_reparse(details) or not stat.S_ISDIR(details.st_mode):
1122
+ raise ProjectConflictError(f"project parent is unsafe: {part}")
1123
+ target = current / pure.name
1124
+ details = os.lstat(target)
1125
+ if _is_link_or_reparse(details) or not stat.S_ISREG(details.st_mode):
1126
+ raise ProjectConflictError(f"project file is unsafe: {relative}")
1127
+ with open(target, "rb") as handle:
1128
+ return handle.read()
1129
+
1130
+
1131
+ def project_status_from_root(root: Path, *, project_root: str) -> dict[str, Any]:
1132
+ """Report status for an already-pinned project root.
1133
+
1134
+ ``project_status`` locates the project first and requires recorded state;
1135
+ this twin takes the root the caller pinned and tolerates a project that has
1136
+ never been tracked, matching ``project_status_at``.
1137
+ """
1138
+
1139
+ project = json.loads(_read_regular(root, PROJECT_FILE).decode("utf-8"))
1140
+ try:
1141
+ raw_state = json.loads(
1142
+ _read_regular(root, STATE_FILE.as_posix()).decode("utf-8")
1143
+ )
1144
+ except FileNotFoundError:
1145
+ raw_state = {
1146
+ "schema_version": STATE_SCHEMA,
1147
+ "project_id": project.get("project_id"),
1148
+ "project_layout_mode": "auto",
1149
+ "last_action": "untracked",
1150
+ "artifacts": {},
1151
+ }
1152
+ if not isinstance(project, dict) or not isinstance(raw_state, dict):
1153
+ raise ValueError("project files must contain objects")
1154
+ state = _normalize_state(raw_state)
1155
+ return _build_status(
1156
+ project=project,
1157
+ state=state,
1158
+ layout=_project_layout_from_root(root, state),
1159
+ project_root=project_root,
1160
+ artifact_state=lambda record: _artifact_state(root, record),
1161
+ )
1162
+
1163
+
1164
+ def project_path_lifecycle(root: Path, relative: str) -> dict[str, str] | None:
1165
+ """Return the one creator-facing state for a tracked path."""
1166
+
1167
+ normalized = _relative_path(relative, allow_operations=True)
1168
+ try:
1169
+ raw_state = json.loads(
1170
+ _read_regular(root, STATE_FILE.as_posix()).decode("utf-8")
1171
+ )
1172
+ except (OSError, ProjectConflictError, UnicodeError, json.JSONDecodeError):
1173
+ return None
1174
+ if not isinstance(raw_state, Mapping):
1175
+ return None
1176
+ state = _normalize_state(raw_state)
1177
+ for record in state["artifacts"].values():
1178
+ if isinstance(record, Mapping) and normalized in record.get("outputs", []):
1179
+ return {"artifact_state": _artifact_state(root, record)}
1180
+ return None
1181
+
1182
+
1183
+ @contextlib.contextmanager
1184
+ def coordinated_project_text_edit(
1185
+ root: Path, relative: str, expected_version: str
1186
+ ) -> Iterator[None]:
1187
+ normalized = _relative_path(relative)
1188
+ if not re.fullmatch(r"[0-9a-f]{64}", expected_version):
1189
+ raise ValueError("expected version must be a SHA-256 digest")
1190
+ # The descriptor twin opens the operations directory with O_NOFOLLOW. Match
1191
+ # it: a lock taken through a redirected `.short-drama` would leave two
1192
+ # dashboards each believing they hold the project.
1193
+ try:
1194
+ operations = os.lstat(root / ".short-drama")
1195
+ except FileNotFoundError:
1196
+ operations = None
1197
+ if operations is not None and _is_link_or_reparse(operations):
1198
+ raise OSError("project operations directory is unsafe")
1199
+ with _project_lock(root):
1200
+ current = sha256_bytes(_read_regular(root, normalized))
1201
+ if current != expected_version:
1202
+ raise ProjectConflictError("file changed since it was opened")
1203
+ yield
1204
+
1205
+
1010
1206
  def _validate_structured_content(relative: str, content: bytes) -> None:
1011
1207
  try:
1012
1208
  text = content.decode("utf-8")
@@ -1668,6 +1864,255 @@ def _manifest_problems(
1668
1864
  return problems
1669
1865
 
1670
1866
 
1867
+ def _same_directory(left: Path, right: Path) -> bool:
1868
+ """Do these two paths name the same directory on disk?
1869
+
1870
+ String comparison is not enough: a case-insensitive volume and a filesystem
1871
+ that stores a different Unicode normalisation both spell one directory two
1872
+ ways, and either spelling would otherwise slip past a containment check.
1873
+ """
1874
+ try:
1875
+ return os.path.samefile(left, right)
1876
+ except OSError:
1877
+ return False
1878
+
1879
+
1880
+ def _require_safe_export_destination(destination: Path, root: Path) -> None:
1881
+ """Refuse any destination that would take the project down with it.
1882
+
1883
+ The export replaces its destination directory wholesale, so a destination
1884
+ that is -- or contains -- the project being exported would delete the
1885
+ source. Ancestors matter as much as descendants here.
1886
+ """
1887
+ if destination == root or root in destination.parents:
1888
+ raise ValueError("export destination must live outside the project root")
1889
+ for ancestor in (root, *root.parents):
1890
+ if destination == ancestor or _same_directory(destination, ancestor):
1891
+ raise ValueError(
1892
+ "export destination must not be the project root or any directory containing it"
1893
+ )
1894
+
1895
+
1896
+ def _require_replaceable_export(destination: Path) -> None:
1897
+ """Only ever overwrite a directory this command itself produced.
1898
+
1899
+ `--overwrite` removes the destination tree. Without this check a mistyped
1900
+ path turns an ordinary directory into a deletion, and nothing else in this
1901
+ tool can erase a directory outside the project.
1902
+ """
1903
+ if not destination.exists():
1904
+ return
1905
+ details = os.lstat(destination)
1906
+ if _is_link_or_reparse(details) or not stat.S_ISDIR(details.st_mode):
1907
+ raise ProjectConflictError("export destination is not a regular directory")
1908
+ manifest = destination / "manifest.json"
1909
+ try:
1910
+ document = json.loads(manifest.read_text(encoding="utf-8"))
1911
+ except (OSError, ValueError) as exc:
1912
+ raise ValueError(
1913
+ "refusing to overwrite a directory that is not a previous export: "
1914
+ f"{destination}"
1915
+ ) from exc
1916
+ if not isinstance(document, Mapping) or document.get("kind") != "creator_export":
1917
+ raise ValueError(
1918
+ "refusing to overwrite a directory that is not a previous export: "
1919
+ f"{destination}"
1920
+ )
1921
+
1922
+
1923
+ def _export_regular_file(source: Path, destination: Path) -> str:
1924
+ """Copy one regular project file into the export tree and return its digest."""
1925
+ details = os.lstat(source)
1926
+ if _is_link_or_reparse(details) or not stat.S_ISREG(details.st_mode):
1927
+ raise ProjectConflictError(f"export source is not a regular file: {source.name}")
1928
+ if "\n" in source.name or "\r" in source.name:
1929
+ # checksums.sha256 is one record per line; such a name cannot be written
1930
+ # in a form any verifier could read back.
1931
+ raise ProjectConflictError(
1932
+ "export source name contains a line break and cannot be checksummed"
1933
+ )
1934
+ destination.parent.mkdir(parents=True, exist_ok=True)
1935
+ shutil.copyfile(source, destination)
1936
+ return sha256_file(destination)
1937
+
1938
+
1939
+ def _export_episode(
1940
+ episode_directory: Path, target: Path, *, include_media: bool
1941
+ ) -> tuple[list[dict[str, str]], list[str]]:
1942
+ files: list[dict[str, str]] = []
1943
+ missing: list[str] = []
1944
+ for name in CREATOR_DOCUMENTS:
1945
+ source = episode_directory / name
1946
+ if not source.exists():
1947
+ missing.append(name)
1948
+ continue
1949
+ digest = _export_regular_file(source, target / name)
1950
+ files.append({"path": name, "sha256": digest})
1951
+ media_root = episode_directory / EXPORT_MEDIA_DIRECTORY
1952
+ if include_media and media_root.exists():
1953
+ media_details = os.lstat(media_root)
1954
+ if _is_link_or_reparse(media_details) or not stat.S_ISDIR(media_details.st_mode):
1955
+ # Silently exporting no media reads as "this episode has none".
1956
+ raise ProjectConflictError(
1957
+ f"{EXPORT_MEDIA_DIRECTORY} is not a regular directory: {episode_directory.name}"
1958
+ )
1959
+ for source in sorted(media_root.rglob("*")):
1960
+ if source.is_dir() and not source.is_symlink():
1961
+ continue
1962
+ relative = source.relative_to(episode_directory).as_posix()
1963
+ digest = _export_regular_file(source, target / relative)
1964
+ files.append({"path": relative, "sha256": digest})
1965
+ return files, missing
1966
+
1967
+
1968
+ def _export_exclusions(root: Path, episodes_name: str) -> list[str]:
1969
+ """Name every top-level project directory this export did not copy.
1970
+
1971
+ A fixed list lies twice over: it names roots a legacy-layout project does
1972
+ not have, and it stays silent about a published 设定集/ that the handover is
1973
+ missing.
1974
+ """
1975
+ excluded: list[str] = []
1976
+ for entry in sorted(root.iterdir(), key=lambda item: item.name):
1977
+ if entry.name == episodes_name or entry.name == PROJECT_FILE:
1978
+ continue
1979
+ if entry.is_dir() or entry.is_symlink():
1980
+ excluded.append(entry.name)
1981
+ return excluded
1982
+
1983
+
1984
+ def _is_portable_windows_path(relative: str) -> bool:
1985
+ for part in PurePosixPath(relative).parts:
1986
+ stem = part.split(".")[0].casefold()
1987
+ if stem in WINDOWS_RESERVED_PATH_STEMS:
1988
+ return False
1989
+ if set(part) & WINDOWS_FORBIDDEN_PATH_CHARACTERS:
1990
+ return False
1991
+ if part.endswith((" ", ".")):
1992
+ return False
1993
+ return True
1994
+
1995
+
1996
+ def build_creator_export(
1997
+ root: Path,
1998
+ *,
1999
+ out: Path,
2000
+ episodes: Sequence[str] | None = None,
2001
+ include_media: bool = True,
2002
+ overwrite: bool = False,
2003
+ ) -> dict[str, Any]:
2004
+ """Copy the current creator documents and produced media into a handover tree.
2005
+
2006
+ This is a snapshot of what the project holds right now. It deliberately
2007
+ asserts nothing about review or creator acceptance; `package` remains the
2008
+ only command that speaks for an approved delivery.
2009
+ """
2010
+ root = find_project(root)
2011
+ destination = out.expanduser().resolve()
2012
+ _require_safe_export_destination(destination, root)
2013
+ layout = _project_layout_from_root(root)
2014
+ if layout["mode"] == "mixed":
2015
+ raise PackageBlockedError("mixed project layouts cannot be exported")
2016
+ episodes_root = root / str(layout["roots"]["episodes"])
2017
+ available = sorted(
2018
+ entry.name
2019
+ for entry in episodes_root.iterdir()
2020
+ if entry.is_dir() and not entry.is_symlink()
2021
+ if EPISODE_ID_RE.fullmatch(entry.name)
2022
+ ) if episodes_root.is_dir() and not episodes_root.is_symlink() else []
2023
+ if episodes:
2024
+ selected = []
2025
+ for episode in episodes:
2026
+ if EPISODE_ID_RE.fullmatch(episode) is None:
2027
+ raise ValueError("episode must use an EP001-style identifier")
2028
+ if episode not in available:
2029
+ raise FileNotFoundError(f"episode directory not found: {episode}")
2030
+ if episode not in selected:
2031
+ selected.append(episode)
2032
+ else:
2033
+ selected = available
2034
+ if not selected:
2035
+ raise FileNotFoundError("no episode directory to export")
2036
+ if destination.exists():
2037
+ if not overwrite:
2038
+ raise FileExistsError(f"export destination already exists: {destination}")
2039
+ _require_replaceable_export(destination)
2040
+
2041
+ project = json.loads((root / PROJECT_FILE).read_text(encoding="utf-8"))
2042
+ destination.parent.mkdir(parents=True, exist_ok=True)
2043
+ temporary = destination.parent / f".{destination.name}.{uuid.uuid4().hex}.tmp"
2044
+ temporary.mkdir()
2045
+ try:
2046
+ exported: list[dict[str, Any]] = []
2047
+ members: list[dict[str, str]] = []
2048
+ episodes_name = str(layout["roots"]["episodes"])
2049
+ for episode in selected:
2050
+ target = temporary / episodes_name / episode
2051
+ target.mkdir(parents=True)
2052
+ files, missing = _export_episode(
2053
+ episodes_root / episode, target, include_media=include_media
2054
+ )
2055
+ for entry in files:
2056
+ members.append(
2057
+ {
2058
+ "path": f"{episodes_name}/{episode}/{entry['path']}",
2059
+ "sha256": entry["sha256"],
2060
+ }
2061
+ )
2062
+ exported.append(
2063
+ {"episode": episode, "files": files, "missing_documents": missing}
2064
+ )
2065
+ config_digest = _export_regular_file(
2066
+ root / PROJECT_FILE, temporary / PROJECT_FILE
2067
+ )
2068
+ members.append({"path": PROJECT_FILE, "sha256": config_digest})
2069
+ manifest = {
2070
+ "schema_version": EXPORT_SCHEMA,
2071
+ "kind": "creator_export",
2072
+ "asserts_approval": False,
2073
+ "project_id": project.get("project_id"),
2074
+ "title": project.get("title"),
2075
+ "created_at": utc_now(),
2076
+ "episodes": exported,
2077
+ "selection": {
2078
+ "episodes": "all" if not episodes else list(selected),
2079
+ "available_episodes": list(available),
2080
+ "include_media": bool(include_media),
2081
+ },
2082
+ "excluded": _export_exclusions(root, episodes_name),
2083
+ "windows_unsafe_paths": sorted(
2084
+ entry["path"]
2085
+ for entry in members
2086
+ if not _is_portable_windows_path(entry["path"])
2087
+ ),
2088
+ }
2089
+ atomic_json(temporary / "manifest.json", manifest)
2090
+ checksum_members = ["manifest.json", *(entry["path"] for entry in members)]
2091
+ checksums = "".join(
2092
+ f"{sha256_file(temporary / relative)} {relative}\n"
2093
+ for relative in sorted(checksum_members)
2094
+ )
2095
+ _atomic_bytes(temporary / "checksums.sha256", checksums.encode("utf-8"))
2096
+ _replace_directory(temporary, destination)
2097
+ finally:
2098
+ if temporary.exists():
2099
+ shutil.rmtree(temporary)
2100
+ return {
2101
+ "kind": "creator_export",
2102
+ "out": str(destination),
2103
+ "episodes": [entry["episode"] for entry in exported],
2104
+ "files": [entry["path"] for entry in members],
2105
+ "excluded": manifest["excluded"],
2106
+ "include_media": bool(include_media),
2107
+ "missing_documents": {
2108
+ entry["episode"]: entry["missing_documents"]
2109
+ for entry in exported
2110
+ if entry["missing_documents"]
2111
+ },
2112
+ "windows_unsafe_paths": manifest["windows_unsafe_paths"],
2113
+ }
2114
+
2115
+
1671
2116
  def verify_delivery_package(root: Path, *, episode: str) -> dict[str, Any]:
1672
2117
  if EPISODE_ID_RE.fullmatch(episode) is None:
1673
2118
  raise ValueError("episode must use an EP001-style identifier")
@@ -1825,6 +2270,8 @@ def build_parser() -> argparse.ArgumentParser:
1825
2270
  init.add_argument("--language", default="zh-CN")
1826
2271
  init.add_argument("--prompt-language", default=DEFAULT_PROMPT_LANGUAGE)
1827
2272
  init.add_argument("--aspect-ratio", default="9:16")
2273
+ init.add_argument("--episode-count", type=int)
2274
+ init.add_argument("--target-seconds", type=float, dest="target_seconds_per_episode")
1828
2275
 
1829
2276
  status = commands.add_parser("status", help="Print a creator-safe project summary.")
1830
2277
  status.add_argument("path", nargs="?", default=".")
@@ -1891,6 +2338,29 @@ def build_parser() -> argparse.ArgumentParser:
1891
2338
  verify = commands.add_parser("verify", help="Re-check a delivered package checksums.")
1892
2339
  verify.add_argument("path")
1893
2340
  verify.add_argument("--episode", required=True)
2341
+
2342
+ export = commands.add_parser(
2343
+ "export",
2344
+ help="Copy the current creator documents and produced media for handover.",
2345
+ )
2346
+ export.add_argument("path")
2347
+ export.add_argument("--out", required=True, type=Path)
2348
+ export.add_argument(
2349
+ "--episode",
2350
+ action="append",
2351
+ dest="episodes",
2352
+ help="Export only this episode; repeat for several. Default: every episode.",
2353
+ )
2354
+ export.add_argument(
2355
+ "--no-media",
2356
+ action="store_true",
2357
+ help=f"Skip 剧集/<EP>/{EXPORT_MEDIA_DIRECTORY}/ and export text only.",
2358
+ )
2359
+ export.add_argument(
2360
+ "--overwrite",
2361
+ action="store_true",
2362
+ help="Replace an existing export directory at --out.",
2363
+ )
1894
2364
  return parser
1895
2365
 
1896
2366
 
@@ -1904,6 +2374,8 @@ def main(argv: list[str] | None = None) -> int:
1904
2374
  language=args.language,
1905
2375
  prompt_language=args.prompt_language,
1906
2376
  aspect_ratio=args.aspect_ratio,
2377
+ episode_count=args.episode_count,
2378
+ target_seconds_per_episode=args.target_seconds_per_episode,
1907
2379
  )
1908
2380
  elif args.command == "status":
1909
2381
  result = project_status(Path(args.path))
@@ -1947,6 +2419,14 @@ def main(argv: list[str] | None = None) -> int:
1947
2419
  includes=args.includes,
1948
2420
  omissions=_parse_omissions(args.omissions),
1949
2421
  )
2422
+ elif args.command == "export":
2423
+ result = build_creator_export(
2424
+ Path(args.path),
2425
+ out=args.out,
2426
+ episodes=args.episodes,
2427
+ include_media=not args.no_media,
2428
+ overwrite=args.overwrite,
2429
+ )
1950
2430
  else:
1951
2431
  result = verify_delivery_package(Path(args.path), episode=args.episode)
1952
2432
  if result["status"] != "verified":
@@ -29,9 +29,11 @@ license: MIT
29
29
  1. 只读当前范围的剧本和已决定的视觉方向。
30
30
  2. 逐场找实体、可见描述、状态变化和含混指代,先不创建资产。
31
31
  3. 对每项决定新建、复用、增加变体或标记未决;含混指代不猜。
32
- 4. 写最小可识别设定:统一使用 `识别锚点`、本集状态和变化原因。
32
+ 4. 写最小可识别设定:统一使用 `识别锚点`、本集状态和变化原因;会持续限制后续动作的身体—物件—空间关系写进有生效范围的状态,不当成一次姿势略过。
33
33
  5. 检查造型/伤势、持物/所有权、道具状态、地点时段/天气/光态与跨集 outgoing。
34
- 6. 整集请求自动处理完整集,末端一次回报关键身份决定和未决指代。
34
+ 6. 给跨镜出现且观众看得出不一致的可见事实写一条连续性锁,其余保持识别锚点即可。
35
+ 7. 项目提示词语言不是中文时,给每个人物条目写一行 `画面代称:<正文里的拼写>`;正文从不点名它就写 `画面代称:无`。地点和道具按需。
36
+ 8. 整集请求自动处理完整集,末端一次回报关键身份决定和未决指代。
35
37
 
36
38
  ## 内容要求
37
39
 
@@ -41,6 +43,12 @@ license: MIT
41
43
  - 不复制剧情摘要,只保留下游生成、分镜或连续性真正需要的事实。
42
44
  - 文字、标识和敏感信息遵守项目政策,不擅自补造品牌或私密信息。
43
45
  - 多集状态写清生效与持续范围;镜头内瞬态留给分镜。
46
+ - 已确认且持续限制动作空间的关系(身体依托、固定、负重、占位或与功能物件的接触)不是普通镜头姿势:可独立识别的物件仍建资产,关系写进状态并交给分镜逐镜投影;只有剧本明确改变时才结束。
47
+ - 连续性锁只锁不随剧情改变的可见事实;锁面用提示词语言写成最小名词短语,会变的部分留给状态。
48
+ - 「画面代称」把提示词正文里的名字对回本条目:分镜的机械核对靠它判断关键帧里点名的人是谁。条目自身的中文名称
49
+ 始终算一个代称,所以中文正文通常不必写这一行;提示词正文语言不是中文时,人物条目必须给出结论——写出正文使用的
50
+ 拼写,或在正文从不点名它时写 `画面代称:无`。同一个名字全集只用一个拼写,大小写也一致(`AST-10` 的同一条要求跨语言同样成立);
51
+ 多个写法用 `、` 分隔。名字与常用词同形(`道具「手机」` 撞上「手机店」)时也写 `画面代称:无`,让核对不按名字找它。
44
52
 
45
53
  ## 按需知识
46
54
 
@@ -51,15 +59,17 @@ license: MIT
51
59
  - 新身份、复用、变体或未决:[身份与变体](references/identity-vs-variant.md)
52
60
  - 人物识别与造型层次:[人物与造型](references/character-and-look.md)
53
61
  - 地点地理、视图和固定锚点:[地点与视图](references/location-and-view.md)
54
- - 功能道具、所有权和状态变化:[道具与状态](references/prop-and-state.md)
62
+ - 功能道具、所有权、状态变化,或画面上的可读文字与标识:[道具与状态](references/prop-and-state.md)
55
63
  - 声音参考的身份与授权边界:[声音方向](references/voice-direction.md)
56
64
  - 跨场、跨镜、跨集状态:[连续性变化](references/continuity-delta.md)
65
+ - 跨镜服装/道具颜色、材质漂移:[跨镜一致性锁](references/continuity-lock.md)
57
66
  - 完成前做一次资产漏项检查:[资产审查清单](references/asset-review-checklist.md)
58
67
 
59
68
  ## 完成
60
69
 
61
70
  所有明确出场都已复用、新建、变体化或标为真实未决;需要保持的视觉事实与状态变化已写入唯一
62
71
  视觉设定文档,即完成。图片提示词、分镜、审查和生产不自动开始。
72
+ 五份创作文档齐备后,可转 `$short-drama` 对跨文档结构做一次机械核对;内容质量仍由创作者审查。
63
73
 
64
74
  ## 安装维护
65
75