@oh-story/dsh 0.1.10 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (444) hide show
  1. package/README.md +11 -11
  2. package/lib/client.js +7 -3
  3. package/lib/client.js.map +2 -2
  4. package/lib/drama/manifest.json +212 -142
  5. package/lib/drama/skills/short-drama/SKILL.md +14 -2
  6. package/lib/drama/skills/short-drama/assets/project-template/short-drama.json +3 -0
  7. package/lib/drama/skills/short-drama/references/creator-documents.md +3 -0
  8. package/lib/drama/skills/short-drama/references/knowhow-index.md +15 -0
  9. package/lib/drama/skills/short-drama/scripts/project_tool.py +131 -0
  10. package/lib/drama/skills/short-drama-assets/SKILL.md +2 -1
  11. package/lib/drama/skills/short-drama-assets/references/asset-review-checklist.md +2 -0
  12. package/lib/drama/skills/short-drama-assets/references/era-anchors.md +37 -0
  13. package/lib/drama/skills/short-drama-assets/references/stage-contract.md +2 -0
  14. package/lib/drama/skills/short-drama-develop/SKILL.md +10 -4
  15. package/lib/drama/skills/short-drama-develop/assets/adaptation-map.example.jsonl +1 -1
  16. package/lib/drama/skills/short-drama-develop/assets/creative-brief.md +8 -0
  17. package/lib/drama/skills/short-drama-develop/references/adaptation-craft.md +80 -13
  18. package/lib/drama/skills/short-drama-develop/references/episode-design.md +95 -12
  19. package/lib/drama/skills/short-drama-develop/references/genre-and-hook-playbook.md +9 -5
  20. package/lib/drama/skills/short-drama-develop/references/genre-cards//345/244/215/344/273/207/346/211/223/350/204/270.md +1 -1
  21. package/lib/drama/skills/short-drama-develop/references/genre-cards//347/263/273/347/273/237/346/265/201.md +51 -0
  22. package/lib/drama/skills/short-drama-develop/references/genre-cards//351/200/206/350/242/255.md +51 -0
  23. package/lib/drama/skills/short-drama-develop/references/genre-cards//351/207/215/347/224/237/347/251/277/350/266/212.md +51 -0
  24. package/lib/drama/skills/short-drama-develop/references/genre-cards.md +10 -7
  25. package/lib/drama/skills/short-drama-develop/references/premise-devices.md +15 -9
  26. package/lib/drama/skills/short-drama-develop/references/serial-character-and-memory.md +3 -2
  27. package/lib/drama/skills/short-drama-develop/references/stage-contract.md +14 -7
  28. package/lib/drama/skills/short-drama-develop/references/story-craft.md +7 -3
  29. package/lib/drama/skills/short-drama-edit/SKILL.md +43 -20
  30. package/lib/drama/skills/short-drama-edit/assets/remotion/README.md +49 -13
  31. package/lib/drama/skills/short-drama-edit/assets/remotion/package.json +4 -2
  32. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Overlay.tsx +31 -0
  33. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Root.tsx +5 -5
  34. package/lib/drama/skills/short-drama-edit/assets/remotion/src/Subtitles.tsx +68 -69
  35. package/lib/drama/skills/short-drama-edit/assets/remotion/src/font.ts +39 -29
  36. package/lib/drama/skills/short-drama-edit/assets/remotion/src/rules.mjs +188 -0
  37. package/lib/drama/skills/short-drama-edit/assets/remotion/src/schema.ts +26 -13
  38. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/Card.tsx +72 -0
  39. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/CornerChip.tsx +39 -0
  40. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/ScreenText.tsx +36 -0
  41. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/SystemPanel.tsx +44 -0
  42. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/TaskPanel.tsx +100 -0
  43. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/chrome.tsx +197 -0
  44. package/lib/drama/skills/short-drama-edit/assets/remotion/src/screen/tokens.ts +62 -0
  45. package/lib/drama/skills/short-drama-edit/assets/remotion/tsconfig.json +1 -0
  46. package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +4 -2
  47. package/lib/drama/skills/short-drama-edit/references/cut-craft.md +3 -1
  48. package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +6 -2
  49. package/lib/drama/skills/short-drama-edit/references/generated-footage.md +12 -6
  50. package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +13 -8
  51. package/lib/drama/skills/short-drama-edit/references/stage-contract.md +2 -0
  52. package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +1153 -121
  53. package/lib/drama/skills/short-drama-edit/scripts/selftest.py +49 -9
  54. package/lib/drama/skills/short-drama-novel-analyze/SKILL.md +3 -3
  55. package/lib/drama/skills/short-drama-novel-analyze/assets/episode-candidate.example.jsonl +2 -2
  56. package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-triage.md +8 -4
  57. package/lib/drama/skills/short-drama-novel-analyze/references/adaptation-value.md +10 -3
  58. package/lib/drama/skills/short-drama-novel-analyze/references/aggregation-and-entities.md +25 -1
  59. package/lib/drama/skills/short-drama-novel-analyze/references/stage-contract.md +1 -0
  60. package/lib/drama/skills/short-drama-review/SKILL.md +2 -2
  61. package/lib/drama/skills/short-drama-review/references/review-method.md +6 -1
  62. package/lib/drama/skills/short-drama-review/references/rubric-assets-prompts.md +11 -0
  63. package/lib/drama/skills/short-drama-review/references/rubric-story-script.md +77 -3
  64. package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +15 -0
  65. package/lib/drama/skills/short-drama-review/references/stage-contract.md +3 -0
  66. package/lib/drama/skills/short-drama-storyboard/SKILL.md +13 -5
  67. package/lib/drama/skills/short-drama-storyboard/references/blocking-playbooks.md +1 -1
  68. package/lib/drama/skills/short-drama-storyboard/references/production-shot-grammar.md +2 -2
  69. package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +82 -14
  70. package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +4 -2
  71. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +5 -3
  72. package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +12 -0
  73. package/lib/drama/skills/short-drama-video-prompts/references/delivery-profile.md +4 -5
  74. package/lib/drama/skills/short-drama-video-prompts/references/generability.md +4 -2
  75. package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +40 -1
  76. package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +20 -14
  77. package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +1 -1
  78. package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +8 -7
  79. package/lib/drama/skills/short-drama-video-prompts/references/review-and-fixtures.md +1 -1
  80. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +1 -0
  81. package/lib/drama/skills/short-drama-video-prompts/references/target-model-profile.md +8 -1
  82. package/lib/drama/skills/short-drama-video-prompts/references/wan-3.0.md +103 -0
  83. package/lib/drama/skills/short-drama-video-prompts/scripts/motion_timing_check.py +8 -7
  84. package/lib/drama/skills/short-drama-write/SKILL.md +8 -3
  85. package/lib/drama/skills/short-drama-write/references/dialogue-craft.md +1 -1
  86. package/lib/drama/skills/short-drama-write/references/script-craft.md +56 -3
  87. package/lib/drama/skills/short-drama-write/references/stage-contract.md +4 -0
  88. package/lib/drama/skills/short-drama-write/scripts/duration_estimate.py +48 -2
  89. package/lib/index.js +71 -21
  90. package/lib/index.js.map +3 -3
  91. package/lib/oh-story/manifest.json +545 -310
  92. package/lib/oh-story/roles/chapter-extractor.md +11 -11
  93. package/lib/oh-story/roles/character-designer.md +1 -2
  94. package/lib/oh-story/roles/consistency-checker.md +11 -9
  95. package/lib/oh-story/roles/narrative-writer.md +24 -25
  96. package/lib/oh-story/roles/story-architect.md +9 -61
  97. package/lib/oh-story/roles/story-explorer.md +20 -125
  98. package/lib/oh-story/roles/story-researcher.md +0 -1
  99. package/lib/oh-story/skills/story/SKILL.md +6 -6
  100. package/lib/oh-story/skills/story/VERSION +1 -1
  101. package/lib/oh-story/skills/story/references/author-memory-maintenance.md +75 -0
  102. package/lib/oh-story/skills/story/references/author-memory.md +40 -118
  103. package/lib/oh-story/skills/story/scripts/author_memory_commit.py +3 -3
  104. package/lib/oh-story/skills/story-deslop/SKILL.md +24 -25
  105. package/lib/oh-story/skills/story-deslop/references/agent-calls.md +5 -0
  106. package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +1 -1
  107. package/lib/oh-story/skills/story-deslop/references/author-memory-maintenance.md +75 -0
  108. package/lib/oh-story/skills/story-deslop/references/author-memory.md +40 -118
  109. package/lib/oh-story/skills/story-deslop/references/banned-words.md +2 -2
  110. package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +21 -4
  111. package/lib/oh-story/skills/story-deslop/references/style-resolution.md +4 -4
  112. package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +3 -3
  113. package/lib/oh-story/skills/story-deslop/scripts/check-ai-patterns.js +155 -8
  114. package/lib/oh-story/skills/story-import/SKILL.md +63 -579
  115. package/lib/oh-story/skills/story-import/references/deep-analysis.md +47 -0
  116. package/lib/oh-story/skills/story-import/references/format-and-structure.md +1 -1
  117. package/lib/oh-story/skills/story-import/references/import-report.md +64 -0
  118. package/lib/oh-story/skills/story-import/references/import-special-cases.md +34 -0
  119. package/lib/oh-story/skills/story-import/references/import-tracking.md +95 -0
  120. package/lib/oh-story/skills/story-import/references/length-routing.md +1 -1
  121. package/lib/oh-story/skills/story-import/references/outline-reverse-rules.md +79 -0
  122. package/lib/oh-story/skills/story-import/references/outline-reverse.md +24 -0
  123. package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +95 -91
  124. package/lib/oh-story/skills/story-import/references/structure-mapping-short.md +24 -12
  125. package/lib/oh-story/skills/story-import/references/tracking-initialization.md +1 -1
  126. package/lib/oh-story/skills/story-import/references/tracking-transaction.md +11 -9
  127. package/lib/oh-story/skills/story-import/scripts/build_outline_brief.py +332 -0
  128. package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +101 -14
  129. package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +278 -23
  130. package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +179 -23
  131. package/lib/oh-story/skills/story-long-analyze/SKILL.md +28 -50
  132. package/lib/oh-story/skills/story-long-analyze/references/author-facing.md +12 -5
  133. package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +1 -1
  134. package/lib/oh-story/skills/story-long-analyze/references/final-checks.md +19 -0
  135. package/lib/oh-story/skills/story-long-analyze/references/index-rebuild.md +11 -0
  136. package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +21 -55
  137. package/lib/oh-story/skills/story-long-analyze/references/stage1-golden-chapters.md +128 -0
  138. package/lib/oh-story/skills/story-long-analyze/references/stage2-extraction.md +229 -0
  139. package/lib/oh-story/skills/story-long-analyze/references/stage3-plot-rhythm.md +480 -0
  140. package/lib/oh-story/skills/story-long-analyze/references/stage4-characters-settings.md +195 -0
  141. package/lib/oh-story/skills/story-long-analyze/references/stage5-report.md +33 -0
  142. package/lib/oh-story/skills/story-long-analyze/references/style-profile-generator.md +17 -0
  143. package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +1 -1
  144. package/lib/oh-story/skills/story-long-analyze/references/synthesis-inputs.md +39 -0
  145. package/lib/oh-story/skills/story-long-analyze/scripts/manage_analysis_run.py +47 -8
  146. package/lib/oh-story/skills/story-long-scan/SKILL.md +41 -193
  147. package/lib/oh-story/skills/story-long-scan/references/platform-ciweimao.md +19 -0
  148. package/lib/oh-story/skills/story-long-scan/references/platform-fanqie.md +45 -0
  149. package/lib/oh-story/skills/story-long-scan/references/platform-jjwxc.md +28 -0
  150. package/lib/oh-story/skills/story-long-scan/references/platform-qidian.md +35 -0
  151. package/lib/oh-story/skills/story-long-scan/references/platform-qimao.md +27 -0
  152. package/lib/oh-story/skills/story-long-scan/references/topic-decision.md +6 -2
  153. package/lib/oh-story/skills/story-long-write/SKILL.md +32 -73
  154. package/lib/oh-story/skills/story-long-write/references/agent-calls.md +47 -0
  155. package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +1 -1
  156. package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +18 -201
  157. package/lib/oh-story/skills/story-long-write/references/author-memory-maintenance.md +75 -0
  158. package/lib/oh-story/skills/story-long-write/references/author-memory.md +40 -118
  159. package/lib/oh-story/skills/story-long-write/references/banned-words.md +2 -2
  160. package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +6 -10
  161. package/lib/oh-story/skills/story-long-write/references/character-relations.md +49 -1
  162. package/lib/oh-story/skills/story-long-write/references/deslop-gates.md +171 -0
  163. package/lib/oh-story/skills/story-long-write/references/emotion-on-page.md +116 -0
  164. package/lib/oh-story/skills/story-long-write/references/emotional-arc-design.md +1 -114
  165. package/lib/oh-story/skills/story-long-write/references/genre-prose-cards.md +1 -1
  166. package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +3 -2
  167. package/lib/oh-story/skills/story-long-write/references/long-format.md +0 -8
  168. package/lib/oh-story/skills/story-long-write/references/long-genre-catalog.md +1 -1
  169. package/lib/oh-story/skills/story-long-write/references/long-genre-mechanics.md +3 -3
  170. package/lib/oh-story/skills/story-long-write/references/opening-design.md +2 -2
  171. package/lib/oh-story/skills/story-long-write/references/outline-conflict.md +4 -4
  172. package/lib/oh-story/skills/story-long-write/references/outline-methods.md +7 -7
  173. package/lib/oh-story/skills/story-long-write/references/outline-rhythm.md +3 -3
  174. package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +14 -69
  175. package/lib/oh-story/skills/story-long-write/references/plot-frameworks.md +1 -1
  176. package/lib/oh-story/skills/story-long-write/references/plot-special-topics.md +2 -2
  177. package/lib/oh-story/skills/story-long-write/references/project-files.md +10 -6
  178. package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +1 -1
  179. package/lib/oh-story/skills/story-long-write/references/reference-index.md +4 -4
  180. package/lib/oh-story/skills/story-long-write/references/stop-and-fix.md +9 -0
  181. package/lib/oh-story/skills/story-long-write/references/style-combat-face.md +1 -58
  182. package/lib/oh-story/skills/story-long-write/references/style-genre-modules.md +1 -1
  183. package/lib/oh-story/skills/story-long-write/references/style-resolution.md +4 -4
  184. package/lib/oh-story/skills/story-long-write/references/tracking-initialization.md +1 -1
  185. package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +11 -9
  186. package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +33 -44
  187. package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +18 -51
  188. package/lib/oh-story/skills/story-long-write/references/workflow-outline.md +157 -0
  189. package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +53 -67
  190. package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +112 -251
  191. package/lib/oh-story/skills/story-long-write/references/workflow-volume.md +142 -0
  192. package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +5 -3
  193. package/lib/oh-story/skills/story-long-write/references/writing-craft.md +18 -56
  194. package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +3 -3
  195. package/lib/oh-story/skills/story-long-write/scripts/build_architect_brief.py +260 -0
  196. package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +96 -16
  197. package/lib/oh-story/skills/story-long-write/scripts/check-ai-patterns.js +155 -8
  198. package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +101 -14
  199. package/lib/oh-story/skills/story-long-write/scripts/check-outline-copy.js +7 -2
  200. package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +2 -1
  201. package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +250 -50
  202. package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +278 -23
  203. package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +179 -23
  204. package/lib/oh-story/skills/story-review/SKILL.md +19 -273
  205. package/lib/oh-story/skills/story-review/references/agent-prompts.md +184 -0
  206. package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +1 -1
  207. package/lib/oh-story/skills/story-review/references/author-memory-maintenance.md +75 -0
  208. package/lib/oh-story/skills/story-review/references/author-memory.md +40 -118
  209. package/lib/oh-story/skills/story-review/references/banned-words.md +2 -2
  210. package/lib/oh-story/skills/story-review/references/batch-review.md +19 -0
  211. package/lib/oh-story/skills/story-review/references/character-relations.md +49 -1
  212. package/lib/oh-story/skills/story-review/references/review-quality.md +2 -1
  213. package/lib/oh-story/skills/story-review/references/review-tracking.md +10 -0
  214. package/lib/oh-story/skills/story-review/references/solo.md +52 -0
  215. package/lib/oh-story/skills/story-review/references/style-resolution.md +4 -4
  216. package/lib/oh-story/skills/story-review/references/tracking-initialization.md +1 -1
  217. package/lib/oh-story/skills/story-review/references/tracking-transaction.md +11 -9
  218. package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +3 -3
  219. package/lib/oh-story/skills/story-review/scripts/check-ai-patterns.js +155 -8
  220. package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +278 -23
  221. package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +179 -23
  222. package/lib/oh-story/skills/story-setup/SKILL.md +69 -458
  223. package/lib/oh-story/skills/story-setup/references/agent-references/agent-quality.md +4 -4
  224. package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +1 -1
  225. package/lib/oh-story/skills/story-setup/references/agent-references/banned-words.md +2 -2
  226. package/lib/oh-story/skills/story-setup/references/agent-references/benchmark-style-load.md +107 -0
  227. package/lib/oh-story/skills/story-setup/references/agent-references/character-relations.md +49 -1
  228. package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +21 -4
  229. package/lib/oh-story/skills/story-setup/references/agent-references/emotion-on-page.md +116 -0
  230. package/lib/oh-story/skills/story-setup/references/agent-references/emotional-arc-design.md +1 -114
  231. package/lib/oh-story/skills/story-setup/references/agent-references/format-and-structure.md +1 -1
  232. package/lib/oh-story/skills/story-setup/references/agent-references/genre-prose-cards.md +1 -1
  233. package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-catalog.md +1 -1
  234. package/lib/oh-story/skills/story-setup/references/agent-references/long-genre-mechanics.md +3 -3
  235. package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +7 -5
  236. package/lib/oh-story/skills/story-setup/references/agent-references/opening-design.md +2 -2
  237. package/lib/oh-story/skills/story-setup/references/agent-references/outline-conflict.md +4 -4
  238. package/lib/oh-story/skills/story-setup/references/agent-references/outline-methods.md +7 -7
  239. package/lib/oh-story/skills/story-setup/references/agent-references/outline-rhythm.md +3 -3
  240. package/lib/oh-story/skills/story-setup/references/agent-references/short-emotional-methods.md +1 -1
  241. package/lib/oh-story/skills/story-setup/references/agent-references/short-genre-formulas.md +0 -15
  242. package/lib/oh-story/skills/story-setup/references/agent-references/short-reversal.md +1 -1
  243. package/lib/oh-story/skills/story-setup/references/agent-references/style-combat-face.md +1 -58
  244. package/lib/oh-story/skills/story-setup/references/agent-references/style-genre-modules.md +1 -1
  245. package/lib/oh-story/skills/story-setup/references/agent-references/style-resolution.md +4 -4
  246. package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +18 -56
  247. package/lib/oh-story/skills/story-setup/references/diagnostics.md +3 -3
  248. package/lib/oh-story/skills/story-short-analyze/SKILL.md +37 -110
  249. package/lib/oh-story/skills/story-short-analyze/references/analysis-character-relations.md +1 -1
  250. package/lib/oh-story/skills/story-short-analyze/references/analysis-method.md +42 -0
  251. package/lib/oh-story/skills/story-short-analyze/references/optional-modules.md +97 -0
  252. package/lib/oh-story/skills/story-short-analyze/references/output-contract.md +9 -47
  253. package/lib/oh-story/skills/story-short-analyze/references/quality-checklist.md +80 -0
  254. package/lib/oh-story/skills/story-short-analyze/references/source-story-quality.md +4 -4
  255. package/lib/oh-story/skills/story-short-analyze/references/stage2-3-structure-emotion.md +308 -0
  256. package/lib/oh-story/skills/story-short-analyze/references/{material-decomposition.md → stage4-6-reversal-summary.md} +303 -374
  257. package/lib/oh-story/skills/story-short-scan/SKILL.md +30 -64
  258. package/lib/oh-story/skills/story-short-scan/references/manual-rank-input.md +36 -0
  259. package/lib/oh-story/skills/story-short-scan/references/platform-dianzhong.md +24 -0
  260. package/lib/oh-story/skills/story-short-scan/references/platform-heiyan.md +21 -0
  261. package/lib/oh-story/skills/story-short-scan/references/topic-match.md +38 -0
  262. package/lib/oh-story/skills/story-short-write/SKILL.md +23 -34
  263. package/lib/oh-story/skills/story-short-write/references/agent-calls.md +13 -0
  264. package/lib/oh-story/skills/story-short-write/references/author-memory-maintenance.md +75 -0
  265. package/lib/oh-story/skills/story-short-write/references/author-memory.md +40 -118
  266. package/lib/oh-story/skills/story-short-write/references/banned-words.md +2 -2
  267. package/lib/oh-story/skills/story-short-write/references/benchmark-recall.md +1 -21
  268. package/lib/oh-story/skills/story-short-write/references/emotional-methods.md +1 -1
  269. package/lib/oh-story/skills/story-short-write/references/genre-writing-formulas.md +0 -15
  270. package/lib/oh-story/skills/story-short-write/references/output-contract.md +9 -47
  271. package/lib/oh-story/skills/story-short-write/references/reference-index.md +17 -49
  272. package/lib/oh-story/skills/story-short-write/references/scene-craft.md +40 -0
  273. package/lib/oh-story/skills/story-short-write/references/short-craft.md +7 -43
  274. package/lib/oh-story/skills/story-short-write/references/short-format.md +3 -3
  275. package/lib/oh-story/skills/story-short-write/references/short-prose-quality.md +2 -1
  276. package/lib/oh-story/skills/story-short-write/references/short-reversal.md +1 -1
  277. package/lib/oh-story/skills/story-short-write/references/style-resolution.md +4 -4
  278. package/lib/oh-story/skills/story-short-write/references/submission-craft.md +1 -1
  279. package/lib/oh-story/skills/story-short-write/references/villain-and-reveal.md +5 -10
  280. package/lib/oh-story/skills/story-short-write/references/workflow-design.md +19 -12
  281. package/lib/oh-story/skills/story-short-write/references/workflow-draft.md +22 -32
  282. package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +84 -17
  283. package/lib/oh-story/skills/story-short-write/references/writing-workflow.md +7 -119
  284. package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +3 -3
  285. package/lib/oh-story/skills/story-short-write/scripts/check-ai-patterns.js +155 -8
  286. package/lib/oh-story/skills/story-short-write/scripts/check-outline-copy.js +7 -2
  287. package/lib/types/client/video-studio.d.ts.map +1 -1
  288. package/lib/types/native-hooks.d.ts +2 -2
  289. package/lib/types/native-hooks.d.ts.map +1 -1
  290. package/lib/types/role-tool.d.ts.map +1 -1
  291. package/lib/types/skill-provider.d.ts.map +1 -1
  292. package/lib/types/video-project.d.ts.map +1 -1
  293. package/lib/types/workspace-route.d.ts.map +1 -1
  294. package/lib/video-recap/CHANGELOG.md +60 -1
  295. package/lib/video-recap/README.en.md +348 -137
  296. package/lib/video-recap/README.md +340 -136
  297. package/lib/video-recap/manifest.json +406 -251
  298. package/lib/video-recap/skills/video-assemble/SKILL.md +30 -15
  299. package/lib/video-recap/skills/video-assemble/references/audio-modes.md +63 -0
  300. package/lib/video-recap/skills/video-assemble/references/explicit-audio-mix.md +75 -0
  301. package/lib/video-recap/skills/video-assemble/references/foreground-compose.md +118 -0
  302. package/lib/video-recap/skills/video-assemble/references/narration-adoption.md +103 -0
  303. package/lib/video-recap/skills/video-assemble/references/packaging.md +45 -0
  304. package/lib/video-recap/skills/video-assemble/references/pair-media.md +74 -0
  305. package/lib/video-recap/skills/video-assemble/references/source-score.md +147 -0
  306. package/lib/video-recap/skills/video-assemble/references/subtitle-track.md +202 -0
  307. package/lib/video-recap/skills/video-assemble/scripts/adoption/__init__.py +1 -0
  308. package/lib/video-recap/skills/video-assemble/scripts/adoption/audio_mix_binding.py +279 -0
  309. package/lib/video-recap/skills/video-assemble/scripts/adoption/frozen_audio.py +187 -0
  310. package/lib/video-recap/skills/video-assemble/scripts/adoption/narration_binding.py +323 -0
  311. package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_inputs.py +109 -0
  312. package/lib/video-recap/skills/video-assemble/scripts/adoption/strict_publish.py +130 -0
  313. package/lib/video-recap/skills/video-assemble/scripts/artifacts.py +10 -46
  314. package/lib/video-recap/skills/video-assemble/scripts/assemble.py +378 -119
  315. package/lib/video-recap/skills/video-assemble/scripts/assemble_constants.py +29 -12
  316. package/lib/video-recap/skills/video-assemble/scripts/assembly_contract.py +95 -44
  317. package/lib/video-recap/skills/video-assemble/scripts/assembly_settings.py +75 -32
  318. package/lib/video-recap/skills/video-assemble/scripts/audio_mix.py +11 -11
  319. package/lib/video-recap/skills/video-assemble/scripts/compose_foreground.py +293 -0
  320. package/lib/video-recap/skills/video-assemble/scripts/export_jianying.py +71 -78
  321. package/lib/video-recap/skills/video-assemble/scripts/jianying/__init__.py +1 -0
  322. package/lib/video-recap/skills/video-assemble/scripts/{jianying_builders.py → jianying/builders.py} +39 -67
  323. package/lib/video-recap/skills/video-assemble/scripts/{jianying_model.py → jianying/model.py} +3 -11
  324. package/lib/video-recap/skills/video-assemble/scripts/{jianying_optional.py → jianying/optional.py} +1 -1
  325. package/lib/video-recap/skills/video-assemble/scripts/jianying/schema.py +53 -0
  326. package/lib/video-recap/skills/video-assemble/scripts/{jianying_templates.py → jianying/templates.py} +1 -1
  327. package/lib/video-recap/skills/video-assemble/scripts/{jianying_timeline_contract.py → jianying/timeline_contract.py} +34 -13
  328. package/lib/video-recap/skills/video-assemble/scripts/{jianying_writer.py → jianying/writer.py} +7 -17
  329. package/lib/video-recap/skills/video-assemble/scripts/lib.py +37 -4
  330. package/lib/video-recap/skills/video-assemble/scripts/media.py +12 -21
  331. package/lib/video-recap/skills/video-assemble/scripts/narration_audio.py +64 -34
  332. package/lib/video-recap/skills/video-assemble/scripts/packaging.py +122 -0
  333. package/lib/video-recap/skills/video-assemble/scripts/pair_media.py +216 -0
  334. package/lib/video-recap/skills/video-assemble/scripts/render_preflight.py +1 -3
  335. package/lib/video-recap/skills/video-assemble/scripts/source_score.py +469 -0
  336. package/lib/video-recap/skills/video-assemble/scripts/source_subtitles.py +25 -9
  337. package/lib/video-recap/skills/video-assemble/scripts/subtitles/__init__.py +1 -0
  338. package/lib/video-recap/skills/video-assemble/scripts/{subtitle_core.py → subtitles/core.py} +13 -6
  339. package/lib/video-recap/skills/video-assemble/scripts/{subtitle_render.py → subtitles/render.py} +5 -4
  340. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track.py +324 -0
  341. package/lib/video-recap/skills/video-assemble/scripts/subtitles/track_binding.py +200 -0
  342. package/lib/video-recap/skills/video-assemble/scripts/timeline.py +30 -46
  343. package/lib/video-recap/skills/video-assemble/scripts/timeline_emit.py +91 -7
  344. package/lib/video-recap/skills/video-assemble/scripts/visual_render.py +22 -8
  345. package/lib/video-recap/skills/video-cut/SKILL.md +34 -16
  346. package/lib/video-recap/skills/video-cut/references/shot-review.md +50 -0
  347. package/lib/video-recap/skills/video-cut/scripts/cut.py +2 -28
  348. package/lib/video-recap/skills/video-cut/scripts/cut_cli.py +58 -62
  349. package/lib/video-recap/skills/video-cut/scripts/cut_contract.py +21 -106
  350. package/lib/video-recap/skills/video-cut/scripts/cut_render.py +14 -9
  351. package/lib/video-recap/skills/video-cut/scripts/lib.py +39 -0
  352. package/lib/video-recap/skills/video-cut/scripts/narration_mapping.py +2 -155
  353. package/lib/video-recap/skills/video-cut/scripts/narrative_selection.py +244 -0
  354. package/lib/video-recap/skills/video-cut/scripts/shot_review.py +357 -0
  355. package/lib/video-recap/skills/video-recap/SKILL.md +86 -42
  356. package/lib/video-recap/skills/video-recap/references/audio-routing.md +126 -0
  357. package/lib/video-recap/skills/video-recap/references/config-playbook.md +12 -8
  358. package/lib/video-recap/skills/video-recap/references/data-schema.md +44 -33
  359. package/lib/video-recap/skills/video-recap/references/resource-library.md +151 -0
  360. package/lib/video-recap/skills/video-recap/references/shift-left-qc-schema.md +1 -1
  361. package/lib/video-recap/skills/video-recap/scripts/doctor.py +123 -30
  362. package/lib/video-recap/skills/video-recap/scripts/final_qc.py +22 -75
  363. package/lib/video-recap/skills/video-recap/scripts/lib.py +3 -3
  364. package/lib/video-recap/skills/video-recap/scripts/library.py +509 -0
  365. package/lib/video-recap/skills/video-recap/scripts/materials.py +63 -91
  366. package/lib/video-recap/skills/video-recap/scripts/mimo_qc.py +8 -70
  367. package/lib/video-recap/skills/video-recap/scripts/project_binding.py +286 -0
  368. package/lib/video-recap/skills/video-recap/scripts/qc/__init__.py +1 -0
  369. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_client.py → qc/mimo_client.py} +1 -1
  370. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_evidence.py → qc/mimo_evidence.py} +33 -66
  371. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_observations.py → qc/mimo_observations.py} +3 -11
  372. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_payload.py → qc/mimo_payload.py} +4 -7
  373. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_report.py → qc/mimo_report.py} +17 -28
  374. package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_runner.py → qc/mimo_runner.py} +2 -2
  375. package/lib/video-recap/skills/video-recap/scripts/qc_contract.py +0 -19
  376. package/lib/video-recap/skills/video-recap/scripts/recap_cli.py +119 -50
  377. package/lib/video-recap/skills/video-recap/scripts/recap_inspect.py +32 -114
  378. package/lib/video-recap/skills/video-recap/scripts/recap_review.py +9 -23
  379. package/lib/video-recap/skills/video-recap/scripts/recap_runner.py +387 -334
  380. package/lib/video-recap/skills/video-recap/scripts/recap_runtime.py +23 -23
  381. package/lib/video-recap/skills/video-recap/scripts/recap_source.py +278 -0
  382. package/lib/video-recap/skills/video-recap/scripts/recap_stage_qc.py +25 -12
  383. package/lib/video-recap/skills/video-recap/scripts/recap_timeline.py +59 -52
  384. package/lib/video-recap/skills/video-recap/scripts/resource_lock.py +162 -0
  385. package/lib/video-recap/skills/video-script/SKILL.md +16 -13
  386. package/lib/video-recap/skills/video-script/references/creative-editing-playbook.md +9 -5
  387. package/lib/video-recap/skills/video-script/references/promotional-copy.md +68 -0
  388. package/lib/video-recap/skills/video-script/references/research-guide.md +1 -1
  389. package/lib/video-recap/skills/video-script/scripts/evidence_bundle.py +24 -76
  390. package/lib/video-recap/skills/video-script/scripts/lib.py +10 -87
  391. package/lib/video-recap/skills/video-script/scripts/narration_lint.py +63 -39
  392. package/lib/video-recap/skills/video-script/scripts/review.py +0 -2
  393. package/lib/video-recap/skills/video-script/scripts/review_grounding.py +35 -59
  394. package/lib/video-recap/skills/video-script/scripts/review_response.py +66 -128
  395. package/lib/video-recap/skills/video-script/scripts/review_runner.py +12 -20
  396. package/lib/video-recap/skills/video-script/scripts/speech_ownership.py +4 -4
  397. package/lib/video-recap/skills/video-script/scripts/validate.py +53 -42
  398. package/lib/video-recap/skills/video-understanding/SKILL.md +6 -4
  399. package/lib/video-recap/skills/video-understanding/references/data-schema.md +50 -156
  400. package/lib/video-recap/skills/video-understanding/references/research-guide.md +4 -4
  401. package/lib/video-recap/skills/video-understanding/scripts/asr.py +85 -65
  402. package/lib/video-recap/skills/video-understanding/scripts/asr_timing_evidence.py +270 -0
  403. package/lib/video-recap/skills/video-understanding/scripts/brief.py +2 -5
  404. package/lib/video-recap/skills/video-understanding/scripts/briefing/__init__.py +1 -0
  405. package/lib/video-recap/skills/video-understanding/scripts/{agent_brief.py → briefing/builder.py} +38 -35
  406. package/lib/video-recap/skills/{video-script/scripts/brief_context.py → video-understanding/scripts/briefing/context.py} +4 -35
  407. package/lib/video-recap/skills/{video-script/scripts/brief_inputs.py → video-understanding/scripts/briefing/inputs.py} +9 -31
  408. package/lib/video-recap/skills/video-understanding/scripts/{brief_timeline.py → briefing/timeline.py} +5 -6
  409. package/lib/video-recap/skills/video-understanding/scripts/consolidate.py +65 -116
  410. package/lib/video-recap/skills/video-understanding/scripts/detect.py +31 -40
  411. package/lib/video-recap/skills/video-understanding/scripts/lib.py +30 -86
  412. package/lib/video-recap/skills/video-understanding/scripts/narration_lint.py +63 -39
  413. package/lib/video-recap/skills/video-understanding/scripts/speech_ownership.py +4 -4
  414. package/lib/video-recap/skills/video-understanding/scripts/storyboard.py +17 -79
  415. package/lib/video-recap/skills/video-understanding/scripts/understanding_brief.py +14 -40
  416. package/lib/video-recap/skills/video-understanding/scripts/understanding_cache.py +64 -46
  417. package/lib/video-recap/skills/video-understanding/scripts/understanding_runner.py +31 -11
  418. package/lib/video-recap/skills/video-understanding/scripts/understanding_storyboard.py +19 -14
  419. package/lib/video-recap/skills/video-understanding/scripts/vlm.py +80 -108
  420. package/lib/video-recap/skills/video-voiceover/SKILL.md +29 -19
  421. package/lib/video-recap/skills/video-voiceover/references/index-tts.md +31 -0
  422. package/lib/video-recap/skills/video-voiceover/scripts/approved_text_policy.py +123 -0
  423. package/lib/video-recap/skills/video-voiceover/scripts/dub.py +51 -59
  424. package/lib/video-recap/skills/video-voiceover/scripts/lib.py +8 -44
  425. package/lib/video-recap/skills/video-voiceover/scripts/providers/__init__.py +1 -0
  426. package/lib/video-recap/skills/video-voiceover/scripts/providers/index_tts.py +184 -0
  427. package/lib/video-recap/skills/video-voiceover/scripts/voiceover.py +212 -134
  428. package/package.json +41 -41
  429. package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +0 -538
  430. package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +0 -603
  431. package/lib/oh-story/skills/story-long-scan/references/scan-output-format.md +0 -207
  432. package/lib/oh-story/skills/story-short-analyze/references/output-templates.md +0 -561
  433. package/lib/video-recap/skills/video-assemble/scripts/jianying_schema.py +0 -98
  434. package/lib/video-recap/skills/video-recap/references/creative-editing-playbook.md +0 -225
  435. package/lib/video-recap/skills/video-recap/references/research-guide.md +0 -74
  436. package/lib/video-recap/skills/video-recap/references/timeline-and-jianying.md +0 -250
  437. package/lib/video-recap/skills/video-script/scripts/agent_brief.py +0 -399
  438. package/lib/video-recap/skills/video-script/scripts/brief_timeline.py +0 -327
  439. package/lib/video-recap/skills/video-script/scripts/narration.py +0 -12
  440. package/lib/video-recap/skills/video-understanding/scripts/brief_context.py +0 -348
  441. package/lib/video-recap/skills/video-understanding/scripts/brief_inputs.py +0 -290
  442. /package/lib/video-recap/skills/video-assemble/scripts/{jianying_tracks.py → jianying/tracks.py} +0 -0
  443. /package/lib/video-recap/skills/video-recap/scripts/{mimo_qc_contract.py → qc/mimo_contract.py} +0 -0
  444. /package/lib/video-recap/skills/video-voiceover/scripts/{fish_audio.py → providers/fish_audio.py} +0 -0
