@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,285 @@
1
+ #!/usr/bin/env python3
2
+ """卷纲取段器 —— 让「不整读卷纲」成立。
3
+
4
+ 按声明作用域选取卷级、单元级和批次材料;未声明的旧段保守纳入并告警。
5
+
6
+ 段头约定(每个 ## / ### / #### 标题的**下一行非空行**):
7
+
8
+ > 作用域:卷级常任
9
+ > 作用域:单元级 D1-03
10
+ > 作用域:批次底稿 D1-03|状态:在用
11
+ > 作用域:批次底稿 D1-02|状态:已退役
12
+
13
+ 退役行(行内覆盖):以 `⊘` 起头的列表行 / 表格行,默认过滤。
14
+
15
+ 用法:
16
+ outline_view.py --toc 卷纲.md
17
+ outline_view.py --unit D1-03 卷纲.md
18
+ outline_view.py --contract 卷纲.md
19
+ outline_view.py --check 卷纲.md
20
+ 任意子命令加 --history 连退役行一起输出
21
+ """
22
+ import argparse
23
+ import io
24
+ import re
25
+ import sys
26
+
27
+ HEADING = re.compile(r"^(#{2,4})\s+(.*\S)\s*$")
28
+ SCOPE = re.compile(r"^>\s*作用域[::]\s*(.+?)\s*$")
29
+ RETIRED_LINE = re.compile(r"^\s*(?:[-*]\s*)?⊘|^\s*\|\s*⊘")
30
+ # 单元块内不该出现的全卷字样——出现即说明这条常任规则放错了地方
31
+ LEAK = re.compile(r"全卷常任|全程生效|全卷生效|本单元起常任|自 ?[LD]\d+-\d+ ?起生效")
32
+ # 指向卷级的指针行不算泄漏:单元块可以引用常任规则,不可以复述它
33
+ POINTER = re.compile(r"全卷常任裁定|已提为卷级|不复读|规则本身见|见「全卷")
34
+ UNIT_IN_TITLE = re.compile(r"(?<![\w-])([LD]\d+-\d+|U\d+)(?![\w-])")
35
+ # 批次底稿里出现跨章祈使=这条约束在写作期还有效,而写作档不给底稿 → 必须下沉
36
+ IMPERATIVE = re.compile(r"全程不许|一律不许|往后任何章|此后不再|往后每|从此不|终局前不")
37
+ CH_RANGE = re.compile(r"章节范围[::]\s*第\s*(\d+)\s*[-–—~至]\s*(\d+)\s*章")
38
+ ARC_ROW = re.compile(r"^\|\s*(\d+)\s*\|")
39
+
40
+
41
+ def read(path):
42
+ with io.open(path, encoding="utf-8-sig") as handle:
43
+ return handle.read()
44
+
45
+
46
+ class Section:
47
+ __slots__ = ("level", "title", "start", "end", "scope", "kind",
48
+ "unit", "status", "lines")
49
+
50
+ def __init__(self, level, title, start):
51
+ self.level = level
52
+ self.title = title
53
+ self.start = start # 标题行号(0 基)
54
+ self.end = None
55
+ self.scope = None # 原始作用域串,None = 未声明
56
+ self.kind = None # 卷级常任 / 单元级 / 批次底稿
57
+ self.unit = None
58
+ self.status = "在用"
59
+ self.lines = []
60
+
61
+
62
+ def parse(text):
63
+ lines = text.split("\n")
64
+ sections = []
65
+ fence = None
66
+ for index, line in enumerate(lines):
67
+ marker = re.match(r"^\s{0,3}(`{3,}|~{3,})", line)
68
+ if marker:
69
+ value = marker.group(1)
70
+ if fence is None:
71
+ fence = value
72
+ elif value[0] == fence[0] and len(value) >= len(fence):
73
+ fence = None
74
+ continue
75
+ if fence:
76
+ continue
77
+ match = HEADING.match(line)
78
+ if match:
79
+ if sections:
80
+ sections[-1].end = index
81
+ sections.append(Section(len(match.group(1)), match.group(2), index))
82
+ if sections:
83
+ sections[-1].end = len(lines)
84
+ for section in sections:
85
+ section.lines = lines[section.start:section.end]
86
+ for candidate in section.lines[1:]:
87
+ if not candidate.strip():
88
+ continue
89
+ scope_match = SCOPE.match(candidate)
90
+ if scope_match:
91
+ section.scope = scope_match.group(1)
92
+ break
93
+ if section.scope:
94
+ head = section.scope.split("|")[0].strip()
95
+ if head == "卷级常任":
96
+ section.kind = "卷级常任"
97
+ elif re.fullmatch(r"单元级(?:\s+[A-Za-z][\w-]*)?", head):
98
+ section.kind = "单元级"
99
+ elif re.fullmatch(r"批次底稿(?:\s+[A-Za-z][\w-]*)?", head):
100
+ section.kind = "批次底稿"
101
+ found = re.search(r"^(?:单元级|批次底稿)\s+([A-Za-z][\w-]*)$", head)
102
+ if found:
103
+ section.unit = found.group(1)
104
+ if "已退役" in section.scope:
105
+ section.status = "已退役"
106
+ if section.unit is None:
107
+ found = re.search(r"剧情单元\s+([A-Za-z][\w-]*)", section.title) or UNIT_IN_TITLE.search(section.title)
108
+ if found:
109
+ section.unit = found.group(1)
110
+ if section.unit is None and section.scope is None:
111
+ for line in section.lines[1:]:
112
+ field = re.match(r"^\s*[-*+]\s*单元ID\s*[::]\s*([A-Za-z][\w-]*)\s*$", line.replace("**", ""))
113
+ if field:
114
+ section.unit = field.group(1)
115
+ break
116
+ return lines, sections
117
+
118
+
119
+ def strip_retired(block_lines, keep_history):
120
+ if keep_history:
121
+ return block_lines
122
+ return [line for line in block_lines if not RETIRED_LINE.match(line)]
123
+
124
+
125
+ def unit_chapter_range(sections, unit):
126
+ for section in sections:
127
+ if section.unit == unit and section.kind == "单元级" and "剧情单元" in section.title:
128
+ for line in section.lines:
129
+ found = CH_RANGE.search(line)
130
+ if found:
131
+ return int(found.group(1)), int(found.group(2))
132
+ return None
133
+
134
+
135
+ def slice_arc(section, span):
136
+ """情绪弧线这类逐章表:只留本单元章区间的行,表头与说明照留。"""
137
+ if span is None:
138
+ return section.lines
139
+ low, high = span
140
+ kept = []
141
+ for line in section.lines:
142
+ row = ARC_ROW.match(line)
143
+ if row and not (low <= int(row.group(1)) <= high):
144
+ continue
145
+ kept.append(line)
146
+ return kept
147
+
148
+
149
+ def emit(sections, chosen, span, keep_history, note, preamble):
150
+ out = ["\n".join(strip_retired(preamble, keep_history)).rstrip()] if preamble else []
151
+ for section in sections:
152
+ if section not in chosen:
153
+ continue
154
+ body = section.lines
155
+ if "情绪弧线" in section.title or "排班" in section.title:
156
+ body = slice_arc(section, span)
157
+ body = strip_retired(body, keep_history)
158
+ out.append("\n".join(body).rstrip())
159
+ text = "\n\n".join(out)
160
+ sys.stdout.write(text + "\n")
161
+ if note:
162
+ sys.stderr.write(note + "\n")
163
+
164
+
165
+ def cmd_toc(sections):
166
+ print("%-3s %-6s %-9s %-8s %-46s %s" %
167
+ ("#", "行", "作用域", "单元", "段", "字符"))
168
+ for number, section in enumerate(sections, 1):
169
+ size = sum(len(line) + 1 for line in section.lines)
170
+ print("%-3d %-6d %-9s %-8s %-46s %d" %
171
+ (number, section.start + 1, section.kind or "**未声明**",
172
+ (section.unit or "-") + ("(退)" if section.status == "已退役" else ""),
173
+ section.title[:44], size))
174
+
175
+
176
+ def cmd_check(sections, strict=False):
177
+ problems = []
178
+ warnings = []
179
+ for section in sections:
180
+ if section.kind == "批次底稿":
181
+ for offset, line in enumerate(section.lines):
182
+ if IMPERATIVE.search(line):
183
+ warnings.append(
184
+ ("W1", "第%d行" % (section.start + offset + 1),
185
+ "批次底稿里有跨章祈使,写作档不给底稿——确认已下沉到单元级段或细纲:%s"
186
+ % line.strip()[:52]))
187
+ for section in sections:
188
+ where = "第%d行「%s」" % (section.start + 1, section.title[:36])
189
+ if section.scope is None:
190
+ (problems if strict else warnings).append(("E1" if strict else "W2", where, "缺「> 作用域:」声明行;旧段保守纳入"))
191
+ continue
192
+ if section.kind is None:
193
+ problems.append(("E2", where, "作用域值不认识:%s" % section.scope))
194
+ if section.kind in ("单元级", "批次底稿") and section.unit is None:
195
+ problems.append(("E3", where, "单元级/批次底稿段未标单元ID"))
196
+ if section.kind in ("单元级", "批次底稿"):
197
+ for offset, line in enumerate(section.lines):
198
+ if LEAK.search(line) and not POINTER.search(line):
199
+ problems.append(
200
+ ("E4", "第%d行" % (section.start + offset + 1),
201
+ "单元块内出现全卷字样,常任规则放错了位置:%s"
202
+ % line.strip()[:56]))
203
+ for code, where, message in problems + warnings:
204
+ print("%s %s %s" % (code, where, message))
205
+ print("---")
206
+ print("段 %d / 问题 %d / 告警 %d(告警不阻断,逐条人工判断)"
207
+ % (len(sections), len(problems), len(warnings)))
208
+ return 1 if problems else 0
209
+
210
+
211
+ def main():
212
+ parser = argparse.ArgumentParser(add_help=True)
213
+ parser.add_argument("path")
214
+ parser.add_argument("--toc", action="store_true")
215
+ parser.add_argument("--unit")
216
+ parser.add_argument("--contract", action="store_true")
217
+ parser.add_argument("--check", action="store_true")
218
+ parser.add_argument("--history", action="store_true")
219
+ parser.add_argument("--strict", action="store_true", help="新建卷纲校验:缺作用域也视为错误")
220
+ # 排纲底稿现在放在卷纲外的 大纲/排纲底稿_{单元ID}.md,默认输出就是写作要的闭包。
221
+ # --stage outline 只为老卷纲保留:它们的供给自查等底稿还写在卷纲里(作用域:批次底稿)。
222
+ parser.add_argument("--stage", choices=["outline", "write"], default="write",
223
+ help="write(默认)=卷级常任+单元级;"
224
+ "outline=另带老卷纲里仍在用的批次底稿段")
225
+ args = parser.parse_args()
226
+
227
+ if hasattr(sys.stdout, "reconfigure"):
228
+ sys.stdout.reconfigure(encoding="utf-8")
229
+ sys.stderr.reconfigure(encoding="utf-8")
230
+
231
+ try:
232
+ lines, sections = parse(read(args.path))
233
+ except (OSError, UnicodeError) as error:
234
+ sys.stderr.write(f"无法读取卷纲:{error}\n")
235
+ return 2
236
+ if not sections:
237
+ sys.stderr.write("卷纲里没有 ## 段,路径对不对?\n")
238
+ return 2
239
+
240
+ if args.toc:
241
+ cmd_toc(sections)
242
+ return 0
243
+ if args.check:
244
+ return cmd_check(sections, args.strict)
245
+
246
+ invalid = [s for s in sections if s.scope and
247
+ (s.kind is None or (s.kind != "卷级常任" and s.unit is None))]
248
+ if invalid:
249
+ sys.stderr.write("作用域声明无效,先运行 --check 修正\n")
250
+ return 1
251
+ active = [s for s in sections if args.history or s.status != "已退役"]
252
+ permanent = [s for s in active if s.kind == "卷级常任"]
253
+ undeclared = [s for s in active if s.scope is None]
254
+ note = ("%d 段未声明作用域,已保守纳入;用 --check 查看" % len(undeclared)) if undeclared else None
255
+ preamble = lines[:sections[0].start]
256
+ if args.contract:
257
+ emit(sections, set(permanent + undeclared), None, args.history, note, preamble)
258
+ return 0
259
+
260
+ if args.unit:
261
+ unit = args.unit
262
+ owned = [s for s in active if s.unit == unit]
263
+ if not owned:
264
+ sys.stderr.write(
265
+ "找不到单元 %s —— 不静默降级。核对单元ID,或先补卷纲。\n" % unit)
266
+ return 1
267
+ if args.stage == "write":
268
+ # 写作档不给批次底稿:它是排纲期的工作底稿,其中有写作期约束力的条目
269
+ # 必须已经下沉到单元级段或细纲(见 artifact-protocols.md 段位契约纪律④)
270
+ live = [s for s in owned if s.kind == "单元级"]
271
+ else:
272
+ live = [s for s in owned
273
+ if s.kind == "单元级"
274
+ or s.kind == "批次底稿"]
275
+ chosen = set(permanent + live + undeclared)
276
+ span = unit_chapter_range(active, unit)
277
+ emit(sections, chosen, span, args.history, note, preamble)
278
+ return 0
279
+
280
+ parser.print_help()
281
+ return 2
282
+
283
+
284
+ if __name__ == "__main__":
285
+ sys.exit(main())
@@ -30,6 +30,9 @@ for _name in dir(core):
30
30
 
