add-coder 0.3.26 → 0.3.28

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 (323) hide show
  1. package/README.md +282 -155
  2. package/dist/index.js +481 -209
  3. package/package.json +15 -4
  4. package/templates/.add-coder-src-hash.json +186 -185
  5. package/templates/adapters/claude/hooks/doc-format-guard.ts +32 -0
  6. package/templates/adapters/claude/hooks/lib/claude-env.ts +7 -0
  7. package/templates/adapters/claude/hooks/notification.ts +24 -0
  8. package/templates/adapters/claude/hooks/permission-denied.ts +33 -0
  9. package/templates/adapters/claude/hooks/permission-gate.ts +12 -0
  10. package/templates/adapters/claude/hooks/post-tool-failure.ts +4 -0
  11. package/templates/adapters/claude/hooks/post-tool-use.ts +17 -0
  12. package/templates/adapters/claude/hooks/pre-compact.ts +10 -0
  13. package/templates/adapters/claude/hooks/pre-tool-use.ts +81 -0
  14. package/templates/adapters/claude/hooks/prompt-submit.ts +82 -0
  15. package/templates/adapters/claude/hooks/review-checklist.ts +4 -0
  16. package/templates/adapters/claude/hooks/session-end.ts +10 -0
  17. package/templates/adapters/claude/hooks/session-start.ts +24 -0
  18. package/templates/adapters/claude/hooks/stop-check.ts +15 -0
  19. package/templates/adapters/claude/hooks/stop-failure.ts +34 -0
  20. package/templates/adapters/claude/hooks/subagent-guard.ts +25 -0
  21. package/templates/adapters/claude/hooks/subagent-stop.ts +12 -0
  22. package/templates/adapters/claude/settings.json +13 -13
  23. package/templates/adapters/codex/config.toml.example +12 -0
  24. package/templates/adapters/codex/hooks/doc-format-guard.ts +30 -0
  25. package/templates/adapters/codex/hooks/notification.ts +26 -0
  26. package/templates/adapters/codex/hooks/permission-gate.ts +15 -0
  27. package/templates/adapters/codex/hooks/post-tool-failure.ts +14 -0
  28. package/templates/adapters/codex/hooks/post-tool-use.ts +85 -0
  29. package/templates/adapters/codex/hooks/pre-compact.ts +20 -0
  30. package/templates/adapters/codex/hooks/pre-tool-use.ts +141 -0
  31. package/templates/adapters/codex/hooks/prompt-submit.ts +38 -0
  32. package/templates/adapters/codex/hooks/review-checklist.ts +5 -0
  33. package/templates/adapters/codex/hooks/session-end.ts +19 -0
  34. package/templates/adapters/codex/hooks/session-start.ts +45 -0
  35. package/templates/adapters/codex/hooks/stop-check.ts +59 -0
  36. package/templates/adapters/codex/hooks/subagent-guard.ts +22 -0
  37. package/templates/adapters/codex/hooks/subagent-stop.ts +20 -0
  38. package/templates/adapters/codex/hooks.json +67 -1
  39. package/templates/adapters/qoder/hooks/doc-format-guard.ts +34 -0
  40. package/templates/adapters/qoder/hooks/lib/context-inject.ts +61 -0
  41. package/templates/adapters/qoder/hooks/lib/qoder-env.ts +23 -0
  42. package/templates/adapters/qoder/hooks/lib/review-checklist.ts +5 -0
  43. package/templates/adapters/qoder/hooks/notification.ts +34 -0
  44. package/templates/adapters/qoder/hooks/permission-gate.ts +26 -0
  45. package/templates/adapters/qoder/hooks/post-tool-failure.ts +30 -0
  46. package/templates/adapters/qoder/hooks/post-tool-use.ts +32 -0
  47. package/templates/adapters/qoder/hooks/pre-compact.ts +16 -0
  48. package/templates/adapters/qoder/hooks/pre-tool-use.ts +112 -0
  49. package/templates/adapters/qoder/hooks/prompt-submit.ts +66 -0
  50. package/templates/adapters/qoder/hooks/review-checklist.ts +24 -0
  51. package/templates/adapters/qoder/hooks/session-end.ts +27 -0
  52. package/templates/adapters/qoder/hooks/session-start.ts +83 -0
  53. package/templates/adapters/qoder/hooks/stop-check.ts +69 -0
  54. package/templates/adapters/qoder/hooks/subagent-guard.ts +16 -0
  55. package/templates/adapters/qoder/hooks/subagent-stop.ts +33 -0
  56. package/templates/adapters/qoder/settings.json +13 -13
  57. package/templates/adapters/trae/hooks/doc-format-guard.ts +30 -0
  58. package/templates/adapters/trae/hooks/notification.ts +22 -0
  59. package/templates/adapters/trae/hooks/permission-gate.ts +15 -0
  60. package/templates/adapters/trae/hooks/post-tool-failure.ts +14 -0
  61. package/templates/adapters/trae/hooks/post-tool-use.ts +24 -0
  62. package/templates/adapters/trae/hooks/pre-compact.ts +15 -0
  63. package/templates/adapters/trae/hooks/pre-tool-use.ts +40 -0
  64. package/templates/adapters/trae/hooks/prompt-submit.ts +29 -0
  65. package/templates/adapters/trae/hooks/review-checklist.ts +5 -0
  66. package/templates/adapters/trae/hooks/session-end.ts +16 -0
  67. package/templates/adapters/trae/hooks/session-start.ts +15 -0
  68. package/templates/adapters/trae/hooks/stop-check.ts +20 -0
  69. package/templates/adapters/trae/hooks/subagent-guard.ts +22 -0
  70. package/templates/adapters/trae/hooks/subagent-stop.ts +15 -0
  71. package/templates/adapters/trae/hooks.json +1 -1
  72. package/templates/adapters/vscode/hooks/doc-format-guard.ts +27 -0
  73. package/templates/adapters/vscode/hooks/notification.ts +22 -0
  74. package/templates/adapters/vscode/hooks/permission-gate.ts +15 -0
  75. package/templates/adapters/vscode/hooks/post-tool-failure.ts +32 -0
  76. package/templates/adapters/vscode/hooks/post-tool-use.ts +25 -0
  77. package/templates/adapters/vscode/hooks/pre-compact.ts +15 -0
  78. package/templates/adapters/vscode/hooks/pre-tool-use.ts +78 -0
  79. package/templates/adapters/vscode/hooks/prompt-submit.ts +74 -0
  80. package/templates/adapters/vscode/hooks/review-checklist.ts +5 -0
  81. package/templates/adapters/vscode/hooks/session-end.ts +16 -0
  82. package/templates/adapters/vscode/hooks/session-start.ts +24 -0
  83. package/templates/adapters/vscode/hooks/stop-check.ts +21 -0
  84. package/templates/adapters/vscode/hooks/subagent-guard.ts +21 -0
  85. package/templates/adapters/vscode/hooks/subagent-stop.ts +15 -0
  86. package/templates/core/docs/ADD-governance-claude-code.md +4 -4
  87. package/templates/core/docs/ADD-governance-codex.md +6 -6
  88. package/templates/core/docs/ADD-governance-qoder-cn.md +5 -5
  89. package/templates/core/docs/ADD-governance-trae.md +3 -3
  90. package/templates/core/docs/ADD-governance-vscode-copilot.md +22 -23
  91. package/templates/core/governance/audit-bridge.ts +47 -0
  92. package/templates/core/governance/common.ts +299 -0
  93. package/templates/core/governance/context-inject.ts +53 -0
  94. package/templates/core/governance/doc-format-guard.ts +440 -0
  95. package/templates/core/governance/notification-router.ts +62 -0
  96. package/templates/core/governance/notify.ts +66 -0
  97. package/templates/core/governance/permission-gate-router.ts +42 -0
  98. package/templates/core/governance/post-tool-failure-router.ts +56 -0
  99. package/templates/core/governance/post-tool-router.ts +212 -0
  100. package/templates/core/governance/pre-compact-guard.ts +41 -0
  101. package/templates/core/governance/pre-tool-guard.ts +309 -0
  102. package/templates/core/governance/preload-templates.ts +241 -0
  103. package/templates/core/governance/prompt-router.ts +222 -0
  104. package/templates/core/governance/review-checklist-guard.ts +186 -0
  105. package/templates/core/governance/rules.ts +720 -0
  106. package/templates/core/governance/session-end.ts +88 -0
  107. package/templates/core/governance/session-start-guard.ts +99 -0
  108. package/templates/core/governance/state-detect.ts +6 -0
  109. package/templates/core/governance/stop-router.ts +140 -0
  110. package/templates/core/governance/subagent-guard-router.ts +35 -0
  111. package/templates/core/governance/subagent-stop-router.ts +93 -0
  112. package/templates/core/governance/subagent-stop.ts +75 -0
  113. package/templates/core/governance/vocabulary.ts +109 -0
  114. package/templates/core/hooks/doc-format-guard.ts +21 -0
  115. package/templates/core/hooks/notification.ts +11 -0
  116. package/templates/core/hooks/permission-gate.ts +7 -0
  117. package/templates/core/hooks/post-tool-failure.ts +7 -0
  118. package/templates/core/hooks/post-tool-use.ts +23 -0
  119. package/templates/core/hooks/pre-compact.ts +9 -0
  120. package/templates/core/hooks/pre-tool-use.ts +30 -0
  121. package/templates/core/hooks/prompt-submit.ts +11 -0
  122. package/templates/core/hooks/review-checklist.ts +4 -0
  123. package/templates/core/hooks/session-end.ts +10 -0
  124. package/templates/core/hooks/session-start.ts +9 -0
  125. package/templates/core/hooks/stop-check.ts +14 -0
  126. package/templates/core/hooks/subagent-guard.ts +8 -0
  127. package/templates/core/hooks/subagent-stop.ts +9 -0
  128. package/templates/core/prisma/add.prisma +140 -82
  129. package/templates/core/rules/project_rules.md +27 -24
  130. package/templates/core/scripts/db-ensure.sh +8 -0
  131. package/templates/core/scripts/gen-plan-index.sh +4 -15
  132. package/templates/core/scripts/mcp-server/notifications/hitl.ts +9 -1
  133. package/templates/core/scripts/mcp-server/notifications/hook.ts +25 -9
  134. package/templates/core/scripts/mcp-server/resources/hitl-approval-widget.ts +34 -0
  135. package/templates/core/scripts/mcp-server/resources/hook-events-report.ts +14 -2
  136. package/templates/core/scripts/mcp-server/resources/index.ts +2 -0
  137. package/templates/core/scripts/mcp-server/sampling/review.ts +25 -10
  138. package/templates/core/scripts/mcp-server/shared/db-types.ts +33 -8
  139. package/templates/core/scripts/mcp-server/shared/dps-scoring.strategy.ts +1 -1
  140. package/templates/core/scripts/mcp-server/shared/env.ts +28 -8
  141. package/templates/core/scripts/mcp-server/shared/fs.ts +3 -0
  142. package/templates/core/scripts/mcp-server/shared/hitl-interaction.strategy.ts +2 -3
  143. package/templates/core/scripts/mcp-server/shared/hitl-lifecycle-mutation.ts +129 -0
  144. package/templates/core/scripts/mcp-server/shared/hitl-proposal-mutation.ts +98 -0
  145. package/templates/core/scripts/mcp-server/shared/hitl-ui.ts +3 -0
  146. package/templates/core/scripts/mcp-server/shared/plan-lifecycle-events.ts +62 -0
  147. package/templates/core/scripts/mcp-server/shared/plan-lifecycle-mutation.ts +78 -0
  148. package/templates/core/scripts/mcp-server/shared/plan-lifecycle-subscriber.ts +145 -0
  149. package/templates/core/scripts/mcp-server/shared/plan-lifecycle.ts +153 -0
  150. package/templates/core/scripts/mcp-server/shared/plan-round-events.ts +56 -0
  151. package/templates/core/scripts/mcp-server/shared/plan-round-mutation.ts +143 -0
  152. package/templates/core/scripts/mcp-server/shared/plan-round-store.ts +47 -0
  153. package/templates/core/scripts/mcp-server/shared/plan-round-subscriber.ts +99 -0
  154. package/templates/core/scripts/mcp-server/shared/plan-status-store.ts +37 -0
  155. package/templates/core/scripts/mcp-server/shared/plan-tracking-mutation.ts +71 -0
  156. package/templates/core/scripts/mcp-server/shared/prisma.ts +12 -6
  157. package/templates/core/scripts/mcp-server/shared/project-root-strategy.ts +1 -2
  158. package/templates/core/scripts/mcp-server/shared/quant-recheck.strategy.ts +16 -0
  159. package/templates/core/scripts/mcp-server/shared/redact.ts +28 -0
  160. package/templates/core/scripts/mcp-server/shared/response.ts +3 -2
  161. package/templates/core/scripts/mcp-server/shared/runtime-context.ts +77 -0
  162. package/templates/core/scripts/mcp-server/shared/schema-topology.ts +159 -0
  163. package/templates/core/scripts/mcp-server/tools/audit.ts +49 -7
  164. package/templates/core/scripts/mcp-server/tools/context.ts +117 -121
  165. package/templates/core/scripts/mcp-server/tools/contract.ts +34 -9
  166. package/templates/core/scripts/mcp-server/tools/gateway/check_add_route_status.ts +50 -3
  167. package/templates/core/scripts/mcp-server/tools/gateway/check_doc_similarity.ts +218 -0
  168. package/templates/core/scripts/mcp-server/tools/gateway/check_dps.ts +22 -7
  169. package/templates/core/scripts/mcp-server/tools/gateway/check_rahs.ts +86 -4
  170. package/templates/core/scripts/mcp-server/tools/gateway/check_spec_sync.ts +19 -6
  171. package/templates/core/scripts/mcp-server/tools/gateway/index.ts +2 -0
  172. package/templates/core/scripts/mcp-server/tools/hitl.ts +160 -75
  173. package/templates/core/scripts/mcp-server/tools/hook-event-report.ts +9 -2
  174. package/templates/core/scripts/mcp-server/tools/index.ts +69 -3
  175. package/templates/core/scripts/mcp-server/tools/plan.ts +152 -17
  176. package/templates/core/scripts/mcp-server/tools/review.ts +44 -15
  177. package/templates/core/scripts/mcp-server.ts +60 -1
  178. package/templates/core/scripts/plan-status-bridge.ts +26 -0
  179. package/templates/core/skills/add-paradigm/SKILL.md +2 -2
  180. package/templates/core/templates/add-route-template.schema.json +24 -56
  181. package/templates/core/templates/collab-contract-template.md +1 -1
  182. package/templates/core/templates/handoff-multi-round-template.schema.json +48 -0
  183. package/templates/core/templates/handoff-single-round-template.schema.json +92 -0
  184. package/templates/core/templates/handoff-single-round.schema.json +1 -1
  185. package/templates/core/templates/hitl-approval-widget.html +222 -37
  186. package/templates/core/templates/spec-template.schema.json +9 -46
  187. package/templates/core/templates/standard-plan-template.schema.json +1 -1
  188. package/templates/core/templates/tasks-template.schema.json +13 -54
  189. package/templates/adapters/claude/hooks/doc-format-guard.sh +0 -202
  190. package/templates/adapters/claude/hooks/lib/common.sh +0 -261
  191. package/templates/adapters/claude/hooks/lib/context-inject.sh +0 -96
  192. package/templates/adapters/claude/hooks/lib/notify.sh +0 -34
  193. package/templates/adapters/claude/hooks/lib/preload-templates.sh +0 -187
  194. package/templates/adapters/claude/hooks/lib/session-end.sh +0 -76
  195. package/templates/adapters/claude/hooks/lib/state-detect.sh +0 -117
  196. package/templates/adapters/claude/hooks/lib/subagent-stop.sh +0 -90
  197. package/templates/adapters/claude/hooks/lib/vocabulary.sh +0 -56
  198. package/templates/adapters/claude/hooks/notification.sh +0 -45
  199. package/templates/adapters/claude/hooks/permission-denied.sh +0 -42
  200. package/templates/adapters/claude/hooks/permission-gate.sh +0 -18
  201. package/templates/adapters/claude/hooks/post-tool-failure.sh +0 -10
  202. package/templates/adapters/claude/hooks/post-tool-use.sh +0 -35
  203. package/templates/adapters/claude/hooks/pre-compact.sh +0 -37
  204. package/templates/adapters/claude/hooks/pre-tool-use.sh +0 -153
  205. package/templates/adapters/claude/hooks/prompt-submit.sh +0 -99
  206. package/templates/adapters/claude/hooks/review-checklist.sh +0 -10
  207. package/templates/adapters/claude/hooks/session-end.sh +0 -21
  208. package/templates/adapters/claude/hooks/session-start.sh +0 -36
  209. package/templates/adapters/claude/hooks/stop-check.sh +0 -45
  210. package/templates/adapters/claude/hooks/stop-failure.sh +0 -44
  211. package/templates/adapters/claude/hooks/subagent-guard.sh +0 -36
  212. package/templates/adapters/claude/hooks/subagent-stop.sh +0 -20
  213. package/templates/adapters/codex/hooks/doc-format-guard.sh +0 -261
  214. package/templates/adapters/codex/hooks/lib/common.sh +0 -261
  215. package/templates/adapters/codex/hooks/lib/context-inject.sh +0 -96
  216. package/templates/adapters/codex/hooks/lib/notify.sh +0 -34
  217. package/templates/adapters/codex/hooks/lib/preload-templates.sh +0 -187
  218. package/templates/adapters/codex/hooks/lib/session-end.sh +0 -76
  219. package/templates/adapters/codex/hooks/lib/state-detect.sh +0 -117
  220. package/templates/adapters/codex/hooks/lib/subagent-stop.sh +0 -90
  221. package/templates/adapters/codex/hooks/lib/vocabulary.sh +0 -56
  222. package/templates/adapters/codex/hooks/notification.sh +0 -45
  223. package/templates/adapters/codex/hooks/permission-gate.sh +0 -18
  224. package/templates/adapters/codex/hooks/post-tool-failure.sh +0 -10
  225. package/templates/adapters/codex/hooks/post-tool-use.sh +0 -138
  226. package/templates/adapters/codex/hooks/pre-compact.sh +0 -37
  227. package/templates/adapters/codex/hooks/pre-tool-use.sh +0 -217
  228. package/templates/adapters/codex/hooks/prompt-submit.sh +0 -87
  229. package/templates/adapters/codex/hooks/review-checklist.sh +0 -10
  230. package/templates/adapters/codex/hooks/session-end.sh +0 -38
  231. package/templates/adapters/codex/hooks/session-start.sh +0 -53
  232. package/templates/adapters/codex/hooks/stop-check.sh +0 -45
  233. package/templates/adapters/codex/hooks/subagent-guard.sh +0 -15
  234. package/templates/adapters/codex/hooks/subagent-stop.sh +0 -47
  235. package/templates/adapters/qoder/hooks/doc-format-guard.sh +0 -260
  236. package/templates/adapters/qoder/hooks/lib/common.sh +0 -261
  237. package/templates/adapters/qoder/hooks/lib/context-inject.sh +0 -96
  238. package/templates/adapters/qoder/hooks/lib/notify.sh +0 -34
  239. package/templates/adapters/qoder/hooks/lib/preload-templates.sh +0 -187
  240. package/templates/adapters/qoder/hooks/lib/session-end.sh +0 -76
  241. package/templates/adapters/qoder/hooks/lib/state-detect.sh +0 -117
  242. package/templates/adapters/qoder/hooks/lib/subagent-stop.sh +0 -90
  243. package/templates/adapters/qoder/hooks/lib/vocabulary.sh +0 -56
  244. package/templates/adapters/qoder/hooks/notification.sh +0 -25
  245. package/templates/adapters/qoder/hooks/permission-gate.sh +0 -8
  246. package/templates/adapters/qoder/hooks/post-tool-failure.sh +0 -8
  247. package/templates/adapters/qoder/hooks/post-tool-use.sh +0 -121
  248. package/templates/adapters/qoder/hooks/pre-compact.sh +0 -37
  249. package/templates/adapters/qoder/hooks/pre-tool-use.sh +0 -154
  250. package/templates/adapters/qoder/hooks/prompt-submit.sh +0 -96
  251. package/templates/adapters/qoder/hooks/review-checklist.sh +0 -157
  252. package/templates/adapters/qoder/hooks/session-end.sh +0 -24
  253. package/templates/adapters/qoder/hooks/session-start.sh +0 -45
  254. package/templates/adapters/qoder/hooks/stop-check.sh +0 -48
  255. package/templates/adapters/qoder/hooks/subagent-guard.sh +0 -27
  256. package/templates/adapters/qoder/hooks/subagent-stop.sh +0 -26
  257. package/templates/adapters/trae/hooks/doc-format-guard.sh +0 -261
  258. package/templates/adapters/trae/hooks/lib/common.sh +0 -261
  259. package/templates/adapters/trae/hooks/lib/context-inject.sh +0 -96
  260. package/templates/adapters/trae/hooks/lib/notify.sh +0 -34
  261. package/templates/adapters/trae/hooks/lib/preload-templates.sh +0 -187
  262. package/templates/adapters/trae/hooks/lib/session-end.sh +0 -76
  263. package/templates/adapters/trae/hooks/lib/state-detect.sh +0 -117
  264. package/templates/adapters/trae/hooks/lib/subagent-stop.sh +0 -90
  265. package/templates/adapters/trae/hooks/lib/vocabulary.sh +0 -56
  266. package/templates/adapters/trae/hooks/notification.sh +0 -45
  267. package/templates/adapters/trae/hooks/permission-gate.sh +0 -18
  268. package/templates/adapters/trae/hooks/post-tool-failure.sh +0 -10
  269. package/templates/adapters/trae/hooks/post-tool-use.sh +0 -138
  270. package/templates/adapters/trae/hooks/pre-compact.sh +0 -37
  271. package/templates/adapters/trae/hooks/pre-tool-use.sh +0 -217
  272. package/templates/adapters/trae/hooks/prompt-submit.sh +0 -87
  273. package/templates/adapters/trae/hooks/review-checklist.sh +0 -10
  274. package/templates/adapters/trae/hooks/session-end.sh +0 -38
  275. package/templates/adapters/trae/hooks/session-start.sh +0 -53
  276. package/templates/adapters/trae/hooks/stop-check.sh +0 -45
  277. package/templates/adapters/trae/hooks/subagent-guard.sh +0 -15
  278. package/templates/adapters/trae/hooks/subagent-stop.sh +0 -47
  279. package/templates/adapters/vscode/hooks/doc-format-guard.sh +0 -201
  280. package/templates/adapters/vscode/hooks/lib/common.sh +0 -261
  281. package/templates/adapters/vscode/hooks/lib/context-inject.sh +0 -96
  282. package/templates/adapters/vscode/hooks/lib/notify.sh +0 -34
  283. package/templates/adapters/vscode/hooks/lib/preload-templates.sh +0 -187
  284. package/templates/adapters/vscode/hooks/lib/session-end.sh +0 -76
  285. package/templates/adapters/vscode/hooks/lib/state-detect.sh +0 -117
  286. package/templates/adapters/vscode/hooks/lib/subagent-stop.sh +0 -90
  287. package/templates/adapters/vscode/hooks/lib/vocabulary.sh +0 -56
  288. package/templates/adapters/vscode/hooks/notification.sh +0 -45
  289. package/templates/adapters/vscode/hooks/permission-gate.sh +0 -18
  290. package/templates/adapters/vscode/hooks/post-tool-failure.sh +0 -28
  291. package/templates/adapters/vscode/hooks/post-tool-use.sh +0 -35
  292. package/templates/adapters/vscode/hooks/pre-compact.sh +0 -37
  293. package/templates/adapters/vscode/hooks/pre-tool-use.sh +0 -153
  294. package/templates/adapters/vscode/hooks/prompt-submit.sh +0 -99
  295. package/templates/adapters/vscode/hooks/review-checklist.sh +0 -10
  296. package/templates/adapters/vscode/hooks/session-end.sh +0 -13
  297. package/templates/adapters/vscode/hooks/session-start.sh +0 -35
  298. package/templates/adapters/vscode/hooks/stop-check.sh +0 -53
  299. package/templates/adapters/vscode/hooks/subagent-guard.sh +0 -37
  300. package/templates/adapters/vscode/hooks/subagent-stop.sh +0 -12
  301. package/templates/core/hooks/doc-format-guard.sh +0 -261
  302. package/templates/core/hooks/lib/common.sh +0 -261
  303. package/templates/core/hooks/lib/context-inject.sh +0 -96
  304. package/templates/core/hooks/lib/notify.sh +0 -34
  305. package/templates/core/hooks/lib/preload-templates.sh +0 -187
  306. package/templates/core/hooks/lib/session-end.sh +0 -76
  307. package/templates/core/hooks/lib/state-detect.sh +0 -117
  308. package/templates/core/hooks/lib/subagent-stop.sh +0 -90
  309. package/templates/core/hooks/lib/vocabulary.sh +0 -56
  310. package/templates/core/hooks/notification.sh +0 -45
  311. package/templates/core/hooks/permission-gate.sh +0 -18
  312. package/templates/core/hooks/post-tool-failure.sh +0 -10
  313. package/templates/core/hooks/post-tool-use.sh +0 -138
  314. package/templates/core/hooks/pre-compact.sh +0 -37
  315. package/templates/core/hooks/pre-tool-use.sh +0 -217
  316. package/templates/core/hooks/prompt-submit.sh +0 -87
  317. package/templates/core/hooks/review-checklist.sh +0 -10
  318. package/templates/core/hooks/session-end.sh +0 -38
  319. package/templates/core/hooks/session-start.sh +0 -53
  320. package/templates/core/hooks/stop-check.sh +0 -45
  321. package/templates/core/hooks/subagent-guard.sh +0 -15
  322. package/templates/core/hooks/subagent-stop.sh +0 -47
  323. package/templates/shared/hooks-lib/common.sh +0 -22