@@ -284,10 +284,15 @@ function checkOne(proseFile, explicitOutline) {
284
284
  }
285
285
 
286
286
  const rate = ((copied * 100) / P.length).toFixed(1)
287
+ // 短篇没有细纲:锚句登记在 小节大纲.md 末尾,措辞按短篇说,长篇原文不变
288
+ const isShort = path.basename(outlineFile) === '小节大纲.md'
289
+ const guidance = isShort
290
+ ? `逐条对照原文判断:确属把小节大纲叙述搬进正文就重写——大纲只锁功能与结果,句子在正文现场写;誓词、关键原话、固定专名等功能性重合可保留。每条都要有结论,不为归零机械改写。保留项由主会话登记进小节大纲.md 末尾「复沓锚句:」行下(每行一条原话,缩进写)再复检——子代理只重写正文或标 \`[需复核]\` 交回,不改小节大纲。`
291
+ : `逐条对照原文判断:确属把细纲叙述搬进正文就重写——细纲只锁功能与结果,句子在正文现场写;系统面板、誓词、案卷原话、固定专名等功能性重合可保留。每条都要有结论,不为归零机械改写。保留项由主会话补进细纲「复沓锚句」——子代理只重写正文或标 \`[需复核]\` 交回,不改大纲细纲。`
287
292
  const out = [
288
- `=== 细纲照搬检测(${path.basename(proseFile)})===`,
293
+ `=== ${isShort ? '小节大纲' : '细纲'}照搬检测(${path.basename(proseFile)})===`,
289
294
  `正文 ${P.length} 字,与 ${path.basename(outlineFile)} 连续重合 >${MIN_RUN - 1} 字的片段 ${hits.length} 处,共 ${copied} 字(${rate}%)。`,
290
- `逐条对照原文判断:确属把细纲叙述搬进正文就重写——细纲只锁功能与结果,句子在正文现场写;系统面板、誓词、案卷原话、固定专名等功能性重合可保留。每条都要有结论,不为归零机械改写。保留项由主会话补进细纲「复沓锚句」——子代理只重写正文或标 \`[需复核]\` 交回,不改大纲细纲。`,
295
+ guidance,
291
296
  ]