31
31
  CHAPTER_CHECK_SCHEMA = "story-chapter-check/v1"
32
32
  CHAPTER_ERROR_SCHEMA = "story-chapter-error/v1"
33
+ # 书内工作目录:分组 segment、writer prompt 留档、逐章事务 JSON 的唯一落点。
34
+ # 与 build_writer_prompt.py 同一口径;提交成功后整目录删除,失败时原样保留供重跑。
35
+ WORK_ROOT = (".story", "work")
33
36
 
34
37
 
35
38
  class CliArgumentError(ValueError):
@@ -72,6 +75,32 @@ def _read_json_object(path: Path, label: str) -> dict[str, Any]:
72
75
  return value
73
76
 
74
77
 
78
+ def chapter_work_dir(project: Path, chapter: int) -> Path:
79
+ width = max(3, len(str(chapter)))
80
+ return project.resolve().joinpath(*WORK_ROOT, f"第{chapter:0{width}d}章")
81
+
82
+
83
+ def _remove_chapter_work_dir(project: Path, chapter: int) -> str | None:
84
+ """Delete this chapter's scratch directory after a successful commit.
85
+
86
+ Only the per-chapter directory is removed; `.story/work` and `.story` are
87
+ pruned only when empty, so book-level author memory under `.story/` stays.
88
+ """
89
+ path = chapter_work_dir(project, chapter)
90
+ if path.is_symlink():
91
+ path.unlink()
92
+ elif path.is_dir():
93
+ shutil.rmtree(path)
94
+ else:
95
+ return None
96
+ for parent in (path.parent, path.parent.parent):
97
+ try:
98
+ parent.rmdir()
99
+ except OSError:
100
+ break
101
+ return "/".join((*WORK_ROOT, path.name))
102
+
103
+
75
104
  def _project_files(project: Path, chapter: int) -> tuple[Path, Path, int]:
