@oh-story/dsh 0.1.8 → 0.1.10

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 (360) hide show
  1. package/README.md +14 -14
  2. package/lib/client.js +237 -84
  3. package/lib/client.js.map +3 -3
  4. package/lib/drama/manifest.json +183 -77
  5. package/lib/drama/skills/short-drama/SKILL.md +5 -1
  6. package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +140 -9
  7. package/lib/drama/skills/short-drama/scripts/project_tool.py +11 -0
  8. package/lib/drama/skills/short-drama-assets/references/character-and-look.md +28 -0
  9. package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +8 -2
  10. package/lib/drama/skills/short-drama-assets/references/voice-direction.md +28 -0
  11. package/lib/drama/skills/short-drama-edit/SKILL.md +146 -0
  12. package/lib/drama/skills/short-drama-edit/agents/openai.yaml +4 -0
  13. package/lib/drama/skills/short-drama-edit/assets/remotion/README.md +60 -0
  14. package/lib/drama/skills/short-drama-edit/assets/remotion/package.json +15 -0
  15. package/lib/drama/skills/short-drama-edit/assets/remotion/remotion.config.ts +6 -0
  16. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Root.tsx +26 -0
  17. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Subtitles.tsx +88 -0
  18. package/lib/drama/skills/short-drama-edit/assets/remotion/src/font.ts +46 -0
  19. package/lib/drama/skills/short-drama-edit/assets/remotion/src/index.ts +4 -0
  20. package/lib/drama/skills/short-drama-edit/assets/remotion/src/schema.ts +32 -0
  21. package/lib/drama/skills/short-drama-edit/assets/remotion/tsconfig.json +13 -0
  22. package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +43 -0
  23. package/lib/drama/skills/short-drama-edit/references/cut-craft.md +38 -0
  24. package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +29 -0
  25. package/lib/drama/skills/short-drama-edit/references/generated-footage.md +54 -0
  26. package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +60 -0
  27. package/lib/drama/skills/short-drama-edit/references/stage-contract.md +51 -0
  28. package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1156 -0
  29. package/lib/drama/skills/short-drama-edit/scripts/selftest.py +520 -0
  30. package/lib/drama/skills/short-drama-image-prompts/SKILL.md +2 -0
  31. package/lib/drama/skills/short-drama-image-prompts/assets/image-prompts.md +5 -0
  32. package/lib/drama/skills/short-drama-image-prompts/assets/lookdev-prompts.md +3 -0
  33. package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +11 -0
  34. package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +2 -2
  35. package/lib/drama/skills/short-drama-novel-analyze/references/chapter-extraction.md +4 -1
  36. package/lib/drama/skills/short-drama-produce/SKILL.md +29 -4
  37. package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +22 -0
  38. package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +13 -12
  39. package/lib/drama/skills/short-drama-produce/references/providers/minimax-speech.md +41 -0
  40. package/lib/drama/skills/short-drama-produce/references/providers/seedance.md +7 -0
  41. package/lib/drama/skills/short-drama-produce/scripts/fixture_adapter.py +18 -1
  42. package/lib/drama/skills/short-drama-produce/scripts/production_tool.py +181 -2
  43. package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +199 -3
  44. package/lib/drama/skills/short-drama-review/SKILL.md +2 -0
  45. package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +7 -0
  46. package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +10 -0
  47. package/lib/drama/skills/short-drama-storyboard/SKILL.md +12 -1
  48. package/lib/drama/skills/short-drama-storyboard/assets/keyframe-prompts.md +4 -0
  49. package/lib/drama/skills/short-drama-storyboard/references/lighting-craft.md +39 -0
  50. package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +78 -2
  51. package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +2 -0
  52. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +12 -5
  53. package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +17 -0
  54. package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +76 -7
  55. package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +5 -2
  56. package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +21 -0
  57. package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +6 -17
  58. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +12 -3
  59. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +61 -5
  60. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +3 -3
  61. package/lib/drama/skills/short-drama-write/SKILL.md +11 -2
  62. package/lib/drama/skills/short-drama-write/references/screenplay-format.md +6 -0
  63. package/lib/drama/skills/short-drama-write/references/stage-contract.md +1 -0
  64. package/lib/index.js +290 -90
  65. package/lib/index.js.map +4 -4
  66. package/lib/novel-to-game/README.md +299 -59
  67. package/lib/novel-to-game/README_ZH.md +299 -56
  68. package/lib/novel-to-game/VERSION +1 -1
  69. package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +3 -1
  70. package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +11 -3
  71. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +397 -8
  72. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +0 -31
  73. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +71 -73
  74. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +78 -89
  75. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +37 -14
  76. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +5 -5
  77. package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +2 -1
  78. package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +11 -16
  79. package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +11 -11
  80. package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +23 -0
  81. package/lib/novel-to-game/manifest.json +77 -87
  82. package/lib/novel-to-game/skills/game-art-direction/SKILL.md +9 -35
  83. package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +27 -106
  84. package/lib/novel-to-game/skills/game-build/SKILL.md +17 -35
  85. package/lib/novel-to-game/skills/game-build/references/blender-asset-workflow.md +24 -0
  86. package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +45 -82
  87. package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +14 -45
  88. package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +30 -65
  89. package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +13 -27
  90. package/lib/novel-to-game/skills/game-concept/SKILL.md +16 -34
  91. package/lib/novel-to-game/skills/game-concept/references/concept-method.md +28 -75
  92. package/lib/novel-to-game/skills/game-qa/SKILL.md +9 -20
  93. package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +10 -6
  94. package/lib/novel-to-game/skills/game-world-design/SKILL.md +19 -32
  95. package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +23 -72
  96. package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +14 -48
  97. package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +36 -103
  98. package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +9 -18
  99. package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +4 -4
  100. package/lib/novel-to-game/skills/novel-to-game/SKILL.md +10 -27
  101. package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +5 -6
  102. package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +6 -11
  103. package/lib/oh-story/manifest.json +419 -254
  104. package/lib/oh-story/roles/chapter-extractor.md +177 -237
  105. package/lib/oh-story/roles/character-designer.md +1 -1
  106. package/lib/oh-story/roles/consistency-checker.md +4 -4
  107. package/lib/oh-story/roles/narrative-writer.md +56 -243
  108. package/lib/oh-story/roles/story-architect.md +6 -6
  109. package/lib/oh-story/roles/story-explorer.md +11 -11
  110. package/lib/oh-story/skills/story/SKILL.md +13 -7
  111. package/lib/oh-story/skills/story/VERSION +1 -1
  112. package/lib/oh-story/skills/story/references/author-memory.md +61 -29
  113. package/lib/oh-story/skills/story/scripts/author_memory_commit.py +934 -170
  114. package/lib/oh-story/skills/story-cover/SKILL.md +10 -1
  115. package/lib/oh-story/skills/story-deslop/SKILL.md +54 -229
  116. package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +29 -56
  117. package/lib/oh-story/skills/story-deslop/references/author-memory.md +61 -29
  118. package/lib/oh-story/skills/story-deslop/references/banned-words.md +3 -1
  119. package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +154 -0
  120. package/lib/oh-story/skills/story-deslop/references/style-resolution.md +27 -0
  121. package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +934 -170
  122. package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +6 -1
  123. package/lib/oh-story/skills/story-deslop/scripts/check-degeneration.js +39 -20
  124. package/lib/oh-story/skills/story-deslop/scripts/normalize-punctuation.js +10 -5
  125. package/lib/oh-story/skills/story-deslop/scripts/style-whitelist.js +39 -0
  126. package/lib/oh-story/skills/story-import/SKILL.md +78 -82
  127. package/lib/oh-story/skills/story-import/references/character-state-reverse.md +1 -1
  128. package/lib/oh-story/skills/story-import/references/format-and-structure.md +2 -2
  129. package/lib/oh-story/skills/story-import/references/state-tracking.md +4 -69
  130. package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +10 -8
  131. package/lib/oh-story/skills/story-import/references/tracking-initialization.md +31 -0
  132. package/lib/oh-story/skills/story-import/references/tracking-transaction.md +9 -34
  133. package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +197 -15
  134. package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +210 -2
  135. package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +3 -0
  136. package/lib/oh-story/skills/story-long-analyze/SKILL.md +91 -258
  137. package/lib/oh-story/skills/story-long-analyze/references/author-facing.md +245 -0
  138. package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +2 -2
  139. package/lib/oh-story/skills/story-long-analyze/references/inspiration-library.md +105 -0
  140. package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +113 -105
  141. package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +169 -125
  142. package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +172 -64
  143. package/lib/oh-story/skills/story-long-analyze/references/semantic-acceptance-fixtures.md +84 -0
  144. package/lib/oh-story/skills/story-long-analyze/references/style-profile-generator.md +42 -132
  145. package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +5 -5
  146. package/lib/oh-story/skills/story-long-analyze/scripts/build_chapter_index.py +466 -0
  147. package/lib/oh-story/skills/story-long-analyze/scripts/inspect_existing_assets.py +702 -0
  148. package/lib/oh-story/skills/story-long-analyze/scripts/inspiration_index.py +864 -0
  149. package/lib/oh-story/skills/story-long-analyze/scripts/manage_analysis_run.py +1106 -0
  150. package/lib/oh-story/skills/story-long-analyze/scripts/render_relation_chart.py +425 -0
  151. package/lib/oh-story/skills/story-long-scan/SKILL.md +4 -1
  152. package/lib/oh-story/skills/story-long-write/SKILL.md +52 -216
  153. package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +29 -56
  154. package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +72 -12
  155. package/lib/oh-story/skills/story-long-write/references/author-memory.md +61 -29
  156. package/lib/oh-story/skills/story-long-write/references/banned-words.md +3 -1
  157. package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +20 -0
  158. package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +26 -0
  159. package/lib/oh-story/skills/story-long-write/references/dialogue-mastery.md +10 -4
  160. package/lib/oh-story/skills/story-long-write/references/emotional-methods.md +2 -2
  161. package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +6 -15
  162. package/lib/oh-story/skills/story-long-write/references/long-format.md +2 -0
  163. package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +2 -0
  164. package/lib/oh-story/skills/story-long-write/references/project-files.md +110 -0
  165. package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +12 -4
  166. package/lib/oh-story/skills/story-long-write/references/reference-index.md +86 -0
  167. package/lib/oh-story/skills/story-long-write/references/style-combat-face.md +1 -1
  168. package/lib/oh-story/skills/story-long-write/references/style-craft.md +9 -29
  169. package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
  170. package/lib/oh-story/skills/story-long-write/references/style-resolution.md +27 -0
  171. package/lib/oh-story/skills/story-long-write/references/tracking-initialization.md +31 -0
  172. package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +9 -34
  173. package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +88 -118
  174. package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +55 -119
  175. package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +15 -11
  176. package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +73 -54
  177. package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +22 -0
  178. package/lib/oh-story/skills/story-long-write/references/writing-craft.md +63 -4
  179. package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +934 -170
  180. package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +531 -0
  181. package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +6 -1
  182. package/lib/oh-story/skills/story-long-write/scripts/check-degeneration.js +39 -20
  183. package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +197 -15
  184. package/lib/oh-story/skills/story-long-write/scripts/check-outline-copy.js +90 -23
  185. package/lib/oh-story/skills/story-long-write/scripts/normalize-punctuation.js +10 -5
  186. package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +285 -0
  187. package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +58 -0
  188. package/lib/oh-story/skills/story-long-write/scripts/style-whitelist.js +39 -0
  189. package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +210 -2
  190. package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +3 -0
  191. package/lib/oh-story/skills/story-review/SKILL.md +80 -87
  192. package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +29 -56
  193. package/lib/oh-story/skills/story-review/references/author-memory.md +61 -29
  194. package/lib/oh-story/skills/story-review/references/banned-words.md +3 -1
  195. package/lib/oh-story/skills/story-review/references/dialogue-mastery.md +10 -4
  196. package/lib/oh-story/skills/story-review/references/review-quality.md +6 -12
  197. package/lib/oh-story/skills/story-review/references/style-resolution.md +27 -0
  198. package/lib/oh-story/skills/story-review/references/tracking-initialization.md +31 -0
  199. package/lib/oh-story/skills/story-review/references/tracking-transaction.md +9 -34
  200. package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +934 -170
  201. package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +6 -1
  202. package/lib/oh-story/skills/story-review/scripts/check-degeneration.js +39 -20
  203. package/lib/oh-story/skills/story-review/scripts/normalize-punctuation.js +10 -5
  204. package/lib/oh-story/skills/story-review/scripts/style-whitelist.js +39 -0
  205. package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +210 -2
  206. package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +3 -0
  207. package/lib/oh-story/skills/story-setup/SKILL.md +58 -43
  208. package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +29 -56
  209. package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +3 -1
  210. package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +154 -0
  211. package/lib/oh-story/skills/story-setup/references/agent-references/dialogue-mastery.md +10 -4
  212. package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +2 -2
  213. package/lib/oh-story/skills/story-setup/references/agent-references/long-emotional-methods.md +2 -2
  214. package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +1 -1
  215. package/lib/oh-story/skills/story-setup/references/agent-references/short-genre-formulas.md +1 -1
  216. package/lib/oh-story/skills/story-setup/references/agent-references/style-combat-face.md +1 -1
  217. package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
  218. package/lib/oh-story/skills/story-setup/references/agent-references/style-resolution.md +27 -0
  219. package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +63 -4
  220. package/lib/oh-story/skills/story-setup/references/diagnostics.md +38 -0
  221. package/lib/oh-story/skills/story-short-analyze/SKILL.md +22 -3
  222. package/lib/oh-story/skills/story-short-analyze/references/analysis-writing-techniques.md +1 -1
  223. package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +5 -1
  224. package/lib/oh-story/skills/story-short-analyze/references/source-story-quality.md +2 -2
  225. package/lib/oh-story/skills/story-short-scan/SKILL.md +5 -1
  226. package/lib/oh-story/skills/story-short-write/SKILL.md +15 -324
  227. package/lib/oh-story/skills/story-short-write/references/author-memory.md +61 -29
  228. package/lib/oh-story/skills/story-short-write/references/banned-words.md +3 -1
  229. package/lib/oh-story/skills/story-short-write/references/benchmark-recall.md +60 -0
  230. package/lib/oh-story/skills/story-short-write/references/dialogue-mastery.md +10 -4
  231. package/lib/oh-story/skills/story-short-write/references/genre-styles//344/270/226/346/203/205/346/211/223/350/204/270.md +2 -2
  232. package/lib/oh-story/skills/story-short-write/references/genre-styles//345/217/214/347/224/267/344/270/273.md +1 -1
  233. 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
  234. package/lib/oh-story/skills/story-short-write/references/genre-styles//345/256/205/346/226/227/345/256/253/346/226/227.md +3 -3
  235. 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
  236. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/202/254/347/226/221.md +5 -5
  237. package/lib/oh-story/skills/story-short-write/references/genre-styles//346/260/221/344/277/227/346/200/252/350/260/210.md +4 -4
  238. package/lib/oh-story/skills/story-short-write/references/genre-styles//347/224/234/345/256/240.md +5 -5
  239. 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
  240. package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +1 -1
  241. package/lib/oh-story/skills/story-short-write/references/genre-writing-techniques.md +1 -1
  242. package/lib/oh-story/skills/story-short-write/references/reference-index.md +55 -0
  243. package/lib/oh-story/skills/story-short-write/references/short-craft.md +12 -14
  244. package/lib/oh-story/skills/story-short-write/references/short-deslop.md +10 -10
  245. package/lib/oh-story/skills/story-short-write/references/short-format.md +3 -1
  246. package/lib/oh-story/skills/story-short-write/references/short-prose-quality.md +2 -2
  247. package/lib/oh-story/skills/story-short-write/references/style-resolution.md +27 -0
  248. package/lib/oh-story/skills/story-short-write/references/workflow-design.md +78 -0
  249. package/lib/oh-story/skills/story-short-write/references/workflow-draft.md +126 -0
  250. package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +32 -0
  251. package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +5 -5
  252. package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +934 -170
  253. package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +6 -1
  254. package/lib/oh-story/skills/story-short-write/scripts/check-degeneration.js +39 -20
  255. package/lib/oh-story/skills/story-short-write/scripts/check-delivery-contract.js +34 -5
  256. package/lib/oh-story/skills/story-short-write/scripts/check-outline-copy.js +90 -23
  257. package/lib/oh-story/skills/story-short-write/scripts/normalize-punctuation.js +10 -5
  258. package/lib/oh-story/skills/story-short-write/scripts/style-whitelist.js +39 -0
  259. package/lib/types/client/drama-production-view.d.ts.map +1 -1
  260. package/lib/types/client/file-activity.d.ts +19 -1
  261. package/lib/types/client/file-activity.d.ts.map +1 -1
  262. package/lib/types/client/index.d.ts.map +1 -1
  263. package/lib/types/client/production-prompts.d.ts +7 -0
  264. package/lib/types/client/production-prompts.d.ts.map +1 -1
  265. package/lib/types/client/production-runtime.d.ts +33 -0
  266. package/lib/types/client/production-runtime.d.ts.map +1 -1
  267. package/lib/types/drama-adapters.d.ts +5 -3
  268. package/lib/types/drama-adapters.d.ts.map +1 -1
  269. package/lib/types/native-hooks.d.ts +52 -0
  270. package/lib/types/native-hooks.d.ts.map +1 -1
  271. package/lib/types/role-identity.d.ts +17 -0
  272. package/lib/types/role-identity.d.ts.map +1 -0
  273. package/lib/types/role-tool.d.ts.map +1 -1
  274. package/lib/types/skill-provider.d.ts.map +1 -1
  275. package/lib/video-recap/CHANGELOG.md +31 -1
  276. package/lib/video-recap/manifest.json +162 -162
  277. package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +4 -9
  278. package/lib/video-recap/skills/video-assemble/scripts/assemble.py +47 -60
  279. package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +77 -115
  280. package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +41 -40
  281. package/lib/video-recap/skills/video-assemble/scripts/audio_automation.py +22 -32
  282. package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +102 -192
  283. package/lib/video-recap/skills/video-assemble/scripts/jianying_builders.py +14 -35
  284. package/lib/video-recap/skills/video-assemble/scripts/jianying_model.py +14 -12
  285. package/lib/video-recap/skills/video-assemble/scripts/jianying_optional.py +8 -9
  286. package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +3 -14
  287. package/lib/video-recap/skills/video-assemble/scripts/jianying_timeline_contract.py +23 -12
  288. package/lib/video-recap/skills/video-assemble/scripts/jianying_tracks.py +1 -4
  289. package/lib/video-recap/skills/video-assemble/scripts/jianying_writer.py +24 -25
  290. package/lib/video-recap/skills/video-assemble/scripts/lib.py +31 -85
  291. package/lib/video-recap/skills/video-assemble/scripts/media.py +100 -144
  292. package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +101 -195
  293. package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +10 -21
  294. package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +90 -169
  295. package/lib/video-recap/skills/video-assemble/scripts/subtitle_core.py +69 -122
  296. package/lib/video-recap/skills/video-assemble/scripts/subtitle_render.py +10 -17
  297. package/lib/video-recap/skills/video-assemble/scripts/timeline.py +33 -48
  298. package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +14 -20
  299. package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +135 -213
  300. package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +49 -57
  301. package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +38 -74
  302. package/lib/video-recap/skills/video-cut/scripts/cut_render.py +46 -95
  303. package/lib/video-recap/skills/video-cut/scripts/lib.py +23 -25
  304. package/lib/video-recap/skills/video-cut/scripts/media_geometry.py +70 -173
  305. package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +28 -53
  306. package/lib/video-recap/skills/video-cut/scripts/sentence_boundaries.py +162 -322
  307. package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +6 -7
  308. package/lib/video-recap/skills/video-recap/scripts/doctor.py +104 -124
  309. package/lib/video-recap/skills/video-recap/scripts/final_qc.py +255 -412
  310. package/lib/video-recap/skills/video-recap/scripts/lib.py +30 -52
  311. package/lib/video-recap/skills/video-recap/scripts/materials.py +97 -120
  312. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_client.py +7 -12
  313. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_evidence.py +46 -97
  314. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_observations.py +18 -36
  315. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_payload.py +29 -47
  316. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_report.py +43 -76
  317. package/lib/video-recap/skills/video-recap/scripts/mimo_qc_runner.py +2 -1
  318. package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +2 -1
  319. package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +2 -2
  320. package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +129 -145
  321. package/lib/video-recap/skills/video-recap/scripts/recap_review.py +27 -20
  322. package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +20 -31
  323. package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +31 -106
  324. package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +43 -113
  325. package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +196 -377
  326. package/lib/video-recap/skills/video-script/scripts/agent_brief.py +1 -1
  327. package/lib/video-recap/skills/video-script/scripts/agent_text.py +63 -172
  328. package/lib/video-recap/skills/video-script/scripts/brief_context.py +80 -135
  329. package/lib/video-recap/skills/video-script/scripts/brief_inputs.py +125 -167
  330. package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +93 -208
  331. package/lib/video-recap/skills/video-script/scripts/deslop_qc.py +20 -46
  332. package/lib/video-recap/skills/video-script/scripts/lib.py +30 -26
  333. package/lib/video-recap/skills/video-script/scripts/narration_lint.py +145 -140
  334. package/lib/video-recap/skills/video-script/scripts/review_response.py +12 -42
  335. package/lib/video-recap/skills/video-script/scripts/review_runner.py +0 -6
  336. package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +39 -100
  337. package/lib/video-recap/skills/video-script/scripts/timeline_fusion.py +33 -124
  338. package/lib/video-recap/skills/video-understanding/scripts/agent_brief.py +1 -1
  339. package/lib/video-recap/skills/video-understanding/scripts/agent_text.py +63 -172
  340. package/lib/video-recap/skills/video-understanding/scripts/asr.py +1 -2
  341. package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +80 -135
  342. package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +125 -167
  343. package/lib/video-recap/skills/video-understanding/scripts/brief_timeline.py +93 -208
  344. package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +1 -1
  345. package/lib/video-recap/skills/video-understanding/scripts/deslop_qc.py +20 -46
  346. package/lib/video-recap/skills/video-understanding/scripts/lib.py +31 -26
  347. package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +145 -140
  348. package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +39 -100
  349. package/lib/video-recap/skills/video-understanding/scripts/timeline_fusion.py +33 -124
  350. package/lib/video-recap/skills/video-understanding/scripts/vlm.py +1 -4
  351. package/lib/video-recap/skills/video-voiceover/scripts/dub.py +7 -13
  352. package/lib/video-recap/skills/video-voiceover/scripts/fish_audio.py +12 -30
  353. package/lib/video-recap/skills/video-voiceover/scripts/lib.py +98 -131
  354. package/lib/video-recap/skills/video-voiceover/scripts/tts_audio.py +17 -28
  355. package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +151 -240
  356. package/package.json +45 -45
  357. package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +0 -59
  358. package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +0 -53
  359. package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +0 -42
  360. package/lib/oh-story/skills/story-long-write/references/state-tracking.md +0 -108