292
297
  hits
293
298
  .sort((a, b) => b.len - a.len)
@@ -35,7 +35,8 @@ UNIT_IN_TITLE = re.compile(r"(?<![\w-])([LD]\d+-\d+|U\d+)(?![\w-])")
35
35
  # 批次底稿里出现跨章祈使=这条约束在写作期还有效,而写作档不给底稿 → 必须下沉
36
36
  IMPERATIVE = re.compile(r"全程不许|一律不许|往后任何章|此后不再|往后每|从此不|终局前不")
37
37
  CH_RANGE = re.compile(r"章节范围[::]\s*第\s*(\d+)\s*[-–—~至]\s*(\d+)\s*章")
38
- ARC_ROW = re.compile(r"^\|\s*(\d+)\s*\|")
38
+ # 情绪弧线逐章行:首列写「12」或模板里的「第12章」都认
39
+ ARC_ROW = re.compile(r"^\|\s*第?\s*(\d+)\s*章?\s*\|")
39
40
 
40
41
 
41
42
  def read(path):
@@ -4,8 +4,10 @@
4
4
  from __future__ import annotations
5
5
 
6
6
  import argparse
7
+ import hashlib
7
8
  import importlib.util
8
9
  import json
10
+ import re
9
11
  import shutil
10
12
  import subprocess