76
105
  root = project.resolve()
77
106
  outline = find_chapter_file(root / "大纲", chapter, outline=True)
@@ -200,6 +229,23 @@ def chapter_check(project: Path, chapter: int) -> dict[str, Any]:
200
229
  }
201
230
 
202
231
 
232
+ def fix_punctuation(project: Path, chapter: int) -> bool:
233
+ """Run the deterministic punctuation normalizer on the chapter body in place.
234
+
235
+ Folding it into `chapter check --fix-punctuation` lets the parent flow close a
236
+ chapter with one call instead of running each cleanup script separately.
237
+ """
238
+ _, body, _ = _project_files(project, chapter)
239
+ node = shutil.which("node")
240
+ script = Path(__file__).with_name("normalize-punctuation.js")
241
+ if node is None or not script.is_file():
242
+ return False # chapter check reports TOOL_UNAVAILABLE as a blocking finding
243
+ before = body.read_bytes()
244
+ subprocess.run([node, str(script), str(body)], text=True, encoding="utf-8",
245
+ capture_output=True, check=False)
246
+ return body.read_bytes() != before
247
+
248
+
203
249
  def chapter_commit(project: Path, chapter: int, input_path: Path, *, accept_current_length: bool) -> dict[str, Any]:
204
250
  checked = chapter_check(project, chapter)