@@ -0,0 +1,159 @@
1
+ import { readdir, readFile } from "fs/promises"
2
+ import { basename, join, relative } from "path"
3
+
4
+ export type SchemaView = "business" | "add" | "all"
5
+ export type SchemaTopologyMode = "single" | "split"
6
+ export type SchemaBoundary = "shared" | "business" | "add"
7
+
8
+ export interface PrismaDeclaration {
9
+ kind: "model" | "enum"
10
+ name: string
11
+ sourcePath: string
12
+ sourceFile: string
13
+ boundary: SchemaBoundary
14
+ body: string
15
+ fieldCount: number
16
+ }
17
+
18
+ export interface PrismaSchemaFile {
19
+ path: string
20
+ file: string
21
+ boundary: SchemaBoundary
22
+ declarations: PrismaDeclaration[]
23
+ }
24
+
25
+ export interface PrismaSchemaTopology {
26
+ mode: SchemaTopologyMode
27
+ view: SchemaView
28
+ schemaRoot: string
29
+ files: PrismaSchemaFile[]
30
+ models: PrismaDeclaration[]
31
+ enums: PrismaDeclaration[]
32
+ }
33
+
34
+ function extractBlock(source: string, braceIndex: number): string {
35
+ let depth = 1
36
+ let cursor = braceIndex + 1
37
+ while (depth > 0 && cursor < source.length) {
38
+ if (source[cursor] === "{") depth += 1
39
+ if (source[cursor] === "}") depth -= 1
40
+ cursor += 1
41
+ }
42
+ if (depth !== 0) throw new Error("Prisma declaration 花括号未闭合")
43
+ return source.slice(braceIndex, cursor)
44
+ }
45
+
46
+ export function parsePrismaDeclarations(
47
+ source: string,
48
+ sourcePath: string,
49
+ boundary: SchemaBoundary,
50
+ ): PrismaDeclaration[] {
51
+ const declarations: PrismaDeclaration[] = []
52
+ const declarationRegex = /^\s*(model|enum)\s+(\w+)\s*\{/gm
53
+ let match: RegExpExecArray | null
54
+ while ((match = declarationRegex.exec(source)) !== null) {
55
+ const braceIndex = source.indexOf("{", match.index)
56
+ const body = extractBlock(source, braceIndex)
57
+ const kind = match[1] as "model" | "enum"
58
+ declarations.push({
59
+ kind,
60
+ name: match[2],
61
+ sourcePath,
62
+ sourceFile: basename(sourcePath),
63
+ boundary,
64
+ body,
65
+ fieldCount: kind === "model"
66
+ ? body.split("\n").filter(line => {
67
+ const trimmed = line.trim()
68
+ return trimmed.length > 0 && trimmed !== "{" && trimmed !== "}" && !trimmed.startsWith("//") && !trimmed.startsWith("@@")
69
+ }).length
70
+ : 0,
71
+ })
72
+ }
73
+ return declarations
74
+ }
75
+
76
+ function boundaryFor(file: string, mode: SchemaTopologyMode): SchemaBoundary {
77
+ if (mode === "single") return "shared"
78
+ return file === "add.prisma" ? "add" : "business"
79
+ }
80
+
81
+ function includedInView(file: string, view: SchemaView): boolean {
82
+ if (view === "all") return true
83
+ return view === "add" ? file === "add.prisma" : file !== "add.prisma"
84
+ }
85
+
86
+ export async function loadPrismaSchemaTopology(input: {
87
+ projectRoot: string
88
+ splitDatabase: boolean
89
+ view?: SchemaView
90
+ }): Promise<PrismaSchemaTopology> {
91
+ const schemaRoot = join(input.projectRoot, "prisma")
92
+ const mode: SchemaTopologyMode = input.splitDatabase ? "split" : "single"
93
+ const view = input.view ?? "all"
94
+ let names: string[]
95
+ try {
96
+ names = (await readdir(schemaRoot)).filter(file => file.endsWith(".prisma")).sort()
97
+ } catch {
98
+ throw new Error(`未找到 Prisma schema 目录: ${schemaRoot}`)
99
+ }
100
+ if (names.length === 0) throw new Error(`Prisma schema 目录中没有 *.prisma: ${schemaRoot}`)
101
+
102
+ const selected = names.filter(file => includedInView(file, view))
103
+ if (selected.length === 0) {
104
+ throw new Error(`Schema view=${view} 没有可扫描文件;mode=${mode};全部文件=${names.join(", ")}`)
105
+ }
106
+ const files: PrismaSchemaFile[] = []
107
+ for (const file of selected) {
108
+ const path = join(schemaRoot, file)
109
+ const boundary = boundaryFor(file, mode)
110
+ const source = await readFile(path, "utf8")
111
+ files.push({ path, file, boundary, declarations: parsePrismaDeclarations(source, path, boundary) })
112
+ }
113
+ const declarations = files.flatMap(file => file.declarations)
114
+ const models = declarations.filter(item => item.kind === "model")
115
+ const enums = declarations.filter(item => item.kind === "enum")
116
+ if (models.length === 0) {
117
+ throw new Error(
118
+ `Schema 诊断: 检测到 ${files.length} 个 Prisma 文件但 view=${view} 解析为 0 个模型;` +
119
+ `mode=${mode};扫描=${files.map(file => relative(input.projectRoot, file.path)).join(", ")}`,
120
+ )
121
+ }
122
+
123
+ const byName = new Map<string, PrismaDeclaration[]>()
124
+ for (const model of models) {
125
+ const key = model.name.toLowerCase()
126
+ byName.set(key, [...(byName.get(key) ?? []), model])
127
+ }
128
+ const conflicts = [...byName.entries()].filter(([, entries]) => entries.length > 1)
129
+ if (conflicts.length > 0) {
130
+ const details = conflicts.map(([name, entries]) =>
131
+ `${name}: ${entries.map(entry => `${relative(input.projectRoot, entry.sourcePath)}[${entry.boundary}]`).join(", ")}`,
132
+ )
133
+ throw new Error(`Schema 模型冲突(fail closed): ${details.join("; ")}`)
134
+ }
135
+
136
+ return { mode, view, schemaRoot, files, models, enums }
137
+ }
138
+
139
+ export function formatSchemaTopology(topology: PrismaSchemaTopology, projectRoot: string): string {
140
+ const lines = [
141
+ "=== Prisma Schema 拓扑 ===",
142
+ `mode: ${topology.mode}`,
143
+ `view: ${topology.view}`,
144
+ `databaseBoundaries: ${topology.mode === "single" ? "shared (business + ADD 同库)" : "business, add (分库;all 仅汇总展示,不代表可跨库 join)"}`,
145
+ `scannedFiles: ${topology.files.length}`,
146
+ ]
147
+ for (const file of topology.files) {
148
+ lines.push(` - ${relative(projectRoot, file.path)} [${file.boundary}] models=${file.declarations.filter(item => item.kind === "model").length}`)
149
+ }
150
+ lines.push("", `模型 (${topology.models.length} 个):`)
151
+ for (const model of topology.models) {
152
+ lines.push(` ${model.name} (${model.fieldCount} 字段) — ${relative(projectRoot, model.sourcePath)} [${model.boundary}]`)
153
+ }
154
+ if (topology.enums.length > 0) {
155
+ lines.push("", `枚举 (${topology.enums.length} 个):`)
156
+ for (const item of topology.enums) lines.push(` ${item.name} — ${relative(projectRoot, item.sourcePath)} [${item.boundary}]`)
157
+ }
158
+ return lines.join("\n")
159
+ }
@@ -1,12 +1,14 @@
1
1
  import * as z from "zod/v4"
2
+ import { createHash } from "crypto"
2
3
  import type { ToolRegistrar } from "./registrar.js"
3
4
  import { textResponse, errorResponse } from "../shared/response.js"
4
5
  import { prisma } from "../shared/prisma.js"
5
- import { PROJECT_ID, PROJECT_ROOT } from "../shared/env.js"
6
+ import { PROJECT_ID, PROJECT_ROOT, getRuntimeContext } from "../shared/env.js"
6
7
  import type { AddUserRow, DevOperationRow } from "../shared/db-types.js"
7
8
  import { AddUserRowSchema, DevOperationRowSchema, validatedDelegate } from "../shared/db-types.js"
8
9
 
9
10
  export function registerAuditTools(server: ToolRegistrar) {
11
+ const runtimeContext = getRuntimeContext()
10
12
 
11
13
  // 无类型边界单点(zod 托管):动态 client → 运行期校验的泛型委托
12
14
  const devDb = validatedDelegate<DevOperationRow>(prisma.devOperation, DevOperationRowSchema, "DevOperation")
@@ -30,7 +32,10 @@ export function registerAuditTools(server: ToolRegistrar) {
30
32
  }, async (args: Record<string, string | number | undefined>, _ctx: unknown) => {
31
33
  try {
32
34
  const { targetType, action, targetId, planKeyword, keyword, sinceMinutes, limit = 20 } = args
33
- const where: Record<string, unknown> = {}
35
+ const where: Record<string, unknown> = {
36
+ projectKey: runtimeContext.projectKey,
37
+ producerAdapterKey: runtimeContext.adapterKey,
38
+ }
34
39
  if (sinceMinutes !== undefined) where.createdAt = { gte: new Date(Date.now() - Number(sinceMinutes) * 60 * 1000) }
35
40
  if (targetType) where.targetType = s(targetType); if (action) where.action = s(action); if (targetId) where.targetId = s(targetId); if (planKeyword) where.planKeyword = s(planKeyword)
36
41
  const kw = s(keyword)
@@ -44,6 +49,7 @@ export function registerAuditTools(server: ToolRegistrar) {
44
49
  const lines = [`=== 开发操作审计日志 (条件: ${fl.length > 0 ? fl.join(", ") : "无过滤条件(最近全部)"}) ===`, `服务项目: ${PROJECT_ID} (${PROJECT_ROOT})`, `共 ${logs.length} 条记录`]
45
50
  for (let i = 0; i < logs.length; i++) {
46
51
  const l = logs[i]; lines.push(`[${i+1}] ${l.createdAt.toISOString()}`, ` action: ${l.action} | targetType: ${l.targetType} | targetId: ${l.targetId || "(无)"}`)
52
+ lines.push(` beforeState: ${JSON.stringify(l.beforeState)}`, ` afterState: ${JSON.stringify(l.afterState)}`)
47
53
  if (l.reason) lines.push(` reason: ${l.reason}`); if (l.planKeyword) lines.push(` planKeyword: ${l.planKeyword}`)
48
54
  }
49
55
  if (planKeyword) { lines.push("=== Plan 分组 ===", `planKeyword=${planKeyword} 的操作链:`); for (const l of logs) lines.push(` ${l.createdAt.toISOString().slice(11,19)} ${l.action}`) }
@@ -60,22 +66,58 @@ export function registerAuditTools(server: ToolRegistrar) {
60
66
  targetType: z.string().describe("目标类型: 'API_ROUTE', 'COMPONENT', 'SCHEMA', 'RULE', 'DOC', 'PLAN', 'SPEC', 'SKILL', 'AGENT' 等"),
61
67
  targetId: z.string().optional().describe("目标标识(相对路径)"),
62
68
  planKeyword: z.string().optional().describe("关联 Plan 的关键词"),
63
- beforeState: z.string().describe("操作前的状态(JSON 字符串)。必填,记录变更前的文件内容或配置状态,用于审计回溯。"),
64
- afterState: z.string().describe("操作后的状态(JSON 字符串)。必填,记录变更后的文件内容或配置状态,用于审计回溯。"),
69
+ beforeState: z.string().describe("操作前的状态(非 null JSON 对象或数组字符串)。必填,用于审计回溯。"),
70
+ afterState: z.string().describe("操作后的状态(非 null JSON 对象或数组字符串)。必填,用于审计回溯。"),
65
71
  reason: z.string().optional().describe("操作原因"),
72
+ operationKey: z.string().optional().describe("调用方提供的幂等键;未提供时按本次结构化操作内容计算"),
66
73
  }),
67
74
  }, async (args: Record<string, string | number | undefined>, _ctx: unknown) => {
68
75
  try {
69
- const { action, targetType, targetId, planKeyword, beforeState, afterState, reason } = args
76
+ const { action, targetType, targetId, planKeyword, beforeState, afterState, reason, operationKey } = args
70
77
  const tId = s(targetId)
71
78
  const pathWarnings: string[] = []
72
79
  if (tId && (tId.startsWith("/") || /^[A-Z]:\\/.test(tId))) pathWarnings.push(`⚠️ targetId 使用了绝对路径: "${tId}"。请使用相对路径。`)
80
+ if (!s(beforeState).trim() || !s(afterState).trim()) return errorResponse("beforeState/afterState 必须提供非空 JSON 字符串。")
73
81
  let parsedBefore: unknown, parsedAfter: unknown
74
82
  try { if (beforeState) parsedBefore = JSON.parse(s(beforeState)); if (afterState) parsedAfter = JSON.parse(s(afterState)) } catch { return errorResponse("beforeState/afterState 必须是有效的 JSON 字符串。") }
83
+ const isStructuredState = (value: unknown): value is Record<string, unknown> | unknown[] => typeof value === "object" && value !== null
84
+ if (!isStructuredState(parsedBefore) || !isStructuredState(parsedAfter)) return errorResponse("beforeState/afterState 必须是非 null 的 JSON 对象或数组。")
75
85
  let systemUser = await userDb.findUnique({ where: { username: "ai-assistant" }, select: { id: true } })
76
86
  if (!systemUser) systemUser = await userDb.create({ data: { id: "ai-assistant", username: "ai-assistant", email: "ai-assistant@internal" } })
77
- const log = await devDb.create({ data: { userId: systemUser.id, planKeyword: s(planKeyword) || "unknown", action: s(action), targetType: s(targetType), targetId: tId || "unknown", beforeState: parsedBefore ?? null, afterState: parsedAfter ?? null, reason: s(reason) || null } })
78
- const lines = [`✅ 开发操作已记录`, ` 落库项目: ${PROJECT_ID} (${PROJECT_ROOT})`, ` ID: ${log.id}`, ` action: ${s(action)}`, ` targetType: ${s(targetType)}`, ` targetId: ${tId || "unknown"}`, ` planKeyword: ${s(planKeyword) || "unknown"}`, ` createdAt: ${log.createdAt.toISOString()}`]
87
+ const operationPayload = {
88
+ projectKey: runtimeContext.projectKey,
89
+ producerAdapterKey: runtimeContext.adapterKey,
90
+ action: s(action),
91
+ targetType: s(targetType),
92
+ targetId: tId || "unknown",
93
+ planKeyword: s(planKeyword) || "unknown",
94
+ beforeState: parsedBefore,
95
+ afterState: parsedAfter,
96
+ reason: s(reason) || null,
97
+ }
98
+ const resolvedOperationKey = s(operationKey).trim() || createHash("sha256")
99
+ .update(JSON.stringify(operationPayload))
100
+ .digest("hex")
101
+ const scopedData: Partial<DevOperationRow> = {
102
+ ...operationPayload,
103
+ userId: systemUser.id,
104
+ contextId: runtimeContext.contextId,
105
+ toolName: "record_dev_operation",
106
+ operationKey: resolvedOperationKey,
107
+ }
108
+ const log = await devDb.upsert({
109
+ where: {
110
+ projectKey_producerAdapterKey_toolName_operationKey: {
111
+ projectKey: runtimeContext.projectKey,
112
+ producerAdapterKey: runtimeContext.adapterKey,
113
+ toolName: "record_dev_operation",
114
+ operationKey: resolvedOperationKey,
115
+ },
116
+ },
117
+ create: scopedData,
118
+ update: {},
119
+ })
120
+ const lines = [`✅ 开发操作已记录`, ` 落库项目: ${PROJECT_ID} (${PROJECT_ROOT})`, ` ID: ${log.id}`, ` action: ${s(action)}`, ` targetType: ${s(targetType)}`, ` targetId: ${tId || "unknown"}`, ` planKeyword: ${s(planKeyword) || "unknown"}`, ` beforeState: ${JSON.stringify(log.beforeState)}`, ` afterState: ${JSON.stringify(log.afterState)}`, ` createdAt: ${log.createdAt.toISOString()}`]
79
121
  if (pathWarnings.length > 0) { lines.push(""); lines.push(...pathWarnings) }
80
122
  lines.push("", `📋 落库回查(必须执行):`, tId ? ` query_audit_logs({ targetId: "${tId}" }) — 确认本条记录已写入` : "")
81
123
  return textResponse(lines.join("\n"))
@@ -6,13 +6,23 @@ import { stat } from "fs/promises"
6
6
  import { existsSync } from "fs"
7
7
  import { join, relative } from "path"
8
8
  import { textResponse, errorResponse } from "../shared/response.js"
9
- import { readFileSafe, readdirRecursive } from "../shared/fs.js"
9
+ import { readFileSafe } from "../shared/fs.js"
10
10
  import { PROJECT_ROOT, MAGIC_DIR } from "../shared/fs.js"
11
11
  import { prisma } from "../shared/prisma.js"
12
- import type { PlanRow } from "../shared/db-types.js"
13
- import { PlanRowSchema, validatedDelegate } from "../shared/db-types.js"
12
+ import type { PlanRow, ReviewRow } from "../shared/db-types.js"
13
+ import { PlanRowSchema, ReviewRowSchema, validatedDelegate } from "../shared/db-types.js"
14
+ import { getRuntimeContext } from "../shared/env.js"
15
+ import { resolvePlanStatus } from "../shared/plan-lifecycle.js"
16
+ import { createPrismaPlanStatusStore } from "../shared/plan-status-store.js"
17
+ import { assertPathInRuntimeScope } from "../shared/runtime-context.js"
18
+ import {
19
+ formatSchemaTopology,
20
+ loadPrismaSchemaTopology,
21
+ type SchemaView,
22
+ } from "../shared/schema-topology.js"
14
23
 
15
24
  export function registerContextTools(server: ToolRegistrar) {
25
+ const runtimeContext = getRuntimeContext()
16
26
 
17
27
  // ===== get_project_context (L166-430) =====
18
28
  server.registerTool(
@@ -91,88 +101,73 @@ export function registerContextTools(server: ToolRegistrar) {
91
101
 
92
102
  if (scope === "all" || scope === "structure" || scope === "add-state") {
93
103
  parts.push("=== ADD 工作流状态 ===")
94
- const plansDir = join(PROJECT_ROOT, MAGIC_DIR, "plans")
95
- const reviewsDir = join(PROJECT_ROOT, MAGIC_DIR, "reviews")
96
- const specsDir = join(PROJECT_ROOT, MAGIC_DIR, "specs")
104
+ parts.push(`contextId: ${runtimeContext.contextId}`)
105
+ parts.push("source: database")
97
106
 
98
- let activePlan = ""
99
- let activePlanPath = ""
100
- if (existsSync(plansDir)) {
101
- const planFiles = (await readdirRecursive(plansDir))
102
- .filter(f => f.endsWith(".md") && !f.includes("add-route") && !f.includes("handoff"))
103
- .sort()
104
- if (planFiles.length > 0) {
105
- activePlan = planFiles[planFiles.length - 1]
106
- activePlanPath = join(plansDir, activePlan)
107
- parts.push(`最近 Plan: ${activePlan}`)
108
- const allPlanFiles = await readdirRecursive(plansDir)
109
- const addRouteFile = allPlanFiles.find(f => f.includes("add-route"))
110
- const planKeyword = activePlan.replace(/-plan-v\d+\.md$/, "")
111
- const handoffFiles = allPlanFiles.filter(f => f.includes("handoff"))
112
- const hasHandoff = handoffFiles.some(f => f.toLowerCase().includes(planKeyword.toLowerCase()))
113
- parts.push(` add-route: ${addRouteFile ? "✅ " + addRouteFile : "❌ 缺失(需回退 Step 0.5)"}`)
114
- parts.push(` handoff: ${hasHandoff ? "✅ 已生成" : "❌ 缺失(需回退 Step 0.5)"}`)
115
- } else { parts.push("最近 Plan: 无") }
116
- } else { parts.push("最近 Plan: 无") }
117
-
118
- let reviewFileName = ""; let reviewP0Count = 0; let reviewP1Count = 0; let reviewBackflowRate = 0
119
- if (existsSync(reviewsDir) && activePlan) {
120
- const reviewFiles = await readdir(reviewsDir)
121
- const planKeyword = activePlan.replace(/-plan-v\d+\.md$/, "")
122
- const matchingReview = reviewFiles.find(f => f.toLowerCase().includes(planKeyword.toLowerCase()) && f.includes("-review-v"))
123
- || reviewFiles.find(f => f.toLowerCase().includes(planKeyword.toLowerCase()))
124
- if (matchingReview) {
125
- reviewFileName = matchingReview
126
- const reviewContent = await readFileSafe(join(reviewsDir, matchingReview)) || ""
127
- const reviewLines = reviewContent.split("\n")
128
- let inP0 = false, inP1 = false
129
- for (const line of reviewLines) {
130
- if (line.match(/P0|ADD.*合规|阻断/)) { inP0 = true; inP1 = false; continue }
131
- if (line.match(/P1|架构设计.*缺口/)) { inP0 = false; inP1 = true; continue }
132
- if (line.match(/P2|中等|影响评估|决策结论|方案对比/)) { inP0 = false; inP1 = false; continue }
133
- if ((inP0 || inP1) && line.trim().startsWith("|") && !line.includes("---")) {
134
- const cols = line.split("|").map(c => c.trim()).filter(Boolean)
135
- if (cols.length >= 4 && cols[0].match(/^\d+$/)) {
136
- if (inP0) reviewP0Count++; else if (inP1) reviewP1Count++
137
- }
138
- }
139
- }
140
- const planContent = await readFileSafe(activePlanPath) || ""
141
- if ((reviewP0Count + reviewP1Count) > 0 && planContent) {
142
- const indicators = ["add-route", "specs/", "handoff", "Task Group", "perExpertTopK", "agri_tech", "ChromaCollectionManager", "迁移路线图", "8 个 Expert", "冗余策略"]
143
- let hit = 0
144
- for (const ind of indicators) { if (planContent.toLowerCase().includes(ind.toLowerCase())) hit++ }
145
- reviewBackflowRate = Math.min(100, Math.round((hit / Math.max(reviewP0Count + reviewP1Count, 1)) * 100))
146
- }
147
- parts.push(""); parts.push(`关联 Review: ${matchingReview}`)
148
- parts.push(` P0 问题: ${reviewP0Count} 个, P1 问题: ${reviewP1Count} 个`)
149
- parts.push(` 回流状态: 约 ${reviewBackflowRate}%`)
150
- parts.push(reviewBackflowRate < 70 ? " ⚠️ Review 结论未充分回流至 Plan — 需执行 0.6.5 卡位" : " ✅ Review 结论基本回流至 Plan")
151
- } else { parts.push(""); parts.push("关联 Review: 无(该 Plan 尚未生成方案评审)") }
152
- }
153
-
154
- if (activePlan) {
155
- const planKeyword = activePlan.replace(/-plan-v\d+\.md$/, "")
156
- const specDirs = existsSync(specsDir) ? await readdir(specsDir) : []
157
- const matchingSpec = specDirs.find(d => d.toLowerCase().includes(planKeyword.toLowerCase()))
107
+ const planDb = validatedDelegate<PlanRow>(prisma.planRecord, PlanRowSchema, "PlanRecord")
108
+ const reviewDb = validatedDelegate<ReviewRow>(prisma.reviewRecord, ReviewRowSchema, "ReviewRecord")
109
+ const resolution = await resolvePlanStatus(
110
+ createPrismaPlanStatusStore(prisma),
111
+ runtimeContext,
112
+ { activeOnly: true },
113
+ )
114
+ let activePlan: PlanRow | null = null
115
+ let reviewRows: ReviewRow[] = []
116
+ if (resolution.availability === "STATUS_UNAVAILABLE") {
117
+ parts.push(`状态: STATUS_UNAVAILABLE ${resolution.reason}`)
118
+ parts.push("裁决: fail closed;未回退 Plan/Handoff/add-route 文件猜测")
119
+ } else if (resolution.planName === null) {
120
+ parts.push("活跃 Plan: 无(DB lifecycle ACTIVE/BLOCKED)")
121
+ } else {
122
+ activePlan = await planDb.findFirst({
123
+ where: {
124
+ projectKey: runtimeContext.projectKey,
125
+ adapterKey: runtimeContext.adapterKey,
126
+ planName: resolution.planName,
127
+ },
128
+ })
129
+ if (!activePlan) throw new Error(`resolver 返回 Plan 后记录消失: ${resolution.planName}`)
130
+ assertPathInRuntimeScope(runtimeContext, activePlan.planPath)
131
+ parts.push(`活跃 Plan: ${activePlan.planName}`)
132
+ parts.push(` lifecycle: ${resolution.lifecycle} | revision: ${resolution.revision}`)
133
+ parts.push(` approval: ${resolution.approvalStatus ?? "—"}`)
134
+ parts.push(` tasks: ${resolution.progress.doneTasks}/${resolution.progress.totalTasks}`)
135
+ parts.push(` Plan: ${activePlan.planPath}`)
136
+ parts.push(` add-route: ${activePlan.addRoutePath && existsSync(activePlan.addRoutePath) ? `✅ ${activePlan.addRoutePath}` : "❌ 缺失"}`)
137
+ parts.push(" handoff: 不参与 active 裁决(仅 Step 8 交接产物)")
138
+ parts.push("")
139
+ parts.push("Specs:")
140
+ parts.push(` spec.md: ${activePlan.specPath && existsSync(activePlan.specPath) ? "✅" : "❌"}`)
141
+ parts.push(` tasks.md: ${activePlan.tasksPath && existsSync(activePlan.tasksPath) ? "✅" : "❌"}`)
142
+ parts.push(` checklist.md: ${activePlan.checklistPath && existsSync(activePlan.checklistPath) ? "" : ""}`)
143
+ reviewRows = await reviewDb.findMany({
144
+ where: {
145
+ projectKey: runtimeContext.projectKey,
146
+ adapterKey: runtimeContext.adapterKey,
147
+ planName: activePlan.planName,
148
+ },
149
+ orderBy: { updatedAt: "desc" },
150
+ })
158
151
  parts.push("")
159
- if (matchingSpec) {
160
- const hasSpec = existsSync(join(specsDir, matchingSpec, "spec.md"))
161
- const hasTasks = existsSync(join(specsDir, matchingSpec, "tasks.md"))
162
- const hasChecklist = existsSync(join(specsDir, matchingSpec, "checklist.md"))
163
- parts.push(`Specs: ${matchingSpec}/`)
164
- parts.push(` spec.md: ${hasSpec ? "✅" : "❌"}`)
165
- parts.push(` tasks.md: ${hasTasks ? "✅" : "❌"}`)
166
- parts.push(` checklist.md: ${hasChecklist ? "✅" : "❌"}`)
167
- } else { parts.push("Specs: ❌ 缺失") }
152
+ if (reviewRows.length === 0) {
153
+ parts.push("关联 Review: ")
154
+ } else {
155
+ parts.push(`关联 Review: ${reviewRows.length} 条(DB scoped)`)
156
+ for (const review of reviewRows) {
157
+ parts.push(` ${review.type}: P0=${review.p0Count}, P1=${review.p1Count}, backflow=${review.backflowRate}`)
158
+ }
159
+ }
168
160
  }
169
161
 
170
162
  parts.push(""); parts.push("=== 悬空 Plan 对账(记录在、文件缺失) ===")
171
- // 孤儿可见性:create_hitl 预置行被中断 / 放弃未清理 → 开局即提示,不静默累积
172
163
  try {
173
- // 无类型边界单点(zod 托管):动态 client → 运行期校验的泛型委托
174
- const planDb = validatedDelegate<PlanRow>(prisma.planRecord, PlanRowSchema, "PlanRecord")
175
- const allPlans = await planDb.findMany({ orderBy: { createdAt: "desc" } })
164
+ const allPlans = await planDb.findMany({
165
+ where: {
166
+ projectKey: runtimeContext.projectKey,
167
+ adapterKey: runtimeContext.adapterKey,
168
+ },
169
+ orderBy: { createdAt: "desc" },
170
+ })
176
171
  const orphans = allPlans.filter(p => !existsSync(p.planPath))
177
172
  if (orphans.length > 0) {
178
173
  const cutoff = Date.now() - 14 * 86400000
@@ -185,20 +180,19 @@ export function registerContextTools(server: ToolRegistrar) {
185
180
 
186
181
  parts.push(""); parts.push("=== 待执行 ADD 操作(按流程顺序) ===")
187
182
  const todoItems: string[] = []
188
- if (!activePlan) { todoItems.push("1. [未开始] 用户提出需求 → 生成 Plan") }
189
- else {
190
- if (!reviewFileName) { todoItems.push("1. [Step 0] 生成 Plan Review(ADD-9 方案评审)") }
191
- else if (reviewP0Count + reviewP1Count > 0 && reviewBackflowRate < 70) {
192
- todoItems.push("1. [0.6.5] Review 结论回流至 Plan — P0/P1 未写入 Plan 体")
193
- todoItems.push("2. [check_dps] DPS 门禁预计不通过(回流完整度 < 70%)")
183
+ if (resolution.availability === "STATUS_UNAVAILABLE") {
184
+ todoItems.push("1. [阻断] 恢复 DB/MCP resolver;禁止按文件继续")
185
+ } else if (!activePlan) { todoItems.push("1. [未开始] 用户提出需求 → 生成 Plan + HITL") }
186
+ else if (resolution.planName !== null) {
187
+ if (resolution.approvalStatus !== "TONGYI") todoItems.push("1. [HITL] 完成当前 Plan 审批")
188
+ if (reviewRows.length === 0) todoItems.push("2. [Step 0] 生成并追踪 Plan Review")
189
+ if (reviewRows.some(review => review.p0Count + review.p1Count > 0 && review.backflowRate === 0)) {
190
+ todoItems.push("3. [0.6.5] Review P0/P1 回流至 Plan")
194
191
  }
195
- const allPlanDir = await readdirRecursive(plansDir)
196
- const hasAddRoute = allPlanDir.some(f => f.includes("add-route"))
197
- if (!hasAddRoute && reviewBackflowRate >= 70) { todoItems.push("2. [Step 0.5] 生成 add-route") }
198
- const planKw = activePlan.replace(/-plan-v\d+\.md$/, "")
199
- const sd = existsSync(specsDir) ? await readdir(specsDir) : []
200
- if (!sd.some(d => d.toLowerCase().includes(planKw.toLowerCase())) && reviewBackflowRate >= 70) { todoItems.push("3. [Step 0] 生成 Specs 三元组") }
201
- if (todoItems.length === 0) { todoItems.push("✅ ADD 就绪 — check_dps ≥ {{dpsPass}} 后可进入 Step 1") }
192
+ if (!activePlan.addRoutePath || !existsSync(activePlan.addRoutePath)) todoItems.push("4. [Step 0.5] 生成 add-route")
193
+ if (!activePlan.specPath || !activePlan.tasksPath || !activePlan.checklistPath) todoItems.push("5. [Step 0] 补齐 Specs 三元组")
194
+ if (activePlan.doneTasks < activePlan.totalTasks) todoItems.push(`6. [实施] 继续未完成 Task(${activePlan.doneTasks}/${activePlan.totalTasks})`)
195
+ if (todoItems.length === 0) todoItems.push("✅ DB lifecycle 与文档链已就绪,可进入 Review/closure")
202
196
  }
203
197
  for (const item of todoItems) parts.push(item)
204
198
  parts.push(""); parts.push("快速指令: 说「执行 add-paradigm Step 0」进入文档先行流程")
@@ -243,39 +237,41 @@ export function registerContextTools(server: ToolRegistrar) {
243
237
  server.registerTool(
244
238
  "get_db_schema",
245
239
  {
246
- description: "获取 Prisma 数据库 Schema 定义(MCP-1 上下文优先)。返回指定模型的结构、字段、关系。AI 助手在编写数据库查询代码时应调用此工具获取真实的 Schema 信息,避免凭记忆假设。",
247
- inputSchema: z.object({ model: z.string().optional().describe("可选的模型名称(不区分大小写)。不指定则返回所有模型概况。指定则返回该模型的完整字段定义。") }),
240
+ description: "获取 topology-aware Prisma Schema。兼容单库多文件 schema folder ADD_DATABASE_URL 分库;返回扫描文件、数据库边界、模型来源和诊断。",
241
+ inputSchema: z.object({
242
+ model: z.string().optional().describe("可选模型或枚举名称(不区分大小写)"),
243
+ view: z.enum(["business", "add", "all"]).optional().default("all").describe("Schema 视图;分库时 all 仅分组汇总,不表示可跨库 join"),
244
+ }),
248
245
  },
249
- async (args: { model?: string }) => {
246
+ async (args: { model?: string; view?: SchemaView }) => {
250
247
  try {
251
- const schemaPath = join(PROJECT_ROOT, "prisma", "schema.prisma")
252
- const schema = await readFileSafe(schemaPath)
253
- if (!schema) return errorResponse("未找到 prisma/schema.prisma 文件")
254
- const modelName = args?.model?.toLowerCase()
255
- if (modelName) {
256
- const modelRegex = new RegExp(`model\\s+${modelName}\\s*\\{`, "i")
257
- const match = schema.match(modelRegex)
258
- if (match) {
259
- const startIdx = match.index ?? 0; const braceIdx = schema.indexOf("{", startIdx)
260
- if (braceIdx !== -1) { let depth = 1; let endIdx = braceIdx + 1; while (depth > 0 && endIdx < schema.length) { if (schema[endIdx] === "{") depth++; else if (schema[endIdx] === "}") depth--; endIdx++ }; return textResponse(`=== Model: ${args.model} ===\n\nmodel ${args.model} ${schema.slice(braceIdx, endIdx)}`) }
248
+ const topology = await loadPrismaSchemaTopology({
249
+ projectRoot: PROJECT_ROOT,
250
+ splitDatabase: Boolean(process.env.ADD_DATABASE_URL?.trim()),
251
+ view: args?.view ?? "all",
252
+ })
253
+ const requested = args?.model?.trim().toLowerCase()
254
+ if (requested) {
255
+ const declaration = [...topology.models, ...topology.enums]
256
+ .find(item => item.name.toLowerCase() === requested)
257
+ if (!declaration) {
258
+ return errorResponse(`未在 view=${topology.view} 找到模型或枚举: ${args.model}。扫描: ${topology.files.map(file => file.file).join(", ")}`)
261
259
  }
262
- const enumMatch = schema.match(new RegExp(`enum\\s+${modelName}\\s*\\{`, "i"))
263
- if (enumMatch) return textResponse(`=== Enum: ${args.model} ===\n\n可用的枚举值见不传参调用结果。`)
264
- return errorResponse(`未找到模型或枚举: ${args.model}。可用模型见不传参调用结果。`)
265
- }
266
- const models: Array<{ name: string; fieldCount: number }> = []
267
- const modelRegex = /model\s+(\w+)\s*\{/g; let m
268
- while ((m = modelRegex.exec(schema)) !== null) {
269
- const startIdx = m.index; const braceIdx = schema.indexOf("{", startIdx)
270
- if (braceIdx !== -1) { let depth = 1; let endIdx = braceIdx + 1; while (depth > 0 && endIdx < schema.length) { if (schema[endIdx] === "{") depth++; else if (schema[endIdx] === "}") depth--; endIdx++ }; const body = schema.slice(braceIdx, endIdx); const fieldCount = body.split("\n").filter((l: string) => l.trim() && !l.trim().startsWith("//") && !l.trim().startsWith("@@")).length; models.push({ name: m[1], fieldCount }) }
260
+ return textResponse([
261
+ `=== ${declaration.kind === "model" ? "Model" : "Enum"}: ${declaration.name} ===`,
262
+ `mode: ${topology.mode}`,
263
+ `view: ${topology.view}`,
264
+ `boundary: ${declaration.boundary}`,
265
+ `source: ${relative(PROJECT_ROOT, declaration.sourcePath)}`,
266
+ "",
267
+ `${declaration.kind} ${declaration.name} ${declaration.body}`,
268
+ ].join("\n"))
271
269
  }
272
- const enums: string[] = []; const enumRegex = /enum\s+(\w+)\s*\{/g
273
- while ((m = enumRegex.exec(schema)) !== null) { enums.push(m[1]) }
274
- const parts = ["=== Prisma Schema 概况 ===", "", `模型 (${models.length} 个):`]
275
- for (const model of models) parts.push(` ${model.name} (${model.fieldCount} 字段)`)
276
- if (enums.length > 0) { parts.push(""); parts.push(`枚举 (${enums.length} 个): ${enums.join(", ")}`) }
277
- parts.push("", '提示: 指定 model 参数获取完整字段定义,例如: get_db_schema({ model: "User" })')
278
- return textResponse(parts.join("\n"))
270
+ return textResponse([
271
+ formatSchemaTopology(topology, PROJECT_ROOT),
272
+ "",
273
+ '提示: 指定 model/view 获取完整定义,例如 get_db_schema({ model: "PlanRecord", view: "add" })',
274
+ ].join("\n"))
279
275
  } catch (error) { return errorResponse(`获取 Schema 失败: ${error instanceof Error ? error.message : String(error)}`) }
280
276
  }
281
277
  )