11
13
  import sys
@@ -101,72 +103,116 @@ def _remove_chapter_work_dir(project: Path, chapter: int) -> str | None:
101
103
  return "/".join((*WORK_ROOT, path.name))
102
104
 
103
105
 
104
- def _project_files(project: Path, chapter: int) -> tuple[Path, Path, int]:
105
- root = project.resolve()
106
- outline = find_chapter_file(root / "大纲", chapter, outline=True)
107
- body = find_chapter_file(root / "正文", chapter, outline=False)
108
- try:
109
- target = target_from_outline(outline.read_text(encoding="utf-8"))
110
- except (OSError, UnicodeError) as exc:
111
- raise WordcountError(f"unable to read outline: {exc}") from exc
112
- return outline, body, target
106
+ def _project_files(
107
+ project: Path, chapter: int, author_range: Any = None,
108
+ ) -> tuple[Path, Path, int, dict[str, int] | None]:
109
+ return read_project_chapter(project, chapter, author_range)
113
110
 
114
111
 
115
- def _json_findings(output: str) -> list[dict[str, Any]]:
112
+ def _json_findings(output: str) -> list[dict[str, Any]] | None:
113
+ """检测器 --json 输出里的 findings;输出不是预期 JSON 时返回 None(按工具故障处理,不当作无问题)。"""
116
114
  try:
