@oh-story/dsh 0.1.9 → 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 (197) hide show
  1. package/README.md +12 -12
  2. package/lib/client.js +204 -72
  3. package/lib/client.js.map +3 -3
  4. package/lib/drama/manifest.json +84 -74
  5. package/lib/drama/skills/short-drama/SKILL.md +2 -2
  6. package/lib/drama/skills/short-drama/scripts/creator_markdown_check.py +14 -0
  7. package/lib/drama/skills/short-drama-assets/references/character-and-look.md +10 -0
  8. package/lib/drama/skills/short-drama-assets/references/continuity-lock.md +6 -9
  9. package/lib/drama/skills/short-drama-assets/references/voice-direction.md +28 -0
  10. package/lib/drama/skills/short-drama-edit/SKILL.md +27 -29
  11. package/lib/drama/skills/short-drama-edit/assets//345/211/252/350/276/221/345/215/225.md +2 -2
  12. package/lib/drama/skills/short-drama-edit/references/cut-craft.md +24 -67
  13. package/lib/drama/skills/short-drama-edit/references/delivery-verify.md +27 -56
  14. package/lib/drama/skills/short-drama-edit/references/generated-footage.md +37 -79
  15. package/lib/drama/skills/short-drama-edit/references/sound-and-subtitles.md +39 -92
  16. package/lib/drama/skills/short-drama-edit/references/stage-contract.md +12 -16
  17. package/lib/drama/skills/short-drama-edit/scripts/edit_tool.py +126 -11
  18. package/lib/drama/skills/short-drama-edit/scripts/selftest.py +64 -0
  19. package/lib/drama/skills/short-drama-image-prompts/SKILL.md +2 -0
  20. package/lib/drama/skills/short-drama-image-prompts/references/common-recipe.md +11 -0
  21. package/lib/drama/skills/short-drama-produce/SKILL.md +5 -1
  22. package/lib/drama/skills/short-drama-produce/references/adapter-contract.md +14 -20
  23. package/lib/drama/skills/short-drama-produce/references/providers/minimax-h3-video.md +8 -26
  24. package/lib/drama/skills/short-drama-produce/references/providers/minimax-speech.md +41 -0
  25. package/lib/drama/skills/short-drama-produce/scripts/provider_adapters.py +142 -1
  26. package/lib/drama/skills/short-drama-review/SKILL.md +2 -3
  27. package/lib/drama/skills/short-drama-review/references/production-quality-gates.md +6 -19
  28. package/lib/drama/skills/short-drama-review/references/rubric-visual-motion.md +9 -35
  29. package/lib/drama/skills/short-drama-storyboard/SKILL.md +10 -6
  30. package/lib/drama/skills/short-drama-storyboard/references/lighting-craft.md +39 -0
  31. package/lib/drama/skills/short-drama-storyboard/references/shot-craft.md +58 -21
  32. package/lib/drama/skills/short-drama-storyboard/references/stage-contract.md +1 -1
  33. package/lib/drama/skills/short-drama-video-prompts/SKILL.md +10 -9
  34. package/lib/drama/skills/short-drama-video-prompts/references/camera-audio-continuity.md +17 -0
  35. package/lib/drama/skills/short-drama-video-prompts/references/minimax-h3.md +12 -38
  36. package/lib/drama/skills/short-drama-video-prompts/references/motion-recipe.md +4 -7
  37. package/lib/drama/skills/short-drama-video-prompts/references/performance-action-timing.md +8 -22
  38. package/lib/drama/skills/short-drama-video-prompts/references/production-prompt-grammar.md +6 -19
  39. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.0.md +2 -3
  40. package/lib/drama/skills/short-drama-video-prompts/references/seedance-2.5.md +34 -9
  41. package/lib/drama/skills/short-drama-video-prompts/references/stage-contract.md +2 -2
  42. package/lib/drama/skills/short-drama-write/SKILL.md +2 -4
  43. package/lib/index.js +289 -91
  44. package/lib/index.js.map +4 -4
  45. package/lib/novel-to-game/README.md +299 -59
  46. package/lib/novel-to-game/README_ZH.md +299 -56
  47. package/lib/novel-to-game/VERSION +1 -1
  48. package/lib/novel-to-game/examples/jin-ping-mei/build/BUILD_BRIEF.md +3 -1
  49. package/lib/novel-to-game/examples/jin-ping-mei/build/app/RUN.md +11 -3
  50. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/new-guofeng.css +397 -8
  51. package/lib/novel-to-game/examples/jin-ping-mei/build/app/css/style.css +0 -31
  52. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/data.js +71 -73
  53. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/engine.js +78 -89
  54. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/main.js +37 -14
  55. package/lib/novel-to-game/examples/jin-ping-mei/build/app/js/text.js +5 -5
  56. package/lib/novel-to-game/examples/jin-ping-mei/build/app/package.json +2 -1
  57. package/lib/novel-to-game/examples/jin-ping-mei/qa/README.md +11 -16
  58. package/lib/novel-to-game/examples/jin-ping-mei/qa/verification.json +11 -11
  59. package/lib/novel-to-game/examples/jin-ping-mei/qa/verify_playable_model.mjs +23 -0
  60. package/lib/novel-to-game/manifest.json +77 -87
  61. package/lib/novel-to-game/skills/game-art-direction/SKILL.md +7 -12
  62. package/lib/novel-to-game/skills/game-art-direction/references/art-direction-method.md +27 -106
  63. package/lib/novel-to-game/skills/game-build/SKILL.md +13 -14
  64. package/lib/novel-to-game/skills/game-build/references/blender-asset-workflow.md +24 -0
  65. package/lib/novel-to-game/skills/game-build/references/build-brief-contract.md +45 -82
  66. package/lib/novel-to-game/skills/game-build/references/generative-media-pipeline.md +14 -45
  67. package/lib/novel-to-game/skills/game-build/references/playable-model-contract.md +30 -65
  68. package/lib/novel-to-game/skills/game-build/references/tts-production-contract.md +13 -27
  69. package/lib/novel-to-game/skills/game-concept/SKILL.md +13 -15
  70. package/lib/novel-to-game/skills/game-concept/references/concept-method.md +28 -75
  71. package/lib/novel-to-game/skills/game-qa/SKILL.md +6 -7
  72. package/lib/novel-to-game/skills/game-qa/references/test-design-method.md +9 -5
  73. package/lib/novel-to-game/skills/game-world-design/SKILL.md +12 -16
  74. package/lib/novel-to-game/skills/game-world-design/references/narrative-design-method.md +23 -72
  75. package/lib/novel-to-game/skills/game-world-design/references/numeric-design-method.md +14 -48
  76. package/lib/novel-to-game/skills/game-world-design/references/world-design-method.md +36 -103
  77. package/lib/novel-to-game/skills/novel-game-analyze/SKILL.md +4 -10
  78. package/lib/novel-to-game/skills/novel-game-analyze/references/gameability-protocol.md +4 -4
  79. package/lib/novel-to-game/skills/novel-to-game/SKILL.md +9 -21
  80. package/lib/novel-to-game/skills/novel-to-game/references/intake-method.md +5 -4
  81. package/lib/novel-to-game/skills/novel-to-game/references/pipeline-contract.md +5 -8
  82. package/lib/oh-story/manifest.json +230 -185
  83. package/lib/oh-story/roles/chapter-extractor.md +177 -237
  84. package/lib/oh-story/roles/consistency-checker.md +4 -4
  85. package/lib/oh-story/roles/narrative-writer.md +24 -25
  86. package/lib/oh-story/roles/story-architect.md +5 -5
  87. package/lib/oh-story/roles/story-explorer.md +11 -11
  88. package/lib/oh-story/skills/story/SKILL.md +13 -7
  89. package/lib/oh-story/skills/story/VERSION +1 -1
  90. package/lib/oh-story/skills/story/references/author-memory.md +54 -24
  91. package/lib/oh-story/skills/story/scripts/author_memory_commit.py +934 -170
  92. package/lib/oh-story/skills/story-cover/SKILL.md +10 -1
  93. package/lib/oh-story/skills/story-deslop/SKILL.md +40 -53
  94. package/lib/oh-story/skills/story-deslop/references/anti-ai-writing.md +11 -10
  95. package/lib/oh-story/skills/story-deslop/references/author-memory.md +54 -24
  96. package/lib/oh-story/skills/story-deslop/references/deslop-gates.md +1 -1
  97. package/lib/oh-story/skills/story-deslop/scripts/author_memory_commit.py +934 -170
  98. package/lib/oh-story/skills/story-import/SKILL.md +77 -81
  99. package/lib/oh-story/skills/story-import/references/character-state-reverse.md +1 -1
  100. package/lib/oh-story/skills/story-import/references/state-tracking.md +4 -69
  101. package/lib/oh-story/skills/story-import/references/structure-mapping-long.md +10 -8
  102. package/lib/oh-story/skills/story-import/references/tracking-transaction.md +8 -6
  103. package/lib/oh-story/skills/story-import/scripts/check-outline-contract.js +34 -16
  104. package/lib/oh-story/skills/story-import/scripts/tracking_commit.py +210 -2
  105. package/lib/oh-story/skills/story-import/scripts/wordcount_core.py +3 -0
  106. package/lib/oh-story/skills/story-long-analyze/SKILL.md +91 -258
  107. package/lib/oh-story/skills/story-long-analyze/references/author-facing.md +245 -0
  108. package/lib/oh-story/skills/story-long-analyze/references/deconstruction-notes.md +2 -2
  109. package/lib/oh-story/skills/story-long-analyze/references/inspiration-library.md +105 -0
  110. package/lib/oh-story/skills/story-long-analyze/references/material-decomposition.md +113 -105
  111. package/lib/oh-story/skills/story-long-analyze/references/output-templates.md +169 -125
  112. package/lib/oh-story/skills/story-long-analyze/references/pipeline-ops.md +172 -64
  113. package/lib/oh-story/skills/story-long-analyze/references/semantic-acceptance-fixtures.md +84 -0
  114. package/lib/oh-story/skills/story-long-analyze/references/style-profile-generator.md +42 -132
  115. package/lib/oh-story/skills/story-long-analyze/references/style-profile-protocol.md +5 -5
  116. package/lib/oh-story/skills/story-long-analyze/scripts/build_chapter_index.py +466 -0
  117. package/lib/oh-story/skills/story-long-analyze/scripts/inspect_existing_assets.py +702 -0
  118. package/lib/oh-story/skills/story-long-analyze/scripts/inspiration_index.py +864 -0
  119. package/lib/oh-story/skills/story-long-analyze/scripts/manage_analysis_run.py +1106 -0
  120. package/lib/oh-story/skills/story-long-analyze/scripts/render_relation_chart.py +425 -0
  121. package/lib/oh-story/skills/story-long-scan/SKILL.md +4 -1
  122. package/lib/oh-story/skills/story-long-write/SKILL.md +38 -29
  123. package/lib/oh-story/skills/story-long-write/references/anti-ai-writing.md +11 -10
  124. package/lib/oh-story/skills/story-long-write/references/artifact-protocols.md +25 -20
  125. package/lib/oh-story/skills/story-long-write/references/author-memory.md +54 -24
  126. package/lib/oh-story/skills/story-long-write/references/benchmark-recall.md +3 -3
  127. package/lib/oh-story/skills/story-long-write/references/cross-book-recall.md +26 -0
  128. package/lib/oh-story/skills/story-long-write/references/dialogue-mastery.md +4 -0
  129. package/lib/oh-story/skills/story-long-write/references/emotional-methods.md +2 -2
  130. package/lib/oh-story/skills/story-long-write/references/long-chapter-quality.md +4 -13
  131. package/lib/oh-story/skills/story-long-write/references/outline-structure-theory.md +1 -1
  132. package/lib/oh-story/skills/story-long-write/references/project-files.md +9 -7
  133. package/lib/oh-story/skills/story-long-write/references/reader-contract-and-progression.md +12 -4
  134. package/lib/oh-story/skills/story-long-write/references/reference-index.md +1 -2
  135. package/lib/oh-story/skills/story-long-write/references/tracking-transaction.md +8 -6
  136. package/lib/oh-story/skills/story-long-write/references/workflow-chapter.md +86 -115
  137. package/lib/oh-story/skills/story-long-write/references/workflow-daily.md +55 -120
  138. package/lib/oh-story/skills/story-long-write/references/workflow-revision.md +15 -11
  139. package/lib/oh-story/skills/story-long-write/references/workflow-setup.md +66 -68
  140. package/lib/oh-story/skills/story-long-write/references/writer-prompt-fallback.md +3 -3
  141. package/lib/oh-story/skills/story-long-write/references/writing-craft.md +4 -12
  142. package/lib/oh-story/skills/story-long-write/scripts/author_memory_commit.py +934 -170
  143. package/lib/oh-story/skills/story-long-write/scripts/build_writer_prompt.py +139 -18
  144. package/lib/oh-story/skills/story-long-write/scripts/check-outline-contract.js +34 -16
  145. package/lib/oh-story/skills/story-long-write/scripts/outline_view.py +5 -3
  146. package/lib/oh-story/skills/story-long-write/scripts/storyctl.py +58 -0
  147. package/lib/oh-story/skills/story-long-write/scripts/tracking_commit.py +210 -2
  148. package/lib/oh-story/skills/story-long-write/scripts/wordcount_core.py +3 -0
  149. package/lib/oh-story/skills/story-review/SKILL.md +68 -85
  150. package/lib/oh-story/skills/story-review/references/anti-ai-writing.md +11 -10
  151. package/lib/oh-story/skills/story-review/references/author-memory.md +54 -24
  152. package/lib/oh-story/skills/story-review/references/dialogue-mastery.md +4 -0
  153. package/lib/oh-story/skills/story-review/references/review-quality.md +4 -10
  154. package/lib/oh-story/skills/story-review/references/tracking-transaction.md +8 -6
  155. package/lib/oh-story/skills/story-review/scripts/author_memory_commit.py +934 -170
  156. package/lib/oh-story/skills/story-review/scripts/tracking_commit.py +210 -2
  157. package/lib/oh-story/skills/story-review/scripts/wordcount_core.py +3 -0
  158. package/lib/oh-story/skills/story-setup/SKILL.md +50 -42
  159. package/lib/oh-story/skills/story-setup/references/agent-references/anti-ai-writing.md +11 -10
  160. package/lib/oh-story/skills/story-setup/references/agent-references/deslop-gates.md +1 -1
  161. package/lib/oh-story/skills/story-setup/references/agent-references/dialogue-mastery.md +4 -0
  162. package/lib/oh-story/skills/story-setup/references/agent-references/long-emotional-methods.md +2 -2
  163. package/lib/oh-story/skills/story-setup/references/agent-references/long-quality.md +1 -1
  164. package/lib/oh-story/skills/story-setup/references/agent-references/writing-craft.md +4 -12
  165. package/lib/oh-story/skills/story-setup/references/diagnostics.md +1 -1
  166. package/lib/oh-story/skills/story-short-analyze/SKILL.md +22 -3
  167. package/lib/oh-story/skills/story-short-analyze/references/material-decomposition.md +5 -1
  168. package/lib/oh-story/skills/story-short-scan/SKILL.md +5 -1
  169. package/lib/oh-story/skills/story-short-write/SKILL.md +6 -147
  170. package/lib/oh-story/skills/story-short-write/references/author-memory.md +54 -24
  171. package/lib/oh-story/skills/story-short-write/references/dialogue-mastery.md +4 -0
  172. package/lib/oh-story/skills/story-short-write/references/reference-index.md +2 -0
  173. package/lib/oh-story/skills/story-short-write/references/workflow-design.md +3 -2
  174. package/lib/oh-story/skills/story-short-write/references/workflow-draft.md +126 -0
  175. package/lib/oh-story/skills/story-short-write/references/workflow-revision.md +32 -0
  176. package/lib/oh-story/skills/story-short-write/scripts/author_memory_commit.py +934 -170
  177. package/lib/types/client/drama-production-view.d.ts.map +1 -1
  178. package/lib/types/client/file-activity.d.ts +19 -1
  179. package/lib/types/client/file-activity.d.ts.map +1 -1
  180. package/lib/types/client/index.d.ts.map +1 -1
  181. package/lib/types/client/production-prompts.d.ts +7 -0
  182. package/lib/types/client/production-prompts.d.ts.map +1 -1
  183. package/lib/types/client/production-runtime.d.ts +16 -0
  184. package/lib/types/client/production-runtime.d.ts.map +1 -1
  185. package/lib/types/drama-adapters.d.ts +5 -3
  186. package/lib/types/drama-adapters.d.ts.map +1 -1
  187. package/lib/types/native-hooks.d.ts +52 -0
  188. package/lib/types/native-hooks.d.ts.map +1 -1
  189. package/lib/types/role-identity.d.ts +17 -0
  190. package/lib/types/role-identity.d.ts.map +1 -0
  191. package/lib/types/role-tool.d.ts.map +1 -1
  192. package/lib/types/skill-provider.d.ts.map +1 -1
  193. package/package.json +45 -45
  194. package/lib/novel-to-game/skills/game-world-design/references/dialogue-design-method.md +0 -58
  195. package/lib/novel-to-game/skills/game-world-design/references/game-writing-craft.md +0 -53
  196. package/lib/novel-to-game/skills/novel-to-game/references/intake-benchmark-reference.md +0 -38
  197. package/lib/oh-story/skills/story-long-write/references/state-tracking.md +0 -108