@@ -0,0 +1,425 @@
1
+ #!/usr/bin/env python3
2
+ """Render the character-relationship charts from 角色/角色关系.md.
3
+
4
+ The Markdown file ``人物关系图/人物关系图.md`` (Mermaid graphs plus a plain
5
+ list) is always written: it shows Chinese names in any Markdown viewer. PNG
6
+ pictures are optional (``--png``) and are drawn only when matplotlib and a font
7
+ that really contains every drawn character are available. Emoji and other
8
+ symbols are left out of the pictures; without a font for the remaining text no
9
+ PNG is written, so names are never replaced by pinyin, initials or empty boxes.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import argparse
15
+ import json
16
+ import math
17
+ import os
18
+ import re
19
+ import shutil
20
+ import subprocess
21
+ import sys
22
+ import tempfile
23
+ import unicodedata
24
+ from pathlib import Path
25
+ from typing import Dict, List, Optional, Tuple
26
+
27
+
28
+ RELATION_FILE = Path("角色") / "角色关系.md"
29
+ CHART_DIR = Path("人物关系图")
30
+ CHART_MD = CHART_DIR / "人物关系图.md"
31
+ CORE_PNG = CHART_DIR / "核心人物关系.png"
32
+ EVOLUTION_PNG = CHART_DIR / "关键关系演变.png"
33
+ # 「甲 → 乙」, 「甲 ↔ 乙」 (both ways) and chains such as 「甲 → 乙 → 丙」.
34
+ ARROW_RE = re.compile(r"\s*(↔|<->|⇔|<=>|→|->|⇒|=>|—>|->)\s*")
35
+ BOTH_WAYS = {"↔", "<->", "⇔", "<=>"}
36
+ # Common Simplified-Chinese capable fonts on macOS, Windows and Linux.
37
+ CJK_FONT_NAMES = (
38
+ "PingFang SC", "Hiragino Sans GB", "Heiti SC", "STHeiti", "Songti SC", "STSong",
39
+ "Microsoft YaHei", "SimHei", "SimSun", "DengXian",
40
+ "Noto Sans CJK SC", "Noto Sans SC", "Noto Serif CJK SC", "Source Han Sans SC",
41
+ "Source Han Sans CN", "WenQuanYi Micro Hei", "WenQuanYi Zen Hei", "Sarasa Gothic SC",
42
+ "Arial Unicode MS",
43
+ )
44
+ # Fonts that answer every character with a placeholder box; they never count.
45
+ FALLBACK_FONT_RE = re.compile(r"last\s*resort|adobe\s*blank", re.IGNORECASE)
46
+ BASE_TEXT = "中文人物关系未命名→…"
47
+ MD_VIEW = "关系图已经写在 人物关系图/人物关系图.md 里,用能预览 Markdown 的编辑器打开就能看到中文。"
48
+ NO_FONT_MESSAGE = (
49
+ "这台电脑上没有找到能显示中文的字体,所以没有生成关系图图片(硬画会把人名变成拼音或方块)。"
50
+ + MD_VIEW + "装上任意一款中文字体(如思源黑体、文泉驿)后再生成一次,就能得到图片版。"
51
+ )
52
+ MISSING_GLYPH_MESSAGE = (
53
+ "这台电脑上的中文字体显示不了关系图里的「%s」,所以没有生成图片(硬画会变成方块)。"
54
+ + MD_VIEW + "装上字更全的中文字体(如思源黑体)后再生成一次,就能得到图片版。"
55
+ )
56
+ PNG_FAILED_MESSAGE = "关系图图片这次没画成。" + MD_VIEW
57
+ NO_MATPLOTLIB_MESSAGE = "当前环境不能画图片," + MD_VIEW
58
+
59
+
60
+ class ChartError(ValueError):
61
+ def __init__(self, code: str, author_message: str) -> None:
62
+ super().__init__(code)
63
+ self.code = code
64
+ self.author_message = author_message
65
+
66
+
67
+ def atomic_write(path: Path, data: bytes) -> None:
68
+ path.parent.mkdir(parents=True, exist_ok=True)
69
+ fd, temporary = tempfile.mkstemp(prefix=".%s." % path.name, suffix=".tmp", dir=str(path.parent))
70
+ try:
71
+ with os.fdopen(fd, "wb") as handle:
72
+ handle.write(data)
73
+ os.replace(temporary, str(path))
74
+ except BaseException:
75
+ try:
76
+ os.unlink(temporary)
77
+ except OSError:
78
+ pass
79
+ raise
80
+
81
+
82
+ def cells(line: str) -> List[str]:
83
+ """Table cells; an escaped 「\\|」 stays inside its cell."""
84
+ stripped = line.strip()
85
+ stripped = stripped[1:] if stripped.startswith("|") else stripped
86
+ stripped = stripped[:-1] if stripped.endswith("|") and not stripped.endswith("\\|") else stripped
87
+ return [cell.strip().replace("\\|", "|") for cell in re.split(r"(?<!\\)\|", stripped)]
88
+
89
+
90
+ def pick(row: Dict[str, str], *names: str) -> str:
91
+ for name in names:
92
+ for key, value in row.items():
93
+ if name in key and value and value not in {"-", "—", "无"}:
94
+ return value
95
+ return ""
96
+
97
+
98
+ SOURCE_KEYS = ("主体", "角色A", "人物A", "A", "甲")
99
+ TARGET_KEYS = ("客体", "角色B", "人物B", "B", "乙")
100
+
101
+
102
+ def exact(row: Dict[str, str], keys: Tuple[str, ...]) -> str:
103
+ for key in keys:
104
+ value = row.get(key, "")
105
+ if value and value not in {"-", "—"}:
106
+ return value
107
+ return ""
108
+
109
+
110
+ def parse_relations(text: str) -> Tuple[List[Dict[str, str]], int]:
111
+ """Relations of a relationship table, plus the number of rows left out.
112
+
113
+ Current files use one 「主体 → 客体」 column; older ones use separate
114
+ 主体/客体 or A/B columns. Any table with such a pair of columns counts.
115
+ """
116
+ relations = [] # type: List[Dict[str, str]]
117
+ skipped = 0
118
+ header = None # type: Optional[List[str]]
119
+ for line in text.splitlines():
120
+ stripped = line.strip()
121
+ if not stripped.startswith("|"):
122
+ header = None
123
+ continue
124
+ row_cells = cells(stripped)
125
+ if header is None:
126
+ names = set(row_cells)
127
+ if (any("主体" in cell and "客体" in cell for cell in row_cells) or "关系方向" in names
128
+ or (names & set(SOURCE_KEYS) and names & set(TARGET_KEYS))):
129
+ header = row_cells
130
+ continue
131
+ if not "".join(row_cells).strip("-: "):
132
+ continue
133
+ row = dict(zip(header, row_cells))
134
+ pair = next((value for key, value in row.items() if ("主体" in key and "客体" in key) or key == "关系方向"), "")
135
+ tokens = [part.strip() for part in ARROW_RE.split(pair)] if pair else []
136
+ names, arrows = tokens[0::2], tokens[1::2]
137
+ if len(names) < 2:
138
+ names = [exact(row, SOURCE_KEYS), exact(row, TARGET_KEYS)]
139
+ arrows = ["→"]
140
+ if len(names) < 2 or not all(names):
141
+ skipped += 1
142
+ continue
143
+ fields = {
144
+ "id": pick(row, "关系ID", "ID"),
145
+ "label": pick(row, "关系动作", "类型", "表面关系"),
146
+ "state": pick(row, "变化后状态", "真实关系", "表面关系"),
147
+ "trigger": pick(row, "触发事件", "触发"),
148
+ }
149
+ for index, arrow in enumerate(arrows):
150
+ ends = [(names[index], names[index + 1])]
151
+ if arrow in BOTH_WAYS:
152
+ ends.append((names[index + 1], names[index]))
153
+ for source, target in ends:
154
+ relations.append(dict(fields, source=source, target=target))
155
+ return relations, skipped
156
+
157
+
158
+ def mermaid_text(value: str, placeholder: str) -> str:
159
+ value = re.sub(r"[`*]|%{2,}", "", value)
160
+ value = value.replace('"', "'").replace("|", "/").replace("\n", " ").strip()
161
+ return value or placeholder
162
+
163
+
164
+ def latest_by_pair(relations: List[Dict[str, str]]) -> List[Dict[str, str]]:
165
+ latest = {} # type: Dict[Tuple[str, str], Dict[str, str]]
166
+ for relation in relations:
167
+ latest[(relation["source"], relation["target"])] = relation
168
+ return list(latest.values())
169
+
170
+
171
+ def evolutions(relations: List[Dict[str, str]]) -> List[Tuple[Tuple[str, str], List[Dict[str, str]]]]:
172
+ history = {} # type: Dict[Tuple[str, str], List[Dict[str, str]]]
173
+ for relation in relations:
174
+ history.setdefault((relation["source"], relation["target"]), []).append(relation)
175
+ return [(pair, rows) for pair, rows in history.items() if len(rows) > 1]
176
+
177
+
178
+ def render_markdown(book: str, relations: List[Dict[str, str]], png_note: str) -> str:
179
+ names = [] # type: List[str]
180
+ for relation in relations:
181
+ for name in (relation["source"], relation["target"]):
182
+ if name not in names:
183
+ names.append(name)
184
+ node = {name: "p%s" % index for index, name in enumerate(names, start=1)}
185
+ lines = ["# 人物关系图:%s" % book, "",
186
+ "> 由 角色/角色关系.md 生成;关系事实以该文件为准。%s" % png_note, "",
187
+ "## 核心人物关系", "", "```mermaid", "graph LR"]
188
+ lines.extend(' %s["%s"]' % (node[name], mermaid_text(name, "未命名")) for name in names)
189
+ for relation in latest_by_pair(relations):
190
+ label = relation["state"] or relation["label"] or "关系"
191
+ lines.append(' %s -->|"%s"| %s' % (node[relation["source"]], mermaid_text(label, "关系"),
192
+ node[relation["target"]]))
193
+ lines.extend(["```", ""])
194
+ for relation in latest_by_pair(relations):
195
+ label = relation["state"] or relation["label"] or "关系"
196
+ lines.append("- %s → %s:%s" % (relation["source"], relation["target"], label))
197
+ lines.extend(["", "## 关键关系演变", ""])
198
+ changes = evolutions(relations)
199
+ if not changes:
200
+ lines.append("本书没有记录到同一对人物前后变化的关系。")
201
+ for (source, target), rows in changes:
202
+ steps = []
203
+ for row in rows:
204
+ state = row["state"] or row["label"] or "关系"
205
+ steps.append("%s(%s)" % (state, row["trigger"]) if row["trigger"] else state)
206
+ lines.append("- %s → %s:%s" % (source, target, " → ".join(steps)))
207
+ return "\n".join(lines) + "\n"
208
+
209
+
210
+ def png_text(value: str, placeholder: str = "") -> str:
211
+ """Text as drawn in the pictures: emoji and other symbols are left out."""
212
+ kept = "".join(char for char in value
213
+ if char in BASE_TEXT or not unicodedata.category(char).startswith(("S", "C", "M")))
214
+ return re.sub(r"\s+", " ", kept).strip() or placeholder
215
+
216
+
217
+ def font_face(path: str): # noqa: ANN201 - matplotlib type
218
+ from matplotlib.ft2font import FT2Font
219
+ return FT2Font(path)
220
+
221
+
222
+ def missing_glyphs(path: str, text: str) -> Optional[List[str]]:
223
+ """Characters of text the font (face 0) lacks; None when the font is unusable."""
224
+ if FALLBACK_FONT_RE.search(os.path.basename(path)):
225
+ return None
226
+ try:
227
+ font = font_face(path)
228
+ if FALLBACK_FONT_RE.search(str(getattr(font, "family_name", ""))):
229
+ return None
230
+ return sorted({char for char in text if not char.isspace() and not font.get_char_index(ord(char))})
231
+ except Exception: # noqa: BLE001 - any font loading failure means "not usable"
232
+ return None
233
+
234
+
235
+ def font_candidates() -> List[str]:
236
+ """Installed fonts worth trying, well-known Chinese fonts first."""
237
+ try:
238
+ from matplotlib import font_manager
239
+ except ImportError:
240
+ return []
241
+ by_name = {} # type: Dict[str, List[str]]
242
+ for entry in font_manager.fontManager.ttflist:
243
+ paths = by_name.setdefault(entry.name, [])
244
+ if entry.fname not in paths:
245
+ paths.append(entry.fname)
246
+ candidates = [path for name in CJK_FONT_NAMES for path in by_name.get(name, [])]
247
+ if shutil.which("fc-list"):
248
+ try:
249
+ listed = subprocess.run(["fc-list", ":lang=zh", "file"], capture_output=True, text=True,
250
+ timeout=10, check=False).stdout
251
+ except (OSError, subprocess.SubprocessError):
252
+ listed = ""
253
+ candidates.extend(line.split(":", 1)[0].strip() for line in listed.splitlines())
254
+ return candidates
255
+
256
+
257
+ def find_cjk_font(text: str) -> Tuple[Optional[str], List[str]]:
258
+ """(font that draws every character of text, []) or (None, characters no Chinese font draws)."""
259
+ text = text + BASE_TEXT
260
+ override = os.environ.get("STORY_ANALYZE_CHART_FONT")
261
+ if override == "none":
262
+ return None, []
263
+ fewest = None # type: Optional[List[str]]
264
+ seen = set()
265
+ for path in [override] if override else font_candidates():
266
+ if not path or path in seen:
267
+ continue
268
+ seen.add(path)
269
+ missing = missing_glyphs(path, text)
270
+ if missing is None or any(char in BASE_TEXT for char in missing):
271
+ continue
272
+ if not missing:
273
+ return path, []
274
+ if fewest is None or len(missing) < len(fewest):
275
+ fewest = missing
276
+ return None, fewest or []
277
+
278
+
279
+ def matplotlib_available() -> bool:
280
+ try:
281
+ import matplotlib # noqa: F401
282
+ except ImportError:
283
+ return False
284
+ return True
285
+
286
+
287
+ def short_label(value: str, limit: int = 10) -> str:
288
+ value = png_text(re.sub(r"[`*]|\[[^\]]*\]", "", value), "关系")
289
+ return value if len(value) <= limit else value[:limit - 1] + "…"
290
+
291
+
292
+ def draw_png(relations: List[Dict[str, str]], font_path: str, root: Path) -> List[str]:
293
+ import matplotlib
294
+ matplotlib.use("Agg")
295
+ from matplotlib import font_manager
296
+ import matplotlib.pyplot as plt
297
+
298
+ font = font_manager.FontProperties(fname=font_path)
299
+ (root / CHART_DIR).mkdir(parents=True, exist_ok=True)
300
+ written = []
301
+ core = latest_by_pair(relations)
302
+ degree = {} # type: Dict[str, int]
303
+ for relation in core:
304
+ for name in (relation["source"], relation["target"]):
305
+ degree[name] = degree.get(name, 0) + 1
306
+ # The most connected character (usually the protagonist) sits in the middle.
307
+ names = sorted(degree, key=lambda name: -degree[name])
308
+ ring = names[1:] if len(names) > 2 else names
309
+ positions = {name: (math.cos(2 * math.pi * index / len(ring)), math.sin(2 * math.pi * index / len(ring)))
310
+ for index, name in enumerate(ring)}
311
+ if len(names) > 2:
312
+ positions[names[0]] = (0.0, 0.0)
313
+ size = min(14, 8 + 0.3 * len(names))
314
+ figure, axis = plt.subplots(figsize=(size, size))
315
+ axis.set_axis_off()
316
+ bend = 0.18 # curve both directions of a pair apart so their labels do not overlap
317
+ for relation in core:
318
+ (x1, y1), (x2, y2) = positions[relation["source"]], positions[relation["target"]]
319
+ axis.annotate("", xy=(x2, y2), xytext=(x1, y1),
320
+ arrowprops={"arrowstyle": "->", "color": "#666666", "shrinkA": 22, "shrinkB": 22,
321
+ "connectionstyle": "arc3,rad=%s" % bend})
322
+ label_x = (x1 + x2) / 2 + bend * (y2 - y1) / 2
323
+ label_y = (y1 + y2) / 2 - bend * (x2 - x1) / 2
324
+ axis.text(label_x, label_y, short_label(relation["state"] or relation["label"] or "关系"),
325
+ fontproperties=font, fontsize=9, ha="center", va="center", color="#333333",
326
+ bbox={"boxstyle": "round", "fc": "white", "ec": "none", "alpha": 0.8})
327
+ for name, (x, y) in positions.items():
328
+ axis.text(x, y, png_text(name, "未命名"), fontproperties=font, fontsize=13, ha="center", va="center",
329
+ bbox={"boxstyle": "round,pad=0.5", "fc": "#e8eef7", "ec": "#4a6fa5"})
330
+ axis.set_aspect("equal")
331
+ axis.set_xlim(-1.4, 1.4)
332
+ axis.set_ylim(-1.4, 1.4)
333
+ figure.savefig(str(root / CORE_PNG), dpi=150, bbox_inches="tight")
334
+ plt.close(figure)
335
+ written.append(CORE_PNG.as_posix())
336
+
337
+ changes = evolutions(relations)
338
+ if changes:
339
+ figure, axis = plt.subplots(figsize=(10, 1.2 + 0.8 * len(changes)))
340
+ axis.set_axis_off()
341
+ for row_index, ((source, target), rows) in enumerate(changes):
342
+ y = len(changes) - row_index
343
+ pair = "%s → %s" % (png_text(source, "未命名"), png_text(target, "未命名"))
344
+ axis.text(0, y, pair, fontproperties=font, fontsize=11, ha="left", va="center")
345
+ for step, row in enumerate(rows):
346
+ x = 3 + step * 2.2
347
+ if step:
348
+ axis.annotate("", xy=(x - 0.6, y), xytext=(x - 1.6, y),
349
+ arrowprops={"arrowstyle": "->", "color": "#a58a4a"})
350
+ axis.text(x, y, png_text(row["state"] or row["label"], "关系"), fontproperties=font,
351
+ fontsize=10, ha="center", va="center",
352
+ bbox={"boxstyle": "round", "fc": "#f3efe6", "ec": "#a58a4a"})
353
+ if row["trigger"]:
354
+ axis.text(x, y - 0.32, png_text(row["trigger"]), fontproperties=font, fontsize=8,
355
+ ha="center", va="center", color="#666666")
356
+ axis.set_xlim(-0.2, 3 + 2.2 * max(len(rows) for _, rows in changes))
357
+ axis.set_ylim(0.3, len(changes) + 0.7)
358
+ figure.savefig(str(root / EVOLUTION_PNG), dpi=150, bbox_inches="tight")
359
+ plt.close(figure)
360
+ written.append(EVOLUTION_PNG.as_posix())
361
+ return written
362
+
363
+
364
+ def render(root: Path, want_png: bool) -> Dict[str, object]:
365
+ source = root / RELATION_FILE
366
+ try:
367
+ text = source.read_text(encoding="utf-8-sig")
368
+ except OSError:
369
+ raise ChartError("relation_file_missing", "还没有 角色/角色关系.md,先整理人物关系,再生成关系图。")
370
+ relations, skipped = parse_relations(text)
371
+ if not relations:
372
+ raise ChartError("relation_table_empty", "角色/角色关系.md 里没有认得出的关系表(需要「主体 → 客体」一列),关系图没有生成。")
373
+ # The Markdown chart is the main result: write it before trying any picture.
374
+ atomic_write(root / CHART_MD, render_markdown(root.name, relations, "").encode("utf-8"))
375
+ png_written = [] # type: List[str]
376
+ messages = [] # type: List[str]
377
+ if skipped:
378
+ messages.append("角色/角色关系.md 里有 %s 行没认出是谁和谁的关系,没画进关系图。" % skipped)
379
+ if want_png:
380
+ drawn = "".join(png_text(value) for relation in relations
381
+ for value in (relation["source"], relation["target"], relation["state"],
382
+ relation["label"], relation["trigger"]))
383
+ font_path, missing = find_cjk_font(drawn)
384
+ if not matplotlib_available():
385
+ messages.append(NO_MATPLOTLIB_MESSAGE)
386
+ elif not font_path:
387
+ messages.append(MISSING_GLYPH_MESSAGE % "".join(missing[:5]) if missing else NO_FONT_MESSAGE)
388
+ else:
389
+ try:
390
+ png_written = draw_png(relations, font_path, root)
391
+ except Exception: # noqa: BLE001 - the Markdown chart is already written
392
+ messages.append(PNG_FAILED_MESSAGE)
393
+ if png_written:
394
+ atomic_write(root / CHART_MD,
395
+ render_markdown(root.name, relations, "图片版见同目录的 PNG。").encode("utf-8"))
396
+ return {
397
+ "ok": True, "markdown": CHART_MD.as_posix(), "png": png_written,
398
+ "relations": len(relations), "skipped_rows": skipped,
399
+ "author_message": "".join(messages) or None,
400
+ }
401
+
402
+
403
+ def main() -> int:
404
+ for stream in (sys.stdout, sys.stderr):
405
+ if hasattr(stream, "reconfigure"):
406
+ stream.reconfigure(encoding="utf-8")
407
+ parser = argparse.ArgumentParser(description=__doc__)
408
+ parser.add_argument("--root", required=True, type=Path, help="拆文库/{书名} 目录")
409
+ parser.add_argument("--png", action="store_true", help="also draw PNG pictures when a Chinese font exists")
410
+ args = parser.parse_args()
411
+ try:
412
+ if not args.root.is_dir():
413
+ raise ChartError("root_not_found", "没找到这本书的拆文目录,确认书名或路径。")
414
+ payload = render(args.root.resolve(), args.png)
415
+ except ChartError as exc:
416
+ payload = {"ok": False, "error": exc.code, "author_message": exc.author_message}
417
+ except (OSError, UnicodeError) as exc:
418
+ payload = {"ok": False, "error": "io_error", "detail": str(exc),
419
+ "author_message": "关系图文件写入失败,检查拆文目录是否可写。"}
420
+ print(json.dumps(payload, ensure_ascii=False, indent=2))
421
+ return 0 if payload.get("ok") else 2
422
+
423
+
424
+ if __name__ == "__main__":
425
+ raise SystemExit(main())
@@ -234,7 +234,10 @@ node scripts/jjwxc-rank-scraper.js --type 12 --list-only # 只
234
234
 
235
235
  ### Phase 4:输出扫榜报告
236
236
 
237
- ```
237
+ 报告写给作者:讲市场结论和能写的方向。脚本名、命令和采集状态码不进报告;某个榜没采到,就说一句「XX 榜这次没拿到(原因),结论不含它」。
238
+
239
+ <!-- author-report -->
240
+ ```md
238
241
  # 长篇网文扫榜报告:{平台名称}
239
242
 
240
243
  ## 市场概况