117
- value = json.loads(output or "{}")
115
+ value = json.loads(output)
118
116
  except json.JSONDecodeError:
119
- return []
120
- findings = value.get("findings", []) if isinstance(value, dict) else []
121
- return findings if isinstance(findings, list) else []
117
+ return None
118
+ findings = value.get("findings") if isinstance(value, dict) else None
119
+ if not isinstance(findings, list) or not all(isinstance(row, dict) for row in findings):
120
+ return None
121
+ return findings
122
+
123
+
124
+ NODE_REQUIRED = (
125
+ "质量检查(AI 句式、退化、标点、细纲照搬)要用 Node.js 运行,本机找不到 node。"
126
+ "安装 Node.js 18 或更高版本并确认 `node --version` 可用后重跑本命令;装好之前本章不能提交。"
127
+ )
128
+
129
+
130
+ def _semantic_advisories(advisories: list[dict[str, Any]]) -> int:
131
+ # check-ai-patterns 给每条 finding 标 review=semantic|mechanical,退化检测器的在入口统一标 mechanical;
132
+ # 其余来源没标的按语义类算,宁多勿漏。
133
+ return sum(1 for row in advisories if row.get("review") != "mechanical")
134
+
135
+
136
+ # 与 hooks(JS core DESLOP_SKIP_MARKER / codex py / bash guard)同一语法:注释内只认 ASCII 空格/Tab,
137
+ # 冒号全角半角都认;裸写不在注释里的不算。
138
+ DESLOP_SKIP = re.compile(r"<!--[ \t]*去味[ \t]*(:|:)[ \t]*跳过[ \t]*-->")
139
+
140
+
141
+ def deslop_skipped(body: Path) -> bool:
142
+ """作者显式说本章不去味时,标题行下的 <!-- 去味:跳过 --> 豁免 AI 句式;与 hooks 同一个首 6 行窗口。"""
143
+ head = re.split(r"\r?\n", body.read_text(encoding="utf-8-sig"))[:6]
144
+ return bool(DESLOP_SKIP.search("\n".join(head)))
122
145
 
