@lemoncat7/dsh-partner 2.8.1 → 2.8.3

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 (833) hide show
  1. package/docs/THIRD_PARTY_LICENSES.md +10 -0
  2. package/docs/blocked-delivery-recovery.md +7 -0
  3. package/docs/concern-attention.md +19 -0
  4. package/docs/observation-contract.md +21 -0
  5. package/docs/pendant-loading.md +30 -0
  6. package/docs/releases/2.8.2.md +7 -0
  7. package/docs/releases/2.8.3.md +20 -0
  8. package/lib/agent-runtime.d.ts +7 -0
  9. package/lib/agent-runtime.d.ts.map +1 -1
  10. package/lib/agent-runtime.js +119 -35
  11. package/lib/agent-runtime.js.map +1 -1
  12. package/lib/api/features/pendant-api.d.ts.map +1 -1
  13. package/lib/api/features/pendant-api.js +11 -2
  14. package/lib/api/features/pendant-api.js.map +1 -1
  15. package/lib/api.d.ts +1 -0
  16. package/lib/api.d.ts.map +1 -1
  17. package/lib/api.js +41 -6
  18. package/lib/api.js.map +1 -1
  19. package/lib/autonomy.d.ts.map +1 -1
  20. package/lib/autonomy.js +9 -2
  21. package/lib/autonomy.js.map +1 -1
  22. package/lib/channel-message.d.ts +2 -0
  23. package/lib/channel-message.d.ts.map +1 -1
  24. package/lib/channel-message.js.map +1 -1
  25. package/lib/channels/direct/message-format.js +127 -127
  26. package/lib/channels/direct/message-format.js.map +1 -1
  27. package/lib/channels/manager.d.ts.map +1 -1
  28. package/lib/channels/manager.js +4 -3
  29. package/lib/channels/manager.js.map +1 -1
  30. package/lib/channels/notification-delivery.d.ts.map +1 -1
  31. package/lib/channels/notification-delivery.js +10 -0
  32. package/lib/channels/notification-delivery.js.map +1 -1
  33. package/lib/channels/weixin/connect.d.ts.map +1 -1
  34. package/lib/client-api.d.ts +17 -2
  35. package/lib/client-api.d.ts.map +1 -1
  36. package/lib/client-api.js +8 -1
  37. package/lib/client-api.js.map +1 -1
  38. package/lib/client.d.ts.map +1 -1
  39. package/lib/client.js +2398 -1970
  40. package/lib/client.js.map +4 -4
  41. package/lib/client.pendant.js +4157 -0
  42. package/lib/collaboration/composition.js +1 -1
  43. package/lib/collaboration/composition.js.map +1 -1
  44. package/lib/concern-attention.d.ts +19 -0
  45. package/lib/concern-attention.d.ts.map +1 -0
  46. package/lib/concern-attention.js +36 -0
  47. package/lib/concern-attention.js.map +1 -0
  48. package/lib/concern-domain.d.ts +10 -1
  49. package/lib/concern-domain.d.ts.map +1 -1
  50. package/lib/concern-domain.js +4 -0
  51. package/lib/concern-domain.js.map +1 -1
  52. package/lib/concern-execution-notes.d.ts +18 -0
  53. package/lib/concern-execution-notes.d.ts.map +1 -0
  54. package/lib/concern-execution-notes.js +41 -0
  55. package/lib/concern-execution-notes.js.map +1 -0
  56. package/lib/concern-execution-receipt.d.ts +4 -0
  57. package/lib/concern-execution-receipt.d.ts.map +1 -0
  58. package/lib/concern-execution-receipt.js +28 -0
  59. package/lib/concern-execution-receipt.js.map +1 -0
  60. package/lib/concern-execution.d.ts +32 -0
  61. package/lib/concern-execution.d.ts.map +1 -0
  62. package/lib/concern-execution.js +112 -0
  63. package/lib/concern-execution.js.map +1 -0
  64. package/lib/concern-management-tool.d.ts.map +1 -1
  65. package/lib/concern-management-tool.js +21 -19
  66. package/lib/concern-management-tool.js.map +1 -1
  67. package/lib/concern-note-source.d.ts +7 -0
  68. package/lib/concern-note-source.d.ts.map +1 -0
  69. package/lib/concern-note-source.js +25 -0
  70. package/lib/concern-note-source.js.map +1 -0
  71. package/lib/concern-resources.d.ts +27 -0
  72. package/lib/concern-resources.d.ts.map +1 -0
  73. package/lib/concern-resources.js +36 -0
  74. package/lib/concern-resources.js.map +1 -0
  75. package/lib/concern-sources.d.ts +1 -1
  76. package/lib/concern-sources.d.ts.map +1 -1
  77. package/lib/concern-store.d.ts +10 -1
  78. package/lib/concern-store.d.ts.map +1 -1
  79. package/lib/concern-store.js +135 -36
  80. package/lib/concern-store.js.map +1 -1
  81. package/lib/concern-tool.d.ts +1 -0
  82. package/lib/concern-tool.d.ts.map +1 -1
  83. package/lib/concern-tool.js +8 -4
  84. package/lib/concern-tool.js.map +1 -1
  85. package/lib/decision/conversation.d.ts +11 -0
  86. package/lib/decision/conversation.d.ts.map +1 -0
  87. package/lib/decision/conversation.js +52 -0
  88. package/lib/decision/conversation.js.map +1 -0
  89. package/lib/decision/events.d.ts +16 -0
  90. package/lib/decision/events.d.ts.map +1 -0
  91. package/lib/decision/events.js +9 -0
  92. package/lib/decision/events.js.map +1 -0
  93. package/lib/domain.d.ts +2 -0
  94. package/lib/domain.d.ts.map +1 -1
  95. package/lib/domain.js.map +1 -1
  96. package/lib/heartbeat.d.ts +12 -10
  97. package/lib/heartbeat.d.ts.map +1 -1
  98. package/lib/heartbeat.js +55 -26
  99. package/lib/heartbeat.js.map +1 -1
  100. package/lib/index.d.ts.map +1 -1
  101. package/lib/index.js +4 -1
  102. package/lib/index.js.map +1 -1
  103. package/lib/memory-prompt-context.js +1 -0
  104. package/lib/memory-prompt-context.js.map +1 -1
  105. package/lib/memory-reflection.js +1 -1
  106. package/lib/observation-contract.d.ts +9 -0
  107. package/lib/observation-contract.d.ts.map +1 -0
  108. package/lib/observation-contract.js +19 -0
  109. package/lib/observation-contract.js.map +1 -0
  110. package/lib/observation-executor.d.ts +5 -0
  111. package/lib/observation-executor.d.ts.map +1 -1
  112. package/lib/observation-executor.js +23 -1
  113. package/lib/observation-executor.js.map +1 -1
  114. package/lib/observation-loop.d.ts +7 -0
  115. package/lib/observation-loop.d.ts.map +1 -1
  116. package/lib/observation-loop.js +65 -4
  117. package/lib/observation-loop.js.map +1 -1
  118. package/lib/observation-progress.d.ts +18 -0
  119. package/lib/observation-progress.d.ts.map +1 -0
  120. package/lib/observation-progress.js +57 -0
  121. package/lib/observation-progress.js.map +1 -0
  122. package/lib/observation-sources.d.ts +14 -0
  123. package/lib/observation-sources.d.ts.map +1 -0
  124. package/lib/observation-sources.js +12 -0
  125. package/lib/observation-sources.js.map +1 -0
  126. package/lib/pendant/badge-art.d.ts +9 -0
  127. package/lib/pendant/badge-art.d.ts.map +1 -0
  128. package/lib/pendant/badge-art.js +68 -0
  129. package/lib/pendant/badge-art.js.map +1 -0
  130. package/lib/pendant/card-image.d.ts.map +1 -1
  131. package/lib/pendant/card-image.js +11 -9
  132. package/lib/pendant/card-image.js.map +1 -1
  133. package/lib/pendant/renderer-loader.d.ts +9 -0
  134. package/lib/pendant/renderer-loader.d.ts.map +1 -0
  135. package/lib/pendant/renderer-loader.js +12 -0
  136. package/lib/pendant/renderer-loader.js.map +1 -0
  137. package/lib/pendant/renderer.d.ts +2 -6
  138. package/lib/pendant/renderer.d.ts.map +1 -1
  139. package/lib/pendant/renderer.js +1 -67
  140. package/lib/pendant/renderer.js.map +1 -1
  141. package/lib/pendant/static-card.d.ts +20 -0
  142. package/lib/pendant/static-card.d.ts.map +1 -0
  143. package/lib/pendant/static-card.js +55 -0
  144. package/lib/pendant/static-card.js.map +1 -0
  145. package/lib/pendant/widget.d.ts.map +1 -1
  146. package/lib/pendant/widget.js +9 -5
  147. package/lib/pendant/widget.js.map +1 -1
  148. package/lib/pendant-renderer.js +14 -14
  149. package/lib/requirements/service.d.ts.map +1 -1
  150. package/lib/requirements/service.js +2 -0
  151. package/lib/requirements/service.js.map +1 -1
  152. package/lib/requirements/worker.d.ts.map +1 -1
  153. package/lib/requirements/worker.js +1 -0
  154. package/lib/requirements/worker.js.map +1 -1
  155. package/lib/scheduler/continuations.d.ts.map +1 -1
  156. package/lib/scheduler/continuations.js +17 -3
  157. package/lib/scheduler/continuations.js.map +1 -1
  158. package/lib/skills/builtin.d.ts.map +1 -1
  159. package/lib/skills/builtin.js +2 -0
  160. package/lib/skills/builtin.js.map +1 -1
  161. package/lib/skills/drama-builtin.d.ts.map +1 -1
  162. package/lib/skills/drama-builtin.js +13 -1
  163. package/lib/skills/drama-builtin.js.map +1 -1
  164. package/lib/skills/story-builtins.d.ts +3 -0
  165. package/lib/skills/story-builtins.d.ts.map +1 -0
  166. package/lib/skills/story-builtins.js +72 -0
  167. package/lib/skills/story-builtins.js.map +1 -0
  168. package/lib/skills/viora-canvas-builtin.d.ts.map +1 -1
  169. package/lib/skills/viora-canvas-builtin.js +12 -1
  170. package/lib/skills/viora-canvas-builtin.js.map +1 -1
  171. package/lib/snapshot-cache.d.ts +7 -0
  172. package/lib/snapshot-cache.d.ts.map +1 -0
  173. package/lib/snapshot-cache.js +26 -0
  174. package/lib/snapshot-cache.js.map +1 -0
  175. package/lib/tasks/result.js +1 -1
  176. package/lib/tasks/result.js.map +1 -1
  177. package/lib/ui/memory/concern-board.d.ts +1 -1
  178. package/lib/ui/memory/concern-board.d.ts.map +1 -1
  179. package/lib/ui/memory/concern-board.js +14 -6
  180. package/lib/ui/memory/concern-board.js.map +1 -1
  181. package/lib/ui/memory/concern-deletion.d.ts.map +1 -1
  182. package/lib/ui/memory/concern-deletion.js +2 -1
  183. package/lib/ui/memory/concern-deletion.js.map +1 -1
  184. package/lib/ui/memory/concern-editor.d.ts.map +1 -1
  185. package/lib/ui/memory/concern-editor.js +5 -3
  186. package/lib/ui/memory/concern-editor.js.map +1 -1
  187. package/lib/ui/memory/concern-execution-fields.d.ts +7 -0
  188. package/lib/ui/memory/concern-execution-fields.d.ts.map +1 -0
  189. package/lib/ui/memory/concern-execution-fields.js +8 -0
  190. package/lib/ui/memory/concern-execution-fields.js.map +1 -0
  191. package/lib/ui/memory/concern-execution-status.d.ts +8 -0
  192. package/lib/ui/memory/concern-execution-status.d.ts.map +1 -0
  193. package/lib/ui/memory/concern-execution-status.js +31 -0
  194. package/lib/ui/memory/concern-execution-status.js.map +1 -0
  195. package/lib/ui/memory/concern-feedback.d.ts +15 -0
  196. package/lib/ui/memory/concern-feedback.d.ts.map +1 -0
  197. package/lib/ui/memory/concern-feedback.js +13 -0
  198. package/lib/ui/memory/concern-feedback.js.map +1 -0
  199. package/lib/ui/memory/concern-panel.d.ts.map +1 -1
  200. package/lib/ui/memory/concern-panel.js +11 -3
  201. package/lib/ui/memory/concern-panel.js.map +1 -1
  202. package/package.json +7 -1
  203. package/resources/skills/novel-game-creation/manifest.json +393 -0
  204. package/resources/skills/novel-game-creation/upstream/LICENSE +21 -0
  205. package/resources/skills/novel-game-creation/upstream/README.md +420 -0
  206. package/resources/skills/novel-game-creation/upstream/README_ZH.md +421 -0
  207. package/resources/skills/novel-game-creation/upstream/VERSION +1 -0
  208. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/BUILD_BRIEF.md +219 -0
  209. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/.vercelignore +3 -0
  210. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/RUN.md +138 -0
  211. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/bg/compound_new_guofeng.webp +0 -0
  212. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/finale/fate_coda.webp +0 -0
  213. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/finale/meng.webp +0 -0
  214. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/finale/pan.webp +0 -0
  215. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/finale/pinger.webp +0 -0
  216. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/finale/xuee.webp +0 -0
  217. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/finale/yue.webp +0 -0
  218. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_accord_five.webp +0 -0
  219. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_afterglow_five.webp +0 -0
  220. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/group/inner_court_alliance.webp +0 -0
  221. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/group/public_day10.webp +0 -0
  222. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/group/public_day15.webp +0 -0
  223. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/group/public_day5.webp +0 -0
  224. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/group/title_new_guofeng.webp +0 -0
  225. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/joint/pan_pinger.webp +0 -0
  226. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/joint/pan_xuee.webp +0 -0
  227. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/joint/yue_meng.webp +0 -0
  228. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/joint/yue_pan.webp +0 -0
  229. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/joint/yue_pinger.webp +0 -0
  230. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/meng/explicit.webp +0 -0
  231. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/meng/prelude.webp +0 -0
  232. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/milestone/meng.webp +0 -0
  233. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/milestone/pan.webp +0 -0
  234. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/milestone/pinger.webp +0 -0
  235. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/milestone/xuee.webp +0 -0
  236. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/milestone/yue.webp +0 -0
  237. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/pan/explicit.webp +0 -0
  238. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/pan/prelude.webp +0 -0
  239. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/pinger/explicit.webp +0 -0
  240. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/pinger/prelude.webp +0 -0
  241. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/xuee/explicit.webp +0 -0
  242. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/xuee/prelude.webp +0 -0
  243. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/yue/explicit.webp +0 -0
  244. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/cg/yue/prelude.webp +0 -0
  245. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/heroine/meng/night.webp +0 -0
  246. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/heroine/pan/night.webp +0 -0
  247. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/heroine/pinger/night.webp +0 -0
  248. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/heroine/xuee/night.webp +0 -0
  249. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/heroine/yue/night.webp +0 -0
  250. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/household/li_jiaoer.webp +0 -0
  251. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/assets/scene/gate_collector.webp +0 -0
  252. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/css/new-guofeng.css +1494 -0
  253. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/css/style.css +2508 -0
  254. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/index.html +18 -0
  255. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/js/assets.js +147 -0
  256. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/js/audio.js +332 -0
  257. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/js/data.js +7470 -0
  258. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/js/engine.js +13066 -0
  259. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/js/main.js +3051 -0
  260. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/js/text.js +32 -0
  261. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/package.json +11 -0
  262. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/app/vercel.json +23 -0
  263. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/build/art/generated-art.json +161 -0
  264. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/example.json +12 -0
  265. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/qa/README.md +26 -0
  266. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/qa/playable-model.json +99 -0
  267. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/qa/verification.json +41 -0
  268. package/resources/skills/novel-game-creation/upstream/examples/jin-ping-mei/qa/verify_playable_model.mjs +152 -0
  269. package/resources/skills/novel-game-creation/upstream/manifest.json +498 -0
  270. package/resources/skills/novel-game-creation/upstream/skills/game-art-direction/SKILL.md +47 -0
  271. package/resources/skills/novel-game-creation/upstream/skills/game-art-direction/agents/openai.yaml +4 -0
  272. package/resources/skills/novel-game-creation/upstream/skills/game-art-direction/references/art-direction-method.md +40 -0
  273. package/resources/skills/novel-game-creation/upstream/skills/game-build/SKILL.md +66 -0
  274. package/resources/skills/novel-game-creation/upstream/skills/game-build/agents/openai.yaml +4 -0
  275. package/resources/skills/novel-game-creation/upstream/skills/game-build/references/blender-asset-workflow.md +24 -0
  276. package/resources/skills/novel-game-creation/upstream/skills/game-build/references/build-brief-contract.md +72 -0
  277. package/resources/skills/novel-game-creation/upstream/skills/game-build/references/generative-media-pipeline.md +25 -0
  278. package/resources/skills/novel-game-creation/upstream/skills/game-build/references/playable-model-contract.md +46 -0
  279. package/resources/skills/novel-game-creation/upstream/skills/game-build/references/tts-production-contract.md +23 -0
  280. package/resources/skills/novel-game-creation/upstream/skills/game-concept/SKILL.md +54 -0
  281. package/resources/skills/novel-game-creation/upstream/skills/game-concept/agents/openai.yaml +4 -0
  282. package/resources/skills/novel-game-creation/upstream/skills/game-concept/references/concept-method.md +47 -0
  283. package/resources/skills/novel-game-creation/upstream/skills/game-qa/SKILL.md +39 -0
  284. package/resources/skills/novel-game-creation/upstream/skills/game-qa/agents/openai.yaml +4 -0
  285. package/resources/skills/novel-game-creation/upstream/skills/game-qa/references/qa-contract.md +66 -0
  286. package/resources/skills/novel-game-creation/upstream/skills/game-qa/references/test-design-method.md +49 -0
  287. package/resources/skills/novel-game-creation/upstream/skills/game-world-design/SKILL.md +50 -0
  288. package/resources/skills/novel-game-creation/upstream/skills/game-world-design/agents/openai.yaml +4 -0
  289. package/resources/skills/novel-game-creation/upstream/skills/game-world-design/references/narrative-design-method.md +35 -0
  290. package/resources/skills/novel-game-creation/upstream/skills/game-world-design/references/numeric-design-method.md +22 -0
  291. package/resources/skills/novel-game-creation/upstream/skills/game-world-design/references/world-design-method.md +57 -0
  292. package/resources/skills/novel-game-creation/upstream/skills/novel-game-analyze/SKILL.md +68 -0
  293. package/resources/skills/novel-game-creation/upstream/skills/novel-game-analyze/agents/openai.yaml +4 -0
  294. package/resources/skills/novel-game-creation/upstream/skills/novel-game-analyze/references/gameability-protocol.md +65 -0
  295. package/resources/skills/novel-game-creation/upstream/skills/novel-to-game/SKILL.md +54 -0
  296. package/resources/skills/novel-game-creation/upstream/skills/novel-to-game/agents/openai.yaml +4 -0
  297. package/resources/skills/novel-game-creation/upstream/skills/novel-to-game/references/intake-method.md +50 -0
  298. package/resources/skills/novel-game-creation/upstream/skills/novel-to-game/references/pipeline-contract.md +60 -0
  299. package/resources/skills/story-creation/manifest.json +1477 -0
  300. package/resources/skills/story-creation/upstream/LICENSE.upstream +21 -0
  301. package/resources/skills/story-creation/upstream/manifest.json +1861 -0
  302. package/resources/skills/story-creation/upstream/roles/chapter-extractor.md +254 -0
  303. package/resources/skills/story-creation/upstream/roles/character-designer.md +168 -0
  304. package/resources/skills/story-creation/upstream/roles/consistency-checker.md +192 -0
  305. package/resources/skills/story-creation/upstream/roles/narrative-writer.md +105 -0
  306. package/resources/skills/story-creation/upstream/roles/story-architect.md +139 -0
  307. package/resources/skills/story-creation/upstream/roles/story-explorer.md +262 -0
  308. package/resources/skills/story-creation/upstream/roles/story-researcher.md +303 -0
  309. package/resources/skills/story-creation/upstream/skills/browser-cdp/SKILL.md +175 -0
  310. package/resources/skills/story-creation/upstream/skills/story/SKILL.md +129 -0
  311. package/resources/skills/story-creation/upstream/skills/story/VERSION +1 -0
  312. package/resources/skills/story-creation/upstream/skills/story/references/author-memory-maintenance.md +75 -0
  313. package/resources/skills/story-creation/upstream/skills/story/references/author-memory.md +117 -0
  314. package/resources/skills/story-creation/upstream/skills/story/scripts/author_memory_commit.py +1635 -0
  315. package/resources/skills/story-creation/upstream/skills/story-cover/SKILL.md +336 -0
  316. package/resources/skills/story-creation/upstream/skills/story-cover/references/cover-styles.md +210 -0
  317. package/resources/skills/story-creation/upstream/skills/story-deslop/SKILL.md +274 -0
  318. package/resources/skills/story-creation/upstream/skills/story-deslop/references/agent-calls.md +5 -0
  319. package/resources/skills/story-creation/upstream/skills/story-deslop/references/anti-ai-writing.md +556 -0
  320. package/resources/skills/story-creation/upstream/skills/story-deslop/references/author-memory-maintenance.md +75 -0
  321. package/resources/skills/story-creation/upstream/skills/story-deslop/references/author-memory.md +117 -0
  322. package/resources/skills/story-creation/upstream/skills/story-deslop/references/banned-words.md +151 -0
  323. package/resources/skills/story-creation/upstream/skills/story-deslop/references/deslop-gates.md +171 -0
  324. package/resources/skills/story-creation/upstream/skills/story-deslop/references/style-resolution.md +27 -0
  325. package/resources/skills/story-creation/upstream/skills/story-deslop/scripts/author_memory_commit.py +1635 -0
  326. package/resources/skills/story-creation/upstream/skills/story-deslop/scripts/check-ai-patterns.js +1586 -0
  327. package/resources/skills/story-creation/upstream/skills/story-deslop/scripts/check-degeneration.js +349 -0
  328. package/resources/skills/story-creation/upstream/skills/story-deslop/scripts/normalize-punctuation.js +415 -0
  329. package/resources/skills/story-creation/upstream/skills/story-deslop/scripts/style-whitelist.js +39 -0
  330. package/resources/skills/story-creation/upstream/skills/story-import/SKILL.md +127 -0
  331. package/resources/skills/story-creation/upstream/skills/story-import/references/character-state-reverse.md +82 -0
  332. package/resources/skills/story-creation/upstream/skills/story-import/references/deep-analysis.md +47 -0
  333. package/resources/skills/story-creation/upstream/skills/story-import/references/format-and-structure.md +168 -0
  334. package/resources/skills/story-creation/upstream/skills/story-import/references/import-report.md +64 -0
  335. package/resources/skills/story-creation/upstream/skills/story-import/references/import-special-cases.md +34 -0
  336. package/resources/skills/story-creation/upstream/skills/story-import/references/import-tracking.md +95 -0
  337. package/resources/skills/story-creation/upstream/skills/story-import/references/length-routing.md +134 -0
  338. package/resources/skills/story-creation/upstream/skills/story-import/references/outline-reverse-rules.md +79 -0
  339. package/resources/skills/story-creation/upstream/skills/story-import/references/outline-reverse.md +24 -0
  340. package/resources/skills/story-creation/upstream/skills/story-import/references/state-tracking.md +43 -0
  341. package/resources/skills/story-creation/upstream/skills/story-import/references/structure-mapping-long.md +448 -0
  342. package/resources/skills/story-creation/upstream/skills/story-import/references/structure-mapping-short.md +187 -0
  343. package/resources/skills/story-creation/upstream/skills/story-import/references/tracking-initialization.md +31 -0
  344. package/resources/skills/story-creation/upstream/skills/story-import/references/tracking-transaction.md +146 -0
  345. package/resources/skills/story-creation/upstream/skills/story-import/scripts/build_outline_brief.py +332 -0
  346. package/resources/skills/story-creation/upstream/skills/story-import/scripts/check-outline-contract.js +520 -0
  347. package/resources/skills/story-creation/upstream/skills/story-import/scripts/tracking_commit.py +1691 -0
  348. package/resources/skills/story-creation/upstream/skills/story-import/scripts/wordcount_core.py +405 -0
  349. package/resources/skills/story-creation/upstream/skills/story-long-analyze/SKILL.md +140 -0
  350. package/resources/skills/story-creation/upstream/skills/story-long-analyze/references/author-facing.md +252 -0
  351. package/resources/skills/story-creation/upstream/skills/story-long-analyze/references/deconstruction-notes.md +270 -0
  352. package/resources/skills/story-creation/upstream/skills/story-long-analyze/references/final-checks.md +19 -0
  353. package/resources/skills/story-creation/upstream/skills/story-long-analyze/references/index-rebuild.md +11 -0
  354. package/resources/skills/story-creation/upstream/skills/story-long-analyze/references/inspiration-library.md +105 -0
  355. package/resources/skills/story-creation/upstream/skills/story-long-analyze/references/pipeline-ops.md +154 -0
  356. package/resources/skills/story-creation/upstream/skills/story-long-analyze/references/semantic-acceptance-fixtures.md +84 -0
  357. package/resources/skills/story-creation/upstream/skills/story-long-analyze/references/stage1-golden-chapters.md +128 -0
  358. package/resources/skills/story-creation/upstream/skills/story-long-analyze/references/stage2-extraction.md +229 -0
  359. package/resources/skills/story-creation/upstream/skills/story-long-analyze/references/stage3-plot-rhythm.md +480 -0
  360. package/resources/skills/story-creation/upstream/skills/story-long-analyze/references/stage4-characters-settings.md +195 -0
  361. package/resources/skills/story-creation/upstream/skills/story-long-analyze/references/stage5-report.md +33 -0
  362. package/resources/skills/story-creation/upstream/skills/story-long-analyze/references/style-profile-generator.md +76 -0
  363. package/resources/skills/story-creation/upstream/skills/story-long-analyze/references/style-profile-protocol.md +133 -0
  364. package/resources/skills/story-creation/upstream/skills/story-long-analyze/references/synthesis-inputs.md +39 -0
  365. package/resources/skills/story-creation/upstream/skills/story-long-analyze/scripts/build_chapter_index.py +466 -0
  366. package/resources/skills/story-creation/upstream/skills/story-long-analyze/scripts/inspect_existing_assets.py +702 -0
  367. package/resources/skills/story-creation/upstream/skills/story-long-analyze/scripts/inspiration_index.py +864 -0
  368. package/resources/skills/story-creation/upstream/skills/story-long-analyze/scripts/manage_analysis_run.py +1145 -0
  369. package/resources/skills/story-creation/upstream/skills/story-long-analyze/scripts/render_relation_chart.py +425 -0
  370. package/resources/skills/story-creation/upstream/skills/story-long-scan/SKILL.md +193 -0
  371. package/resources/skills/story-creation/upstream/skills/story-long-scan/references/genre-trends.md +197 -0
  372. package/resources/skills/story-creation/upstream/skills/story-long-scan/references/platform-ciweimao.md +19 -0
  373. package/resources/skills/story-creation/upstream/skills/story-long-scan/references/platform-fanqie.md +45 -0
  374. package/resources/skills/story-creation/upstream/skills/story-long-scan/references/platform-jjwxc.md +28 -0
  375. package/resources/skills/story-creation/upstream/skills/story-long-scan/references/platform-qidian.md +35 -0
  376. package/resources/skills/story-creation/upstream/skills/story-long-scan/references/platform-qimao.md +27 -0
  377. package/resources/skills/story-creation/upstream/skills/story-long-scan/references/publishing-guide.md +114 -0
  378. package/resources/skills/story-creation/upstream/skills/story-long-scan/references/reader-profiling.md +105 -0
  379. package/resources/skills/story-creation/upstream/skills/story-long-scan/references/topic-decision.md +82 -0
  380. package/resources/skills/story-creation/upstream/skills/story-long-scan/scripts/aggregate-rank.js +682 -0
  381. package/resources/skills/story-creation/upstream/skills/story-long-scan/scripts/cdp-utils.js +242 -0
  382. package/resources/skills/story-creation/upstream/skills/story-long-scan/scripts/ciweimao-rank-scraper.js +240 -0
  383. package/resources/skills/story-creation/upstream/skills/story-long-scan/scripts/fanqie-rank-scraper.js +658 -0
  384. package/resources/skills/story-creation/upstream/skills/story-long-scan/scripts/jjwxc-rank-scraper.js +400 -0
  385. package/resources/skills/story-creation/upstream/skills/story-long-scan/scripts/qidian-rank-scraper.js +592 -0
  386. package/resources/skills/story-creation/upstream/skills/story-long-scan/scripts/qimao-rank-scraper.js +435 -0
  387. package/resources/skills/story-creation/upstream/skills/story-long-write/SKILL.md +136 -0
  388. package/resources/skills/story-creation/upstream/skills/story-long-write/references/agent-calls.md +47 -0
  389. package/resources/skills/story-creation/upstream/skills/story-long-write/references/anti-ai-writing.md +556 -0
  390. package/resources/skills/story-creation/upstream/skills/story-long-write/references/artifact-protocols.md +183 -0
  391. package/resources/skills/story-creation/upstream/skills/story-long-write/references/author-memory-maintenance.md +75 -0
  392. package/resources/skills/story-creation/upstream/skills/story-long-write/references/author-memory.md +117 -0
  393. package/resources/skills/story-creation/upstream/skills/story-long-write/references/banned-words.md +151 -0
  394. package/resources/skills/story-creation/upstream/skills/story-long-write/references/benchmark-recall.md +16 -0
  395. package/resources/skills/story-creation/upstream/skills/story-long-write/references/character-basics.md +445 -0
  396. package/resources/skills/story-creation/upstream/skills/story-long-write/references/character-design-methods.md +428 -0
  397. package/resources/skills/story-creation/upstream/skills/story-long-write/references/character-relations.md +433 -0
  398. package/resources/skills/story-creation/upstream/skills/story-long-write/references/commercial-core-methods.md +502 -0
  399. package/resources/skills/story-creation/upstream/skills/story-long-write/references/cross-book-recall.md +88 -0
  400. package/resources/skills/story-creation/upstream/skills/story-long-write/references/deslop-gates.md +171 -0
  401. package/resources/skills/story-creation/upstream/skills/story-long-write/references/dialogue-mastery.md +288 -0
  402. package/resources/skills/story-creation/upstream/skills/story-long-write/references/emotion-on-page.md +116 -0
  403. package/resources/skills/story-creation/upstream/skills/story-long-write/references/emotional-arc-design.md +313 -0
  404. package/resources/skills/story-creation/upstream/skills/story-long-write/references/emotional-methods.md +209 -0
  405. package/resources/skills/story-creation/upstream/skills/story-long-write/references/female-audience-writing.md +188 -0
  406. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//344/270/234/346/226/271/344/273/231/344/276/240.md +51 -0
  407. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//344/274/240/347/273/237/347/216/204/345/271/273.md +51 -0
  408. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//345/216/206/345/217/262/345/217/244/344/273/243.md +51 -0
  409. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//345/216/206/345/217/262/350/204/221/346/264/236.md +51 -0
  410. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//345/217/214/347/224/267/344/270/273.md +53 -0
  411. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//345/217/244/350/250/200/350/204/221/346/264/236.md +51 -0
  412. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//345/217/244/351/243/216/344/270/226/346/203/205.md +51 -0
  413. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//345/245/263/351/242/221/346/202/254/347/226/221.md +51 -0
  414. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//345/245/263/351/242/221/347/247/215/347/224/260.md +51 -0
  415. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//345/256/253/346/226/227/345/256/205/346/226/227.md +51 -0
  416. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//345/271/264/344/273/243.md +53 -0
  417. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//345/277/253/347/251/277.md +51 -0
  418. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//346/202/254/347/226/221/347/201/265/345/274/202.md +51 -0
  419. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//346/202/254/347/226/221/350/204/221/346/264/236.md +51 -0
  420. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//346/210/230/347/245/236/350/265/230/345/251/277.md +51 -0
  421. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//346/212/227/346/210/230/350/260/215/346/210/230.md +51 -0
  422. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//346/230/237/345/205/211/347/222/200/347/222/250.md +51 -0
  423. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//346/260/221/345/233/275/350/250/200/346/203/205.md +51 -0
  424. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//346/270/270/346/210/217/344/275/223/350/202/262.md +51 -0
  425. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//347/216/204/345/271/273/350/204/221/346/264/236.md +53 -0
  426. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//347/216/204/345/271/273/350/250/200/346/203/205.md +51 -0
  427. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//347/216/260/350/250/200/350/204/221/346/264/236.md +51 -0
  428. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//347/247/221/345/271/273/346/234/253/344/270/226.md +51 -0
  429. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//350/201/214/345/234/272/345/251/232/346/201/213.md +51 -0
  430. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//350/245/277/346/226/271/345/245/207/345/271/273.md +51 -0
  431. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//350/261/252/351/227/250/346/200/273/350/243/201.md +53 -0
  432. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//351/203/275/345/270/202/344/277/256/347/234/237.md +51 -0
  433. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//351/203/275/345/270/202/346/227/245/345/270/270.md +53 -0
  434. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//351/203/275/345/270/202/347/247/215/347/224/260.md +51 -0
  435. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//351/203/275/345/270/202/350/204/221/346/264/236.md +53 -0
  436. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//351/203/275/345/270/202/351/253/230/346/255/246.md +53 -0
  437. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards//351/235/222/346/230/245/347/224/234/345/256/240.md +51 -0
  438. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-prose-cards.md +91 -0
  439. package/resources/skills/story-creation/upstream/skills/story-long-write/references/genre-readers.md +235 -0
  440. package/resources/skills/story-creation/upstream/skills/story-long-write/references/long-chapter-hooks.md +225 -0
  441. package/resources/skills/story-creation/upstream/skills/story-long-write/references/long-chapter-quality.md +177 -0
  442. package/resources/skills/story-creation/upstream/skills/story-long-write/references/long-format.md +36 -0
  443. package/resources/skills/story-creation/upstream/skills/story-long-write/references/long-genre-catalog.md +394 -0
  444. package/resources/skills/story-creation/upstream/skills/story-long-write/references/long-genre-mechanics.md +415 -0
  445. package/resources/skills/story-creation/upstream/skills/story-long-write/references/long-reversal.md +335 -0
  446. package/resources/skills/story-creation/upstream/skills/story-long-write/references/long-suspense.md +352 -0
  447. package/resources/skills/story-creation/upstream/skills/story-long-write/references/opening-design.md +349 -0
  448. package/resources/skills/story-creation/upstream/skills/story-long-write/references/outline-conflict.md +457 -0
  449. package/resources/skills/story-creation/upstream/skills/story-long-write/references/outline-methods.md +287 -0
  450. package/resources/skills/story-creation/upstream/skills/story-long-write/references/outline-rhythm.md +389 -0
  451. package/resources/skills/story-creation/upstream/skills/story-long-write/references/outline-structure-theory.md +273 -0
  452. package/resources/skills/story-creation/upstream/skills/story-long-write/references/plot-core-methods.md +522 -0
  453. package/resources/skills/story-creation/upstream/skills/story-long-write/references/plot-emotion-system.md +354 -0
  454. package/resources/skills/story-creation/upstream/skills/story-long-write/references/plot-frameworks.md +711 -0
  455. package/resources/skills/story-creation/upstream/skills/story-long-write/references/plot-special-topics.md +622 -0
  456. package/resources/skills/story-creation/upstream/skills/story-long-write/references/project-files.md +114 -0
  457. package/resources/skills/story-creation/upstream/skills/story-long-write/references/reader-contract-and-progression.md +117 -0
  458. package/resources/skills/story-creation/upstream/skills/story-long-write/references/reference-index.md +86 -0
  459. package/resources/skills/story-creation/upstream/skills/story-long-write/references/stop-and-fix.md +9 -0
  460. package/resources/skills/story-creation/upstream/skills/story-long-write/references/style-combat-face.md +352 -0
  461. package/resources/skills/story-creation/upstream/skills/story-long-write/references/style-craft.md +327 -0
  462. package/resources/skills/story-creation/upstream/skills/story-long-write/references/style-genre-modules.md +510 -0
  463. package/resources/skills/story-creation/upstream/skills/story-long-write/references/style-resolution.md +27 -0
  464. package/resources/skills/story-creation/upstream/skills/story-long-write/references/tracking-initialization.md +31 -0
  465. package/resources/skills/story-creation/upstream/skills/story-long-write/references/tracking-transaction.md +146 -0
  466. package/resources/skills/story-creation/upstream/skills/story-long-write/references/workflow-chapter.md +104 -0
  467. package/resources/skills/story-creation/upstream/skills/story-long-write/references/workflow-daily.md +75 -0
  468. package/resources/skills/story-creation/upstream/skills/story-long-write/references/workflow-outline.md +157 -0
  469. package/resources/skills/story-creation/upstream/skills/story-long-write/references/workflow-revision.md +89 -0
  470. package/resources/skills/story-creation/upstream/skills/story-long-write/references/workflow-setup.md +170 -0
  471. package/resources/skills/story-creation/upstream/skills/story-long-write/references/workflow-volume.md +142 -0
  472. package/resources/skills/story-creation/upstream/skills/story-long-write/references/writer-prompt-fallback.md +24 -0
  473. package/resources/skills/story-creation/upstream/skills/story-long-write/references/writing-craft.md +442 -0
  474. package/resources/skills/story-creation/upstream/skills/story-long-write/scripts/author_memory_commit.py +1635 -0
  475. package/resources/skills/story-creation/upstream/skills/story-long-write/scripts/build_architect_brief.py +260 -0
  476. package/resources/skills/story-creation/upstream/skills/story-long-write/scripts/build_writer_prompt.py +611 -0
  477. package/resources/skills/story-creation/upstream/skills/story-long-write/scripts/check-ai-patterns.js +1586 -0
  478. package/resources/skills/story-creation/upstream/skills/story-long-write/scripts/check-degeneration.js +349 -0
  479. package/resources/skills/story-creation/upstream/skills/story-long-write/scripts/check-outline-contract.js +520 -0
  480. package/resources/skills/story-creation/upstream/skills/story-long-write/scripts/check-outline-copy.js +313 -0
  481. package/resources/skills/story-creation/upstream/skills/story-long-write/scripts/normalize-punctuation.js +415 -0
  482. package/resources/skills/story-creation/upstream/skills/story-long-write/scripts/outline_view.py +286 -0
  483. package/resources/skills/story-creation/upstream/skills/story-long-write/scripts/storyctl.py +561 -0
  484. package/resources/skills/story-creation/upstream/skills/story-long-write/scripts/style-whitelist.js +39 -0
  485. package/resources/skills/story-creation/upstream/skills/story-long-write/scripts/tracking_commit.py +1691 -0
  486. package/resources/skills/story-creation/upstream/skills/story-long-write/scripts/wordcount_core.py +405 -0
  487. package/resources/skills/story-creation/upstream/skills/story-review/SKILL.md +231 -0
  488. package/resources/skills/story-creation/upstream/skills/story-review/references/agent-prompts.md +184 -0
  489. package/resources/skills/story-creation/upstream/skills/story-review/references/anti-ai-writing.md +556 -0
  490. package/resources/skills/story-creation/upstream/skills/story-review/references/author-memory-maintenance.md +75 -0
  491. package/resources/skills/story-creation/upstream/skills/story-review/references/author-memory.md +117 -0
  492. package/resources/skills/story-creation/upstream/skills/story-review/references/banned-words.md +151 -0
  493. package/resources/skills/story-creation/upstream/skills/story-review/references/batch-review.md +19 -0
  494. package/resources/skills/story-creation/upstream/skills/story-review/references/character-relations.md +433 -0
  495. package/resources/skills/story-creation/upstream/skills/story-review/references/dialogue-mastery.md +288 -0
  496. package/resources/skills/story-creation/upstream/skills/story-review/references/plot-core-methods.md +522 -0
  497. package/resources/skills/story-creation/upstream/skills/story-review/references/quality-rubric.md +55 -0
  498. package/resources/skills/story-creation/upstream/skills/story-review/references/review-quality.md +246 -0
  499. package/resources/skills/story-creation/upstream/skills/story-review/references/review-tracking.md +10 -0
  500. package/resources/skills/story-creation/upstream/skills/story-review/references/rubrics/fanqie.md +18 -0
  501. package/resources/skills/story-creation/upstream/skills/story-review/references/rubrics/qidian.md +19 -0
  502. package/resources/skills/story-creation/upstream/skills/story-review/references/rubrics/zhihu.md +20 -0
  503. package/resources/skills/story-creation/upstream/skills/story-review/references/solo.md +52 -0
  504. package/resources/skills/story-creation/upstream/skills/story-review/references/style-resolution.md +27 -0
  505. package/resources/skills/story-creation/upstream/skills/story-review/references/tracking-initialization.md +31 -0
  506. package/resources/skills/story-creation/upstream/skills/story-review/references/tracking-transaction.md +146 -0
  507. package/resources/skills/story-creation/upstream/skills/story-review/scripts/author_memory_commit.py +1635 -0
  508. package/resources/skills/story-creation/upstream/skills/story-review/scripts/check-ai-patterns.js +1586 -0
  509. package/resources/skills/story-creation/upstream/skills/story-review/scripts/check-degeneration.js +349 -0
  510. package/resources/skills/story-creation/upstream/skills/story-review/scripts/normalize-punctuation.js +415 -0
  511. package/resources/skills/story-creation/upstream/skills/story-review/scripts/style-whitelist.js +39 -0
  512. package/resources/skills/story-creation/upstream/skills/story-review/scripts/tracking_commit.py +1691 -0
  513. package/resources/skills/story-creation/upstream/skills/story-review/scripts/wordcount_core.py +405 -0
  514. package/resources/skills/story-creation/upstream/skills/story-setup/SKILL.md +165 -0
  515. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/agent-quality.md +72 -0
  516. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/agent-reference-profiles.md +55 -0
  517. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/anti-ai-writing.md +556 -0
  518. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/banned-words.md +151 -0
  519. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/benchmark-style-load.md +107 -0
  520. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/character-basics.md +445 -0
  521. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/character-design-methods.md +428 -0
  522. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/character-relations.md +433 -0
  523. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/deslop-gates.md +171 -0
  524. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/dialogue-mastery.md +288 -0
  525. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/emotion-on-page.md +116 -0
  526. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/emotional-arc-design.md +313 -0
  527. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/format-and-structure.md +168 -0
  528. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//344/270/234/346/226/271/344/273/231/344/276/240.md +51 -0
  529. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//344/274/240/347/273/237/347/216/204/345/271/273.md +51 -0
  530. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//345/216/206/345/217/262/345/217/244/344/273/243.md +51 -0
  531. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//345/216/206/345/217/262/350/204/221/346/264/236.md +51 -0
  532. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//345/217/214/347/224/267/344/270/273.md +53 -0
  533. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//345/217/244/350/250/200/350/204/221/346/264/236.md +51 -0
  534. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//345/217/244/351/243/216/344/270/226/346/203/205.md +51 -0
  535. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//345/245/263/351/242/221/346/202/254/347/226/221.md +51 -0
  536. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//345/245/263/351/242/221/347/247/215/347/224/260.md +51 -0
  537. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//345/256/253/346/226/227/345/256/205/346/226/227.md +51 -0
  538. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//345/271/264/344/273/243.md +53 -0
  539. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//345/277/253/347/251/277.md +51 -0
  540. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//346/202/254/347/226/221/347/201/265/345/274/202.md +51 -0
  541. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//346/202/254/347/226/221/350/204/221/346/264/236.md +51 -0
  542. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//346/210/230/347/245/236/350/265/230/345/251/277.md +51 -0
  543. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//346/212/227/346/210/230/350/260/215/346/210/230.md +51 -0
  544. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//346/230/237/345/205/211/347/222/200/347/222/250.md +51 -0
  545. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//346/260/221/345/233/275/350/250/200/346/203/205.md +51 -0
  546. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//346/270/270/346/210/217/344/275/223/350/202/262.md +51 -0
  547. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//347/216/204/345/271/273/350/204/221/346/264/236.md +53 -0
  548. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//347/216/204/345/271/273/350/250/200/346/203/205.md +51 -0
  549. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//347/216/260/350/250/200/350/204/221/346/264/236.md +51 -0
  550. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//347/247/221/345/271/273/346/234/253/344/270/226.md +51 -0
  551. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//350/201/214/345/234/272/345/251/232/346/201/213.md +51 -0
  552. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//350/245/277/346/226/271/345/245/207/345/271/273.md +51 -0
  553. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//350/261/252/351/227/250/346/200/273/350/243/201.md +53 -0
  554. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//351/203/275/345/270/202/344/277/256/347/234/237.md +51 -0
  555. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//351/203/275/345/270/202/346/227/245/345/270/270.md +53 -0
  556. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//351/203/275/345/270/202/347/247/215/347/224/260.md +51 -0
  557. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//351/203/275/345/270/202/350/204/221/346/264/236.md +53 -0
  558. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//351/203/275/345/270/202/351/253/230/346/255/246.md +53 -0
  559. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards//351/235/222/346/230/245/347/224/234/345/256/240.md +51 -0
  560. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-prose-cards.md +91 -0
  561. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/genre-readers.md +235 -0
  562. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/long-chapter-hooks.md +225 -0
  563. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/long-emotional-methods.md +209 -0
  564. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/long-genre-catalog.md +394 -0
  565. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/long-genre-mechanics.md +415 -0
  566. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/long-quality.md +42 -0
  567. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/long-reversal.md +335 -0
  568. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/long-suspense.md +352 -0
  569. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/opening-design.md +349 -0
  570. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/outline-conflict.md +457 -0
  571. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/outline-methods.md +287 -0
  572. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/outline-rhythm.md +389 -0
  573. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/plot-core-methods.md +522 -0
  574. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/short-chapter-hooks.md +225 -0
  575. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/short-emotional-methods.md +179 -0
  576. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/short-genre-formulas.md +417 -0
  577. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/short-paragraph-hooks.md +146 -0
  578. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/short-quality.md +39 -0
  579. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/short-reversal.md +355 -0
  580. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/short-suspense.md +352 -0
  581. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/style-combat-face.md +352 -0
  582. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/style-genre-modules.md +510 -0
  583. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/style-resolution.md +27 -0
  584. package/resources/skills/story-creation/upstream/skills/story-setup/references/agent-references/writing-craft.md +442 -0
  585. package/resources/skills/story-creation/upstream/skills/story-setup/references/diagnostics.md +38 -0
  586. package/resources/skills/story-creation/upstream/skills/story-short-analyze/SKILL.md +224 -0
  587. package/resources/skills/story-creation/upstream/skills/story-short-analyze/references/analysis-character-basics.md +453 -0
  588. package/resources/skills/story-creation/upstream/skills/story-short-analyze/references/analysis-character-design.md +438 -0
  589. package/resources/skills/story-creation/upstream/skills/story-short-analyze/references/analysis-character-relations.md +395 -0
  590. package/resources/skills/story-creation/upstream/skills/story-short-analyze/references/analysis-method.md +42 -0
  591. package/resources/skills/story-creation/upstream/skills/story-short-analyze/references/analysis-paragraph-hooks.md +146 -0
  592. package/resources/skills/story-creation/upstream/skills/story-short-analyze/references/analysis-reader-profile.md +245 -0
  593. package/resources/skills/story-creation/upstream/skills/story-short-analyze/references/analysis-report-style.md +45 -0
  594. package/resources/skills/story-creation/upstream/skills/story-short-analyze/references/analysis-short-genres.md +65 -0
  595. package/resources/skills/story-creation/upstream/skills/story-short-analyze/references/analysis-short-hooks.md +77 -0
  596. package/resources/skills/story-creation/upstream/skills/story-short-analyze/references/analysis-short-mechanics.md +82 -0
  597. package/resources/skills/story-creation/upstream/skills/story-short-analyze/references/analysis-short-patterns.md +50 -0
  598. package/resources/skills/story-creation/upstream/skills/story-short-analyze/references/analysis-short-suspense.md +98 -0
  599. package/resources/skills/story-creation/upstream/skills/story-short-analyze/references/analysis-writing-techniques.md +477 -0
  600. package/resources/skills/story-creation/upstream/skills/story-short-analyze/references/deconstruction-examples.md +79 -0
  601. package/resources/skills/story-creation/upstream/skills/story-short-analyze/references/optional-modules.md +97 -0
  602. package/resources/skills/story-creation/upstream/skills/story-short-analyze/references/output-contract.md +127 -0
  603. package/resources/skills/story-creation/upstream/skills/story-short-analyze/references/quality-checklist.md +80 -0
  604. package/resources/skills/story-creation/upstream/skills/story-short-analyze/references/real-market-data.md +90 -0
  605. package/resources/skills/story-creation/upstream/skills/story-short-analyze/references/source-story-quality.md +221 -0
  606. package/resources/skills/story-creation/upstream/skills/story-short-analyze/references/stage2-3-structure-emotion.md +308 -0
  607. package/resources/skills/story-creation/upstream/skills/story-short-analyze/references/stage4-6-reversal-summary.md +611 -0
  608. package/resources/skills/story-creation/upstream/skills/story-short-analyze/references/zhihu-style.md +54 -0
  609. package/resources/skills/story-creation/upstream/skills/story-short-scan/SKILL.md +182 -0
  610. package/resources/skills/story-creation/upstream/skills/story-short-scan/references/manual-rank-input.md +36 -0
  611. package/resources/skills/story-creation/upstream/skills/story-short-scan/references/platform-dianzhong.md +24 -0
  612. package/resources/skills/story-creation/upstream/skills/story-short-scan/references/platform-heiyan.md +21 -0
  613. package/resources/skills/story-creation/upstream/skills/story-short-scan/references/real-market-data.md +90 -0
  614. package/resources/skills/story-creation/upstream/skills/story-short-scan/references/topic-match.md +38 -0
  615. package/resources/skills/story-creation/upstream/skills/story-short-scan/scripts/aggregate-rank.js +682 -0
  616. package/resources/skills/story-creation/upstream/skills/story-short-scan/scripts/cdp-utils.js +242 -0
  617. package/resources/skills/story-creation/upstream/skills/story-short-scan/scripts/dz-browse-scraper.js +232 -0
  618. package/resources/skills/story-creation/upstream/skills/story-short-scan/scripts/heiyan-booklist-scraper.js +345 -0
  619. package/resources/skills/story-creation/upstream/skills/story-short-write/SKILL.md +136 -0
  620. package/resources/skills/story-creation/upstream/skills/story-short-write/references/agent-calls.md +13 -0
  621. package/resources/skills/story-creation/upstream/skills/story-short-write/references/author-memory-maintenance.md +75 -0
  622. package/resources/skills/story-creation/upstream/skills/story-short-write/references/author-memory.md +117 -0
  623. package/resources/skills/story-creation/upstream/skills/story-short-write/references/banned-words.md +151 -0
  624. package/resources/skills/story-creation/upstream/skills/story-short-write/references/benchmark-recall.md +40 -0
  625. package/resources/skills/story-creation/upstream/skills/story-short-write/references/cross-book-recall.md +62 -0
  626. package/resources/skills/story-creation/upstream/skills/story-short-write/references/dialogue-mastery.md +288 -0
  627. package/resources/skills/story-creation/upstream/skills/story-short-write/references/emotional-methods.md +179 -0
  628. package/resources/skills/story-creation/upstream/skills/story-short-write/references/genre-styles//344/270/226/346/203/205/346/211/223/350/204/270.md +159 -0
  629. package/resources/skills/story-creation/upstream/skills/story-short-write/references/genre-styles//345/217/214/347/224/267/344/270/273.md +156 -0
  630. package/resources/skills/story-creation/upstream/skills/story-short-write/references/genre-styles//345/244/215/344/273/207/346/211/223/350/204/270.md +163 -0
  631. package/resources/skills/story-creation/upstream/skills/story-short-write/references/genre-styles//345/256/205/346/226/227/345/256/253/346/226/227.md +167 -0
  632. package/resources/skills/story-creation/upstream/skills/story-short-write/references/genre-styles//346/200/273/350/243/201/350/261/252/351/227/250.md +178 -0
  633. package/resources/skills/story-creation/upstream/skills/story-short-write/references/genre-styles//346/202/254/347/226/221.md +166 -0
  634. package/resources/skills/story-creation/upstream/skills/story-short-write/references/genre-styles//346/260/221/344/277/227/346/200/252/350/260/210.md +159 -0
  635. package/resources/skills/story-creation/upstream/skills/story-short-write/references/genre-styles//346/262/231/351/233/225/350/204/221/346/264/236.md +151 -0
  636. package/resources/skills/story-creation/upstream/skills/story-short-write/references/genre-styles//347/224/234/345/256/240.md +153 -0
  637. package/resources/skills/story-creation/upstream/skills/story-short-write/references/genre-styles//350/277/275/345/246/273/347/201/253/350/221/254/345/234/272.md +210 -0
  638. package/resources/skills/story-creation/upstream/skills/story-short-write/references/genre-writing-formulas.md +417 -0
  639. package/resources/skills/story-creation/upstream/skills/story-short-write/references/genre-writing-techniques.md +490 -0
  640. package/resources/skills/story-creation/upstream/skills/story-short-write/references/hooks-chapter.md +225 -0
  641. package/resources/skills/story-creation/upstream/skills/story-short-write/references/hooks-paragraph.md +146 -0
  642. package/resources/skills/story-creation/upstream/skills/story-short-write/references/output-contract.md +127 -0
  643. package/resources/skills/story-creation/upstream/skills/story-short-write/references/reference-index.md +23 -0
  644. package/resources/skills/story-creation/upstream/skills/story-short-write/references/scene-craft.md +40 -0
  645. package/resources/skills/story-creation/upstream/skills/story-short-write/references/short-craft.md +147 -0
  646. package/resources/skills/story-creation/upstream/skills/story-short-write/references/short-deslop.md +133 -0
  647. package/resources/skills/story-creation/upstream/skills/story-short-write/references/short-format.md +199 -0
  648. package/resources/skills/story-creation/upstream/skills/story-short-write/references/short-prose-quality.md +224 -0
  649. package/resources/skills/story-creation/upstream/skills/story-short-write/references/short-reversal.md +355 -0
  650. package/resources/skills/story-creation/upstream/skills/story-short-write/references/short-suspense.md +352 -0
  651. package/resources/skills/story-creation/upstream/skills/story-short-write/references/style-resolution.md +27 -0
  652. package/resources/skills/story-creation/upstream/skills/story-short-write/references/submission-craft.md +35 -0
  653. package/resources/skills/story-creation/upstream/skills/story-short-write/references/villain-and-reveal.md +153 -0
  654. package/resources/skills/story-creation/upstream/skills/story-short-write/references/workflow-design.md +85 -0
  655. package/resources/skills/story-creation/upstream/skills/story-short-write/references/workflow-draft.md +116 -0
  656. package/resources/skills/story-creation/upstream/skills/story-short-write/references/workflow-revision.md +99 -0
  657. package/resources/skills/story-creation/upstream/skills/story-short-write/references/writing-workflow.md +50 -0
  658. package/resources/skills/story-creation/upstream/skills/story-short-write/scripts/author_memory_commit.py +1635 -0
  659. package/resources/skills/story-creation/upstream/skills/story-short-write/scripts/check-ai-patterns.js +1586 -0
  660. package/resources/skills/story-creation/upstream/skills/story-short-write/scripts/check-degeneration.js +349 -0
  661. package/resources/skills/story-creation/upstream/skills/story-short-write/scripts/check-delivery-contract.js +241 -0
  662. package/resources/skills/story-creation/upstream/skills/story-short-write/scripts/check-outline-copy.js +313 -0
  663. package/resources/skills/story-creation/upstream/skills/story-short-write/scripts/check-phase2-contract.js +363 -0
  664. package/resources/skills/story-creation/upstream/skills/story-short-write/scripts/normalize-punctuation.js +415 -0
  665. package/resources/skills/story-creation/upstream/skills/story-short-write/scripts/style-whitelist.js +39 -0
  666. package/resources/skills/video-recap-production/manifest.json +673 -0
  667. package/resources/skills/video-recap-production/upstream/CHANGELOG.md +371 -0
  668. package/resources/skills/video-recap-production/upstream/LICENSE +21 -0
  669. package/resources/skills/video-recap-production/upstream/README.en.md +468 -0
  670. package/resources/skills/video-recap-production/upstream/README.md +459 -0
  671. package/resources/skills/video-recap-production/upstream/manifest.json +839 -0
  672. package/resources/skills/video-recap-production/upstream/skills/video-assemble/SKILL.md +112 -0
  673. package/resources/skills/video-recap-production/upstream/skills/video-assemble/references/audio-modes.md +63 -0
  674. package/resources/skills/video-recap-production/upstream/skills/video-assemble/references/explicit-audio-mix.md +75 -0
  675. package/resources/skills/video-recap-production/upstream/skills/video-assemble/references/foreground-compose.md +118 -0
  676. package/resources/skills/video-recap-production/upstream/skills/video-assemble/references/jianying/LICENSE.duo-video +21 -0
  677. package/resources/skills/video-recap-production/upstream/skills/video-assemble/references/jianying/SOURCE.md +15 -0
  678. package/resources/skills/video-recap-production/upstream/skills/video-assemble/references/jianying/empty_draft_meta_info.json +69 -0
  679. package/resources/skills/video-recap-production/upstream/skills/video-assemble/references/jianying/empty_jy_combination_segment.json +84 -0
  680. package/resources/skills/video-recap-production/upstream/skills/video-assemble/references/jianying/empty_jy_combination_video_material.json +98 -0
  681. package/resources/skills/video-recap-production/upstream/skills/video-assemble/references/jianying/empty_jy_draft.json +204 -0
  682. package/resources/skills/video-recap-production/upstream/skills/video-assemble/references/jianying/empty_jy_material_video.json +83 -0
  683. package/resources/skills/video-recap-production/upstream/skills/video-assemble/references/jianying/empty_jy_meta_material_value.json +23 -0
  684. package/resources/skills/video-recap-production/upstream/skills/video-assemble/references/jianying/empty_jy_project_info.json +133 -0
  685. package/resources/skills/video-recap-production/upstream/skills/video-assemble/references/jianying/empty_jy_segment.json +70 -0
  686. package/resources/skills/video-recap-production/upstream/skills/video-assemble/references/jianying/empty_jy_text_styles.json +30 -0
  687. package/resources/skills/video-recap-production/upstream/skills/video-assemble/references/jianying/empty_yj_material_audio.json +41 -0
  688. package/resources/skills/video-recap-production/upstream/skills/video-assemble/references/jianying/empty_yj_material_text.json +109 -0
  689. package/resources/skills/video-recap-production/upstream/skills/video-assemble/references/narration-adoption.md +103 -0
  690. package/resources/skills/video-recap-production/upstream/skills/video-assemble/references/packaging.md +45 -0
  691. package/resources/skills/video-recap-production/upstream/skills/video-assemble/references/pair-media.md +74 -0
  692. package/resources/skills/video-recap-production/upstream/skills/video-assemble/references/source-score.md +147 -0
  693. package/resources/skills/video-recap-production/upstream/skills/video-assemble/references/subtitle-track.md +202 -0
  694. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/adoption/__init__.py +1 -0
  695. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/adoption/audio_mix_binding.py +279 -0
  696. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/adoption/frozen_audio.py +187 -0
  697. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/adoption/narration_binding.py +323 -0
  698. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/adoption/strict_inputs.py +109 -0
  699. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/adoption/strict_publish.py +130 -0
  700. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/artifacts.py +47 -0
  701. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/assemble.py +591 -0
  702. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/assemble_constants.py +51 -0
  703. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/assembly_contract.py +318 -0
  704. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/assembly_settings.py +138 -0
  705. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/audio_automation.py +195 -0
  706. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/audio_mix.py +465 -0
  707. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/compose_foreground.py +293 -0
  708. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/export_jianying.py +254 -0
  709. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/jianying/__init__.py +1 -0
  710. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/jianying/builders.py +718 -0
  711. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/jianying/model.py +105 -0
  712. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/jianying/optional.py +23 -0
  713. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/jianying/schema.py +53 -0
  714. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/jianying/templates.py +37 -0
  715. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/jianying/timeline_contract.py +223 -0
  716. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/jianying/tracks.py +72 -0
  717. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/jianying/writer.py +383 -0
  718. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/lib.py +218 -0
  719. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/media.py +190 -0
  720. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/narration_audio.py +413 -0
  721. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/packaging.py +122 -0
  722. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/pair_media.py +216 -0
  723. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/render_preflight.py +33 -0
  724. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/source_score.py +469 -0
  725. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/source_subtitles.py +423 -0
  726. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/subtitles/__init__.py +1 -0
  727. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/subtitles/core.py +330 -0
  728. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/subtitles/render.py +82 -0
  729. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/subtitles/track.py +324 -0
  730. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/subtitles/track_binding.py +200 -0
  731. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/timeline.py +217 -0
  732. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/timeline_emit.py +167 -0
  733. package/resources/skills/video-recap-production/upstream/skills/video-assemble/scripts/visual_render.py +448 -0
  734. package/resources/skills/video-recap-production/upstream/skills/video-cut/SKILL.md +108 -0
  735. package/resources/skills/video-recap-production/upstream/skills/video-cut/references/shot-review.md +50 -0
  736. package/resources/skills/video-recap-production/upstream/skills/video-cut/scripts/cut.py +41 -0
  737. package/resources/skills/video-recap-production/upstream/skills/video-cut/scripts/cut_cli.py +308 -0
  738. package/resources/skills/video-recap-production/upstream/skills/video-cut/scripts/cut_contract.py +424 -0
  739. package/resources/skills/video-recap-production/upstream/skills/video-cut/scripts/cut_render.py +304 -0
  740. package/resources/skills/video-recap-production/upstream/skills/video-cut/scripts/lib.py +114 -0
  741. package/resources/skills/video-recap-production/upstream/skills/video-cut/scripts/media_geometry.py +238 -0
  742. package/resources/skills/video-recap-production/upstream/skills/video-cut/scripts/narration_mapping.py +71 -0
  743. package/resources/skills/video-recap-production/upstream/skills/video-cut/scripts/narrative_selection.py +244 -0
  744. package/resources/skills/video-recap-production/upstream/skills/video-cut/scripts/sentence_boundaries.py +585 -0
  745. package/resources/skills/video-recap-production/upstream/skills/video-cut/scripts/shot_review.py +357 -0
  746. package/resources/skills/video-recap-production/upstream/skills/video-recap/SKILL.md +260 -0
  747. package/resources/skills/video-recap-production/upstream/skills/video-recap/references/audio-routing.md +126 -0
  748. package/resources/skills/video-recap-production/upstream/skills/video-recap/references/config-playbook.md +65 -0
  749. package/resources/skills/video-recap-production/upstream/skills/video-recap/references/data-schema.md +545 -0
  750. package/resources/skills/video-recap-production/upstream/skills/video-recap/references/resource-library.md +151 -0
  751. package/resources/skills/video-recap-production/upstream/skills/video-recap/references/shift-left-qc-schema.md +23 -0
  752. package/resources/skills/video-recap-production/upstream/skills/video-recap/scripts/doctor.py +556 -0
  753. package/resources/skills/video-recap-production/upstream/skills/video-recap/scripts/final_qc.py +527 -0
  754. package/resources/skills/video-recap-production/upstream/skills/video-recap/scripts/lib.py +220 -0
  755. package/resources/skills/video-recap-production/upstream/skills/video-recap/scripts/library.py +509 -0
  756. package/resources/skills/video-recap-production/upstream/skills/video-recap/scripts/materials.py +406 -0
  757. package/resources/skills/video-recap-production/upstream/skills/video-recap/scripts/mimo_qc.py +33 -0
  758. package/resources/skills/video-recap-production/upstream/skills/video-recap/scripts/project_binding.py +286 -0
  759. package/resources/skills/video-recap-production/upstream/skills/video-recap/scripts/qc/__init__.py +1 -0
  760. package/resources/skills/video-recap-production/upstream/skills/video-recap/scripts/qc/mimo_client.py +13 -0
  761. package/resources/skills/video-recap-production/upstream/skills/video-recap/scripts/qc/mimo_contract.py +9 -0
  762. package/resources/skills/video-recap-production/upstream/skills/video-recap/scripts/qc/mimo_evidence.py +287 -0
  763. package/resources/skills/video-recap-production/upstream/skills/video-recap/scripts/qc/mimo_observations.py +175 -0
  764. package/resources/skills/video-recap-production/upstream/skills/video-recap/scripts/qc/mimo_payload.py +206 -0
  765. package/resources/skills/video-recap-production/upstream/skills/video-recap/scripts/qc/mimo_report.py +318 -0
  766. package/resources/skills/video-recap-production/upstream/skills/video-recap/scripts/qc/mimo_runner.py +130 -0
  767. package/resources/skills/video-recap-production/upstream/skills/video-recap/scripts/qc_contract.py +320 -0
  768. package/resources/skills/video-recap-production/upstream/skills/video-recap/scripts/recap.py +9 -0
  769. package/resources/skills/video-recap-production/upstream/skills/video-recap/scripts/recap_cli.py +209 -0
  770. package/resources/skills/video-recap-production/upstream/skills/video-recap/scripts/recap_inspect.py +452 -0
  771. package/resources/skills/video-recap-production/upstream/skills/video-recap/scripts/recap_review.py +70 -0
  772. package/resources/skills/video-recap-production/upstream/skills/video-recap/scripts/recap_runner.py +696 -0
  773. package/resources/skills/video-recap-production/upstream/skills/video-recap/scripts/recap_runtime.py +260 -0
  774. package/resources/skills/video-recap-production/upstream/skills/video-recap/scripts/recap_source.py +278 -0
  775. package/resources/skills/video-recap-production/upstream/skills/video-recap/scripts/recap_stage_qc.py +160 -0
  776. package/resources/skills/video-recap-production/upstream/skills/video-recap/scripts/recap_timeline.py +621 -0
  777. package/resources/skills/video-recap-production/upstream/skills/video-recap/scripts/resource_lock.py +162 -0
  778. package/resources/skills/video-recap-production/upstream/skills/video-script/SKILL.md +251 -0
  779. package/resources/skills/video-recap-production/upstream/skills/video-script/references/creative-editing-playbook.md +229 -0
  780. package/resources/skills/video-recap-production/upstream/skills/video-script/references/promotional-copy.md +68 -0
  781. package/resources/skills/video-recap-production/upstream/skills/video-script/references/research-guide.md +75 -0
  782. package/resources/skills/video-recap-production/upstream/skills/video-script/scripts/agent_text.py +301 -0
  783. package/resources/skills/video-recap-production/upstream/skills/video-script/scripts/deslop_qc.py +211 -0
  784. package/resources/skills/video-recap-production/upstream/skills/video-script/scripts/evidence_bundle.py +440 -0
  785. package/resources/skills/video-recap-production/upstream/skills/video-script/scripts/lib.py +254 -0
  786. package/resources/skills/video-recap-production/upstream/skills/video-script/scripts/narration_lint.py +707 -0
  787. package/resources/skills/video-recap-production/upstream/skills/video-script/scripts/review.py +40 -0
  788. package/resources/skills/video-recap-production/upstream/skills/video-script/scripts/review_grounding.py +238 -0
  789. package/resources/skills/video-recap-production/upstream/skills/video-script/scripts/review_response.py +669 -0
  790. package/resources/skills/video-recap-production/upstream/skills/video-script/scripts/review_runner.py +186 -0
  791. package/resources/skills/video-recap-production/upstream/skills/video-script/scripts/speech_ownership.py +143 -0
  792. package/resources/skills/video-recap-production/upstream/skills/video-script/scripts/timeline_fusion.py +108 -0
  793. package/resources/skills/video-recap-production/upstream/skills/video-script/scripts/validate.py +175 -0
  794. package/resources/skills/video-recap-production/upstream/skills/video-understanding/SKILL.md +77 -0
  795. package/resources/skills/video-recap-production/upstream/skills/video-understanding/references/data-schema.md +193 -0
  796. package/resources/skills/video-recap-production/upstream/skills/video-understanding/references/prompt-templates.md +37 -0
  797. package/resources/skills/video-recap-production/upstream/skills/video-understanding/references/research-guide.md +74 -0
  798. package/resources/skills/video-recap-production/upstream/skills/video-understanding/scripts/agent_text.py +301 -0
  799. package/resources/skills/video-recap-production/upstream/skills/video-understanding/scripts/asr.py +279 -0
  800. package/resources/skills/video-recap-production/upstream/skills/video-understanding/scripts/asr_timing_evidence.py +270 -0
  801. package/resources/skills/video-recap-production/upstream/skills/video-understanding/scripts/brief.py +9 -0
  802. package/resources/skills/video-recap-production/upstream/skills/video-understanding/scripts/briefing/__init__.py +1 -0
  803. package/resources/skills/video-recap-production/upstream/skills/video-understanding/scripts/briefing/builder.py +402 -0
  804. package/resources/skills/video-recap-production/upstream/skills/video-understanding/scripts/briefing/context.py +317 -0
  805. package/resources/skills/video-recap-production/upstream/skills/video-understanding/scripts/briefing/inputs.py +268 -0
  806. package/resources/skills/video-recap-production/upstream/skills/video-understanding/scripts/briefing/timeline.py +326 -0
  807. package/resources/skills/video-recap-production/upstream/skills/video-understanding/scripts/consolidate.py +596 -0
  808. package/resources/skills/video-recap-production/upstream/skills/video-understanding/scripts/deslop_qc.py +211 -0
  809. package/resources/skills/video-recap-production/upstream/skills/video-understanding/scripts/detect.py +504 -0
  810. package/resources/skills/video-recap-production/upstream/skills/video-understanding/scripts/extract.py +62 -0
  811. package/resources/skills/video-recap-production/upstream/skills/video-understanding/scripts/lib.py +419 -0
  812. package/resources/skills/video-recap-production/upstream/skills/video-understanding/scripts/narration_lint.py +707 -0
  813. package/resources/skills/video-recap-production/upstream/skills/video-understanding/scripts/speech_ownership.py +143 -0
  814. package/resources/skills/video-recap-production/upstream/skills/video-understanding/scripts/storyboard.py +445 -0
  815. package/resources/skills/video-recap-production/upstream/skills/video-understanding/scripts/timeline_fusion.py +108 -0
  816. package/resources/skills/video-recap-production/upstream/skills/video-understanding/scripts/understand.py +9 -0
  817. package/resources/skills/video-recap-production/upstream/skills/video-understanding/scripts/understanding_brief.py +190 -0
  818. package/resources/skills/video-recap-production/upstream/skills/video-understanding/scripts/understanding_cache.py +345 -0
  819. package/resources/skills/video-recap-production/upstream/skills/video-understanding/scripts/understanding_runner.py +387 -0
  820. package/resources/skills/video-recap-production/upstream/skills/video-understanding/scripts/understanding_storyboard.py +167 -0
  821. package/resources/skills/video-recap-production/upstream/skills/video-understanding/scripts/vlm.py +674 -0
  822. package/resources/skills/video-recap-production/upstream/skills/video-voiceover/SKILL.md +91 -0
  823. package/resources/skills/video-recap-production/upstream/skills/video-voiceover/references/index-tts.md +31 -0
  824. package/resources/skills/video-recap-production/upstream/skills/video-voiceover/scripts/approved_text_policy.py +123 -0
  825. package/resources/skills/video-recap-production/upstream/skills/video-voiceover/scripts/dub.py +623 -0
  826. package/resources/skills/video-recap-production/upstream/skills/video-voiceover/scripts/lib.py +352 -0
  827. package/resources/skills/video-recap-production/upstream/skills/video-voiceover/scripts/providers/__init__.py +1 -0
  828. package/resources/skills/video-recap-production/upstream/skills/video-voiceover/scripts/providers/fish_audio.py +91 -0
  829. package/resources/skills/video-recap-production/upstream/skills/video-voiceover/scripts/providers/index_tts.py +184 -0
  830. package/resources/skills/video-recap-production/upstream/skills/video-voiceover/scripts/tts_audio.py +102 -0
  831. package/resources/skills/video-recap-production/upstream/skills/video-voiceover/scripts/voiceover.py +771 -0
  832. package/resources/skills/viora-canvas-production/manifest.json +5 -1
  833. package/resources/skills/viora-canvas-production/references/story-handoff.md +49 -0