@@ -117,11 +117,26 @@ def require_known_keys(mapping: dict[str, Any], allowed: set[str], label: str) -
117
117
  require(not unknown, f"{label} contains unsupported fields: {', '.join(sorted(unknown))}")
118
118
 
119
119
 
120
+ # 事务校验期间收集全部超长字段,一次报完;None 表示逐条立即报错(初始化、状态读取等路径)。
121
+ _LENGTH_ISSUES: list[str] | None = None
122
+
123
+
124
+ def length_hint(label: str, text: str, max_bytes: int) -> str:
125
+ """把字节上限换算成字数:模型写的是中文,只看得懂「要删几个字」。"""
126
+ size = len(text.encode("utf-8"))
127
+ allowed = max(1, max_bytes * len(text) // size)
128
+ return (f"{label} exceeds {max_bytes} bytes(现 {len(text)} 字,上限约 {allowed} 字,"
129
+ f"至少删 {len(text) - allowed} 字)")
130
+
131
+
120
132
  def clean_text(value: object, label: str, *, allow_empty: bool = False, max_bytes: int = 768) -> str:
121
133
  require(isinstance(value, str), f"{label} must be a string")
122
134
  cleaned = " ".join(value.replace("|", "|").split())
123
135
  require(allow_empty or bool(cleaned), f"{label} must not be empty")
124
- require(len(cleaned.encode("utf-8")) <= max_bytes, f"{label} exceeds {max_bytes} bytes")
136
+ if len(cleaned.encode("utf-8")) > max_bytes:
137
+ if _LENGTH_ISSUES is None:
138
+ raise TrackingError(length_hint(label, cleaned, max_bytes))
139
+ _LENGTH_ISSUES.append(length_hint(label, cleaned, max_bytes))
125
140
  return cleaned
126
141
 
127
142
 
@@ -277,9 +292,15 @@ def archive_retired_tracking_paths(tracking: Path) -> list[str]:
277
292
  return retired
278
293
 
279
294
 
295
+ def _both_blank(position: dict[str, Any]) -> bool:
296
+ return all(isinstance(position.get(key), str) and not position[key].strip() for key in ("story_time", "scene"))
297
+
298
+
280
299
  def validate_position(value: object, label: str = "context.position") -> dict[str, Any]:
281
300
  position = as_mapping(value, label)
282
301
  require_known_keys(position, {"volume", "volume_start_chapter", "story_time", "scene"}, label)
302
+ require(not _both_blank(position),
303
+ f"{label}.story_time and {label}.scene must not be empty: fill where this chapter ends")
283
304
  return {
284
305
  "volume": safe_file_component(position.get("volume"), f"{label}.volume"),
285
306
  "volume_start_chapter": as_int(
@@ -781,7 +802,13 @@ def render_delta(chapter: int, title: str, delta: dict[str, Any], core_names: se
781
802
  lines.extend(f"- {item}" for item in retired)
782
803
  payload = "\n".join(lines) + "\n"
783
804
  size = byte_size(payload)
784
- require(size <= DELTA_MAX_BYTES, f"chapter delta is {size} bytes; hard cap is {DELTA_MAX_BYTES}")
805
+ if size > DELTA_MAX_BYTES:
806
+ longest = sorted((line for line in lines if line.startswith("- ")), key=len, reverse=True)[:3]
807
+ allowed = DELTA_MAX_BYTES * len(payload) // size
808
+ raise TrackingError(
809
+ f"chapter delta is {size} bytes; hard cap is {DELTA_MAX_BYTES}(逐章记录现 {len(payload)} 字,"
810
+ f"上限约 {allowed} 字,至少压缩 {len(payload) - allowed} 字;最长几项:"
811
+ + ";".join(f"{line[2:18]}…({len(line) - 2} 字)" for line in longest) + ")")
785
812
  return payload
786
813
 
787
814
 
@@ -901,6 +928,17 @@ def normalize_initial_document(document: object) -> dict[str, Any]:
901
928
 
902
929
 
903
930
  def normalize_transaction(project: Path, state: dict[str, Any], document: object) -> dict[str, Any]:
931
+ global _LENGTH_ISSUES
932
+ _LENGTH_ISSUES = []
933
+ try:
934
+ transaction = _normalize_transaction(project, state, document)
935
+ finally:
936
+ issues, _LENGTH_ISSUES = _LENGTH_ISSUES, None
937
+ require(not issues, f"{len(issues)} 个字段超长,一次改完再提交:" + ";".join(issues))
938
+ return transaction
939
+
940
+
941
+ def _normalize_transaction(project: Path, state: dict[str, Any], document: object) -> dict[str, Any]:
904
942
  root = as_mapping(document, "transaction")
905
943
  require_known_keys(
906
944
  root,
@@ -999,6 +1037,13 @@ def merge_transaction(state: dict[str, Any], transaction: dict[str, Any]) -> dic
999
1037
  "a revision must resubmit every current context item; retire them in an append transaction instead: "
1000
1038
  + ";".join(sorted(dropped)),
1001
1039
  )
1040
+ kept = set(next_context["long_term_constraints"]) | set(next_context["continuity_risks"])
1041
+ still_listed = sorted(set(transaction["delta"]["retired_context_items"]) & kept)
1042
+ require(
1043
+ not still_listed,
1044
+ "delta.retired_context_items lists items that are still in context; remove them from context: "
1045
+ + ";".join(still_listed),
1046
+ )
1002
1047
  undeclared = sorted(dropped - set(transaction["delta"]["retired_context_items"]))
1003
1048
  require(
1004
1049
  not undeclared,
@@ -1188,6 +1233,126 @@ def check_project(project: Path) -> dict[str, Any]:
1188
1233
  return state
1189
1234
 
1190
1235
 
1236
+ # 事务草稿里各文本字段的字数上限(按全中文估:UTF-8 每字 3 字节),写进提示,免得模型反复试错。
1237
+ DRAFT_LIMITS = {
1238
+ "delta.result": 160, "character_changes[].change": 120, "foreshadow_changes[].summary": 120,
1239
+ "timeline_events[].objective_fact / reader_knowledge": 160, "context.position.story_time / scene": 80,
1240
+ "character_snapshots.*.goal / state": 100, "逐章记录总量": 1024,
1241
+ }
1242
+
1243
+
1244
+ def draft_transaction(project: Path, chapter: int) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]:
1245
+ """按当前 state 预填一份逐章事务:修订号、模式、章名和整份提交的上下文当前值都填好,
1246
+ 调用方只写本章变化。另返回在场核心角色的当前快照,供有变化时整份改写后放进事务。
1247
+ 新章(append)的故事时间与场景留空:它们必须是本章结束时的位置,沿用上一章的值能静默提交,
1248
+ 所以不预填,上一章的值另行返回作参考。"""
1249
+ state = load_state(project)
1250
+ last = state["last_committed_chapter"]
1251
+ require(1 <= chapter <= last + 1, f"draft chapter must be between 1 and {last + 1}")
1252
+ mode = "append" if chapter == last + 1 else "revision"
1253
+ title = ""
1254
+ for outline in sorted((project / "大纲").glob(f"细纲_第{chapter:03d}章*.md")):
1255
+ match = re.search(rf"^#{{1,4}}\s*第\s*0*{chapter}\s*章\s*[::]\s*(.+?)\s*$",
1256
+ outline.read_text(encoding="utf-8"), re.M)
1257
+ if match:
1258
+ title = match.group(1).strip()
1259
+ break
1260
+ context = json.loads(json.dumps(state["context"], ensure_ascii=False))
1261
+ previous_position = {key: context["position"][key] for key in ("story_time", "scene")}
1262
+ delta: dict[str, Any] = {
1263
+ "result": "", "character_changes": [], "foreshadow_changes": [], "timeline_events": [],
1264
+ "constraints": [], "next_chapter_commitments": [],
1265
+ }
1266
+ if mode == "append":
1267
+ delta.update({"retired_context_items": [], "retired_characters": []})
1268
+ context["position"].update({"story_time": "", "scene": ""})
1269
+ document = {
1270
+ "schema_version": INPUT_SCHEMA_VERSION,
1271
+ "mode": mode,
1272
+ "chapter": chapter,
1273
+ "chapter_title": title,
1274
+ "expected_state_revision": state["state_revision"],
1275
+ "delta": delta,
1276
+ "context": {key: context[key] for key in
1277
+ ("position", "long_term_constraints", "active_character_names", "continuity_risks")},
1278
+ "character_snapshots": {},
1279
+ }
1280
+ snapshots = {name: state["characters"][name] for name in context["active_character_names"]
1281
+ if name in state["characters"]}
1282
+ return document, snapshots, previous_position if mode == "append" else {}
1283
+
1284
+
1285
+ def rebuild_stale_draft(document: dict[str, Any], existing: dict[str, Any], append: bool,
1286
+ current_characters: Any = ()) -> str:
1287
+ """修订号变了:中间提交过别的事务。以当前状态为底,只自动合并确定安全的部分,其余列给调用方核对。
1288
+
1289
+ 新章草稿之间只可能插进修订事务,修订不能删约束与风险条目,所以当前约束与风险覆盖旧底稿:
1290
+ 旧草稿多出来的就是本章新增,delta 里声明退役的就是本章删除,可以精确合并。
1291
+ 修订草稿之间可能插进新章(新章能退役条目),在场角色名修订也能删,都无法判断谁新:
1292
+ 取当前状态,差异列出来;只有本章带了快照的新角色名自动加回。"""
1293
+ def items(value: object) -> list[str]:
1294
+ return [item for item in value if isinstance(item, str)] if isinstance(value, list) else []
1295
+
1296
+ def norm(item: str) -> str:
1297
+ return " ".join(item.replace("|", "|").split())
1298
+
1299
+ notes = ["修订号已变:context 按当前状态重建,本章已填的 delta" + ("、新增与退役的约束、结尾位置" if append else "")
1300
+ + "已合并"]
1301
+ for key in ("delta", "character_snapshots"):
1302
+ if isinstance(existing.get(key), dict):
1303
+ document[key] = existing[key]
1304
+ if existing.get("chapter_title"):
1305
+ document["chapter_title"] = existing["chapter_title"]
1306
+ old_context = existing.get("context") if isinstance(existing.get("context"), dict) else {}
1307
+ delta = document["delta"] if isinstance(document["delta"], dict) else {}
1308
+ context = document["context"]
1309
+ retired = {norm(item) for item in items(delta.get("retired_context_items"))}
1310
+ left_out: list[str] = []
1311
+ for key in ("long_term_constraints", "continuity_risks"):
1312
+ current = {norm(item) for item in context[key]}
1313
+ extra = []
1314
+ for item in items(old_context.get(key)):
1315
+ if norm(item) not in current:
1316
+ current.add(norm(item))
1317
+ extra.append(item)
1318
+ if append:
1319
+ context[key] = [item for item in context[key] if norm(item) not in retired] + extra
1320
+ else:
1321
+ left_out += extra
1322
+ snapshots = document["character_snapshots"] if isinstance(document["character_snapshots"], dict) else {}
1323
+ names = context["active_character_names"]
1324
+ for name in items(old_context.get("active_character_names")):
1325
+ if name in names:
1326
+ continue
1327
+ if append and name in snapshots:
1328
+ names.append(name)
1329
+ else:
1330
+ left_out.append(f"在场角色 {name}")
1331
+ names[:] = [name for name in names if name not in set(items(delta.get("retired_characters")))]
1332
+ known = set(current_characters)
1333
+ for name in list(snapshots):
1334
+ if name not in known and name not in names:
1335
+ snapshots.pop(name)
1336
+ left_out.append(f"角色快照 {name}")
1337
+ if left_out:
1338
+ notes.append("草稿里有、当前状态没有的条目没有自动带回(可能已被别的提交退役),本章确实需要就加回:"
1339
+ + ";".join(left_out))
1340
+ old_position = old_context.get("position") if isinstance(old_context.get("position"), dict) else {}
1341
+ position = context["position"]
1342
+ if append:
1343
+ for key in ("story_time", "scene"):
1344
+ if isinstance(old_position.get(key), str) and old_position[key].strip():
1345
+ position[key] = old_position[key]
1346
+ differs = [f"{key}:草稿写的是「{old_position[key]}」,当前状态是「{position[key]}」"
1347
+ for key in ("volume", "volume_start_chapter")
1348
+ if key in old_position and old_position[key] != position[key]]
1349
+ if differs:
1350
+ notes.append("卷信息与当前状态不同,已取当前状态,本章确实换卷就改回(" + ";".join(differs) + ")")
1351
+ if snapshots:
1352
+ notes.append("带过来的角色快照(" + "、".join(snapshots) + ")可能被中间的提交改过,逐个对照 current_snapshots 重核")
1353
+ return ";".join(notes)
1354
+
1355
+
1191
1356
  def build_parser() -> argparse.ArgumentParser:
1192
1357
  parser = argparse.ArgumentParser(description=__doc__)
1193
1358
  subparsers = parser.add_subparsers(dest="command", required=True)
@@ -1197,12 +1362,55 @@ def build_parser() -> argparse.ArgumentParser:
1197
1362
  subparser.add_argument("--input", type=Path, required=True, help="UTF-8 JSON input document")
1198
1363
  check_parser = subparsers.add_parser("check")
1199
1364
  check_parser.add_argument("--project", type=Path, required=True, help="book project root containing 追踪/")
1365
+ draft_parser = subparsers.add_parser("draft", help="write a pre-filled chapter transaction to fill in")
1366
+ draft_parser.add_argument("--project", type=Path, required=True, help="book project root containing 追踪/")
1367
+ draft_parser.add_argument("--chapter", type=int, required=True)
1368
+ draft_parser.add_argument("--out", type=Path, help="default: <project>/.story/work/第NNN章/tracking.json")
1369
+ draft_parser.add_argument("--force", action="store_true",
1370
+ help="discard an existing draft instead of only refreshing its revision")
1200
1371
  return parser
1201
1372
 
1202
1373
 
1203
1374
  def main() -> int:
1204
1375
  args = build_parser().parse_args()
1205
1376
  try:
1377
+ if args.command == "draft":
1378
+ document, snapshots, previous_position = draft_transaction(args.project, args.chapter)
1379
+ out = args.out or args.project / ".story" / "work" / f"第{args.chapter:03d}章" / "tracking.json"
1380
+ out.parent.mkdir(parents=True, exist_ok=True)
1381
+ refreshed = ""
1382
+ if out.exists() and not args.force:
1383
+ # 重跑 draft 不冲掉已经填好的变化。修订号变了说明中间提交过别的事务:context 按当前
1384
+ # 状态重建(沿用旧 context 会把那次变更整份覆盖回去),只带过本章自己填的部分。
1385
+ existing = read_json(out)
1386
+ require(isinstance(existing, dict) and existing.get("chapter") == document["chapter"]
1387
+ and existing.get("mode") == document["mode"],
1388
+ f"{out} already holds a draft for a different chapter or mode; pass --force to replace it")
1389
+ if existing.get("expected_state_revision") == document["expected_state_revision"]:
1390
+ document, refreshed = existing, "已有草稿且修订号未变,原样保留"
1391
+ else:
1392
+ refreshed = rebuild_stale_draft(document, existing, bool(previous_position),
1393
+ load_state(args.project)["characters"])
1394
+ out.write_text(json.dumps(document, ensure_ascii=False, indent=1) + "\n", encoding="utf-8")
1395
+ fill = ("只填 delta 里本章的变化;context 其余字段已是当前值,要撤下的长期约束或连贯性风险从 context 删掉并把原文放进 "
1396
+ "delta.retired_context_items(仅 append);本章有变化的核心角色把下面的当前快照整份改好放进 character_snapshots,"
1397
+ "并在 character_changes 写一句变化。不要从脚本源码或 state 文件里另找格式。")
1398
+ blank = not (document["context"]["position"].get("story_time") or document["context"]["position"].get("scene"))
1399
+ if previous_position and blank:
1400
+ fill = ("context.position 的 story_time 与 scene 留空,填本章结束时的故事时间与场景(上一章结束时见 "
1401
+ "previous_position;换卷时连同 volume 与 volume_start_chapter 一起改)。") + fill
1402
+ payload = {
1403
+ "draft": str(out),
1404
+ "mode": document["mode"],
1405
+ "expected_state_revision": document["expected_state_revision"],
1406
+ "fill": (f"{refreshed};要从头生成加 --force。" if refreshed else "") + fill,
1407
+ "limits_chars": DRAFT_LIMITS,
1408
+ "current_snapshots": snapshots,
1409
+ }
1410
+ if previous_position:
1411
+ payload["previous_position"] = previous_position
1412
+ emit(json.dumps(payload, ensure_ascii=False))
1413
+ return 0
1206
1414
  if args.command == "init":
1207
1415
  result = initialize(args.project, read_json(args.input))
1208
1416
  elif args.command == "commit":
@@ -172,6 +172,9 @@ def target_from_outline(value: str) -> int:
172
172
 
173
173
 
174
174
  def _chapter_number_from_name(name: str, *, outline: bool) -> int | None:
175
+ # 改稿流程把原文备份为 第N章_章名_原稿_{日期}.md,与 hooks 一样不把它当本章正文。
176
+ if not outline and "_原稿_" in name:
177
+ return None
175
178
  pattern = r"^细纲_第0*(\d+)章.*\.md$" if outline else r"^第0*(\d+)章(?:[_\-  ].*)?\.md$"
176
179
  match = re.match(pattern, name)
177
180
  return int(match.group(1)) if match else None
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: story-setup
3
- version: 1.2.10
3
+ version: 1.3.0
4
4
  description: "网文写作工具集基础设施部署与检查。为 Claude Code / OpenCode / Codex / Google Antigravity / ZCode / OpenClaw / Reasonix 提供内置适配;Web AI / 通用 Agent 可走 skills + AGENTS.md 文件模式。触发方式:/story-setup、$story-setup、「准备写书」「帮我搭一下环境」「配置写作项目」「检查写作环境」。"
5
5
  metadata: {"openclaw":{"source":"https://github.com/zenstory-ai/oh-story-claudecode"}}
6
6
  ---
@@ -24,10 +24,11 @@ metadata: {"openclaw":{"source":"https://github.com/zenstory-ai/oh-story-claudec
24
24
  > 判据是「有没有 `SKILL.md`」:只看正在执行的 `SKILL.md` 同级的 `references/`。项目内 `.claude/skills/story-setup/`、`.codex/skills/story-setup/` 和 OpenCode 的 `skills/story-setup/` 只有 `references/agent-references/`、不含 `SKILL.md`,不会是执行目录,也不要拿它们核对。Antigravity / ZCode / OpenClaw / Reasonix / generic 的项目副本是整份 skill 拷贝、自带 `SKILL.md`,9 个子目录本就齐全,照常核对即可。
25
25
 
26
26
  1. 检查当前目录是否已部署过(存在 `.story-deployed`)
27
- - `agents_version` 缺失、非整数或小于 `30` → 标记为待更新,继续执行当前部署
28
- - `agents_version: 30` → 使用 AskUserQuestion 确认是否重新部署;提示里写明重新部署只用**当前本地 skill 包**刷新项目文件,要拿 skill 本身的新版本得先更新 oh-story-claudecode(`npx skills add` 或 marketplace),再回来重跑
29
- - `agents_version` 大于 `30` → 当前 story-setup 比项目部署旧;停止以避免降级覆盖,提示先更新 oh-story-claudecode,不写任何部署文件
27
+ - `agents_version` 缺失、非整数或小于 `32` → 标记为待更新,继续执行当前部署
28
+ - `agents_version: 32` → 使用 AskUserQuestion 确认是否重新部署;提示里写明重新部署只用**当前本地 skill 包**刷新项目文件,要拿 skill 本身的新版本得先更新 oh-story-claudecode(`npx skills add` 或 marketplace),再回来重跑
29
+ - `agents_version` 大于 `32` → 当前 story-setup 比项目部署旧;停止以避免降级覆盖,提示先更新 oh-story-claudecode,不写任何部署文件
30
30
  - 同时读 `target_cli` 字段。**已部署项目以 sentinel 里的值为准**:非空时(逗号分隔的多端组合原样保留)跳过下面第 5-12 步的环境探测与选择,直接按这些端重新部署。只有字段缺失或为空,才回落到探测。用户明确要求增删目标端时,用 AskUserQuestion 在现有值基础上改,改完的值写回 sentinel。
31
+ - `target_cli` 不含 opencode、但项目里有 `.opencode/plugins/story-hooks.ts` 或 `.opencode/agents/`(多端部署时 OpenCode 曾被版本门拦下)→ 用 AskUserQuestion 问是否把 OpenCode 加回来;选加回则先过「OpenCode 部署前置」,通过后写回 `target_cli`
31
32
  2. 检查是否有书名目录(包含 `追踪/` 子目录的目录,或用户自定义结构)
32
33
  - 有 → 识别为长篇项目,显示当前项目信息
33
34
  - 无 → 识别为新项目或短篇项目
@@ -37,7 +38,7 @@ metadata: {"openclaw":{"source":"https://github.com/zenstory-ai/oh-story-claudec
37
38
  4. 检查 `.active-book` 文件是否存在
38
39
  - 存在 → 显示当前活跃书目
39
40
  - 不存在 → 跳过
40
- 5. 检查 `opencode.json` 或 `.opencode/` 是否存在
41
+ 5. 检查 `opencode.json`、`opencode.jsonc` 或 `.opencode/` 是否存在
41
42
  - 存在 → 识别为 opencode 项目,`target_cli = opencode`
42
43
  - 不存在 → 跳过
43
44
  6. 检查 `.codex/`、`.codex/config.toml`、`.codex/agents/`、`.codex/hooks.json`、`AGENTS.md` 中的 Codex 段
@@ -63,7 +64,7 @@ metadata: {"openclaw":{"source":"https://github.com/zenstory-ai/oh-story-claudec
63
64
 
64
65
  12. 如 `.claude/` 或 `CLAUDE.md`、OpenCode、Codex、Antigravity、ZCode、OpenClaw、Reasonix、generic 标记同时存在 → 使用 AskUserQuestion 让用户选择目标环境(选项:Claude Code / OpenCode / Codex / Google Antigravity / ZCode / OpenClaw / Reasonix / 通用 Web AI 或其他 Agent / 任意组合)
65
66
  13. 如八类标记都不存在(全新项目)→ 使用 AskUserQuestion 让用户选择目标环境
66
- - 用户选择 opencode → `target_cli = opencode`,部署时创建 `opencode.json` 和 `.opencode/`
67
+ - 用户选择 opencode → `target_cli = opencode`,部署时创建 `.opencode/`
67
68
  - 用户选择 claude-code → 按现有逻辑处理
68
69
  - 用户选择 codex → `target_cli = codex`,部署时创建 `.codex/`
69
70
  - 用户选择 antigravity → `target_cli = antigravity`,部署时创建 `.agents/skills`、`.agents/agents`、`.agents/rules`、`.agents/hooks` 并合并 `.agents/hooks.json`
@@ -101,7 +102,6 @@ metadata: {"openclaw":{"source":"https://github.com/zenstory-ai/oh-story-claudec
101
102
  | `skills/story-setup/references/opencode/plugin.ts` | `.opencode/plugins/story-hooks.ts` | story-setup managed | replace | TypeScript plugin file exists | target_cli 含 opencode |
102
103
  | `skills/story-setup/references/opencode/story_hook_core.js` | `.opencode/plugins/lib/story_hook_core.js` | story-setup managed | replace | Node syntax valid;与 ZCode 副本字节一致;被 story-hooks.ts import | target_cli 含 opencode |
103
104
  | `skills/story-setup/references/opencode/commands/` | `.opencode/commands/` | story-setup managed | replace | 13 command files exist | target_cli 含 opencode |
104
- | `skills/story-setup/references/opencode/opencode.json.patch` | merge into `opencode.json` | user+managed | merge by plugin/permission key | plugin entry registered | target_cli 含 opencode |
105
105
  | repository `skills/story-setup/references/agent-references/` | `skills/story-setup/references/agent-references/` | story-setup managed | replace | every reference resolves | target_cli 含 opencode |
106
106
  | `skills/story-setup/references/opencode/pre-commit.sh` | `.git/hooks/pre-commit` | user+managed | append or create | file exists and is executable;含 marker 块则替换块内容,不含则检测 exit 0 位置智能插入 | target_cli 含 opencode |
107
107
  | `skills/story-setup/references/codex/AGENTS.md.tmpl` | `AGENTS.md` | user+managed | marker/section merge | contains Codex story skill routing sections | target_cli 含 codex |
@@ -128,14 +128,16 @@ metadata: {"openclaw":{"source":"https://github.com/zenstory-ai/oh-story-claudec
128
128
  | repository `skills/{browser-cdp,story*}/` | `skills/{browser-cdp,story*}/` | story-setup managed for known skill names | replace known skill dirs only | 13 `SKILL.md` files exist; OpenClaw-compatible frontmatter | target_cli 含 openclaw 或 generic 或 reasonix |
129
129
  | repository `skills/story-setup/references/agent-references/` | 随上一行整份 skill 拷贝落地,本行 no-op | story-setup managed | 不单独复制 | every reference resolves | target_cli 含 openclaw 或 generic 或 reasonix |
130
130
 
131
- ### opencode.json 合并算法
131
+ ### OpenCode 部署前置(先于上表任何 OpenCode 行执行)
132
132
 
133
- 部署 `opencode.json.patch` 时按以下规则合并:
133
+ 只适配 OpenCode 2.x:1.x 的插件 loader 读不了 `story-hooks.ts`,只记一行日志后照常运行,写正文守卫整场缺席;1.x 也不认 agents 的 `permissions:` 规则,三个只读 agent 会拿到写文件与 shell 权限。版本门 fail-closed。
134
134
 
135
- 1. 读取现有 `opencode.json`(如存在),解析 JSON
136
- 2. 合并 `plugin` 数组:将 `./.opencode/plugins/story-hooks.ts` 加入数组,去重
137
- 3. 保留用户已有的其他配置字段(`permission`、`model`、`provider` 等),不覆盖
138
- 4. 写入合并后的 `opencode.json`
135
+ 1. 运行 `opencode --version`,取第一个 `主.次.修` 版本号:
136
+ - 主版本 ≥ 2 → 继续
137
+ - 主版本 < 2 → 停止 OpenCode 部署(其它 target 照常),告诉用户先升级到 2.x(先 `npm rm -g opencode-ai`,再 `npm i -g @opencode/cli` 或 `curl -fsSL https://opencode.ai/v2/install | bash`),装好后重跑 story-setup
138
+ - 命令不可用或解析不出版本 → 同样停止 OpenCode 部署,请用户在自己的终端运行 `opencode --version`:用户在对话里确认显示 2.x 后才继续;是 1.x 按上一条处理
139
+ - 停止 OpenCode 部署时:target 只有 opencode 则不写、不更新 `.story-deployed`(已有的原样保留,不抬 `agents_version`),也不写任何 OpenCode 文件;多 target 时其它端照常部署,写入的 `target_cli` 不含 opencode,报告首行写明 OpenCode 未部署及原因,并告诉作者升级后重跑 story-setup、选择把 OpenCode 加回来
140
+ 2. 插件由 OpenCode 自动发现 `.opencode/plugins/*.ts` 加载,不写 `opencode.json`。项目根已有 `opencode.json` / `opencode.jsonc` 时,从其 `plugin`、`plugins` 数组删掉指向 `.opencode/plugins/story-hooks.ts` 的项(旧版部署留下;2.x 丢弃单文件路径并告警),数组删空就删掉该键,其余内容原样保留。
139
141
 
140
142
  ### Step 2:部署 CLAUDE.md
141
143
 
@@ -181,8 +183,8 @@ metadata: {"openclaw":{"source":"https://github.com/zenstory-ai/oh-story-claudec
181
183
  - 读取 `skills/story-setup/references/codex/agents/` 下所有 `.toml` 文件,复制到用户项目 `.codex/agents/`
182
184
  - Agent 文件属于 story-setup 管理文件,可安全覆盖;`references/codex/agents/` 里的 TOML 由仓库根的 `scripts/generate-codex-agents.py` 从 Claude agent 模板确定性生成后提交入库,部署只做复制
183
185
  - 校验每个 TOML 都能解析,且包含 Codex 必需字段:`name`、`description`、`developer_instructions`
184
- - 只读职责 agent(`chapter-extractor`、`consistency-checker`、`story-explorer`)必须保留 `sandbox_mode = "read-only"`
185
- - **部署后必须 trust + 新开 Codex 会话**(报告文案与 fallback 规则见「验证 Codex 部署」);若运行时返回 `unknown agent_type`,调用方必须降级 solo/direct 并报告 fallback。
186
+ - 只读职责 agent(`consistency-checker`、`story-explorer`)必须保留 `sandbox_mode = "read-only"`;`chapter-extractor` 要写批次输入文件,不设只读沙箱
187
+ - **部署后必须 trust + 新开 Codex 会话**(报告文案与 fallback 规则见「验证 Codex 部署」);hooks 未在 `/hooks` 信任前会被 Codex 静默跳过,包括写正文前的大纲守卫;若运行时返回 `unknown agent_type`,调用方必须降级 solo/direct 并报告 fallback。
186
188
  - 将 `skills/story-setup/references/agent-references/` 同步复制到 `.codex/skills/story-setup/references/agent-references/`,作为 Codex agent 的项目内参考资料主路径
187
189
 
188
190
  #### 部署 Antigravity Agents(target_cli 含 antigravity 时)
@@ -190,7 +192,7 @@ metadata: {"openclaw":{"source":"https://github.com/zenstory-ai/oh-story-claudec
190
192
  - 先确认 `node` 在 PATH;Antigravity agent 生成与项目 hooks 都依赖 Node。缺失时停止 Antigravity 这一目标的部署,不留下半成品,并提示安装 Node 后重跑。
191
193
  - 执行 `node "{story-setup skill目录}/scripts/generate-antigravity-agents.mjs" --source "{story-setup skill目录}/references/templates/agents" --dest "{项目}/.agents/agents"`。生成器先渲染全部 7 个 agent,再原子替换这 7 个已知 `.agents/agents/agent-name/agent.md` 定义(`agent-name` 为实际名称),并清理旧版同名扁平 `.md`;保留其他用户 agent,任一源 frontmatter 异常时不得留下半更新目录,也不得沿 managed agent symlink 写出项目外。
192
194
  - 校验 7 个 `.md`:`name` 与文件名一致;`mainAgent: false`、`subagent: true`;模型只使用 `flash` / `pro`;工具只来自 Antigravity 官方名称 `view_file`、`find_by_name`、`grep_search`、`write_to_file`、`replace_file_content`、`multi_replace_file_content`、`run_command`;不得残留 Claude 的 `Read/Glob/Grep/Write/Edit/Bash` 工具名或 `.claude/skills/` reference 前缀。
193
- - 只读 agent(`chapter-extractor`、`consistency-checker`、`story-explorer`)不得包含写文件或命令工具;其他 agent 按 Claude 真源的能力边界映射。
195
+ - 只读 agent(`consistency-checker`、`story-explorer`)不得包含写文件或命令工具;`chapter-extractor` 只有读取与写文件、无命令工具;其他 agent 按 Claude 真源的能力边界映射。
194
196
  - Antigravity 通过 `invoke_subagent` 的 `TypeName` 调用这些 agent。部署后新开 Antigravity conversation,再用 `story-review` 验证 full/lean;运行时无法解析某个 custom agent 时按 skill 的 solo/direct fallback 执行。
195
197
 
196
198
  #### 配置 OpenCode Agent 模型
@@ -203,7 +205,7 @@ OpenCode agents 部署是 `replace`,会覆盖上次写入的 `model:`。所以
203
205
 
204
206
  ##### Step 2:获取模型列表
205
207
 
206
- 优先执行 `opencode models --verbose`,它输出含 cost(input/output/cache 单价)、context、capabilities 的 metadata;不可用或解析失败时回退到 `opencode models` 纯文本(每行 `provider/model`)。两者都用 60000ms(60 秒)超时,因为首次运行需加载 models.dev 缓存。
208
+ 优先在项目根执行 `opencode api model.list -H "x-opencode-directory:<项目根绝对路径>"`,输出 JSON:`data[]` 每项的 `providerID/id` 即模型 ID,`cost[]` 为每百万 token 的 input/output 单价(空数组即无成本数据),`limit.context` 为上下文长度;不可用或解析失败时回退到 `opencode models` 纯文本(每行 `provider/model`)。两者都用 60000ms(60 秒)超时,因为首次运行需加载 models.dev 缓存。
207
209
 
208
210
  - 成功 → 进入「模型分级」
209
211
  - 超时 → 重试一次(缓存可能未预热);仍然超时则按「保留已有模型配置」缓存回填已有 `model:`、跳过自动配置,在安装报告中输出手动配置指南
@@ -211,9 +213,9 @@ OpenCode agents 部署是 `replace`,会覆盖上次写入的 `model:`。所以
211
213
 
212
214
  ##### Step 3:模型分级
213
215
 
214
- **优先按成本分级(有 `--verbose` 时)**:按每模型实际 cost 从低到高分档——低端取最便宜/免费档、中端取中价档、高端取最贵或上下文/能力最强档。免费模型按真实 cost=0 归低端,**不按名字里的营销词**(如 `nemotron-3-ultra-free` 名含 `ultra` 但 cost=0,应归低端)。无 cost 数据的模型也据此进入候选,不被丢弃。
216
+ **优先按成本分级(有 `model.list` 成本数据时)**:按每模型实际 cost 从低到高分档——低端取最便宜/免费档、中端取中价档、高端取最贵或上下文/能力最强档。免费模型按真实 cost=0 归低端,**不按名字里的营销词**(如 `nemotron-3-ultra-free` 名含 `ultra` 但 cost=0,应归低端)。无 cost 数据的模型也据此进入候选,不被丢弃。
215
217
 
216
- **回退按关键词分级(无 `--verbose` 或无 cost 时)**:按模型 ID 中最后一个 `/` 之后的模型名按 `-`、`.`、`_` 分割为段,逐段精确匹配关键词(不区分大小写)。例如 `minimax-m3` 拆为 `[minimax, m3]`,不匹配 `mini` 也不匹配 `max`;`claude-haiku-4.5` 拆为 `[claude, haiku, 4, 5]`,匹配 `haiku`。关键词分级是启发式,安装报告中标注 `分级依据:关键词(heuristic)`。
218
+ **回退按关键词分级(只有 `opencode models` 或无 cost 时)**:按模型 ID 中最后一个 `/` 之后的模型名按 `-`、`.`、`_` 分割为段,逐段精确匹配关键词(不区分大小写)。例如 `minimax-m3` 拆为 `[minimax, m3]`,不匹配 `mini` 也不匹配 `max`;`claude-haiku-4.5` 拆为 `[claude, haiku, 4, 5]`,匹配 `haiku`。关键词分级是启发式,安装报告中标注 `分级依据:关键词(heuristic)`。
217
219
 
218
220
  | 等级 | 匹配关键词 | 对应 Agent |
219
221
  |------|-----------|-----------|
@@ -274,15 +276,19 @@ OpenCode agents 部署是 `replace`,会覆盖上次写入的 `model:`。所以
274
276
 
275
277
  ##### Step 5:写入 model 字段
276
278
 
277
- 对应用户选择的 agent 文件(`.opencode/agents/*.md`,由部署清单中 OpenCode agents 部署步骤在此步骤之前已部署),在 frontmatter 末尾、closing `---` 之前,以**零缩进的顶层字段**插入 `model:`(不要插进 `permission:` 等多行 map 的缩进块内部)。值含 YAML 特殊字符时加引号,确保不破坏 frontmatter:
279
+ 对应用户选择的 agent 文件(`.opencode/agents/*.md`,由部署清单中 OpenCode agents 部署步骤在此步骤之前已部署),在 frontmatter 末尾、closing `---` 之前,以**零缩进的顶层字段**插入 `model:`(不要插进 `permissions:` 规则列表等多行块的缩进内部)。值含 YAML 特殊字符时加引号,确保不破坏 frontmatter:
278
280
 
279
281
  ```yaml
280
282
  ---
281
283
  description: ...
282
284
  mode: subagent
283
- permission:
284
- read: allow
285
- edit: deny
285
+ permissions:
286
+ - action: "*"
287
+ resource: "*"
288
+ effect: deny
289
+ - action: read
290
+ resource: "*"
291
+ effect: allow
286
292
  steps: 12
287
293
  model: provider/model-id
288
294
  ---
@@ -377,15 +383,15 @@ Reasonix(DeepSeek-Reasonix CLI)当前只部署 skills 与 `AGENTS.md`,不
377
383
  - 写入以下字段(YAML `key: value` 格式,hook 用 `references/templates/hooks/lib/sentinel.sh` 读取):
378
384
  ```
379
385
  deployed_at: <date -u +"%Y-%m-%dT%H:%M:%SZ">
380
- agents_version: 30
381
- setup_skill_version: 1.2.10
386
+ agents_version: 32
387
+ setup_skill_version: 1.3.0
382
388
  target_cli: claude-code(或 opencode、codex、antigravity、zcode、openclaw、reasonix、generic,或其任意组合)
383
389
  resolver_strategy: project-local-skill-reference
384
390
  references_dir: .claude/skills/story-setup/references/agent-references(Codex 写 .codex/skills/...;Antigravity 写 .agents/skills/...;ZCode 写 .zcode/skills/...;OpenClaw / Reasonix / generic 写 skills/...;多端用逗号分隔)
385
391
  ```
386
392
  - 此文件供 session-start.sh 和写作 skill 检测部署状态,避免重复提示
387
393
  - target_cli 含 claude-code 时,同时创建一次性标记文件 `.claude/.agents-pending-restart`(空文件即可)。session-start.sh 在下一个会话启动时据此确认 agents 已随新会话注册,并自动删除该标记——用来向用户确认「重启已生效」。ZCode 不创建该标记,因为它不部署项目 agents。
388
- - 如果 `.story-deployed` 已存在但 `agents_version` 缺失、非整数或小于 `30`,按本次流程更新 hooks/agents/rules/reference bundle(具体变更见 `UPGRADING.md`);大于 `30` 时已在 Phase 1 停止,不得降级覆盖
394
+ - 如果 `.story-deployed` 已存在但 `agents_version` 缺失、非整数或小于 `32`,按本次流程更新 hooks/agents/rules/reference bundle(具体变更见 `UPGRADING.md`);大于 `32` 时已在 Phase 1 停止,不得降级覆盖
389
395
 
390
396
  ## Phase 3:验证安装
391
397
 
@@ -403,11 +409,12 @@ Reasonix(DeepSeek-Reasonix CLI)当前只部署 skills 与 `AGENTS.md`,不
403
409
  - 检查 `.claude/skills/story-setup/references/agent-references/` 下 reference 文件完整
404
410
  - 检查所有 `story-setup/references/agent-references/<file>.md` 都能解析到 deployed bundle
405
411
  5. 验证部署标记:
406
- - 检查 `.story-deployed` 是否存在且包含时间戳、`agents_version: 30`、`setup_skill_version: 1.2.10`、`target_cli`、`resolver_strategy`、`references_dir`
407
- 6. 输出安装报告:
408
- - 列出所有已部署的文件
409
- - 列出需要注意的事项(如已有配置已合并)
410
- - **⚠️ 重启提示(必须醒目输出)**:本次部署写入了 `.claude/agents/`,但这些 custom agent 只在「会话启动」时才会被 Claude Code 注册成 `subagent_type`。**请新开一个 Claude Code 会话再开始写作**,否则当前会话里 story-review / story-long-write 等想 spawn `story-architect`、`narrative-writer` 等时会拿到「subagent_type 不可用」并降级 solo(单视角,失去多 agent 协作)。判断是否生效:新会话里跑 `/story-review`,报告头若是 `Effective Mode: full/lean` 即注册成功;若是 `Fallback: ... -> solo` 说明还在旧会话或未注册。
412
+ - 检查 `.story-deployed` 是否存在且包含时间戳、`agents_version: 32`、`setup_skill_version: 1.3.0`、`target_cli`、`resolver_strategy`、`references_dir`
413
+ 6. 输出安装报告。读者是不懂编程的作者,按这个顺序写:
414
+ - **先写「现在可以做什么」**:用写书的话列本次部署后真正可用的事(如「可以开新书、续写:说 /story-long-write」「可以拆一本对标书」),端的限制如实翻译(如「这个工具里审稿由我一个人完成,没有分工助手」)。
415
+ - **再写「你还需要做的事」**:逐条可照做,如「新开一个会话」「在 Codex 里打开 /hooks,把 oh-story 的几条信任一下」「先安装 Node」;没有就写「无需其他操作」。这两段不出现脚本名、字段名、状态名或文件路径;各端「安装报告必须提示」的内容先翻译进这两段。
416
+ - **最后是简短的「部署明细」**:已部署文件、已合并的配置、删掉的残留路径、下面的模型配置摘要和技术原因,放在报告末尾。
417
+ - **⚠️ 重启提示(必须醒目,放进「你还需要做的事」)**:本次部署写入了 `.claude/agents/`,但这些 custom agent 只在「会话启动」时才会被 Claude Code 注册成 `subagent_type`。**请新开一个 Claude Code 会话再开始写作**,否则当前会话里 story-review / story-long-write 等想 spawn `story-architect`、`narrative-writer` 等时会拿到「subagent_type 不可用」并降级 solo(单视角,失去多 agent 协作)。判断是否生效:新会话里跑 `/story-review`,报告开头「这次怎么审的」写着几个视角(完整审或精简审)即注册成功;若写着「我一个人审」说明还在旧会话或未注册。
411
418
  - 重启后即可使用 `/story-long-write` 或 `/story-short-write`
412
419
  - 如果执行了「配置 OpenCode Agent 模型」,输出 Agent 模型配置摘要:
413
420
  ```
@@ -430,16 +437,17 @@ Reasonix(DeepSeek-Reasonix CLI)当前只部署 skills 与 `AGENTS.md`,不
430
437
  手动配置方法:编辑 .opencode/agents/{agent名}.md,在 frontmatter 中添加:
431
438
  model: provider/model-id
432
439
 
433
- 可用模型列表与成本可通过 opencode models --verbose 查看(输出含每模型 cost/context)。
440
+ 可用模型列表可通过 opencode models 查看;成本与上下文长度见 opencode api model.list 的 cost/limit 字段。
434
441
  模型库与定价见 OpenCode 官方模型源 https://models.dev/。
435
442
  ```
436
443
  7. 验证 opencode 部署(仅当 target_cli 含 opencode 时):
437
- - 检查 `.opencode/agents/` 下的 7 个 agent 定义文件是否存在,且 frontmatter 包含 `mode: subagent` 和 `permission` 字段
444
+ - 检查 `.opencode/agents/` 下的 7 个 agent 定义文件是否存在,且 frontmatter 包含 `mode: subagent` 和 `permissions` 规则列表
438
445
  - 检查 `.opencode/plugins/story-hooks.ts` 是否存在
439
- - 检查 `.opencode/plugins/lib/story_hook_core.js` 存在且 `node --check` 通过(story-hooks.ts import 之,与 `.zcode` 副本字节一致的共享写正文守卫核;置于 `lib/` 子目录以避开 OpenCode 单层 `.opencode/plugins/*.js` 插件自动发现)
446
+ - 检查 `.opencode/plugins/lib/story_hook_core.js` 存在且 `node --check` 通过(story-hooks.ts import 之,与 `.zcode` 副本字节一致的共享写正文守卫核;置于 `lib/` 子目录以避开 OpenCode 对 `.opencode/plugins/*.js` 的插件自动发现,`lib/` 里不得放 `index.*` / `server.*`)
440
447
  - 检查 `.opencode/commands/` 下的 13 个 command 文件是否存在
441
448
  - 检查 `skills/story-setup/references/agent-references/` 下 reference 文件完整且数量与源目录一致
442
- - 检查 `opencode.json` 的 `plugin` 数组是否包含 story-hooks 条目
449
+ - 检查 `opencode.json` / `opencode.jsonc`(如有)的 `plugin`、`plugins` 数组不再含指向 story-hooks.ts 的项
450
+ - `opencode` 可用时在项目根执行 `opencode api plugin.list -H "x-opencode-directory:<项目根绝对路径>"`,确认 `id` 为 `oh-story.story-hooks` 的条目 `state.status` 为 `active`(首次请求可能返回空列表,隔几秒重试)
443
451
  - 检查 `.git/hooks/pre-commit` 是否存在且有执行权限(Windows 上跳过执行权限检查)
444
452
  - 检查 `.opencode/agents/` 下 agent 文件 frontmatter 可被 YAML 解析、`model:`(如有配置)是合法顶层标量,而非仅 grep 到 `model:` 子串
445
453
  8. 验证 Codex 部署(仅当 target_cli 含 codex 时):
@@ -448,14 +456,14 @@ Reasonix(DeepSeek-Reasonix CLI)当前只部署 skills 与 `AGENTS.md`,不
448
456
  - 检查 `.codex/hooks.json` 存在且 JSON 有效,Unix `command` 仅通过 `run-story-hook.sh` 启动,Windows `commandWindows` 仅通过 `run-story-hook.cmd` 启动;不存在直调 `story_codex_hook.py` 的注册
449
457
  - 检查 `.codex/hooks/story_codex_hook.py`、`run-story-hook.sh`、`run-story-hook.cmd` 存在,Python 语法有效,POSIX/Windows launcher 能从嵌套 cwd 定位项目根
450
458
  - 检查 `.codex/skills/story-setup/references/agent-references/` 下 reference 文件完整且数量与源目录一致
451
- - 安装报告必须提示:Codex 需要 trust 项目 `.codex/` 配置层,并在 `/hooks` review/trust 非 managed hooks;部署后新开 Codex 会话让 custom agents 生效;若当前运行时仍返回 `unknown agent_type`,按各 skill 的 fallback 规则降级 solo/direct
459
+ - 安装报告必须提示:Codex 需要 trust 项目 `.codex/` 配置层,并在 `/hooks` review/trust 非 managed hooks——**未信任前 Codex 静默跳过这些 hooks,写正文前的大纲守卫也不生效,且不报错**;自动化里的 `codex exec` 可加 `--dangerously-bypass-hook-trust`;部署后新开 Codex 会话让 custom agents 生效;若当前运行时仍返回 `unknown agent_type`,按各 skill 的 fallback 规则降级 solo/direct
452
460
  9. 验证 Antigravity 部署(仅当 target_cli 含 antigravity 时):
453
461
  - 检查 `.agents/skills/` 下 13 个 story skills 为真实目录且 `SKILL.md` 可读;`.agents/skills/story-setup/references/agent-references/` 完整
454
462
  - 检查 `.agents/agents/` 下 7 个 Markdown agent 可解析,名称、模型档、官方工具白名单、只读边界与 `.agents/skills/` reference 前缀正确
455
463
  - 检查 `.agents/rules/oh-story.md` 为 `trigger: always_on` 且未超过 12,000 字符
456
464
  - 检查 `.agents/hooks.json` 有效、顶层 `oh-story` group 恰有 PreToolUse/PostToolUse/PreInvocation/Stop,用户 hook groups 保留;检查 `.agents/hooks/story_antigravity_hook.js` 与 `story_hook_core.js` 语法有效
457
465
  - 用 fixture 验证:PreToolUse 缺纲/追踪时 deny、普通写入 allow、commit advisory;PostToolUse stdout 恒为 `{}` 且把正文 findings 写进 session artifact;下一次 PreInvocation 注入 findings;Stop 对未处理 findings 最多 continue 一次;干净正文清除 pending state
458
- - 安装报告必须提示:新开 Antigravity conversation 刷新 customization;Hooks 依赖 PATH 中的 `node`;外部 hook API 没有 PreCompact/PostCompact,compact 恢复由 Always-On Rule 读取 `追踪/上下文.md`;IDE 与交互式 `agy` 仍建议分别实机 smoke test;`agy 1.1.22 -p` 每次 headless 启动都可能在静默鉴权前扫描 workspace,鉴权后不重载 custom agents/hooks,因此当前不在支持面内,可能报 `subagent not found` 或回退写入 `~/.gemini/antigravity-cli/scratch/`;命令行写作从项目目录进入交互式 `agy`,确认 `/skills`、`/agents`、`/hooks` 已发现 oh-story 后再发任务,测试后检查 scratch 无意外小说产物
466
+ - 安装报告必须提示:新开 Antigravity conversation 刷新 customization;Hooks 依赖 PATH 中的 `node`;外部 hook API 没有 PreCompact/PostCompact,compact 恢复由 Always-On Rule 读取 `追踪/上下文.md`;IDE 与交互式 `agy` 仍建议分别实机 smoke test;命令行写作从项目目录进入交互式 `agy`,确认 `/skills`、`/agents`、`/hooks` 已发现 oh-story 后再发任务;print 模式 `agy -p` 必须带 `--add-dir "$PWD"`(实测 agy 1.2.10 带上才加载工作区 `.agents/` 的 13 个 skills、7 个 agents 与 hooks,不带则都不加载,可能回退写入 `~/.gemini/antigravity-cli/scratch/`),测试后检查 scratch 无意外小说产物
459
467
  10. 验证 ZCode 部署(仅当 target_cli 含 zcode 时):
460
468
  - 检查根 `AGENTS.md` 含 ZCode `$story-*` 路由、大纲守卫和 solo/direct fallback
461
469
  - 检查 `.zcode/skills/` 下 13 个 Skills 与 `.zcode/commands/` 下 13 个 Commands,验证 frontmatter 和命名
@@ -500,7 +508,7 @@ Reasonix(DeepSeek-Reasonix CLI)当前只部署 skills 与 `AGENTS.md`,不
500
508
  1. 优先识别 story-setup 管理块标记(如果旧项目已有标记,只替换标记内内容)
501
509
  2. 无标记时,读取用户现有 CLAUDE.md,按 `##` 标题切分为 section map
502
510
  3. 读取模板 CLAUDE.md.tmpl,同样切分
503
- 4. 模板中的标准 section(Skill 路由表、文件结构、协作规则、Compact 后恢复上下文)**覆盖**用户同名 section
511
+ 4. 模板中的标准 section(Skill 路由表、文件结构、协作规则、与作者协作、Compact 后恢复上下文;模板有而用户文件没有的 section 直接补入)**覆盖**用户同名 section
504
512
  5. 用户独有的 section(自定义内容)**保留**不动
505
513
  6. 未知冲突用 AskUserQuestion 让用户选择保留哪个版本
506
514
 
@@ -510,15 +518,15 @@ Reasonix(DeepSeek-Reasonix CLI)当前只部署 skills 与 `AGENTS.md`,不
510
518
  1. 优先识别 story-setup 管理块标记(如果旧项目已有标记,只替换标记内内容)
511
519
  2. 无标记时,读取用户现有 AGENTS.md,按 `##` 标题切分为 section map
512
520
  3. OpenCode 使用 `skills/story-setup/references/opencode/AGENTS.md.tmpl`;Codex 使用 `skills/story-setup/references/codex/AGENTS.md.tmpl`;ZCode 使用 `skills/story-setup/references/zcode/AGENTS.md.tmpl`;OpenClaw 使用 `skills/story-setup/references/openclaw/AGENTS.md.tmpl`;Reasonix 使用 `skills/story-setup/references/reasonix/AGENTS.md.tmpl`;通用 Web AI / 其他 Agent 使用 `skills/story-setup/references/generic/AGENTS.md.tmpl`
513
- 4. 模板中的标准 section(Skill 路由表、文件结构、协作规则、Compact 后恢复上下文)覆盖同名 section;用户独有 section 保留
521
+ 4. 模板中的标准 section(Skill 路由表、文件结构、协作规则、与作者协作、Compact 后恢复上下文;模板有而用户文件没有的 section 直接补入)覆盖同名 section;用户独有 section 保留
514
522
  5. 多端同时部署时,Codex/OpenCode/ZCode/OpenClaw/Reasonix/generic 共同可用的通用段落只保留一份;工具特有说明以小节区分,避免互相覆盖
515
523
 
516
524
  ## 重新部署
517
525
 
518
526
  - `.story-deployed` 不存在 → 全新安装,Phase 2 全部执行
519
- - `.story-deployed` 存在且 `agents_version: 30` → 提示已部署,AskUserQuestion 确认是否重新部署;提示里写明重新部署只用当前本地 skill 包刷新项目文件,skill 本身的更新走 `npx skills add` 或 marketplace
520
- - `.story-deployed` 存在但 `agents_version` 缺失、非整数或小于 `30` → 提示需要更新,重新执行 Phase 2 覆盖 agents/hooks/rules/reference bundle,CLAUDE.md / AGENTS.md / settings.local.json / .codex/hooks.json / `.agents/hooks.json` / .zcode/config.json 走合并策略
521
- - `.story-deployed` 存在且 `agents_version` 大于 `30` → 当前 skill 版本过旧,停止并提示先更新 oh-story-claudecode;不覆盖项目中的更新部署
527
+ - `.story-deployed` 存在且 `agents_version: 32` → 提示已部署,AskUserQuestion 确认是否重新部署;提示里写明重新部署只用当前本地 skill 包刷新项目文件,skill 本身的更新走 `npx skills add` 或 marketplace
528
+ - `.story-deployed` 存在但 `agents_version` 缺失、非整数或小于 `32` → 提示需要更新,重新执行 Phase 2 覆盖 agents/hooks/rules/reference bundle,CLAUDE.md / AGENTS.md / settings.local.json / .codex/hooks.json / `.agents/hooks.json` / .zcode/config.json 走合并策略
529
+ - `.story-deployed` 存在且 `agents_version` 大于 `32` → 当前 skill 版本过旧,停止并提示先更新 oh-story-claudecode;不覆盖项目中的更新部署
522
530
 
523
531
  ---
524
532
 
@@ -2,9 +2,9 @@
2
2
 
3
3
  > 本文件的句长、视角、标点、修辞与禁用词是默认写法,服从 [style-resolution.md](style-resolution.md) 的逐维裁决。所选 Gate 检查表达效果,不因作者有意选择某写法就机械删除;获准命中按书级 `.deslop-whitelist` 处理。
4
4
 
5
- <!-- 同名副本×5 字节同步,改动后跑 scripts/check-shared-files.sh -->
5
+ <!-- 同名副本×4 字节同步,改动后跑 scripts/check-shared-files.sh -->
6
6
 
7
- > 识别AI写作指纹、系统性去AI三遍法、禁用词约束、改写范例库。用于正文写作后做去AI味自检和改写时查阅。
7
+ > 识别AI写作指纹、改写顺序、禁用词约束、改写范例库。用于正文写作后做去AI味自检和改写时查阅。
8
8
 
9
9
  ---
10
10
 
@@ -15,7 +15,7 @@
15
15
  | 写完正文后做去AI自检 | 核心规则 -> AI写作模式检测 -> 质量维度检查 |
16
16
  | 改写某段AI味重的文字 | 改写范例库 + 冲突对话改写范例 |
17
17
  | 检查是否用了禁用词 | 禁用词与句式速查 -> AI高频词(模式1) |
18
- | 系统性去除整章AI味 | 系统性去AI三遍法 |
18
+ | 系统性去除整章AI味 | 改写顺序 |
19
19
  | 检查章尾是否有总结升华 | AI写作指纹 -> 章末总结体 |
20
20
  | 判断情绪描写是否告知式 | Show Don't Tell原则 + 去AI味补充技法 |
21
21
  | 快速扫描全章质量 | 快速自检口诀 + 质量维度检查 |
@@ -372,9 +372,11 @@ AI 喜欢把事情凑成三个以显"完整"。-> 砍到只剩最有力的一条
372
372
 
373
373
  ---
374
374
 
375
- ## 系统性去AI三遍法
375
+ ## 改写顺序(只排所选 Gate 的先后)
376
376
 
377
- ### Pass 1:去泛化(Strip Generic)
377
+ 下面三步只决定所选 Gate 内问题的处理先后,不另起一轮全篇去味;某一步没有对应的所选 Gate 就跳过。
378
+
379
+ ### 第一步:去泛化(Strip Generic)
378
380
  - 抽象情绪总结句 -> 按规则 2 判断:删重复说明,保留准确直写,需要时用原文已有信息落地
379
381
  - 假深度句 -> 删
380
382
  - 意义膨胀 -> 缩小到具体影响
@@ -384,9 +386,9 @@ AI 喜欢把事情凑成三个以显"完整"。-> 砍到只剩最有力的一条
384
386
  - 过度使用"于是""然而""此刻" -> 删掉一半
385
387
  - 所有角色说话一样"高级" -> 区分语气
386
388
 
387
- **原则**:能删就删,不能删就用具体细节替换。这一遍去掉80%的AI味。
389
+ **原则**:能删就删,不能删就用具体细节替换。
388
390
 
389
- ### Pass 2:去书面化(Cut Professional Diction)
391
+ ### 第二步:去书面化(Cut Professional Diction)
390
392
  - 分析性用词("机制""结构""逻辑""体系"出现在小说中)-> 换成日常表达
391
393
  - 抽象名词滥用 -> 直接说事
392
394
  - 体制内用语("进一步""深入""推进""落实")-> 删
@@ -394,7 +396,7 @@ AI 喜欢把事情凑成三个以显"完整"。-> 砍到只剩最有力的一条
394
396
 
395
397
  **例外**:保留专业感的场景(历史题材正式用语、文学向刻意密度、喜剧夸张修辞)。
396
398
 
397
- ### Pass 3:回自然感(Restore Natural Presence)
399
+ ### 第三步:回自然感(Restore Natural Presence)
398
400
  - 具体的感官细节(气味、温度、触感)
399
401
  - 角色说话方式的区分(不同人不同语气)
400
402
  - 句首变化:连续 3+ 句用同一主语或同一词性开头时换开法(动作、场景、对话引入)
@@ -407,7 +409,7 @@ AI 喜欢把事情凑成三个以显"完整"。-> 砍到只剩最有力的一条
407
409
 
408
410
  ### 执行范围
409
411
 
410
- 调用方指定 Gate 时,只处理选定 Gate;三遍法只安排顺序,不重新分级或扩大范围。未指定范围时,按实际问题选择适用检查。
412
+ 调用方指定 Gate 时,只处理选定 Gate;改写顺序只排先后,不重新分级或扩大范围。未指定范围时,按实际问题选择适用检查。
411
413
 
412
414
  ### 自检清单
413
415
  - 对话自然度检查:对话是否使用口语化表达,是否避免了书面语/正式腔调
@@ -551,5 +553,4 @@ AI 喜欢把事情凑成三个以显"完整"。-> 砍到只剩最有力的一条
551
553
  - [ ] **detector advisory 逐条复核**:`micro-action-tic` / `stock-reaction-tic` / `abstract-summary-tic` / `cliche-density-tic` / `metaphor-density-tic` / `reasoning-chain-tic` / `system-notice-formality-tic` / `overcompressed-prose-tic` / `low-connective-density-tic` / `action-list-tic` 命中时按脚本给出的修法处理:先通读判断是不是机械复现,确属再改;功能性写法保留或标 `[需复核]`,不做同义词轮换、不机械注水
552
554
  - [ ] **不做硬指标投机**:不为反检测强制每句换行、50-60 字一行、对话 50%-60%、英文点省略号,或把 `地/得` 全改成 `的`
553
555
  - [ ] **任务卡点服从原文边界**:抽象总结若改成角色办事被卡住,必须来自原文已有任务/证据/手续/物件缺口;不新增原文没有的事件链
554
- - [ ] **去AI三遍法执行**:轻度只做Pass1,中度做Pass1+2,重度完整三遍
555
556
  - [ ] **对话自然度测试**:无书面语痕迹 = 通过