123
146
 
124
147
  def check_blocking_quality(outline: Path, body: Path) -> dict[str, Any]:
148
+ """Run the deterministic prose checks.
149
+
150
+ status: pass(无 blocking)/ fail(有 blocking 正文问题)/ unavailable(检测工具缺失或崩溃,
151
+ 不是正文问题,见 tool_errors)。
152
+ """
125
153
  node = shutil.which("node")
126
154
  if node is None:
127
155
  return {
128
- "status": "fail",
129
- "blocking_findings": [{"type": "TOOL_UNAVAILABLE", "message": "node is required for quality checks"}],
156
+ "status": "unavailable",
157
+ "blocking_findings": [],
130
158
  "advisories": [],
159
+ "semantic_advisories": 0,
160
+ "tool_errors": [{"type": "TOOL_UNAVAILABLE", "tool": "node", "message": NODE_REQUIRED}],
131
161
  }
132
162
  root = Path(__file__).parent
163
+ exempt = deslop_skipped(body)
133
164
  blocking: list[dict[str, Any]] = []
134
165
  advisories: list[dict[str, Any]] = []
166
+ tool_errors: list[dict[str, Any]] = []
135
167
  for name, script in (
136
168
  ("ai-pattern", "check-ai-patterns.js"),
137
169
  ("degeneration", "check-degeneration.js"),
138
170
  ):
139
171
  path = root / script
140
172
  if not path.is_file():
141
- blocking.append({"type": "TOOL_UNAVAILABLE", "message": f"missing {script}"})
173
+ tool_errors.append({"type": "TOOL_UNAVAILABLE", "tool": script, "message": f"missing {script}"})
142
174
  continue
143
175
  completed = subprocess.run(
144
176
  [node, str(path), "--check", "--json", "--fail-on=blocking", str(body)],
145
177
  text=True, encoding="utf-8", capture_output=True, check=False,
146
178
  )
147
179
  findings = _json_findings(completed.stdout)
180
+ if findings is None or completed.returncode not in {0, 1}:
181
+ tool_errors.append({"type": "TOOL_ERROR", "tool": script, "source": name,
182
+ "message": completed.stderr.strip() or f"{script} exited {completed.returncode} without JSON findings"})
183
+ continue
148
184
  for finding in findings:
149
185
  row = {"source": name, **finding}
186
+ if name == "degeneration":
187
+ # 退化类(复读、截断、拒绝语、工程词)不是 AI 味,不计入触发去 AI 味审查的语义条数。
188
+ row.setdefault("review", "mechanical")
189
+ if exempt and name == "ai-pattern" and finding.get("severity") == "blocking":
190
+ # 豁免只管 AI 句式;退化类(复读、截断、拒绝语、工程词)照常必须修。
191
+ advisories.append({**row, "severity": "advisory", "exempted": "去味:跳过"})
192
+ continue
150
193
  (blocking if finding.get("severity") == "blocking" else advisories).append(row)
151
- if completed.returncode not in {0, 1}:
152
- blocking.append({"type": "TOOL_ERROR", "source": name, "message": completed.stderr.strip()})
153
194
 
154
195
  punctuation = root / "normalize-punctuation.js"
155
196
  if not punctuation.is_file():
156
- blocking.append({"type": "TOOL_UNAVAILABLE", "message": "missing normalize-punctuation.js"})
197
+ tool_errors.append({"type": "TOOL_UNAVAILABLE", "tool": punctuation.name,
198
+ "message": "missing normalize-punctuation.js"})
157
199
  else:
158
200
  completed = subprocess.run(
159
201
  [node, str(punctuation), "--check", str(body)],
160
202
  text=True, encoding="utf-8", capture_output=True, check=False,
161
203
  )
162
- if completed.returncode != 0:
204
+ if completed.returncode == 1:
163
205
  blocking.append(
164
206
  {"type": "PUNCTUATION_NOT_NORMALIZED", "message": (completed.stdout or completed.stderr).strip()}
165
207
  )
208
+ elif completed.returncode != 0:
209
+ tool_errors.append({"type": "TOOL_ERROR", "tool": punctuation.name,
210
+ "message": completed.stderr.strip() or f"exited {completed.returncode}"})
166
211
 
167
212
  outline_copy = root / "check-outline-copy.js"
168
213
  if not outline_copy.is_file():
169
- blocking.append({"type": "TOOL_UNAVAILABLE", "message": "missing check-outline-copy.js"})
214
+ tool_errors.append({"type": "TOOL_UNAVAILABLE", "tool": outline_copy.name,
215
+ "message": "missing check-outline-copy.js"})
170
216
  else:
171
217
  completed = subprocess.run(
172
218
  [node, str(outline_copy), "--outline", str(outline), str(body)],
@@ -174,28 +220,72 @@ def check_blocking_quality(outline: Path, body: Path) -> dict[str, Any]:
174
220
  )
175
221
  if completed.returncode != 0:
176
222
  advisories.append(
177
- {"source": "outline-copy", "type": "OUTLINE_COPY_REVIEW", "message": completed.stdout.strip()}
223
+ {"source": "outline-copy", "type": "OUTLINE_COPY_REVIEW", "review": "semantic",
224
+ "message": completed.stdout.strip()}
178
225
  )
179
226
  return {
180
- "status": "fail" if blocking else "pass",
227
+ "status": "unavailable" if tool_errors else ("fail" if blocking else "pass"),
181
228
  "blocking_findings": blocking,
182
229
  "advisories": advisories,
230
+ "semantic_advisories": _semantic_advisories(advisories),
231
+ "tool_errors": tool_errors,
183
232
  }
184
233
 
185
234
 