@@ -0,0 +1,1635 @@
1
+ #!/usr/bin/env python3
2
+ """Maintain evidence-backed author preferences and deterministic Markdown views.
3
+
4
+ The language model supplies compact semantic transactions. This tool validates
5
+ and applies them in memory, renders every derived view, and writes the JSON state
6
+ last as the commit point. Author memory lives in two kinds of store: the
7
+ project-level store under the workspace holds global / genre / workflow items
8
+ (`AP` ids); each book keeps its own book-level store under the book directory
9
+ (`BP` ids) so memory travels with the book. Both stay separate from each book's
10
+ story-continuity tracking. When the book root is the workspace itself, the
11
+ book-level store moves into a `书级/` subdirectory so the two never share a file.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import argparse
17
+ import copy
18
+ import hashlib
19
+ import json
20
+ import os
21
+ import stat
22
+ import sys
23
+ import tempfile
24
+ from datetime import datetime, timezone
25
+ from pathlib import Path
26
+ from typing import Any
27
+
28
+
29
+ INPUT_SCHEMA_VERSION = 1
30
+ STATE_SCHEMA_VERSION = 1
31
+ STATE_MAX_BYTES = 2 * 1024 * 1024
32
+ PROFILE_MAX_BYTES = 12288
33
+ PENDING_MAX_BYTES = 12288
34
+ JOURNAL_MAX_BYTES = 24576
35
+ QUERY_MAX_BYTES = 2048
36
+ ASSERTION_MAX_BYTES = 120 # 新建条目的断言限一句话;解释进 reason(不进 query 载荷)。
37
+ OMITTED_IDS_MAX = 20 # omitted_ids 封顶,omitted 保留真实总数——漏项列表不许把载荷本身挤炸。
38
+ LEGACY_ASSERTION_MAX_BYTES = 768 # 存量条目的读取上限;强化老条目不受新上限约束。
39
+
40
+ # query 的输出是要原样贴进执行 agent prompt 的注入载荷,QUERY_MAX_BYTES
41
+ # 是它在 prompt 里的注意力预算,不该放大。防「作者以为载入了、实际被静默
42
+ # 截断挤掉」靠三层:①新建条目的断言限 ASSERTION_MAX_BYTES,从源头短(强化
43
+ # 已有条目不受限,否则存量长断言再也无法被确认,只会派生重复条目);②写入
44
+ # 端按下列任务组合(与 references/author-memory-maintenance.md 的映射表同包跟版)估算
45
+ # 最坏查询情形——全局条目+各 scope 维度上最重的单一切片(一次查询只带一
46
+ # 个 book/genre/workflow,不同书的条目不会同现;切片按 casefold 归并,与
47
+ # same_scope_value 同一口径,轻重按 compact 字节+列表分隔符算,与真实载荷
48
+ # 同一把尺),装不下时在返回的 warnings 里点名将被略过的条目、指向「整理作
49
+ # 者记忆」,写入本身永不因注入预算失败;③查询按 重要度→本书例外→最近更新
50
+ # 排序装填,被略过的恒是重要度较低的条目,漏下的 ID 按同一优先级顺序报进
51
+ # omitted_ids。
52
+ QUERY_COMBOS: dict[str, tuple[str, ...]] = {
53
+ "正文初稿/续写": ("prose_style", "story_design"),
54
+ "去AI味/改写": ("prose_style",),
55
+ "设定/大纲": ("story_design", "workflow", "interaction"),
56
+ "审稿": ("delivery", "interaction", "prose_style"),
57
+ }
58
+
59
+ KINDS = ("prose_style", "story_design", "workflow", "delivery", "interaction")
60
+ KIND_TITLES = {
61
+ "prose_style": "文风与表达",
62
+ "story_design": "故事设计",
63
+ "workflow": "创作流程",
64
+ "delivery": "交付格式",
65
+ "interaction": "协作方式",
66
+ }
67
+ SCOPE_LEVELS = ("global", "genre", "book", "workflow")
68
+ STATUSES = ("active", "pending", "conflict", "rejected", "superseded")
69
+ CONFIDENCE_LEVELS = ("low", "medium", "high")
70
+ IMPORTANCE_LEVELS = ("low", "medium", "high")
71
+ # 作者记忆只记作者明确表达的偏好。repeated_correction / inferred_pattern 两条
72
+ # 由 agent 主动推断写入的管道已经移除(#436):它们只在攒待确认清单的审阅负
73
+ # 担,不是记忆质量;文档也明说不装全量消息 hook,隐式捕获本就承诺不了完整
74
+ # 性。SOURCES 里保留这两个值只为存量 state 仍能通过校验、仍能 decide/forget,
75
+ # 新写入一律按 WRITE_SOURCES 校验。
76
+ SOURCES = (
77
+ "explicit_user",
78
+ "accepted_suggestion",
79
+ "repeated_correction",
80
+ "inferred_pattern",
81
+ "manual",
82
+ )
83
+ WRITE_SOURCES = ("explicit_user", "accepted_suggestion", "manual")
84
+ RANK = {"low": 0, "medium": 1, "high": 2}
85
+
86
+ # 两级 store(#435):项目级存 global/genre/workflow,ID 前缀 AP,位于
87
+ # {工作区}/.story/作者记忆/;书级只存该书的 book 条目,ID 前缀 BP,位于
88
+ # {书}/.story/作者记忆/,书归档、迁移时记忆随书走。ID 前缀就是路由键——
89
+ # decide/forget 看 item_id 前缀,remember/replace 看 scope.level,一份事务
90
+ # 只写一个 store。项目级 store 里升级前写入的存量 book 条目不再参与查询与估算,
91
+ # 用 migrate 搬进书目录后才回来——不做双读,双读会让迁移永远没人做。
92
+ STORE_PREFIX = {"project": "AP", "book": "BP"}
93
+ ID_PREFIXES = tuple(STORE_PREFIX.values())
94
+ # 单书布局(书根就是工作区)下书级 store 的子目录:两级 store 的默认落点在这种
95
+ # 布局里是同一个 state 文件,书级改住 {工作区}/.story/作者记忆/书级/。
96
+ SINGLE_ROOT_BOOK_DIR = "书级"
97
+
98
+
99
+ class AuthorMemoryError(ValueError):
100
+ """Expected validation or state error."""
101
+
102
+
103
+ def require(condition: bool, message: str) -> None:
104
+ if not condition:
105
+ raise AuthorMemoryError(message)
106
+
107
+
108
+ def as_mapping(value: object, label: str) -> dict[str, Any]:
109
+ require(isinstance(value, dict), f"{label} must be a JSON object")
110
+ return value
111
+
112
+
113
+ def as_list(value: object, label: str) -> list[Any]:
114
+ require(isinstance(value, list), f"{label} must be a JSON array")
115
+ return value
116
+
117
+
118
+ def as_int(value: object, label: str, *, minimum: int = 0) -> int:
119
+ require(isinstance(value, int) and not isinstance(value, bool), f"{label} must be an integer")
120
+ require(value >= minimum, f"{label} must be >= {minimum}")
121
+ return value
122
+
123
+
124
+ def require_known_keys(mapping: dict[str, Any], allowed: set[str], label: str) -> None:
125
+ unknown = set(mapping) - allowed
126
+ require(not unknown, f"{label} contains unsupported fields: {', '.join(sorted(unknown))}")
127
+
128
+
129
+ def clean_text(value: object, label: str, *, max_bytes: int = 768) -> str:
130
+ require(isinstance(value, str), f"{label} must be a string")
131
+ cleaned = " ".join(value.replace("|", "|").split())
132
+ require(bool(cleaned), f"{label} must not be empty")
133
+ require(len(cleaned.encode("utf-8")) <= max_bytes, f"{label} exceeds {max_bytes} bytes")
134
+ return cleaned
135
+
136
+
137
+ def optional_text(value: object, label: str, *, max_bytes: int = 768) -> str | None:
138
+ if value is None:
139
+ return None
140
+ return clean_text(value, label, max_bytes=max_bytes)
141
+
142
+
143
+ def choice(value: object, allowed: tuple[str, ...], label: str) -> str:
144
+ require(isinstance(value, str) and value in allowed, f"{label} must be one of: {', '.join(allowed)}")
145
+ return value
146
+
147
+
148
+ def is_item_id(value: object) -> bool:
149
+ return (
150
+ isinstance(value, str)
151
+ and len(value) >= 3
152
+ and value[:2] in ID_PREFIXES
153
+ and value[2:].isdecimal()
154
+ and int(value[2:]) >= 1
155
+ )
156
+
157
+
158
+ def id_number(item_id: str) -> int:
159
+ return int(item_id[2:])
160
+
161
+
162
+ def id_store(item_id: str) -> str:
163
+ return "book" if item_id.startswith(STORE_PREFIX["book"]) else "project"
164
+
165
+
166
+ def clean_id_list(value: object, label: str, *, maximum: int = 32) -> list[str]:
167
+ raw = as_list(value, label)
168
+ require(len(raw) <= maximum, f"{label} may contain at most {maximum} items")
169
+ result: list[str] = []
170
+ for index, item in enumerate(raw):
171
+ item_id = clean_text(item, f"{label}[{index}]", max_bytes=32)
172
+ require(is_item_id(item_id), f"{label}[{index}] is not an author-memory id")
173
+ if item_id not in result:
174
+ result.append(item_id)
175
+ return result
176
+
177
+
178
+ def emit(document: object, *, error: bool = False) -> None:
179
+ payload = json.dumps(document, ensure_ascii=False, sort_keys=True)
180
+ stream = sys.stderr if error else sys.stdout
181
+ stream.flush()
182
+ stream.buffer.write((payload + "\n").encode("utf-8"))
183
+ stream.buffer.flush()
184
+
185
+
186
+ def json_payload(document: object) -> str:
187
+ return json.dumps(document, ensure_ascii=False, indent=2, sort_keys=True) + "\n"
188
+
189
+
190
+ def read_json(path: Path) -> object:
191
+ try:
192
+ require(path.stat().st_size <= STATE_MAX_BYTES, f"{path} exceeds {STATE_MAX_BYTES} bytes")
193
+ return json.loads(path.read_text(encoding="utf-8"))
194
+ except (OSError, json.JSONDecodeError) as exc:
195
+ raise AuthorMemoryError(f"unable to read JSON {path}: {exc}") from exc
196
+
197
+
198
+ def atomic_write_text(path: Path, payload: str) -> None:
199
+ path.parent.mkdir(parents=True, exist_ok=True)
200
+ mode = stat.S_IMODE(path.stat().st_mode) if path.exists() else 0o644
201
+ fd, temporary_name = tempfile.mkstemp(prefix=f".{path.name}.", suffix=".tmp", dir=path.parent)
202
+ temporary = Path(temporary_name)
203
+ try:
204
+ with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as handle:
205
+ handle.write(payload)
206
+ handle.flush()
207
+ os.fsync(handle.fileno())
208
+ os.chmod(temporary, mode)
209
+ os.replace(temporary, path)
210
+ finally:
211
+ temporary.unlink(missing_ok=True)
212
+
213
+
214
+ def write_if_changed(path: Path, payload: str) -> None:
215
+ try:
216
+ if path.read_text(encoding="utf-8") == payload:
217
+ return
218
+ except FileNotFoundError:
219
+ pass
220
+ atomic_write_text(path, payload)
221
+
222
+
223
+ # ---------------------------------------------------------------------------
224
+ # Stores
225
+ # ---------------------------------------------------------------------------
226
+
227
+
228
+ class Store:
229
+ """一个 state 文件的落点:项目级(工作区)或书级(书目录)。"""
230
+
231
+ __slots__ = ("kind", "root", "book")
232
+
233
+ def __init__(self, kind: str, root: Path, book: str | None) -> None:
234
+ self.kind = kind
235
+ self.root = root
236
+ self.book = book
237
+
238
+ @property
239
+ def prefix(self) -> str:
240
+ return STORE_PREFIX[self.kind]
241
+
242
+ @property
243
+ def state_path(self) -> Path:
244
+ return self.root / "_author-memory-state.json"
245
+
246
+ @property
247
+ def label(self) -> str:
248
+ return "项目级" if self.kind == "project" else f"书级({self.book})"
249
+
250
+
251
+ def project_store(workspace: Path) -> Store:
252
+ return Store("project", workspace.resolve() / ".story" / "作者记忆", None)
253
+
254
+
255
+ def peek_book_name(state_path: Path) -> str | None:
256
+ """不校验整份 state,只取书名——书级 store 一旦建立,书名以 state 为准,
257
+ 书目录改名不影响。"""
258
+ if not state_path.exists():
259
+ return None
260
+ document = read_json(state_path)
261
+ name = document.get("book") if isinstance(document, dict) else None
262
+ return name if isinstance(name, str) and name.strip() else None
263
+
264
+
265
+ def enclosing_workspace(workspace: Path) -> Path | None:
266
+ """--workspace 指到的其实是多书工作区里的一本书时,返回真正的创作工作区。
267
+
268
+ 认三种迹象,任一成立即是:①工作区的上一层目录叫 长篇 / 短篇(story-setup 的书
269
+ 目录约定,返回再上一层);②某个祖先目录有 .active-book;③某个祖先目录有项目级
270
+ store(不带 book 字段的 state)——但工作区自己已有项目级 store 时不认这一条,最
271
+ 近的项目级 store 就是它自己,免得主目录里一份误建的 store 挡住正常的单书工作区。
272
+ 读不出来的祖先 state 不算迹象。"""
273
+ resolved = workspace.resolve()
274
+ if resolved.parent.name in {"长篇", "短篇"}:
275
+ return resolved.parent.parent
276
+ own = project_store(resolved).state_path
277
+ owns_project_store = own.exists() and peek_book_name(own) is None
278
+ for ancestor in resolved.parents:
279
+ if (ancestor / ".active-book").exists():
280
+ return ancestor
281
+ if owns_project_store:
282
+ continue
283
+ state_path = project_store(ancestor).state_path
284
+ try:
285
+ document = read_json(state_path) if state_path.is_file() else None
286
+ except AuthorMemoryError:
287
+ document = None
288
+ if isinstance(document, dict) and "book" not in document:
289
+ return ancestor
290
+ return None
291
+
292
+
293
+ def same_directory(first: Path, second: Path) -> bool:
294
+ # samefile 认得大小写不敏感文件系统(macOS APFS 默认)上只差大小写的同一目录。
295
+ if first.exists() and second.exists():
296
+ return os.path.samefile(first, second)
297
+ return first.resolve() == second.resolve()
298
+
299
+
300
+ def is_single_root(workspace: Path, book_root: Path | None) -> bool:
301
+ """单书布局:书根就是工作区(正文/、大纲/、追踪/ 直接在工作区根)。
302
+
303
+ 同一目录还可能是多书工作区里的一本书被误传成了 --workspace(书目录自己也含
304
+ .story/作者记忆/)。那时按单书布局处理会把书级 state 挪进 书级/、在原处补一份
305
+ 空的项目级 state,此后正确的调用全部失败,所以一律报错、零写入。"""
306
+ if book_root is None or not same_directory(book_root, workspace):
307
+ return False
308
+ outer = enclosing_workspace(workspace)
309
+ require(
310
+ outer is None,
311
+ f"{workspace} 是创作工作区 {outer} 里的一本书,不是单书工作区:--workspace 应传 {outer},"
312
+ f"这个目录放 --book-root",
313
+ )
314
+ return True
315
+
316
+
317
+ def book_memory_root(workspace: Path, book_root: Path) -> Path:
318
+ root = book_root.resolve() / ".story" / "作者记忆"
319
+ # 单书布局下两级 store 的默认落点是同一个 state 文件;书级改住子目录,
320
+ # 两份 state、两套派生视图、两条修订线仍各自独立。
321
+ return root / SINGLE_ROOT_BOOK_DIR if is_single_root(workspace, book_root) else root
322
+
323
+
324
+ def sole_legacy_book_name(project: Store) -> str | None:
325
+ """项目级 store 里存量 book 条目只指向一本书时返回该书名(单书布局的升级默认)。"""
326
+ if not project.state_path.exists():
327
+ return None
328
+ document = read_json(project.state_path)
329
+ items = document.get("items") if isinstance(document, dict) else None
330
+ names: dict[str, str] = {}
331
+ for item in (items.values() if isinstance(items, dict) else ()):
332
+ scope = item.get("scope") if isinstance(item, dict) else None
333
+ if (
334
+ isinstance(scope, dict) and scope.get("level") == "book"
335
+ and isinstance(scope.get("value"), str) and scope["value"].strip()
336
+ and item.get("status") in {"active", "pending", "conflict"}
337
+ ):
338
+ names.setdefault(scope["value"].casefold(), scope["value"])
339
+ return next(iter(names.values())) if len(names) == 1 else None
340
+
341
+
342
+ def book_store(workspace: Path, book_root: Path, book: str | None) -> Store:
343
+ require(book_root.exists() and book_root.is_dir(), f"book root does not exist: {book_root}")
344
+ resolved = book_root.resolve()
345
+ root = book_memory_root(workspace, book_root)
346
+ name = optional_text(book, "book", max_bytes=180)
347
+ stored = peek_book_name(root / "_author-memory-state.json")
348
+ if name is None:
349
+ name = stored
350
+ elif stored is not None:
351
+ require(
352
+ name.casefold() == stored.casefold(),
353
+ f"--book「{name}」与 {root} 里记录的书「{stored}」不一致",
354
+ )
355
+ if name is None and is_single_root(workspace, book_root):
356
+ # 单书工作区的目录名常常不是书名;升级前的本书条目只指向一本书时,以它为准,
357
+ # 否则 migrate 会按目录名找不到存量、静默迁移零条。
358
+ name = sole_legacy_book_name(project_store(workspace))
359
+ if name is None:
360
+ name = clean_text(resolved.name, "book root name", max_bytes=180)
361
+ return Store("book", root, name)
362
+
363
+
364
+ def relocate_misplaced_book_state(workspace: Path, book_root: Path | None) -> None:
365
+ """单书布局的自愈:旧版把书级 state(带 state.book)写在了项目级位置,此后项目级
366
+ 读写一律失败。带 --book-root {工作区} 运行任一命令时,把它原子移进书级子目录,
367
+ 再在项目级位置补一份空 state 重建视图。state 内容不变,不推进任何修订。"""
368
+ if not is_single_root(workspace, book_root):
369
+ return
370
+ project = project_store(workspace)
371
+ misplaced = peek_book_name(project.state_path)
372
+ if misplaced is None:
373
+ return
374
+ target = Store("book", book_memory_root(workspace, book_root), misplaced)
375
+ require(
376
+ not target.state_path.exists(),
377
+ f"{project.state_path} 与 {target.state_path} 都是书级 state,无法自动归位;"
378
+ f"保留修订较新的一份移到 {target.state_path},另一份备份后移走再重跑",
379
+ )
380
+ state = validate_state(read_json(project.state_path), store=target)
381
+ target.root.mkdir(parents=True, exist_ok=True)
382
+ os.replace(project.state_path, target.state_path)
383
+ write_snapshot(target, state)
384
+ write_snapshot(project, empty_state())
385
+
386
+
387
+ def resolve_target_store(kind: str, workspace: Path, book_root: Path | None, book: str | None) -> Store:
388
+ if kind == "project":
389
+ return project_store(workspace)
390
+ require(
391
+ book_root is not None,
392
+ "book 级条目须传 --book-root {书目录}——记忆随书存放在 {书}/.story/作者记忆/,不再写进工作区的项目级 store",
393
+ )
394
+ return book_store(workspace, book_root, book)
395
+
396
+
397
+ def empty_state(book: str | None = None) -> dict[str, Any]:
398
+ state: dict[str, Any] = {
399
+ "schema_version": STATE_SCHEMA_VERSION,
400
+ "state_revision": 0,
401
+ "next_item_number": 1,
402
+ "items": {},
403
+ "journal": [],
404
+ "applied_transactions": {},
405
+ }
406
+ if book is not None:
407
+ state["book"] = book
408
+ return state
409
+
410
+
411
+ # ---------------------------------------------------------------------------
412
+ # Validation
413
+ # ---------------------------------------------------------------------------
414
+
415
+
416
+ def normalize_scope(value: object, label: str) -> dict[str, str | None]:
417
+ scope = as_mapping(value, label)
418
+ require_known_keys(scope, {"level", "value"}, label)
419
+ level = choice(scope.get("level"), SCOPE_LEVELS, f"{label}.level")
420
+ raw_value = scope.get("value")
421
+ if level == "global":
422
+ require(raw_value is None, f"{label}.value must be null for global scope")
423
+ normalized_value = None
424
+ else:
425
+ normalized_value = clean_text(raw_value, f"{label}.value", max_bytes=180)
426
+ return {"level": level, "value": normalized_value}
427
+
428
+
429
+ def normalize_evidence(value: object, label: str) -> dict[str, str | None]:
430
+ evidence = as_mapping(value, label)
431
+ require_known_keys(evidence, {"quote", "source_ref"}, label)
432
+ return {
433
+ "quote": clean_text(evidence.get("quote"), f"{label}.quote", max_bytes=768),
434
+ "source_ref": optional_text(evidence.get("source_ref"), f"{label}.source_ref", max_bytes=240),
435
+ }
436
+
437
+
438
+ def normalize_item(value: object, label: str) -> dict[str, Any]:
439
+ item = as_mapping(value, label)
440
+ allowed = {
441
+ "id", "kind", "scope", "assertion", "confidence", "importance", "status", "source",
442
+ "reason", "conflicts_with", "confirmation_count", "evidence", "created_revision",
443
+ "updated_revision", "superseded_by",
444
+ }
445
+ require_known_keys(item, allowed, label)
446
+ item_id = clean_text(item.get("id"), f"{label}.id", max_bytes=32)
447
+ require(is_item_id(item_id), f"{label}.id is invalid")
448
+ evidence = [normalize_evidence(entry, f"{label}.evidence[{index}]") for index, entry in enumerate(as_list(item.get("evidence"), f"{label}.evidence"))]
449
+ require(bool(evidence), f"{label}.evidence must not be empty")
450
+ status = choice(item.get("status"), STATUSES, f"{label}.status")
451
+ conflicts = clean_id_list(item.get("conflicts_with"), f"{label}.conflicts_with")
452
+ superseded_by = optional_text(item.get("superseded_by"), f"{label}.superseded_by", max_bytes=32)
453
+ if superseded_by is not None:
454
+ require(is_item_id(superseded_by), f"{label}.superseded_by is invalid")
455
+ return {
456
+ "id": item_id,
457
+ "kind": choice(item.get("kind"), KINDS, f"{label}.kind"),
458
+ "scope": normalize_scope(item.get("scope"), f"{label}.scope"),
459
+ "assertion": clean_text(item.get("assertion"), f"{label}.assertion", max_bytes=LEGACY_ASSERTION_MAX_BYTES),
460
+ "confidence": choice(item.get("confidence"), CONFIDENCE_LEVELS, f"{label}.confidence"),
461
+ "importance": choice(item.get("importance"), IMPORTANCE_LEVELS, f"{label}.importance"),
462
+ "status": status,
463
+ "source": choice(item.get("source"), SOURCES, f"{label}.source"),
464
+ "reason": clean_text(item.get("reason"), f"{label}.reason", max_bytes=480),
465
+ "conflicts_with": conflicts,
466
+ "confirmation_count": as_int(item.get("confirmation_count"), f"{label}.confirmation_count", minimum=1),
467
+ "evidence": evidence,
468
+ "created_revision": as_int(item.get("created_revision"), f"{label}.created_revision", minimum=1),
469
+ "updated_revision": as_int(item.get("updated_revision"), f"{label}.updated_revision", minimum=1),
470
+ "superseded_by": superseded_by,
471
+ }
472
+
473
+
474
+ def scope_fits_store(scope: dict[str, str | None], book: str | None) -> bool:
475
+ if book is None:
476
+ return True # 项目级:存量 book 条目仍合法,只为老库能通过校验并被 migrate 搬走
477
+ return scope["level"] == "book" and (scope["value"] or "").casefold() == book.casefold()
478
+
479
+
480
+ def validate_state(value: object, *, store: Store) -> dict[str, Any]:
481
+ state = as_mapping(value, "state")
482
+ allowed = {"schema_version", "state_revision", "next_item_number", "items", "journal", "applied_transactions", "book"}
483
+ require_known_keys(state, allowed, "state")
484
+ require(state.get("schema_version") == STATE_SCHEMA_VERSION, f"state.schema_version must be {STATE_SCHEMA_VERSION}")
485
+ if store.kind == "book":
486
+ book = clean_text(state.get("book"), "state.book", max_bytes=180)
487
+ require(store.book is not None and book.casefold() == store.book.casefold(), f"state.book「{book}」与目标书「{store.book}」不一致")
488
+ else:
489
+ require(
490
+ "book" not in state,
491
+ "project-level state must not carry state.book:这份其实是书级 state。--workspace 指到了"
492
+ "长篇/、短篇/ 下的书目录时,改传创作工作区根、书目录放 --book-root;只有书根就是工作区"
493
+ "(单书布局)时,带 --book-root {工作区} 重跑任一命令即自动移进 .story/作者记忆/书级/",
494
+ )
495
+ book = None
496
+ revision = as_int(state.get("state_revision"), "state.state_revision")
497
+ next_number = as_int(state.get("next_item_number"), "state.next_item_number", minimum=1)
498
+ raw_items = as_mapping(state.get("items"), "state.items")
499
+ items: dict[str, Any] = {}
500
+ max_number = 0
501
+ for raw_id, raw_item in raw_items.items():
502
+ normalized = normalize_item(raw_item, f"state.items.{raw_id}")
503
+ require(raw_id == normalized["id"], f"state.items key {raw_id} does not match item id")
504
+ require(raw_id.startswith(store.prefix), f"state.items.{raw_id} does not belong to the {store.label} store(前缀应为 {store.prefix})")
505
+ require(scope_fits_store(normalized["scope"], book), f"state.items.{raw_id} scope does not belong to book「{book}」")
506
+ max_number = max(max_number, id_number(raw_id))
507
+ require(normalized["created_revision"] <= normalized["updated_revision"] <= revision, f"state.items.{raw_id} revision is ahead of state")
508
+ items[raw_id] = normalized
509
+ require(next_number > max_number, "state.next_item_number must be greater than every allocated item id")
510
+ for item_id, item in items.items():
511
+ for conflict_id in item["conflicts_with"]:
512
+ require(conflict_id in items and conflict_id != item_id, f"state.items.{item_id} has an invalid conflict id")
513
+ if item["superseded_by"] is not None:
514
+ require(item["superseded_by"] in items and item["superseded_by"] != item_id, f"state.items.{item_id} has an invalid superseded_by id")
515
+ if item["status"] == "active":
516
+ require(not item["conflicts_with"], f"active item {item_id} cannot retain conflicts")
517
+ if item["status"] == "pending":
518
+ require(not item["conflicts_with"], f"pending item {item_id} cannot retain conflicts")
519
+ if item["status"] == "conflict":
520
+ require(bool(item["conflicts_with"]), f"conflict item {item_id} must reference an active item")
521
+ require(all(items[conflict_id]["status"] == "active" for conflict_id in item["conflicts_with"]), f"conflict item {item_id} must reference only active items")
522
+ if item["status"] != "superseded":
523
+ require(item["superseded_by"] is None, f"only superseded item {item_id} may set superseded_by")
524
+ journal = as_list(state.get("journal"), "state.journal")
525
+ require(len(journal) == revision, "state.journal length must equal state.state_revision")
526
+ journal_revisions: dict[str, int] = {}
527
+ for index, entry in enumerate(journal):
528
+ mapping = as_mapping(entry, f"state.journal[{index}]")
529
+ require_known_keys(mapping, {"revision", "transaction_id", "committed_at", "summaries"}, f"state.journal[{index}]")
530
+ entry_revision = as_int(mapping.get("revision"), f"state.journal[{index}].revision", minimum=1)
531
+ require(entry_revision == index + 1, f"state.journal[{index}].revision must be {index + 1}")
532
+ transaction_id = clean_text(mapping.get("transaction_id"), f"state.journal[{index}].transaction_id", max_bytes=128)
533
+ require(transaction_id not in journal_revisions, f"state.journal repeats transaction_id {transaction_id}")
534
+ journal_revisions[transaction_id] = entry_revision
535
+ clean_text(mapping.get("committed_at"), f"state.journal[{index}].committed_at", max_bytes=64)
536
+ summaries = as_list(mapping.get("summaries"), f"state.journal[{index}].summaries")
537
+ require(bool(summaries), f"state.journal[{index}].summaries must not be empty")
538
+ for summary_index, summary in enumerate(summaries):
539
+ clean_text(summary, f"state.journal[{index}].summaries[{summary_index}]", max_bytes=768)
540
+ transactions = as_mapping(state.get("applied_transactions"), "state.applied_transactions")
541
+ require(set(transactions) == set(journal_revisions), "state.applied_transactions must match state.journal transaction ids")
542
+ for transaction_id, record in transactions.items():
543
+ clean_text(transaction_id, "state.applied_transactions key", max_bytes=128)
544
+ mapping = as_mapping(record, f"state.applied_transactions.{transaction_id}")
545
+ require_known_keys(mapping, {"revision", "digest", "item_ids"}, f"state.applied_transactions.{transaction_id}")
546
+ transaction_revision = as_int(mapping.get("revision"), f"state.applied_transactions.{transaction_id}.revision", minimum=1)
547
+ require(transaction_revision == journal_revisions[transaction_id], f"state.applied_transactions.{transaction_id}.revision does not match journal")
548
+ digest = clean_text(mapping.get("digest"), f"state.applied_transactions.{transaction_id}.digest", max_bytes=64)
549
+ require(len(digest) == 64 and all(char in "0123456789abcdef" for char in digest), f"state.applied_transactions.{transaction_id}.digest is invalid")
550
+ item_ids = clean_id_list(mapping.get("item_ids"), f"state.applied_transactions.{transaction_id}.item_ids")
551
+ require(bool(item_ids), f"state.applied_transactions.{transaction_id}.item_ids must not be empty")
552
+ require(all(item_id in items for item_id in item_ids), f"state.applied_transactions.{transaction_id}.item_ids references an unknown item")
553
+ result = {
554
+ "schema_version": STATE_SCHEMA_VERSION,
555
+ "state_revision": revision,
556
+ "next_item_number": next_number,
557
+ "items": items,
558
+ "journal": copy.deepcopy(journal),
559
+ "applied_transactions": copy.deepcopy(transactions),
560
+ }
561
+ if book is not None:
562
+ result["book"] = book
563
+ return result
564
+
565
+
566
+ def load_state(store: Store) -> dict[str, Any] | None:
567
+ if not store.state_path.exists():
568
+ return None
569
+ return validate_state(read_json(store.state_path), store=store)
570
+
571
+
572
+ def normalize_preference(value: object, label: str, *, allow_status: bool) -> dict[str, Any]:
573
+ preference = as_mapping(value, label)
574
+ allowed = {"kind", "scope", "assertion", "quote", "source_ref", "source", "confidence", "importance", "reason"}
575
+ if allow_status:
576
+ allowed |= {"status", "conflicts_with"}
577
+ require_known_keys(preference, allowed, label)
578
+ raw_source = preference.get("source")
579
+ require(
580
+ raw_source not in {"repeated_correction", "inferred_pattern"},
581
+ f"{label}.source「{raw_source}」已不再写入:作者记忆只记作者明确表达的偏好,"
582
+ f"不从重复修改或成稿轨迹推断;范围含糊的原话用 explicit_user 并置 status=pending",
583
+ )
584
+ source = choice(raw_source, WRITE_SOURCES, f"{label}.source")
585
+ status = choice(preference.get("status"), ("active", "pending", "conflict"), f"{label}.status") if allow_status else "active"
586
+ conflicts = clean_id_list(preference.get("conflicts_with", []), f"{label}.conflicts_with") if allow_status else []
587
+ if status == "active":
588
+ require(not conflicts, f"{label}.conflicts_with must be empty for active status")
589
+ elif status == "conflict":
590
+ require(bool(conflicts), f"{label}.conflicts_with is required for conflict status")
591
+ else:
592
+ require(not conflicts, f"{label}.conflicts_with is only valid for conflict status")
593
+ return {
594
+ "kind": choice(preference.get("kind"), KINDS, f"{label}.kind"),
595
+ "scope": normalize_scope(preference.get("scope"), f"{label}.scope"),
596
+ # 这里按存量上限收;ASSERTION_MAX_BYTES 只在真正新建条目时校验
597
+ # (require_new_item_assertion),好让存量长断言仍能被强化。
598
+ "assertion": clean_text(preference.get("assertion"), f"{label}.assertion", max_bytes=LEGACY_ASSERTION_MAX_BYTES),
599
+ "quote": clean_text(preference.get("quote"), f"{label}.quote", max_bytes=768),
600
+ "source_ref": optional_text(preference.get("source_ref"), f"{label}.source_ref", max_bytes=240),
601
+ "source": source,
602
+ "confidence": choice(preference.get("confidence"), CONFIDENCE_LEVELS, f"{label}.confidence"),
603
+ "importance": choice(preference.get("importance"), IMPORTANCE_LEVELS, f"{label}.importance"),
604
+ "status": status,
605
+ "reason": clean_text(preference.get("reason"), f"{label}.reason", max_bytes=480),
606
+ "conflicts_with": conflicts,
607
+ }
608
+
609
+
610
+ def normalize_transaction(value: object) -> dict[str, Any]:
611
+ transaction = as_mapping(value, "transaction")
612
+ require_known_keys(transaction, {"schema_version", "transaction_id", "expected_state_revision", "operations"}, "transaction")
613
+ require(transaction.get("schema_version") == INPUT_SCHEMA_VERSION, f"transaction.schema_version must be {INPUT_SCHEMA_VERSION}")
614
+ transaction_id = clean_text(transaction.get("transaction_id"), "transaction.transaction_id", max_bytes=128)
615
+ operations = as_list(transaction.get("operations"), "transaction.operations")
616
+ require(1 <= len(operations) <= 32, "transaction.operations must contain 1-32 operations")
617
+ normalized_operations: list[dict[str, Any]] = []
618
+ for index, raw_operation in enumerate(operations):
619
+ label = f"transaction.operations[{index}]"
620
+ operation = as_mapping(raw_operation, label)
621
+ action = operation.get("action")
622
+ if action == "remember":
623
+ require_known_keys(operation, {"action", "preference"}, label)
624
+ normalized_operations.append({"action": action, "preference": normalize_preference(operation.get("preference"), f"{label}.preference", allow_status=True)})
625
+ elif action == "decide":
626
+ require_known_keys(operation, {"action", "item_id", "decision", "quote", "reason"}, label)
627
+ normalized_operations.append({
628
+ "action": action,
629
+ "item_id": clean_id_list([operation.get("item_id")], f"{label}.item_id", maximum=1)[0],
630
+ "decision": choice(operation.get("decision"), ("activate", "reject"), f"{label}.decision"),
631
+ "quote": clean_text(operation.get("quote"), f"{label}.quote", max_bytes=768),
632
+ "reason": clean_text(operation.get("reason"), f"{label}.reason", max_bytes=480),
633
+ })
634
+ elif action == "replace":
635
+ require_known_keys(operation, {"action", "old_ids", "preference"}, label)
636
+ old_ids = clean_id_list(operation.get("old_ids"), f"{label}.old_ids")
637
+ require(bool(old_ids), f"{label}.old_ids must not be empty")
638
+ normalized_operations.append({"action": action, "old_ids": old_ids, "preference": normalize_preference(operation.get("preference"), f"{label}.preference", allow_status=False)})
639
+ elif action == "forget":
640
+ require_known_keys(operation, {"action", "item_id", "quote", "reason"}, label)
641
+ normalized_operations.append({
642
+ "action": action,
643
+ "item_id": clean_id_list([operation.get("item_id")], f"{label}.item_id", maximum=1)[0],
644
+ "quote": clean_text(operation.get("quote"), f"{label}.quote", max_bytes=768),
645
+ "reason": clean_text(operation.get("reason"), f"{label}.reason", max_bytes=480),
646
+ })
647
+ else:
648
+ raise AuthorMemoryError(f"{label}.action must be one of: remember, decide, replace, forget")
649
+ return {
650
+ "schema_version": INPUT_SCHEMA_VERSION,
651
+ "transaction_id": transaction_id,
652
+ "expected_state_revision": as_int(transaction.get("expected_state_revision"), "transaction.expected_state_revision"),
653
+ "operations": normalized_operations,
654
+ }
655
+
656
+
657
+ def normalize_record_event(value: object) -> dict[str, Any]:
658
+ event = as_mapping(value, "event")
659
+ require_known_keys(event, {"schema_version", "event_id", "operation"}, "event")
660
+ require(event.get("schema_version") == INPUT_SCHEMA_VERSION, f"event.schema_version must be {INPUT_SCHEMA_VERSION}")
661
+ event_id = clean_text(event.get("event_id"), "event.event_id", max_bytes=120)
662
+ normalized = normalize_transaction({
663
+ "schema_version": INPUT_SCHEMA_VERSION,
664
+ "transaction_id": f"record:{event_id}",
665
+ "expected_state_revision": 0,
666
+ "operations": [event.get("operation")],
667
+ })
668
+ return {"event_id": event_id, "operation": normalized["operations"][0]}
669
+
670
+
671
+ # ---------------------------------------------------------------------------
672
+ # Routing: which store does an operation belong to?
673
+ # ---------------------------------------------------------------------------
674
+
675
+
676
+ def scope_store_kind(scope: dict[str, str | None]) -> str:
677
+ return "book" if scope["level"] == "book" else "project"
678
+
679
+
680
+ def store_kind_label(kind: str) -> str:
681
+ return "书级(BP)" if kind == "book" else "项目级(AP)"
682
+
683
+
684
+ def operation_store_kind(operation: dict[str, Any], label: str) -> str:
685
+ action = operation["action"]
686
+ if action in {"decide", "forget"}:
687
+ return id_store(operation["item_id"])
688
+ preference = operation["preference"]
689
+ kind = scope_store_kind(preference["scope"])
690
+ if action == "remember":
691
+ for conflict_id in preference["conflicts_with"]:
692
+ require(
693
+ id_store(conflict_id) == kind,
694
+ f"{label}.conflicts_with 只能引用同一 store 的条目({conflict_id} 在{store_kind_label(id_store(conflict_id))}):"
695
+ f"本书例外不算与全局规则冲突,直接 remember 为 book 条目即可",
696
+ )
697
+ return kind
698
+ for old_id in operation["old_ids"]:
699
+ require(
700
+ id_store(old_id) == kind,
701
+ f"{label} replace 不能跨 store:{old_id} 在{store_kind_label(id_store(old_id))},新条目范围属于{store_kind_label(kind)};"
702
+ f"跨 store 改版拆成 forget+remember,存量 book 条目先 migrate",
703
+ )
704
+ return kind
705
+
706
+
707
+ def transaction_store_kind(transaction: dict[str, Any]) -> str:
708
+ kinds = {
709
+ operation_store_kind(operation, f"transaction.operations[{index}]")
710
+ for index, operation in enumerate(transaction["operations"])
711
+ }
712
+ require(
713
+ len(kinds) == 1,
714
+ "一份事务只能写一个 store:项目级(global/genre/workflow 范围或 AP 编号)与书级(book 范围或 BP 编号)的操作要分开提交",
715
+ )
716
+ return kinds.pop()
717
+
718
+
719
+ # ---------------------------------------------------------------------------
720
+ # Transactions
721
+ # ---------------------------------------------------------------------------
722
+
723
+
724
+ def transaction_digest(transaction: dict[str, Any]) -> str:
725
+ canonical = json.dumps(transaction, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
726
+ return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
727
+
728
+
729
+ def fingerprint(preference: dict[str, Any]) -> str:
730
+ """同一条偏好的身份:kind+scope+断言,scope.value 与 same_scope_value 同样按
731
+ casefold 比——否则「Urban」「urban」会各建一条,两条同断言一起挤进 prompt。"""
732
+ scope = preference["scope"]
733
+ value = {
734
+ "kind": preference["kind"],
735
+ "scope": {"level": scope["level"], "value": None if scope["value"] is None else scope["value"].casefold()},
736
+ "assertion": preference["assertion"].casefold(),
737
+ }
738
+ return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
739
+
740
+
741
+ def require_new_item_assertion(preference: dict[str, Any]) -> None:
742
+ """新建条目的断言限一句话。强化已有条目走不到这里——存量长断言必须还能
743
+ 被确认,否则作者重申老偏好只会派生一条重复条目,库反而更挤。"""
744
+ size = len(preference["assertion"].encode("utf-8"))
745
+ require(
746
+ size <= ASSERTION_MAX_BYTES,
747
+ f"新条目的 assertion {size} 字节,超出 {ASSERTION_MAX_BYTES} 字节上限——"
748
+ f"断言限一句话,需要解释的背景写进 reason。若这是对已有条目的重申,"
749
+ f"原样使用该条目的 assertion 即可强化(不受本上限约束);"
750
+ f"若确实是几条互不依赖的偏好,才拆成几条分别记录。",
751
+ )
752
+
753
+
754
+ def require_scope_fits_store(state: dict[str, Any], scope: dict[str, str | None]) -> None:
755
+ book = state.get("book")
756
+ if book is None:
757
+ require(
758
+ scope["level"] != "book",
759
+ "book 级条目须传 --book-root {书目录}——记忆随书存放在 {书}/.story/作者记忆/,不再写进工作区的项目级 store",
760
+ )
761
+ else:
762
+ require(
763
+ scope_fits_store(scope, book),
764
+ f"book 条目的范围「{scope['value']}」与本书 store「{book}」不一致——只有这本书的条目才住在这个书目录",
765
+ )
766
+
767
+
768
+ def next_item_id(state: dict[str, Any]) -> str:
769
+ prefix = STORE_PREFIX["book"] if state.get("book") is not None else STORE_PREFIX["project"]
770
+ item_id = f"{prefix}{state['next_item_number']:03d}"
771
+ state["next_item_number"] += 1
772
+ return item_id
773
+
774
+
775
+ def allocate_item(state: dict[str, Any], preference: dict[str, Any], revision: int) -> dict[str, Any]:
776
+ require_new_item_assertion(preference)
777
+ require_scope_fits_store(state, preference["scope"])
778
+ return {
779
+ "id": next_item_id(state),
780
+ "kind": preference["kind"],
781
+ "scope": copy.deepcopy(preference["scope"]),
782
+ "assertion": preference["assertion"],
783
+ "confidence": preference["confidence"],
784
+ "importance": preference["importance"],
785
+ "status": preference["status"],
786
+ "source": preference["source"],
787
+ "reason": preference["reason"],
788
+ "conflicts_with": list(preference["conflicts_with"]),
789
+ "confirmation_count": 1,
790
+ "evidence": [{"quote": preference["quote"], "source_ref": preference["source_ref"]}],
791
+ "created_revision": revision,
792
+ "updated_revision": revision,
793
+ "superseded_by": None,
794
+ }
795
+
796
+
797
+ def best_level(first: str, second: str) -> str:
798
+ return first if RANK[first] >= RANK[second] else second
799
+
800
+
801
+ def add_evidence(item: dict[str, Any], quote: str, source_ref: str | None) -> None:
802
+ evidence = {"quote": quote, "source_ref": source_ref}
803
+ if evidence not in item["evidence"]:
804
+ item["evidence"].append(evidence)
805
+
806
+
807
+ def require_item(state: dict[str, Any], item_id: str, label: str) -> dict[str, Any]:
808
+ require(item_id in state["items"], f"{label} references unknown item {item_id}")
809
+ return state["items"][item_id]
810
+
811
+
812
+ def apply_remember(state: dict[str, Any], preference: dict[str, Any], revision: int) -> str:
813
+ require_scope_fits_store(state, preference["scope"])
814
+ for conflict_id in preference["conflicts_with"]:
815
+ conflict = require_item(state, conflict_id, "remember")
816
+ require(conflict["status"] == "active", f"remember conflict {conflict_id} must be active")
817
+ preference_fingerprint = fingerprint(preference)
818
+ for item in state["items"].values():
819
+ if item["status"] not in {"active", "pending", "conflict"} or fingerprint(item) != preference_fingerprint:
820
+ continue
821
+ require(not (item["status"] == "conflict" and preference["status"] == "active"), f"conflict item {item['id']} must be resolved with replace or rejected")
822
+ require(not (item["status"] == "active" and preference["status"] == "conflict"), f"active item {item['id']} cannot be recategorized as its own conflict")
823
+ add_evidence(item, preference["quote"], preference["source_ref"])
824
+ item["confirmation_count"] += 1
825
+ item["confidence"] = best_level(item["confidence"], preference["confidence"])
826
+ item["importance"] = best_level(item["importance"], preference["importance"])
827
+ item["updated_revision"] = revision
828
+ item["reason"] = preference["reason"]
829
+ if item["status"] == "pending" and preference["status"] == "active":
830
+ item["status"] = "active"
831
+ elif item["status"] == "pending" and preference["status"] == "conflict":
832
+ item["status"] = "conflict"
833
+ item["conflicts_with"] = list(preference["conflicts_with"])
834
+ elif item["status"] == "conflict" and preference["status"] == "conflict":
835
+ item["conflicts_with"] = sorted(set(item["conflicts_with"]) | set(preference["conflicts_with"]))
836
+ return f"强化 {item['id']}:{item['assertion']}"
837
+ item = allocate_item(state, preference, revision)
838
+ state["items"][item["id"]] = item
839
+ return f"新增 {item['id']}({item['status']}):{item['assertion']}"
840
+
841
+
842
+ def apply_decide(state: dict[str, Any], operation: dict[str, Any], revision: int) -> str:
843
+ item = require_item(state, operation["item_id"], "decide")
844
+ require(item["status"] in {"pending", "conflict"}, f"decide requires pending/conflict item, got {item['status']}")
845
+ if operation["decision"] == "activate":
846
+ require(item["status"] == "pending" and not item["conflicts_with"], "conflict candidates must be activated with replace")
847
+ item["status"] = "active"
848
+ verb = "确认"
849
+ else:
850
+ item["status"] = "rejected"
851
+ verb = "拒绝"
852
+ add_evidence(item, operation["quote"], None)
853
+ item["reason"] = operation["reason"]
854
+ item["updated_revision"] = revision
855
+ return f"{verb} {item['id']}:{item['assertion']}"
856
+
857
+
858
+ def release_conflicts(state: dict[str, Any], removed_ids: set[str], revision: int) -> int:
859
+ """被撤下的 active 条目不再是任何候选的冲突对象;冲突对象清空的候选退回 pending。"""
860
+ released = 0
861
+ for candidate in state["items"].values():
862
+ if candidate["status"] != "conflict":
863
+ continue
864
+ retained = [item_id for item_id in candidate["conflicts_with"] if item_id not in removed_ids]
865
+ if retained == candidate["conflicts_with"]:
866
+ continue
867
+ candidate["conflicts_with"] = retained
868
+ candidate["updated_revision"] = revision
869
+ if not retained:
870
+ candidate["status"] = "pending"
871
+ released += 1
872
+ return released
873
+
874
+
875
+ def apply_replace(state: dict[str, Any], operation: dict[str, Any], revision: int) -> str:
876
+ old_items = [require_item(state, item_id, "replace") for item_id in operation["old_ids"]]
877
+ for item in old_items:
878
+ require(item["status"] in {"active", "conflict", "pending"}, f"replace target {item['id']} is already {item['status']}")
879
+ replacement = allocate_item(state, operation["preference"], revision)
880
+ replacement["status"] = "active"
881
+ replacement["conflicts_with"] = []
882
+ state["items"][replacement["id"]] = replacement
883
+ for item in old_items:
884
+ item["status"] = "superseded"
885
+ item["superseded_by"] = replacement["id"]
886
+ item["updated_revision"] = revision
887
+ released = release_conflicts(state, {item["id"] for item in old_items}, revision)
888
+ replaced = ", ".join(item["id"] for item in old_items)
889
+ suffix = f";{released} 个其他冲突候选退回待确认" if released else ""
890
+ return f"用 {replacement['id']} 替代 {replaced}:{replacement['assertion']}{suffix}"
891
+
892
+
893
+ def apply_forget(state: dict[str, Any], operation: dict[str, Any], revision: int) -> str:
894
+ item = require_item(state, operation["item_id"], "forget")
895
+ require(item["status"] in {"active", "pending", "conflict"}, f"forget target {item['id']} is already {item['status']}")
896
+ item["status"] = "superseded"
897
+ item["superseded_by"] = None
898
+ item["reason"] = operation["reason"]
899
+ item["updated_revision"] = revision
900
+ add_evidence(item, operation["quote"], None)
901
+ released = release_conflicts(state, {item["id"]}, revision)
902
+ suffix = f";{released} 个冲突候选退回待确认" if released else ""
903
+ return f"忘记 {item['id']}:{item['assertion']}{suffix}"
904
+
905
+
906
+ def committed_now() -> str:
907
+ return datetime.now(timezone.utc).replace(microsecond=0).isoformat()
908
+
909
+
910
+ def seal_transaction(
911
+ updated: dict[str, Any],
912
+ transaction_id: str,
913
+ digest: str,
914
+ summaries: list[str],
915
+ *,
916
+ store: Store,
917
+ ) -> dict[str, Any]:
918
+ """把本次修订写进 journal / applied_transactions,并整份校验。"""
919
+ revision = updated["state_revision"] + 1
920
+ updated["state_revision"] = revision
921
+ updated["journal"].append({
922
+ "revision": revision,
923
+ "transaction_id": transaction_id,
924
+ "committed_at": committed_now(),
925
+ "summaries": summaries,
926
+ })
927
+ item_ids = sorted(
928
+ (item_id for item_id, item in updated["items"].items() if item["updated_revision"] == revision),
929
+ key=id_number,
930
+ )
931
+ require(bool(item_ids), "transaction did not update any author-memory item")
932
+ updated["applied_transactions"][transaction_id] = {
933
+ "revision": revision,
934
+ "digest": digest,
935
+ "item_ids": item_ids,
936
+ }
937
+ return validate_state(updated, store=store)
938
+
939
+
940
+ def apply_transaction(state: dict[str, Any], transaction: dict[str, Any], digest: str, *, store: Store) -> tuple[dict[str, Any], list[str]]:
941
+ applied = state["applied_transactions"].get(transaction["transaction_id"])
942
+ if applied is not None:
943
+ require(applied["digest"] == digest, "transaction_id was already used with different content")
944
+ return state, [f"事务已应用于修订 {applied['revision']},本次为幂等重放"]
945
+ require(transaction["expected_state_revision"] == state["state_revision"], f"stale state revision: expected {transaction['expected_state_revision']}, current {state['state_revision']}")
946
+ updated = copy.deepcopy(state)
947
+ revision = updated["state_revision"] + 1
948
+ summaries: list[str] = []
949
+ for operation in transaction["operations"]:
950
+ if operation["action"] == "remember":
951
+ summaries.append(apply_remember(updated, operation["preference"], revision))
952
+ elif operation["action"] == "decide":
953
+ summaries.append(apply_decide(updated, operation, revision))
954
+ elif operation["action"] == "replace":
955
+ summaries.append(apply_replace(updated, operation, revision))
956
+ else:
957
+ summaries.append(apply_forget(updated, operation, revision))
958
+ return seal_transaction(updated, transaction["transaction_id"], digest, summaries, store=store), summaries
959
+
960
+
961
+ # ---------------------------------------------------------------------------
962
+ # Derived views
963
+ # ---------------------------------------------------------------------------
964
+
965
+
966
+ def scope_label(scope: dict[str, str | None]) -> str:
967
+ if scope["level"] == "global":
968
+ return "全局"
969
+ labels = {"genre": "题材", "book": "本书", "workflow": "流程"}
970
+ return f"{labels[scope['level']]}:{scope['value']}"
971
+
972
+
973
+ def store_caption(state: dict[str, Any]) -> str:
974
+ book = state.get("book")
975
+ if book is None:
976
+ return "项目级记忆(全局、题材、流程);各书的书级记忆住在各自书目录的 .story/作者记忆/。"
977
+ return f"本书「{book}」的书级记忆;全局、题材、流程记忆住在工作区的 .story/作者记忆/。"
978
+
979
+
980
+ def render_profile(state: dict[str, Any]) -> str:
981
+ lines = [
982
+ "# 作者画像",
983
+ "",
984
+ "<!-- 由 author_memory_commit.py 生成,请勿手改;修改请提交事务。 -->",
985
+ "",
986
+ f"> 状态修订:{state['state_revision']}。{store_caption(state)}仅列出已确认偏好;当前明确要求、本书设定与硬性门禁优先。",
987
+ "",
988
+ ]
989
+ active = [item for item in state["items"].values() if item["status"] == "active"]
990
+ for kind in KINDS:
991
+ lines.extend([f"## {KIND_TITLES[kind]}", ""])
992
+ items = sorted((item for item in active if item["kind"] == kind), key=lambda item: id_number(item["id"]))
993
+ if not items:
994
+ lines.extend(["- 暂无", ""])
995
+ continue
996
+ for item in items:
997
+ # 必须显示 importance:它决定超编时谁留在 prompt 里,而「整理作者
998
+ # 记忆」只以本文件为输入——不显示就无从判断该退役哪条。
999
+ lines.append(
1000
+ f"- **{item['id']}**〔{scope_label(item['scope'])}|重要 {item['importance']}"
1001
+ f"|把握 {item['confidence']}|确认 {item['confirmation_count']} 次〕{item['assertion']}"
1002
+ )
1003
+ lines.append("")
1004
+ return "\n".join(lines).rstrip() + "\n"
1005
+
1006
+
1007
+ def render_pending(state: dict[str, Any]) -> str:
1008
+ lines = [
1009
+ "# 待确认的作者习惯",
1010
+ "",
1011
+ "<!-- 由 author_memory_commit.py 生成,请勿手改;修改请提交事务。 -->",
1012
+ "",
1013
+ f"> 状态修订:{state['state_revision']}。{store_caption(state)}待确认项不参与创作约束,也不应打断当前任务。",
1014
+ "",
1015
+ ]
1016
+ items = sorted((item for item in state["items"].values() if item["status"] in {"pending", "conflict"}), key=lambda item: id_number(item["id"]))
1017
+ if not items:
1018
+ lines.extend(["暂无待确认项。", ""])
1019
+ for item in items:
1020
+ lines.extend([
1021
+ f"## {item['id']} · {'冲突' if item['status'] == 'conflict' else '待确认'}",
1022
+ "",
1023
+ f"- 候选习惯:{item['assertion']}",
1024
+ f"- 范围:{scope_label(item['scope'])}",
1025
+ f"- 原话:“{item['evidence'][-1]['quote']}”",
1026
+ f"- 依据:{item['reason']}",
1027
+ f"- 置信度 / 重要度:{item['confidence']} / {item['importance']}",
1028
+ ])
1029
+ if item["conflicts_with"]:
1030
+ lines.append(f"- 冲突对象:{', '.join(item['conflicts_with'])}")
1031
+ lines.append("")
1032
+ return "\n".join(lines).rstrip() + "\n"
1033
+
1034
+
1035
+ def render_journal(state: dict[str, Any]) -> str:
1036
+ lines = [
1037
+ "# 作者记忆变更记录",
1038
+ "",
1039
+ "<!-- 由 author_memory_commit.py 生成,请勿手改;最近记录在前。 -->",
1040
+ "",
1041
+ f"> {store_caption(state)}",
1042
+ "",
1043
+ ]
1044
+ if not state["journal"]:
1045
+ lines.extend(["暂无变更。", ""])
1046
+ for entry in reversed(state["journal"][-100:]):
1047
+ lines.extend([f"## r{entry['revision']} · {entry['committed_at']}", "", f"- 事务:`{entry['transaction_id']}`"])
1048
+ lines.extend(f"- {summary}" for summary in entry["summaries"])
1049
+ lines.append("")
1050
+ return "\n".join(lines).rstrip() + "\n"
1051
+
1052
+
1053
+ def render_views(state: dict[str, Any]) -> dict[str, str]:
1054
+ views = {
1055
+ "作者画像.md": render_profile(state),
1056
+ "待确认.md": render_pending(state),
1057
+ "变更记录.md": render_journal(state),
1058
+ }
1059
+ limits = {"作者画像.md": PROFILE_MAX_BYTES, "待确认.md": PENDING_MAX_BYTES, "变更记录.md": JOURNAL_MAX_BYTES}
1060
+ for name, payload in views.items():
1061
+ require(len(payload.encode("utf-8")) <= limits[name], f"{name} exceeds {limits[name]} bytes; consolidate old memory first")
1062
+ return views
1063
+
1064
+
1065
+ def write_snapshot(store: Store, state: dict[str, Any]) -> None:
1066
+ views = render_views(state)
1067
+ state_payload = json_payload(state)
1068
+ require(len(state_payload.encode("utf-8")) <= STATE_MAX_BYTES, f"_author-memory-state.json exceeds {STATE_MAX_BYTES} bytes")
1069
+ for name, payload in views.items():
1070
+ write_if_changed(store.root / name, payload)
1071
+ # State is the authority and therefore the last commit point.
1072
+ write_if_changed(store.state_path, state_payload)
1073
+
1074
+
1075
+ # ---------------------------------------------------------------------------
1076
+ # Query & budget
1077
+ # ---------------------------------------------------------------------------
1078
+
1079
+
1080
+ def same_scope_value(item_value: str | None, requested: str | None) -> bool:
1081
+ return requested is not None and item_value is not None and item_value.casefold() == requested.casefold()
1082
+
1083
+
1084
+ def summarize_assertion(assertion: str, *, limit: int = 14) -> str:
1085
+ return assertion if len(assertion) <= limit else assertion[:limit] + "…"
1086
+
1087
+
1088
+ def compact_item(item: dict[str, Any]) -> dict[str, Any]:
1089
+ """query 载荷只带这四个字段——估算与真实输出必须同一把尺。"""
1090
+ return {"id": item["id"], "kind": item["kind"], "scope": item["scope"], "assertion": item["assertion"]}
1091
+
1092
+
1093
+ def compact_bytes(item: dict[str, Any]) -> int:
1094
+ return len(json.dumps(compact_item(item), ensure_ascii=False).encode("utf-8"))
1095
+
1096
+
1097
+ SCOPE_RANK = {"book": 0, "genre": 1, "workflow": 2, "global": 3}
1098
+
1099
+
1100
+ def query_sort_key(item: dict[str, Any]) -> tuple[int, int, int, int, int]:
1101
+ """重要度→本书例外→最近更新→确认次数→编号。
1102
+
1103
+ 重要度必须排在 scope 之前:超编时先丢的应当是不重要的条目,而不是「凡
1104
+ 全局一律先丢」。scope 在前会让任意数量的 low 本书琐事挤掉 high 的全局
1105
+ 铁律——那恰恰是作者最不愿意丢的那一类。同重要度之内才按本书例外优先。
1106
+ 两个 store 的修订号互不可比,但 book 条目只来自书级 store、其余只来自项目
1107
+ 级,同 scope 必同 store,所以按修订号比「最近更新」在合并后仍成立。
1108
+ """
1109
+ return (
1110
+ -RANK[item["importance"]],
1111
+ SCOPE_RANK[item["scope"]["level"]],
1112
+ -item["updated_revision"],
1113
+ -item["confirmation_count"],
1114
+ id_number(item["id"]),
1115
+ )
1116
+
1117
+
1118
+ def fit_items(
1119
+ sorted_items: list[dict[str, Any]],
1120
+ revision: int,
1121
+ *,
1122
+ extra: dict[str, Any] | None = None,
1123
+ ) -> tuple[dict[str, Any], list[str]]:
1124
+ """按 query 输出信封把条目装进 QUERY_MAX_BYTES:装不下的跳过而不中断
1125
+ (一条长的不挡后面的短条),漏下的 ID 报进 omitted_ids(封顶
1126
+ OMITTED_IDS_MAX 条,omitted 保留真实总数)。返回 (结果文档, 全部漏下 ID)。
1127
+
1128
+ 漏项恒按候选优先级排序,不按被丢弃的先后:收尾回吐的条目优先级高于循环
1129
+ 里跳过的,若按追加顺序排,omitted_ids 的封顶正好会把最该报的那条切掉。
1130
+ `extra` 是同样计入信封的附加字段(book_revision 等)。
1131
+ """
1132
+ result: dict[str, Any] = {
1133
+ "ok": True,
1134
+ "command": "query",
1135
+ "initialized": True,
1136
+ "revision": revision,
1137
+ "items": [],
1138
+ "omitted": 0,
1139
+ "omitted_ids": [],
1140
+ }
1141
+ result.update(extra or {})
1142
+ order = {item["id"]: index for index, item in enumerate(sorted_items)}
1143
+ kept: list[dict[str, Any]] = []
1144
+ dropped: set[str] = set()
1145
+
1146
+ def ordered_omitted() -> list[str]:
1147
+ return sorted(dropped, key=order.__getitem__)
1148
+
1149
+ def envelope_bytes() -> int:
1150
+ omitted = ordered_omitted()
1151
+ result["items"] = [compact_item(item) for item in kept]
1152
+ result["omitted"] = len(omitted)
1153
+ result["omitted_ids"] = omitted[:OMITTED_IDS_MAX]
1154
+ return len((json.dumps(result, ensure_ascii=False, sort_keys=True) + "\n").encode("utf-8"))
1155
+
1156
+ for item in sorted_items:
1157
+ kept.append(item)
1158
+ if envelope_bytes() > QUERY_MAX_BYTES:
1159
+ kept.pop()
1160
+ dropped.add(item["id"])
1161
+ # omitted 计数落定后包可能恰好贴边超出一两个字节,回吐条目直到装下。
1162
+ while kept and envelope_bytes() > QUERY_MAX_BYTES:
1163
+ dropped.add(kept.pop()["id"])
1164
+ envelope_bytes()
1165
+ return result, ordered_omitted()
1166
+
1167
+
1168
+ def build_query_result(
1169
+ candidates: list[dict[str, Any]],
1170
+ project_state: dict[str, Any] | None,
1171
+ book_state: dict[str, Any] | None,
1172
+ ) -> tuple[dict[str, Any], list[str]]:
1173
+ """真实查询与写入端估算共用的唯一信封构造:候选已按优先级排好,这里补上
1174
+ book_revision 等附加字段再装填。两处信封差一个字段就是一次假阴性——回执
1175
+ 说没事、query 照样丢条。"""
1176
+ extra: dict[str, Any] = {}
1177
+ if book_state is not None:
1178
+ extra["book_revision"] = book_state["state_revision"]
1179
+ revision = project_state["state_revision"] if project_state is not None else 0
1180
+ return fit_items(candidates, revision, extra=extra)
1181
+
1182
+
1183
+ def merged_query(
1184
+ project_state: dict[str, Any] | None,
1185
+ book_state: dict[str, Any] | None,
1186
+ kinds: set[str],
1187
+ requested_scopes: dict[str, str | None],
1188
+ ) -> tuple[dict[str, Any], list[str]]:
1189
+ """真实查询的纯函数部分:项目级按 kind/scope 过滤(存量 book 条目不参与,
1190
+ migrate 后才回来),书级整个 store 就是这本书的,只按 status/kind 过滤;
1191
+ 合并后按 重要度→本书例外→最近更新 装填。"""
1192
+
1193
+ def relevant(item: dict[str, Any]) -> bool:
1194
+ if item["status"] != "active" or item["kind"] not in kinds:
1195
+ return False
1196
+ level = item["scope"]["level"]
1197
+ if level == "global":
1198
+ return True
1199
+ return level != "book" and same_scope_value(item["scope"]["value"], requested_scopes[level])
1200
+
1201
+ candidates: list[dict[str, Any]] = []
1202
+ if project_state is not None:
1203
+ candidates.extend(item for item in project_state["items"].values() if relevant(item))
1204
+ if book_state is not None:
1205
+ candidates.extend(active_of_kinds(book_state, tuple(kinds)))
1206
+ candidates.sort(key=query_sort_key)
1207
+ return build_query_result(candidates, project_state, book_state)
1208
+
1209
+
1210
+ def slice_weight(items: list[dict[str, Any]]) -> int:
1211
+ """切片在真实载荷里的占位:compact 字节+每条在 JSON 数组里的分隔符。
1212
+
1213
+ 只比 compact 字节会挑错切片——条目多、单条短的切片字节和更小,实际占位
1214
+ 却更大,于是估算判「装得下」而真实查询溢出(warnings 假阴性)。
1215
+ """
1216
+ return sum(compact_bytes(item) + 2 for item in items)
1217
+
1218
+
1219
+ def active_of_kinds(state: dict[str, Any] | None, kinds: tuple[str, ...]) -> list[dict[str, Any]]:
1220
+ if state is None:
1221
+ return []
1222
+ return [item for item in state["items"].values() if item["status"] == "active" and item["kind"] in kinds]
1223
+
1224
+
1225
+ def worst_case_items(
1226
+ project_state: dict[str, Any] | None,
1227
+ book_state: dict[str, Any] | None,
1228
+ kinds: tuple[str, ...],
1229
+ ) -> list[dict[str, Any]]:
1230
+ """某个任务组合的最坏查询候选:全局条目+各 scope 维度上最重的单一切片。
1231
+
1232
+ 一次查询只带一个 book/genre/workflow,不同书的条目不会同现,所以按切片
1233
+ 取最重而不是全加起来,多书工作区才不会被粗算误伤。切片按 casefold 归并,
1234
+ 与 same_scope_value 同一口径——否则只差大小写的同名书在这里算两个切片、
1235
+ 在真实查询里却合成一个,估算就成了下界。
1236
+
1237
+ book 维度就是当前可见的书级 store(整个 store 是这本书的一片),所以写书级
1238
+ 条目时「本书+全局」是精确的;写项目级条目而没传 --book-root 时看不到任何
1239
+ 书级 store,估算只覆盖项目级。项目级里的存量 book 条目不参与查询,也不参与
1240
+ 估算。
1241
+ """
1242
+ pool = [item for item in active_of_kinds(project_state, kinds) if item["scope"]["level"] != "book"]
1243
+ worst = [item for item in pool if item["scope"]["level"] == "global"]
1244
+ slices: dict[str, dict[str, list[dict[str, Any]]]] = {"genre": {}, "workflow": {}}
1245
+ for item in pool:
1246
+ level = item["scope"]["level"]
1247
+ if level != "global":
1248
+ slices[level].setdefault((item["scope"]["value"] or "").casefold(), []).append(item)
1249
+ for level in ("genre", "workflow"):
1250
+ if slices[level]:
1251
+ worst.extend(max(slices[level].values(), key=slice_weight))
1252
+ worst.extend(active_of_kinds(book_state, kinds))
1253
+ worst.sort(key=query_sort_key)
1254
+ return worst
1255
+
1256
+
1257
+ def legacy_book_items(project_state: dict[str, Any] | None) -> list[dict[str, Any]]:
1258
+ """项目级 store 里升级前写入、还活着的 book 条目——只有 migrate 会碰它们。"""
1259
+ if project_state is None:
1260
+ return []
1261
+ return [
1262
+ item for item in project_state["items"].values()
1263
+ if item["scope"]["level"] == "book" and item["status"] in {"active", "pending", "conflict"}
1264
+ ]
1265
+
1266
+
1267
+ def query_budget_warnings(project_state: dict[str, Any] | None, book_state: dict[str, Any] | None) -> list[str]:
1268
+ """写入回执的预算提醒:每个任务组合按最坏查询情形试装,装不下的点名。
1269
+
1270
+ 写入永不因注入预算失败;提醒指向「整理作者记忆」。点名带断言首句——只给
1271
+ 编号的话,作者不打开 作者画像.md 就无从判断丢的是什么。
1272
+ """
1273
+ warnings: list[str] = []
1274
+ for task, kinds in QUERY_COMBOS.items():
1275
+ worst = worst_case_items(project_state, book_state, kinds)
1276
+ _, omitted = build_query_result(worst, project_state, book_state)
1277
+ if not omitted:
1278
+ continue
1279
+ assertions = {item["id"]: item["assertion"] for item in worst}
1280
+ shown = ";".join(f"{item_id}「{summarize_assertion(assertions[item_id])}」" for item_id in omitted[:3])
1281
+ more = f" 等 {len(omitted)} 条" if len(omitted) > 3 else ""
1282
+ warnings.append(
1283
+ f"「{task}」最坏查询装不下 {len(omitted)} 条,它们不会进入 prompt:"
1284
+ f"{shown}{more}——超出 {QUERY_MAX_BYTES} 字节注入预算。"
1285
+ f"说「整理作者记忆」可合并同义条、退役过时条。"
1286
+ )
1287
+ return warnings
1288
+
1289
+
1290
+ def visible_states(
1291
+ store: Store,
1292
+ updated: dict[str, Any],
1293
+ workspace: Path,
1294
+ book_root: Path | None,
1295
+ book: str | None,
1296
+ ) -> tuple[dict[str, Any] | None, dict[str, Any] | None, list[str]]:
1297
+ """写入后可见的两级状态:刚写的那份用内存里的,另一份只读加载(没有就 None)。
1298
+
1299
+ 此时写入已经落盘,另一份读不出来(书目录不存在、--book 与书级记录不符、state
1300
+ 损坏)只能降级为提醒:若在这里报错,调用方会告诉作者「没记住」,换个 event_id
1301
+ 重试就派生重复条目。"""
1302
+ try:
1303
+ if store.kind == "project":
1304
+ other = load_state(book_store(workspace, book_root, book)) if book_root is not None else None
1305
+ return updated, other, []
1306
+ return load_state(project_store(workspace)), updated, []
1307
+ except AuthorMemoryError as exc:
1308
+ other_label = "书级" if store.kind == "project" else "项目级"
1309
+ notice = f"已写入{store.label} store;但{other_label} store 读取失败,本次预算提醒没算上它:{exc}"
1310
+ return (updated, None, [notice]) if store.kind == "project" else (None, updated, [notice])
1311
+
1312
+
1313
+ # ---------------------------------------------------------------------------
1314
+ # Commands
1315
+ # ---------------------------------------------------------------------------
1316
+
1317
+
1318
+ def prepare_workspace(workspace: Path, book_root: Path | None = None) -> None:
1319
+ """每个命令的入口:校验工作区,并在单书布局下先把误放的书级 state 归位——
1320
+ 归位之前项目级 store 读不出来,放在任何读取之前才能让只读查询也自愈。"""
1321
+ require(workspace.exists() and workspace.is_dir(), f"workspace does not exist: {workspace}")
1322
+ relocate_misplaced_book_state(workspace, book_root)
1323
+
1324
+
1325
+ def store_fields(store: Store) -> dict[str, Any]:
1326
+ fields: dict[str, Any] = {"store": store.kind, "root": str(store.root)}
1327
+ if store.book is not None:
1328
+ fields["book"] = store.book
1329
+ return fields
1330
+
1331
+
1332
+ def command_init(workspace: Path, book_root: Path | None, book: str | None) -> dict[str, Any]:
1333
+ prepare_workspace(workspace, book_root)
1334
+ store = book_store(workspace, book_root, book) if book_root is not None else project_store(workspace)
1335
+ state = load_state(store)
1336
+ if state is None:
1337
+ state = empty_state(store.book)
1338
+ write_snapshot(store, state)
1339
+ return {"ok": True, "command": "init", "revision": state["state_revision"], **store_fields(store)}
1340
+
1341
+
1342
+ def command_commit(workspace: Path, book_root: Path | None, book: str | None, input_path: Path) -> dict[str, Any]:
1343
+ prepare_workspace(workspace, book_root)
1344
+ transaction = normalize_transaction(read_json(input_path))
1345
+ store = resolve_target_store(transaction_store_kind(transaction), workspace, book_root, book)
1346
+ require(store.state_path.exists(), f"{store.label} author memory is not initialized; run init first")
1347
+ state = load_state(store)
1348
+ digest = transaction_digest(transaction)
1349
+ updated, summaries = apply_transaction(state, transaction, digest, store=store)
1350
+ replayed = updated is state
1351
+ # 幂等重放时也重写快照,修复缺失或过期的派生视图。
1352
+ write_snapshot(store, updated)
1353
+ project_state, book_state, load_warnings = visible_states(store, updated, workspace, book_root, book)
1354
+ return {
1355
+ "ok": True,
1356
+ "command": "commit",
1357
+ "revision": updated["state_revision"],
1358
+ "transaction_id": transaction["transaction_id"],
1359
+ "replayed": replayed,
1360
+ "item_ids": updated["applied_transactions"][transaction["transaction_id"]]["item_ids"],
1361
+ "summaries": summaries,
1362
+ "warnings": load_warnings + query_budget_warnings(project_state, book_state),
1363
+ **store_fields(store),
1364
+ }
1365
+
1366
+
1367
+ def command_record(workspace: Path, book_root: Path | None, book: str | None, input_path: Path) -> dict[str, Any]:
1368
+ prepare_workspace(workspace, book_root)
1369
+ event = normalize_record_event(read_json(input_path))
1370
+ store = resolve_target_store(operation_store_kind(event["operation"], "event.operation"), workspace, book_root, book)
1371
+ state = load_state(store)
1372
+ if state is None:
1373
+ state = empty_state(store.book)
1374
+ transaction_id = f"record:{event['event_id']}"
1375
+ applied = state["applied_transactions"].get(transaction_id)
1376
+ expected_revision = applied["revision"] - 1 if applied is not None else state["state_revision"]
1377
+ transaction = {
1378
+ "schema_version": INPUT_SCHEMA_VERSION,
1379
+ "transaction_id": transaction_id,
1380
+ "expected_state_revision": expected_revision,
1381
+ "operations": [event["operation"]],
1382
+ }
1383
+ digest = transaction_digest(transaction)
1384
+ updated, summaries = apply_transaction(state, transaction, digest, store=store)
1385
+ replayed = updated is state
1386
+ write_snapshot(store, updated)
1387
+ record = updated["applied_transactions"][transaction_id]
1388
+ item_ids = record["item_ids"]
1389
+ receipt = f"Author Memory Receipt: r{record['revision']} · {', '.join(item_ids)}"
1390
+ project_state, book_state, load_warnings = visible_states(store, updated, workspace, book_root, book)
1391
+ return {
1392
+ "ok": True,
1393
+ "command": "record",
1394
+ "revision": updated["state_revision"],
1395
+ "applied_revision": record["revision"],
1396
+ "event_id": event["event_id"],
1397
+ "replayed": replayed,
1398
+ "item_ids": item_ids,
1399
+ "receipt": receipt,
1400
+ "summaries": summaries,
1401
+ "warnings": load_warnings + query_budget_warnings(project_state, book_state),
1402
+ **store_fields(store),
1403
+ }
1404
+
1405
+
1406
+ def command_query(
1407
+ workspace: Path,
1408
+ book_root: Path | None,
1409
+ kinds: list[str] | None,
1410
+ book: str | None,
1411
+ genre: str | None,
1412
+ workflow: str | None,
1413
+ ) -> dict[str, Any]:
1414
+ prepare_workspace(workspace, book_root)
1415
+ require(
1416
+ bool(kinds),
1417
+ "query 必须显式传 --kind(按调用方 skill 写好的 query 命令选类型,映射表见 references/author-memory-maintenance.md),不再默认返回全部类型",
1418
+ )
1419
+ project_state = load_state(project_store(workspace))
1420
+ book_state = load_state(book_store(workspace, book_root, book)) if book_root is not None else None
1421
+ if project_state is None and book_state is None:
1422
+ return {"ok": True, "command": "query", "initialized": False, "revision": 0, "items": [], "omitted": 0, "omitted_ids": []}
1423
+ requested_scopes = {
1424
+ "genre": optional_text(genre, "query.genre", max_bytes=180),
1425
+ "workflow": optional_text(workflow, "query.workflow", max_bytes=180),
1426
+ }
1427
+ # 装不下的**跳过而不中断**(一条长的不挡后面的短条),漏下的 ID 报进
1428
+ # omitted_ids:非空=记忆超编该整理了,不是「没有更多了」。写入端的
1429
+ # warnings 与这里共用 build_query_result 同一个信封,估算即实况。
1430
+ result, _ = merged_query(project_state, book_state, set(kinds), requested_scopes)
1431
+ require(len((json.dumps(result, ensure_ascii=False, sort_keys=True) + "\n").encode("utf-8")) <= QUERY_MAX_BYTES, "query result exceeds its fixed byte budget")
1432
+ return result
1433
+
1434
+
1435
+ def check_store(store: Store) -> dict[str, Any]:
1436
+ state = load_state(store)
1437
+ require(state is not None, f"{store.label} author memory is not initialized")
1438
+ views = render_views(state)
1439
+ for name, expected in views.items():
1440
+ view_path = store.root / name
1441
+ require(view_path.exists(), f"missing derived view: {view_path}")
1442
+ require(view_path.read_text(encoding="utf-8") == expected, f"derived view is stale or edited: {view_path}")
1443
+ counts = {status: sum(1 for item in state["items"].values() if item["status"] == status) for status in STATUSES}
1444
+ return {"revision": state["state_revision"], "counts": counts}
1445
+
1446
+
1447
+ def command_check(workspace: Path, book_root: Path | None, book: str | None) -> dict[str, Any]:
1448
+ prepare_workspace(workspace, book_root)
1449
+ project = project_store(workspace)
1450
+ result: dict[str, Any] = {"ok": True, "command": "check"}
1451
+ if book_root is not None:
1452
+ store = book_store(workspace, book_root, book)
1453
+ result["book"] = {"name": store.book, "root": str(store.root), **check_store(store)}
1454
+ if project.state_path.exists():
1455
+ result["project"] = {"root": str(project.root), **check_store(project)}
1456
+ else:
1457
+ result["project"] = {"root": str(project.root), **check_store(project)}
1458
+ # 兼容旧调用:顶层 revision / counts 指项目级 store。
1459
+ primary = result.get("project") or result["book"]
1460
+ result["revision"] = primary["revision"]
1461
+ result["counts"] = primary["counts"]
1462
+ return result
1463
+
1464
+
1465
+ MIGRATE_PREFIX = "migrate:"
1466
+
1467
+
1468
+ def migration_digest(item: dict[str, Any]) -> str:
1469
+ """迁移事务的摘要只看条目不可变的身份:编号、类型、范围、断言。证据会随强化
1470
+ 增长、状态会随 decide/forget 变化,它们若进摘要,中途失败后作者动过条目就会
1471
+ 永远「already used with different content」。"""
1472
+ return transaction_digest({"id": item["id"], "kind": item["kind"], "scope": item["scope"], "assertion": item["assertion"]})
1473
+
1474
+
1475
+ def migrated_item(source: dict[str, Any], new_id: str, revision: int, id_map: dict[str, str]) -> dict[str, Any]:
1476
+ """把项目级的存量 book 条目原样搬进书级 store:断言、证据、确认次数、重要度
1477
+ 全部保留,只换编号与修订。冲突对象只保留同书已迁移的(重映射为 BP),
1478
+ 引用全局条目的冲突关系不再成立——本书例外按优先级覆盖全局,不算冲突——
1479
+ 对象清空的候选退回 pending。"""
1480
+ item = copy.deepcopy(source)
1481
+ item["id"] = new_id
1482
+ item["created_revision"] = revision
1483
+ item["updated_revision"] = revision
1484
+ item["superseded_by"] = None
1485
+ item["conflicts_with"] = [id_map[old] for old in source["conflicts_with"] if old in id_map]
1486
+ if item["status"] == "conflict" and not item["conflicts_with"]:
1487
+ item["status"] = "pending"
1488
+ return item
1489
+
1490
+
1491
+ def migration_records(state: dict[str, Any]) -> dict[str, str]:
1492
+ """书级 store 里「源 AP 编号 → 已建 BP 编号」;每个源条目一笔 migrate:APxxx 事务。"""
1493
+ records: dict[str, str] = {}
1494
+ for transaction_id, record in state["applied_transactions"].items():
1495
+ if transaction_id.startswith(MIGRATE_PREFIX) and is_item_id(transaction_id[len(MIGRATE_PREFIX):]):
1496
+ records[transaction_id[len(MIGRATE_PREFIX):]] = record["item_ids"][0]
1497
+ return records
1498
+
1499
+
1500
+ def apply_single(state: dict[str, Any], transaction_id: str, digest: str, mutate, summaries: list[str], *, store: Store) -> dict[str, Any]:
1501
+ updated = copy.deepcopy(state)
1502
+ mutate(updated, updated["state_revision"] + 1)
1503
+ return seal_transaction(updated, transaction_id, digest, summaries, store=store)
1504
+
1505
+
1506
+ def command_migrate(workspace: Path, book_root: Path | None, book: str | None) -> dict[str, Any]:
1507
+ """把项目级 store 里某本书的存量 book 条目搬进该书目录的书级 store。
1508
+
1509
+ 书级每个源条目一笔事务(migrate:APxxx,摘要只看不可变字段),所以每个源条目
1510
+ 至多迁一次,与本次批次里还有哪些条目无关、源条目状态怎么变都不会锁死;项目级
1511
+ 随后一笔事务把这批源条目标 superseded 并注明去向。先写书级再写项目级,中途
1512
+ 失败直接重跑:书级已有记录的直接复用编号,只补项目级。
1513
+ """
1514
+ prepare_workspace(workspace, book_root)
1515
+ require(book_root is not None, "migrate 须传 --book-root {书目录}:书名到书目录的映射由调用方给出")
1516
+ project = project_store(workspace)
1517
+ store = book_store(workspace, book_root, book)
1518
+ assert store.book is not None
1519
+ project_state = load_state(project)
1520
+ book_state = load_state(store)
1521
+
1522
+ def result(migrated: list[dict[str, str]]) -> dict[str, Any]:
1523
+ return {
1524
+ "ok": True,
1525
+ "command": "migrate",
1526
+ "migrated": migrated,
1527
+ "project_revision": project_state["state_revision"] if project_state is not None else 0,
1528
+ "revision": book_state["state_revision"] if book_state is not None else 0,
1529
+ "warnings": query_budget_warnings(project_state, book_state),
1530
+ **store_fields(store),
1531
+ }
1532
+
1533
+ if project_state is None:
1534
+ return result([])
1535
+ legacy = sorted(
1536
+ (item for item in legacy_book_items(project_state) if same_scope_value(item["scope"]["value"], store.book)),
1537
+ key=lambda item: id_number(item["id"]),
1538
+ )
1539
+ if book_state is None:
1540
+ if not legacy:
1541
+ return result([])
1542
+ book_state = empty_state(store.book)
1543
+ id_map = migration_records(book_state)
1544
+
1545
+ # 1) 书级:还没迁的源条目各建一条 BP;已迁的复用编号。
1546
+ for item in legacy:
1547
+ if item["id"] in id_map:
1548
+ continue
1549
+ transaction_id = f"{MIGRATE_PREFIX}{item['id']}"
1550
+ new_id = f"{STORE_PREFIX['book']}{book_state['next_item_number']:03d}"
1551
+
1552
+ preview = migrated_item(item, new_id, 1, id_map)
1553
+
1554
+ def create(updated: dict[str, Any], revision: int, source=item, new_id=new_id) -> None:
1555
+ updated["next_item_number"] += 1
1556
+ updated["items"][new_id] = migrated_item(source, new_id, revision, id_map)
1557
+
1558
+ book_state = apply_single(
1559
+ book_state, transaction_id, migration_digest(item), create,
1560
+ [f"迁移 {item['id']} → {new_id}({preview['status']}):{item['assertion']}"],
1561
+ store=store,
1562
+ )
1563
+ id_map[item["id"]] = new_id
1564
+ # 幂等重跑也重写快照,修复缺失或过期的派生视图。
1565
+ write_snapshot(store, book_state)
1566
+
1567
+ # 2) 项目级:这批源条目标 superseded 并注明去向。
1568
+ migrated = [{"from": item["id"], "to": id_map[item["id"]]} for item in legacy]
1569
+ if legacy:
1570
+ transaction_id = f"{MIGRATE_PREFIX}{store.book.casefold()}:r{project_state['state_revision'] + 1}"
1571
+ digest = transaction_digest({"transaction_id": transaction_id, "migrated": migrated})
1572
+
1573
+ def move_out(updated: dict[str, Any], revision: int) -> None:
1574
+ for item in legacy:
1575
+ target = updated["items"][item["id"]]
1576
+ target["status"] = "superseded"
1577
+ target["superseded_by"] = None
1578
+ target["conflicts_with"] = []
1579
+ target["reason"] = f"已迁移到书级 store「{store.book}」:{id_map[item['id']]}"
1580
+ target["updated_revision"] = revision
1581
+ release_conflicts(updated, set(id_map), revision)
1582
+
1583
+ summaries = [f"迁出 {pair['from']} → {pair['to']}:{project_state['items'][pair['from']]['assertion']}" for pair in migrated]
1584
+ project_state = apply_single(project_state, transaction_id, digest, move_out, summaries, store=project)
1585
+ write_snapshot(project, project_state)
1586
+ return result(migrated)
1587
+
1588
+
1589
+ def build_parser() -> argparse.ArgumentParser:
1590
+ parser = argparse.ArgumentParser(description=__doc__)
1591
+ subparsers = parser.add_subparsers(dest="command", required=True)
1592
+
1593
+ def add_location(child: argparse.ArgumentParser) -> None:
1594
+ child.add_argument("--workspace", required=True, type=Path)
1595
+ child.add_argument("--book-root", type=Path, help="书目录;书级条目(scope.level=book / BP 编号)的落点")
1596
+ child.add_argument("--book", help="书名;传了 --book-root 时默认取 store 记录的书名或目录名")
1597
+
1598
+ for command in ("init", "check", "migrate"):
1599
+ add_location(subparsers.add_parser(command))
1600
+ for command in ("commit", "record"):
1601
+ child = subparsers.add_parser(command)
1602
+ add_location(child)
1603
+ child.add_argument("--input", required=True, type=Path)
1604
+ query = subparsers.add_parser("query")
1605
+ add_location(query)
1606
+ query.add_argument("--kind", action="append", choices=KINDS)
1607
+ query.add_argument("--genre")
1608
+ query.add_argument("--workflow")
1609
+ return parser
1610
+
1611
+
1612
+ def main() -> int:
1613
+ args = build_parser().parse_args()
1614
+ try:
1615
+ if args.command == "init":
1616
+ result = command_init(args.workspace, args.book_root, args.book)
1617
+ elif args.command == "commit":
1618
+ result = command_commit(args.workspace, args.book_root, args.book, args.input)
1619
+ elif args.command == "record":
1620
+ result = command_record(args.workspace, args.book_root, args.book, args.input)
1621
+ elif args.command == "query":
1622
+ result = command_query(args.workspace, args.book_root, args.kind, args.book, args.genre, args.workflow)
1623
+ elif args.command == "migrate":
1624
+ result = command_migrate(args.workspace, args.book_root, args.book)
1625
+ else:
1626
+ result = command_check(args.workspace, args.book_root, args.book)
1627
+ emit(result)
1628
+ return 0
1629
+ except AuthorMemoryError as exc:
1630
+ emit({"ok": False, "error": str(exc)}, error=True)
1631
+ return 2
1632
+
1633
+
1634
+ if __name__ == "__main__":
1635
+ raise SystemExit(main())