205
251
  require(checked["quality"]["status"] == "pass", "blocking quality findings must be fixed before commit")
@@ -220,6 +266,12 @@ def chapter_commit(project: Path, chapter: int, input_path: Path, *, accept_curr
220
266
  checked["tracking_committed"] = state["last_committed_chapter"] >= chapter
221
267
  checked["next_chapter_started"] = state["last_committed_chapter"] > chapter
222
268
  checked["wordcount"] = state["wordcount_records"].get(str(chapter))
269
+ # The commit is already durable; a cleanup failure is reported, never turned into a failed commit.
270
+ try:
271
+ checked["work_dir_removed"] = _remove_chapter_work_dir(project, chapter)
272
+ except OSError as exc:
273
+ checked["work_dir_removed"] = None
274
+ checked["work_dir_cleanup_error"] = str(exc)
223
275
  return checked
224
276
 
225
277
 
@@ -243,6 +295,9 @@ def _build_parser() -> StructuredArgumentParser:
243
295
  subparser.add_argument("--chapter", type=int, required=True)
244
296
  if command != "check":
245
297
  subparser.add_argument("--input", type=Path, required=True)
298
+ else:
299
+ subparser.add_argument("--fix-punctuation", action="store_true",
300
+ help="先就地整理正文标点,再做检查")
246
301
  return parser
247
302
 
248
303
 
@@ -286,7 +341,10 @@ def main(argv: Sequence[str] | None = None) -> int:
286
341
  return _wordcount_command(args)
287
342
  try:
288
343
  if args.chapter_command == "check":
344
+ fixed = fix_punctuation(args.project, args.chapter) if args.fix_punctuation else None
289
345
  result = chapter_check(args.project, args.chapter)
346
+ if fixed is not None:
347
+ result["punctuation_fixed"] = fixed
290
348
  else:
291
349
  result = chapter_commit(
292
350
  args.project, args.chapter, args.input,
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ // Book-local only: never inherit an unrelated workspace or another book's choices.
7
+ function loadStyleWhitelist(file) {
8
+ const parent = path.dirname(path.resolve(file));
9
+ const book = path.basename(parent) === '正文' ? path.dirname(parent) : parent;
10
+ try {
11
+ return fs.readFileSync(path.join(book, '.deslop-whitelist'), 'utf8')
12
+ .split(/\r?\n/).map(line => line.trim())
13
+ .filter(line => line && !line.startsWith('#'))
14
+ .sort((a, b) => b.length - a.length);
15
+ } catch (error) {
16
+ if (error.code === 'ENOENT') return [];
17
+ throw error;
18
+ }
19
+ }
20
+
21
+ function styleSpans(text, whitelist) {
22
+ const spans = [];
23
+ for (const literal of whitelist) {
24
+ for (let at = text.indexOf(literal); at !== -1; at = text.indexOf(literal, at + 1)) {
25
+ spans.push([at, at + literal.length]);
26
+ }
27
+ }
28
+ return spans;
29
+ }
30
+
31
+ function maskStyleText(text, whitelist) {
32
+ const chars = text.split('');
33
+ for (const [start, end] of styleSpans(text, whitelist)) {
34
+ chars.fill('?', start, end);
35
+ }
36
+ return chars.join('');
37
+ }
38
+
39
+ module.exports = { loadStyleWhitelist, styleSpans, maskStyleText };