186
- def chapter_check(project: Path, chapter: int) -> dict[str, Any]:
187
- outline, body_path, target = _project_files(project, chapter)
235
+ # chapter check 顶层 status 与退出码:一眼分清「能提交」「要作者定」「正文要改」「工具坏了」。
236
+ CHECK_EXIT_CODES = {"ready": 0, "needs_decision": 0, "blocked": 1, "invalid": 1, "tool_unavailable": 3}
237
+ ERROR_EXIT_CODES = {"TOOL_UNAVAILABLE": 3}
238
+ ACCEPT_FLOOR_RATIO = 0.5
239
+ BASELINE_FILE = "over_length_baseline.json"
240
+
241
+
242
+ class ChapterRefused(WordcountError):
243
+ def __init__(self, code: str, message: str) -> None:
244
+ super().__init__(message)
245
+ self.code = code
246
+
247
+
248
+ def _check_status(length: dict[str, Any], quality: dict[str, Any]) -> str:
249
+ if quality["status"] == "unavailable":
250
+ return "tool_unavailable"
251
+ if quality["status"] != "pass":
252
+ return "blocked"
253
+ if length["status"] == "invalid":
254
+ return "invalid"
255
+ return "ready" if length["status"] in {"internal_pass", "borderline"} else "needs_decision"
256
+
257
+
258
+ def _body_digest(path: Path) -> str:
259
+ return hashlib.sha256(path.read_bytes()).hexdigest()
260
+
261
+
262
+ def _record_over_baseline(project: Path, chapter: int, body_path: Path, actual: int) -> None:
263
+ """第一次看到 over 时记下原稿指纹:接受超长前要证明那一次净删压缩真的做过。"""
264
+ path = chapter_work_dir(project, chapter) / BASELINE_FILE
265
+ try:
266
+ previous = json.loads(path.read_text(encoding="utf-8"))
267
+ except (OSError, UnicodeError, json.JSONDecodeError):
268
+ previous = None
269
+ # 已有基线且这次更短:是压缩后的复检,保留基线;更长则是重写出的新稿,重新记。
270
+ if isinstance(previous, dict) and isinstance(previous.get("actual"), int) and actual <= previous["actual"]:
271
+ return
272
+ path.parent.mkdir(parents=True, exist_ok=True)
273
+ path.write_text(json.dumps({"body_sha256": _body_digest(body_path), "actual": actual}), encoding="utf-8")
274
+
275
+
276
+ def chapter_check(project: Path, chapter: int, author_range: Any = None) -> dict[str, Any]:
277
+ outline, body_path, target, effective_range = _project_files(project, chapter, author_range)
188
278
  try:
189
279
  body = body_path.read_text(encoding="utf-8")
190
280
  except (OSError, UnicodeError) as exc:
191
281
  raise WordcountError(f"unable to read body: {exc}") from exc
192
- length = evaluate_wordcount(body, target, chapter=chapter)
282
+ length = evaluate_wordcount(body, target, chapter=chapter, author_range=effective_range)
193
283
  quality = check_blocking_quality(outline, body_path)
194
- length_ok = length["status"] in {"internal_pass", "borderline"}
195
- if quality["status"] != "pass" or length["status"] == "invalid":
196
- actions: list[str] = []
197
- elif length_ok:
284
+ status = _check_status(length, quality)
285
+ if status == "ready":
198
286
  actions = ["commit"]
287
+ elif status != "needs_decision":
288
+ actions: list[str] = []
199
289
  elif length["status"] == "over":
200
290
  actions = ["compress-once", "accept-current-length", "revise-outline-or-target", "discard"]
201
291
  else:
@@ -216,8 +306,10 @@ def chapter_check(project: Path, chapter: int) -> dict[str, Any]:
216
306
  "max": actual - length["user_band"]["min"],
217
307
  },
218
308
  }
309
+ _record_over_baseline(project, chapter, body_path, actual)
219
310
  return {
220
311
  "schema": CHAPTER_CHECK_SCHEMA,
312
+ "status": status,
221
313
  "chapter": chapter,
222
314
  "length": length,
223
315
  "quality": quality,
@@ -229,43 +321,102 @@ def chapter_check(project: Path, chapter: int) -> dict[str, Any]:
229
321
  }
230
322
 
231
323
 
232
- def fix_punctuation(project: Path, chapter: int) -> bool:
324
+ def fix_punctuation(project: Path, chapter: int, author_range: Any = None) -> bool:
233
325
  """Run the deterministic punctuation normalizer on the chapter body in place.
234
326
 
235
327
  Folding it into `chapter check --fix-punctuation` lets the parent flow close a
236
328
  chapter with one call instead of running each cleanup script separately.
237
329
  """
238
- _, body, _ = _project_files(project, chapter)
330
+ _, body, _, _ = _project_files(project, chapter, author_range)
239
331
  node = shutil.which("node")
240
332
  script = Path(__file__).with_name("normalize-punctuation.js")
241
333
  if node is None or not script.is_file():
242
- return False # chapter check reports TOOL_UNAVAILABLE as a blocking finding
334
+ return False # chapter check reports the missing tool in quality.tool_errors
243
335
  before = body.read_bytes()
244
336
  subprocess.run([node, str(script), str(body)], text=True, encoding="utf-8",
245
337
  capture_output=True, check=False)
246
338
  return body.read_bytes() != before
247
339
 
248
340
 
249
- def chapter_commit(project: Path, chapter: int, input_path: Path, *, accept_current_length: bool) -> dict[str, Any]:
250
- checked = chapter_check(project, chapter)
251
- require(checked["quality"]["status"] == "pass", "blocking quality findings must be fixed before commit")
341
+ def _require_acceptable_length(
342
+ project: Path, chapter: int, checked: dict[str, Any], *, force: bool, author_range: Any = None,
343
+ ) -> None:
344
+ """接受当前长度的两道底线:欠长不低于目标一半;超长先做过一次净删压缩(删掉原稿到作者范围上限
345
+ 差额的至少一半)。作者明确拍板用 --force 越过。"""
346
+ if force:
347
+ return
348
+ length = checked["length"]
349
+ if length["status"] == "under" and length["actual"] < length["target"] * ACCEPT_FLOOR_RATIO:
350
+ raise ChapterRefused(
351
+ "BELOW_ACCEPT_FLOOR",
352
+ f"本章 {length['actual']} 字,不到字数目标 {length['target']} 的一半,不能直接按当前长度接受;"
353
+ "先补细纲或改字数目标,作者明确要这个长度时加 --force",
354
+ )
355
+ if length["status"] == "over":
356
+ path = chapter_work_dir(project, chapter) / BASELINE_FILE
357
+ try:
358
+ baseline = json.loads(path.read_text(encoding="utf-8"))
359
+ except (OSError, UnicodeError, json.JSONDecodeError):
360
+ baseline = None
361
+ _, body_path, _, _ = _project_files(project, chapter, author_range)
362
+ valid = isinstance(baseline, dict) and isinstance(baseline.get("actual"), int)
363
+ # 压缩要真删:至少删掉原稿到作者字数范围上限差额的一半(至少 1 字),删一两个字不算做过。
364
+ ceiling = None
365
+ if valid:
366
+ gap = baseline["actual"] - length["user_band"]["max"]
367
+ ceiling = baseline["actual"] - max(1, (gap + 1) // 2)
368
+ compressed = (
369
+ valid
370
+ and baseline.get("body_sha256") != _body_digest(body_path)
371
+ and length["actual"] <= ceiling
372
+ )
373
+ if not compressed:
374
+ detail = (f"(原稿 {baseline['actual']} 字,压缩后要不超过 {ceiling} 字,现在 {length['actual']} 字)"
375
+ if valid else "")
376
+ raise ChapterRefused(
377
+ "COMPRESSION_REQUIRED",
378
+ f"超长章节先按 compress-once 做一次净删压缩并重跑 chapter check{detail},仍超长再接受当前长度;"
379
+ "作者明确不压缩时加 --force",
380
+ )
381
+
382
+
383
+ def chapter_commit(
384
+ project: Path, chapter: int, input_path: Path, *, accept_current_length: bool,
385
+ author_range: Any = None, force: bool = False,
386
+ ) -> dict[str, Any]:
387
+ checked = chapter_check(project, chapter, author_range)
388
+ if checked["status"] == "tool_unavailable":
389
+ raise ChapterRefused(
390
+ "TOOL_UNAVAILABLE",
391
+ ";".join(item["message"] for item in checked["quality"]["tool_errors"]) or NODE_REQUIRED,
392
+ )
393
+ if checked["quality"]["status"] != "pass":
394
+ raise ChapterRefused("QUALITY_BLOCKED", "blocking quality findings must be fixed before commit")
252
395
  length_status = checked["length"]["status"]
253
396
  in_user_band = length_status in {"internal_pass", "borderline"}
254
397
  if accept_current_length:
255
398
  require(length_status in {"under", "over"}, "accept-current-length requires a valid out-of-band chapter")
399
+ _require_acceptable_length(project, chapter, checked, force=force, author_range=author_range)
256
400
  resolution = "accepted_current_length"
257
401
  else:
258
- require(in_user_band, "chapter length is outside the user band; use accept-current-length or revise it")
402
+ if not in_user_band:
403
+ raise ChapterRefused(
404
+ "LENGTH_OUT_OF_BAND",
405
+ "chapter length is outside the user band; use accept-current-length or revise it",
406
+ )
259
407
  resolution = "within_user_band"
260
408
  document = _read_json_object(input_path, "tracking transaction")
261
409
  require(document.get("chapter") == chapter, "tracking transaction chapter does not match command")
262
410
  require("wordcount" not in document, "tracking transaction must not provide wordcount")
263
- document["wordcount"] = build_project_wordcount_record(project, chapter, resolution=resolution)
411
+ document["wordcount"] = build_project_wordcount_record(
412
+ project, chapter, resolution=resolution, author_range=author_range,
413
+ )
264
414
  tracking = _tracking_module()
265
415
  state = _tracking_call(tracking.apply_transaction, project, document)
266
416
  checked["tracking_committed"] = state["last_committed_chapter"] >= chapter
267
417
  checked["next_chapter_started"] = state["last_committed_chapter"] > chapter
268
418
  checked["wordcount"] = state["wordcount_records"].get(str(chapter))
419
+ checked["mode"] = document.get("mode")
269
420
  # The commit is already durable; a cleanup failure is reported, never turned into a failed commit.
270
421
  try:
271
422
  checked["work_dir_removed"] = _remove_chapter_work_dir(project, chapter)
@@ -286,22 +437,47 @@ def _build_parser() -> StructuredArgumentParser:
286
437
  subparser.add_argument("--chapter")
287
438
  subparser.add_argument("--case-id")
288
439
  if command != "measure":
289
- subparser.add_argument("--target", required=True)
440
+ subparser.add_argument("--target", required=command == "check")
441
+ _add_range_arguments(subparser)
442
+ if command == "checkpoint":
443
+ subparser.add_argument("--project", type=Path,
444
+ help="书目录:与 --chapter 一起从细纲读字数目标(未给 --target 时)和「字数范围」")
290
445
  chapter = commands.add_parser("chapter")
291
446
  chapter_commands = chapter.add_subparsers(dest="chapter_command", required=True)
292
447
  for command in ("check", "commit", "accept-current-length"):
293
448
  subparser = chapter_commands.add_parser(command)
294
449
  subparser.add_argument("--project", type=Path, required=True)
295
450
  subparser.add_argument("--chapter", type=int, required=True)
451
+ _add_range_arguments(subparser)
296
452
  if command != "check":
297
453
  subparser.add_argument("--input", type=Path, required=True)
298
454
  else:
299
455
  subparser.add_argument("--fix-punctuation", action="store_true",
300
456
  help="先就地整理正文标点,再做检查")
457
+ if command == "accept-current-length":
458
+ subparser.add_argument("--force", action="store_true",
459
+ help="作者明确拍板:越过欠长一半下限与超长先压缩一次的要求")
301
460
  return parser
302
461
 
303
462
 
304
- def _wordcount_command(args: argparse.Namespace) -> int:
463
+ def _add_range_arguments(subparser: argparse.ArgumentParser) -> None:
464
+ subparser.add_argument("--min-chars", type=int, help="作者本轮给定的字数下限(须与 --max-chars 同给),优先于细纲字数范围与默认 ±15%%")
465
+ subparser.add_argument("--max-chars", type=int, help="作者本轮给定的字数上限")
466
+
467
+
468
+ def _cli_author_range(args: argparse.Namespace) -> dict[str, int] | None:
469
+ low, high = getattr(args, "min_chars", None), getattr(args, "max_chars", None)
470
+ if low is None and high is None:
471
+ return None
472
+ if low is None or high is None:
473
+ raise CliArgumentError("--min-chars and --max-chars must be given together")
474
+ try:
475
+ return normalize_author_range({"min": low, "max": high})
476
+ except WordcountError as exc:
477
+ raise CliArgumentError(str(exc)) from exc
478
+
479
+
480
+ def _wordcount_command(args: argparse.Namespace, author_range: dict[str, int] | None) -> int:
305
481
  try:
306
482
  body = Path(args.file).read_text(encoding="utf-8")
307
483
  except (OSError, UnicodeError):
@@ -316,12 +492,31 @@ def _wordcount_command(args: argparse.Namespace) -> int:
316
492
  if args.wordcount_command == "measure":
317
493
  result = measure_wordcount(body, chapter=args.chapter, case_id=args.case_id)
318
494
  elif args.wordcount_command == "checkpoint":
495
+ target = args.target
496
+ if args.project is not None:
497
+ # 与 chapter check 同一优先级:命令行上下限 > 细纲「字数范围」 > 默认带。
498
+ try:
499
+ chapter = int(args.chapter or "")
500
+ _, outline = read_chapter_outline(args.project, chapter)
501
+ if target is None:
502
+ target = target_from_outline(outline)
503
+ author_range = effective_author_range(outline, author_range)
504
+ except (ValueError, WordcountError):
505
+ _json_line(invalid_wordcount_result("INVALID_OUTLINE", chapter=args.chapter, case_id=args.case_id))
506
+ return 2
507
+ if target is None:
508
+ _json_line(invalid_wordcount_result("INVALID_ARGUMENT", chapter=args.chapter, case_id="--target or --project is required"))
509
+ return 2
319
510
  try:
320
- result = checkpoint_wordcount(body, args.target, chapter=args.chapter, case_id=args.case_id)
511
+ result = checkpoint_wordcount(
512
+ body, target, chapter=args.chapter, case_id=args.case_id, author_range=author_range,
513
+ )
321
514
  except WordcountError:
322
515
  result = invalid_wordcount_result("INVALID_TARGET", chapter=args.chapter, case_id=args.case_id)
323
516
  else:
324
- result = evaluate_wordcount(body, args.target, chapter=args.chapter, case_id=args.case_id)
517
+ result = evaluate_wordcount(
518
+ body, args.target, chapter=args.chapter, case_id=args.case_id, author_range=author_range,
519
+ )
325
520
  _json_line(result)
326
521
  return 2 if result.get("status") == "invalid" else 0
327
522
 
@@ -330,31 +525,36 @@ def main(argv: Sequence[str] | None = None) -> int:
330
525
  raw = list(argv) if argv is not None else sys.argv[1:]
331
526
  try:
332
527
  args = _build_parser().parse_args(raw)
528
+ author_range = _cli_author_range(args)
333
529
  except CliArgumentError as exc:
334
530
  payload = (
335
- {"schema": CHAPTER_ERROR_SCHEMA, "error_code": "INVALID_ARGUMENT", "message": str(exc)}
531
+ {"schema": CHAPTER_ERROR_SCHEMA, "status": "error", "error_code": "INVALID_ARGUMENT", "message": str(exc)}
336
532
  if raw[:1] == ["chapter"] else invalid_wordcount_result("INVALID_ARGUMENT", case_id=str(exc))
337
533
  )
338
534
  _json_line(payload)
339
535
  return 2
340
536
  if args.command == "wordcount":
341
- return _wordcount_command(args)
537
+ return _wordcount_command(args, author_range)
342
538
  try:
343
539
  if args.chapter_command == "check":
344
- fixed = fix_punctuation(args.project, args.chapter) if args.fix_punctuation else None
345
- result = chapter_check(args.project, args.chapter)
540
+ fixed = fix_punctuation(args.project, args.chapter, author_range) if args.fix_punctuation else None
541
+ result = chapter_check(args.project, args.chapter, author_range)
346
542
  if fixed is not None:
347
543
  result["punctuation_fixed"] = fixed
348
544
  else:
349
545
  result = chapter_commit(
350
546
  args.project, args.chapter, args.input,
351
547
  accept_current_length=args.chapter_command == "accept-current-length",
548
+ author_range=author_range, force=getattr(args, "force", False),
352
549
  )
353
550
  except (WordcountError, OSError, UnicodeError) as exc:
354
- _json_line({"schema": CHAPTER_ERROR_SCHEMA, "error_code": "CHECK_FAILED", "message": str(exc)})
355
- return 2
551
+ code = exc.code if isinstance(exc, ChapterRefused) else "CHECK_FAILED"
552
+ _json_line({"schema": CHAPTER_ERROR_SCHEMA, "status": "error", "error_code": code, "message": str(exc)})
553
+ return ERROR_EXIT_CODES.get(code, 2)
356
554
  _json_line(result)
357
- return 2 if result["quality"]["status"] != "pass" else 0
555
+ if args.chapter_command != "check":
556
+ return 0
557
+ return CHECK_EXIT_CODES[result["status"]]
358
558
 
359
559
 
360
560
  if __name__ == "__main__":