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,143 @@
1
+ import { createHash } from "crypto"
2
+ import type { RuntimeContextKey } from "./runtime-context.js"
3
+ import {
4
+ createPlanRoundChangedEnvelope,
5
+ publishPlanRoundChanged,
6
+ } from "./plan-round-events.js"
7
+ import type { PrismaNotifyTransaction } from "./plan-lifecycle-events.js"
8
+
9
+ export interface PlanRoundPlanRow {
10
+ id: string
11
+ projectKey: string
12
+ adapterKey: string
13
+ planName: string
14
+ lifecycle: string
15
+ revision: number
16
+ }
17
+
18
+ export interface PlanRoundOperationRow {
19
+ id: string
20
+ projectKey: string
21
+ producerAdapterKey: string
22
+ contextId: string
23
+ toolName: string
24
+ operationKey: string
25
+ planKeyword: string
26
+ action: string
27
+ targetType: string
28
+ targetId: string
29
+ beforeState: unknown
30
+ afterState: unknown
31
+ reason: string | null
32
+ createdAt: Date
33
+ }
34
+
35
+ export interface PlanRoundTransaction extends PrismaNotifyTransaction {
36
+ planRecord: {
37
+ findFirst(args: { where: Record<string, unknown> }): Promise<PlanRoundPlanRow | null>
38
+ }
39
+ addUser: {
40
+ findUnique(args: { where: Record<string, unknown>; select: Record<string, unknown> }): Promise<{ id: string } | null>
41
+ create(args: { data: Record<string, unknown> }): Promise<{ id: string }>
42
+ }
43
+ devOperation: {
44
+ upsert(args: { where: Record<string, unknown>; create: Record<string, unknown>; update: Record<string, unknown> }): Promise<PlanRoundOperationRow>
45
+ }
46
+ }
47
+
48
+ export interface PlanRoundDatabase {
49
+ $transaction<T>(work: (tx: PlanRoundTransaction) => Promise<T>): Promise<T>
50
+ }
51
+
52
+ export interface ClosePlanRoundInput {
53
+ context: RuntimeContextKey
54
+ planName: string
55
+ round: number
56
+ beforeState: Record<string, unknown> | unknown[]
57
+ afterState: Record<string, unknown> | unknown[]
58
+ reason?: string
59
+ operationKey?: string
60
+ }
61
+
62
+ export interface ClosePlanRoundResult {
63
+ plan: PlanRoundPlanRow
64
+ operation: PlanRoundOperationRow
65
+ }
66
+
67
+ export function defaultPlanRoundOperationKey(input: {
68
+ context: RuntimeContextKey
69
+ planId: string
70
+ round: number
71
+ }): string {
72
+ return createHash("sha256")
73
+ .update(`${input.context.contextId}:${input.planId}:ROUND_CLOSED:${input.round}`)
74
+ .digest("hex")
75
+ }
76
+
77
+ export async function closePlanRoundAndPublish(
78
+ database: PlanRoundDatabase,
79
+ input: ClosePlanRoundInput,
80
+ ): Promise<ClosePlanRoundResult> {
81
+ if (!Number.isInteger(input.round) || input.round < 1) throw new Error("round 必须是大于 0 的整数")
82
+
83
+ return database.$transaction(async (tx) => {
84
+ const plan = await tx.planRecord.findFirst({
85
+ where: {
86
+ projectKey: input.context.projectKey,
87
+ adapterKey: input.context.adapterKey,
88
+ planName: input.planName,
89
+ },
90
+ })
91
+ if (!plan) throw new Error(`当前 RuntimeContextKey 下不存在 Plan: ${input.planName}`)
92
+ if (plan.projectKey !== input.context.projectKey || plan.adapterKey !== input.context.adapterKey) {
93
+ throw new Error("拒绝关闭 scope 外 PlanRound")
94
+ }
95
+
96
+ let user = await tx.addUser.findUnique({ where: { username: "ai-assistant" }, select: { id: true } })
97
+ if (!user) {
98
+ user = await tx.addUser.create({
99
+ data: { id: "ai-assistant", username: "ai-assistant", email: "ai-assistant@internal" },
100
+ })
101
+ }
102
+ const baseOperationKey = defaultPlanRoundOperationKey({
103
+ context: input.context,
104
+ planId: plan.id,
105
+ round: input.round,
106
+ })
107
+ const operationKey = input.operationKey?.trim()
108
+ ? createHash("sha256").update(`${baseOperationKey}:${input.operationKey.trim()}`).digest("hex")
109
+ : baseOperationKey
110
+ const operation = await tx.devOperation.upsert({
111
+ where: {
112
+ projectKey_producerAdapterKey_toolName_operationKey: {
113
+ projectKey: input.context.projectKey,
114
+ producerAdapterKey: input.context.adapterKey,
115
+ toolName: "plan_round_close",
116
+ operationKey,
117
+ },
118
+ },
119
+ create: {
120
+ userId: user.id,
121
+ projectKey: input.context.projectKey,
122
+ producerAdapterKey: input.context.adapterKey,
123
+ contextId: input.context.contextId,
124
+ toolName: "plan_round_close",
125
+ operationKey,
126
+ planKeyword: input.planName,
127
+ action: "ROUND_CLOSED",
128
+ targetType: "PLAN_ROUND",
129
+ targetId: `${input.planName}::round${input.round}`,
130
+ beforeState: input.beforeState,
131
+ afterState: input.afterState,
132
+ reason: input.reason ?? null,
133
+ },
134
+ update: {},
135
+ })
136
+
137
+ await publishPlanRoundChanged(tx, createPlanRoundChangedEnvelope({
138
+ context: input.context,
139
+ planId: plan.id,
140
+ }))
141
+ return { plan, operation }
142
+ })
143
+ }
@@ -0,0 +1,47 @@
1
+ import type { RuntimeContextKey } from "./runtime-context.js"
2
+ import type { PlanRoundOperationRow } from "./plan-round-mutation.js"
3
+
4
+ export interface PlanRoundReadClient {
5
+ planRecord: {
6
+ findFirst(args: { where: Record<string, unknown>; orderBy: Record<string, unknown> }): Promise<{ id: string; planName: string } | null>
7
+ }
8
+ devOperation: {
9
+ findMany(args: { where: Record<string, unknown>; orderBy: Record<string, unknown> }): Promise<PlanRoundOperationRow[]>
10
+ }
11
+ }
12
+
13
+ export interface PlanRoundSnapshot {
14
+ context: RuntimeContextKey
15
+ planId: string | null
16
+ planName: string | null
17
+ rounds: PlanRoundOperationRow[]
18
+ }
19
+
20
+ export async function queryPlanRounds(
21
+ client: PlanRoundReadClient,
22
+ input: { context: RuntimeContextKey; planName?: string; planId?: string; round?: number },
23
+ ): Promise<PlanRoundSnapshot> {
24
+ const explicitPlan = Boolean(input.planName || input.planId)
25
+ const plan = await client.planRecord.findFirst({
26
+ where: {
27
+ projectKey: input.context.projectKey,
28
+ adapterKey: input.context.adapterKey,
29
+ ...(input.planName ? { planName: input.planName } : {}),
30
+ ...(input.planId ? { id: input.planId } : {}),
31
+ ...(!explicitPlan ? { lifecycle: { in: ["ACTIVE", "BLOCKED"] } } : {}),
32
+ },
33
+ orderBy: { updatedAt: "desc" },
34
+ })
35
+ if (!plan) return { context: input.context, planId: null, planName: null, rounds: [] }
36
+
37
+ const where: Record<string, unknown> = {
38
+ projectKey: input.context.projectKey,
39
+ producerAdapterKey: input.context.adapterKey,
40
+ toolName: "plan_round_close",
41
+ action: "ROUND_CLOSED",
42
+ planKeyword: plan.planName,
43
+ }
44
+ if (input.round !== undefined) where.targetId = `${plan.planName}::round${input.round}`
45
+ const rounds = await client.devOperation.findMany({ where, orderBy: { createdAt: "asc" } })
46
+ return { context: input.context, planId: plan.id, planName: plan.planName, rounds }
47
+ }
@@ -0,0 +1,99 @@
1
+ import type { RuntimeContextKey } from "./runtime-context.js"
2
+ import type { LifecycleListenClient, LifecycleListenClientFactory, PgNotification } from "./plan-lifecycle-subscriber.js"
3
+ import type { PlanRoundSnapshot } from "./plan-round-store.js"
4
+ import {
5
+ isPlanRoundEventForContext,
6
+ parsePlanRoundChangedEnvelope,
7
+ PLAN_ROUND_CHANNEL,
8
+ type PlanRoundChangedEnvelope,
9
+ } from "./plan-round-events.js"
10
+
11
+ export interface PlanRoundSubscriberOptions {
12
+ context: RuntimeContextKey
13
+ clientFactory: LifecycleListenClientFactory
14
+ queryRounds(input: { context: RuntimeContextKey; planId?: string; reason: "initial" | "notification" | "reconnect" }): Promise<PlanRoundSnapshot>
15
+ onSnapshot(snapshot: PlanRoundSnapshot, envelope?: PlanRoundChangedEnvelope): void | Promise<void>
16
+ onError?(error: Error): void
17
+ reconnectDelayMs?: number
18
+ }
19
+
20
+ export class PlanRoundSubscriber {
21
+ readonly #options: PlanRoundSubscriberOptions
22
+ #client: LifecycleListenClient | null = null
23
+ #stopping = false
24
+ #startedOnce = false
25
+ #reconnectTimer: ReturnType<typeof setTimeout> | null = null
26
+ #refreshQueue: Promise<void> = Promise.resolve()
27
+
28
+ constructor(options: PlanRoundSubscriberOptions) { this.#options = options }
29
+
30
+ async start(): Promise<void> {
31
+ if (this.#client) return
32
+ this.#stopping = false
33
+ try { await this.#connectAndHydrate("initial") } catch (error) { this.#scheduleReconnect(); throw error }
34
+ }
35
+
36
+ async stop(): Promise<void> {
37
+ this.#stopping = true
38
+ if (this.#reconnectTimer) clearTimeout(this.#reconnectTimer)
39
+ this.#reconnectTimer = null
40
+ const client = this.#client
41
+ this.#client = null
42
+ if (!client) return
43
+ client.removeAllListeners()
44
+ try { await client.query(`UNLISTEN ${PLAN_ROUND_CHANNEL}`) } catch { /* connection may already be gone */ }
45
+ await client.end().catch(() => undefined)
46
+ await this.#refreshQueue
47
+ }
48
+
49
+ async #connectAndHydrate(reason: "initial" | "reconnect"): Promise<void> {
50
+ const client = await this.#options.clientFactory()
51
+ this.#client = client
52
+ client.on("notification", (message: PgNotification) => this.#handleNotification(message))
53
+ client.on("error", (error: Error) => this.#handleDisconnect(client, error))
54
+ client.on("end", () => this.#handleDisconnect(client, new Error("PostgreSQL PlanRound LISTEN session ended")))
55
+ try {
56
+ await client.connect()
57
+ await client.query(`LISTEN ${PLAN_ROUND_CHANNEL}`)
58
+ await this.#refresh(undefined, reason)
59
+ this.#startedOnce = true
60
+ } catch (error) {
61
+ if (this.#client === client) this.#client = null
62
+ client.removeAllListeners()
63
+ await client.end().catch(() => undefined)
64
+ const normalized = error instanceof Error ? error : new Error(String(error))
65
+ this.#options.onError?.(normalized)
66
+ throw normalized
67
+ }
68
+ }
69
+
70
+ #handleNotification(message: PgNotification): void {
71
+ if (this.#stopping || message.channel !== PLAN_ROUND_CHANNEL) return
72
+ const envelope = parsePlanRoundChangedEnvelope(message.payload)
73
+ if (!envelope || !isPlanRoundEventForContext(envelope, this.#options.context)) return
74
+ this.#refreshQueue = this.#refreshQueue
75
+ .then(() => this.#refresh(envelope, "notification"))
76
+ .catch((error: unknown) => this.#options.onError?.(error instanceof Error ? error : new Error(String(error))))
77
+ }
78
+
79
+ #handleDisconnect(client: LifecycleListenClient, error: Error): void {
80
+ if (this.#stopping || this.#client !== client) return
81
+ this.#client = null
82
+ client.removeAllListeners()
83
+ this.#options.onError?.(error)
84
+ this.#scheduleReconnect()
85
+ }
86
+
87
+ #scheduleReconnect(): void {
88
+ if (this.#stopping || this.#reconnectTimer) return
89
+ this.#reconnectTimer = setTimeout(() => {
90
+ this.#reconnectTimer = null
91
+ this.#connectAndHydrate(this.#startedOnce ? "reconnect" : "initial").catch(() => this.#scheduleReconnect())
92
+ }, this.#options.reconnectDelayMs ?? 1000)
93
+ }
94
+
95
+ async #refresh(envelope: PlanRoundChangedEnvelope | undefined, reason: "initial" | "notification" | "reconnect"): Promise<void> {
96
+ const snapshot = await this.#options.queryRounds({ context: this.#options.context, planId: envelope?.planId, reason })
97
+ await this.#options.onSnapshot(snapshot, envelope)
98
+ }
99
+ }
@@ -0,0 +1,37 @@
1
+ import type { HitlRow, PlanRow } from "./db-types.js"
2
+ import { HitlRowSchema, PlanRowSchema, validatedDelegate } from "./db-types.js"
3
+ import type { PlanStatusStore } from "./plan-lifecycle.js"
4
+
5
+ /**
6
+ * 数据库是 lifecycle 真相源。所有查询都强制携带 RuntimeContextKey,禁止同名
7
+ * Plan 在 adapter 间串线;LISTEN/NOTIFY 只负责唤醒本 store 重新查询。
8
+ */
9
+ export function createPrismaPlanStatusStore(client: Record<string, unknown>): PlanStatusStore {
10
+ const plans = validatedDelegate<PlanRow>(client.planRecord, PlanRowSchema, "PlanRecord")
11
+ const hitls = validatedDelegate<HitlRow>(client.hitlRecord, HitlRowSchema, "HitlRecord")
12
+
13
+ return {
14
+ async findPlan({ context, planName, planId, activeOnly }) {
15
+ const where: Record<string, unknown> = {
16
+ projectKey: context.projectKey,
17
+ adapterKey: context.adapterKey,
18
+ }
19
+ if (planName) where.planName = planName
20
+ if (planId) where.id = planId
21
+ if (activeOnly) where.lifecycle = { in: ["ACTIVE", "BLOCKED"] }
22
+ return plans.findFirst({ where, orderBy: { updatedAt: "desc" } })
23
+ },
24
+ async findLatestPlanApproval({ context, planName }) {
25
+ const record = await hitls.findFirst({
26
+ where: {
27
+ projectKey: context.projectKey,
28
+ adapterKey: context.adapterKey,
29
+ planName,
30
+ type: "PLAN",
31
+ },
32
+ orderBy: { round: "desc" },
33
+ })
34
+ return record?.status ?? null
35
+ },
36
+ }
37
+ }
@@ -0,0 +1,71 @@
1
+ import type { RuntimeContextKey } from "./runtime-context.js"
2
+ import {
3
+ createPlanLifecycleChangedEnvelope,
4
+ publishPlanLifecycleChanged,
5
+ type PrismaNotifyTransaction,
6
+ } from "./plan-lifecycle-events.js"
7
+ import type { PlanLifecycleStatus } from "./plan-lifecycle.js"
8
+
9
+ export interface TrackedPlanRow {
10
+ id: string
11
+ projectKey: string
12
+ adapterKey: string
13
+ planName: string
14
+ lifecycle: PlanLifecycleStatus
15
+ revision: number
16
+ }
17
+
18
+ export interface PlanTrackingTransaction extends PrismaNotifyTransaction {
19
+ planRecord: {
20
+ findFirst(args: { where: Record<string, unknown> }): Promise<TrackedPlanRow | null>
21
+ create(args: { data: Record<string, unknown> }): Promise<TrackedPlanRow>
22
+ update(args: { where: Record<string, unknown>; data: Record<string, unknown> }): Promise<TrackedPlanRow>
23
+ }
24
+ }
25
+
26
+ export interface PlanTrackingDatabase {
27
+ $transaction<T>(work: (tx: PlanTrackingTransaction) => Promise<T>): Promise<T>
28
+ }
29
+
30
+ export async function trackPlanAndPublish(
31
+ database: PlanTrackingDatabase,
32
+ input: {
33
+ context: RuntimeContextKey
34
+ planName: string
35
+ planPath: string
36
+ projection: Record<string, unknown>
37
+ },
38
+ ): Promise<{ created: boolean; plan: TrackedPlanRow }> {
39
+ return database.$transaction(async (tx) => {
40
+ const scope = {
41
+ projectKey: input.context.projectKey,
42
+ adapterKey: input.context.adapterKey,
43
+ planName: input.planName,
44
+ }
45
+ const existing = await tx.planRecord.findFirst({ where: scope })
46
+ const plan = existing
47
+ ? await tx.planRecord.update({
48
+ where: { id: existing.id, revision: existing.revision },
49
+ data: { ...input.projection, planPath: input.planPath, revision: { increment: 1 } },
50
+ })
51
+ : await tx.planRecord.create({
52
+ data: {
53
+ ...scope,
54
+ ...input.projection,
55
+ planPath: input.planPath,
56
+ lifecycle: "DRAFT",
57
+ revision: 0,
58
+ },
59
+ })
60
+ const expectedRevision = existing ? existing.revision + 1 : 0
61
+ if (plan.revision !== expectedRevision) {
62
+ throw new Error(`Plan track revision 未按预期变化: expected=${expectedRevision}, actual=${plan.revision}`)
63
+ }
64
+ await publishPlanLifecycleChanged(tx, createPlanLifecycleChangedEnvelope({
65
+ context: input.context,
66
+ planId: plan.id,
67
+ revision: plan.revision,
68
+ }))
69
+ return { created: existing === null, plan }
70
+ })
71
+ }
@@ -1,8 +1,8 @@
1
- import { DATABASE_URL, PROJECT_ROOT } from "./env.js"
2
- import { join, dirname, resolve as pathResolve } from "path"
1
+ import { DATABASE_URL, PROJECT_ROOT, MAGIC_DIR } from "./env.js"
2
+ import { join, dirname } from "path"
3
3
  import { existsSync } from "fs"
4
- import { fileURLToPath } from "url"
5
4
  import { createRequire } from "module"
5
+ import { findUpSync } from "find-up"
6
6
 
7
7
  const require = createRequire(import.meta.url)
8
8
 
@@ -11,14 +11,20 @@ const require = createRequire(import.meta.url)
11
11
  // 双库分离项目(add.prisma=postgres + 业务 schema=mysql)需显式设置环境变量
12
12
  // PRISMA_CLIENT_DIR=add-prisma,否则会加载业务库 client 导致 provider mismatch
13
13
  const clientDir = process.env.PRISMA_CLIENT_DIR || "prisma"
14
+ // 锚点查找兜底(find-up 包):magicDir 向上查找项目根,替代手算层级(轮次 3)
15
+ const anchorRoot = (() => {
16
+ const hit = findUpSync(MAGIC_DIR, { cwd: import.meta.dirname, type: "directory" });
17
+ return hit ? dirname(hit) : null;
18
+ })();
14
19
  const candidates = [
15
20
  join(PROJECT_ROOT, `src/generated/${clientDir}/client.ts`),
16
21
  join(PROJECT_ROOT, `src/generated/${clientDir}/client.js`),
17
22
  join(process.cwd(), `src/generated/${clientDir}/client.ts`),
18
23
  join(process.cwd(), `src/generated/${clientDir}/client.js`),
19
- // 从当前文件位置反推:shared/prisma.ts → 上 4 层到项目根
20
- (() => { const d = dirname(fileURLToPath(import.meta.url)); const root = pathResolve(d, "..", "..", "..", ".."); return join(root, `src/generated/${clientDir}/client.ts`) })(),
21
- (() => { const d = dirname(fileURLToPath(import.meta.url)); const root = pathResolve(d, "..", "..", "..", ".."); return join(root, `src/generated/${clientDir}/client.js`) })(),
24
+ ...(anchorRoot ? [
25
+ join(anchorRoot, `src/generated/${clientDir}/client.ts`),
26
+ join(anchorRoot, `src/generated/${clientDir}/client.js`),
27
+ ] : []),
22
28
  ]
23
29
  // 双库分离项目提示:检测到 add-prisma 客户端但未显式选择时提醒用户自行决策
24
30
  if (clientDir === "prisma" && existsSync(join(PROJECT_ROOT, "src/generated/add-prisma/client.ts"))) {
@@ -4,8 +4,7 @@
4
4
  // >>> CAIJUE GENERATED START >>>
5
5
  export const PROJECT_ROOT_PRIORITY = ["env_var", "dirname_fallback", "cwd_fallback"] as const;
6
6
  export type ProjectRootTier = (typeof PROJECT_ROOT_PRIORITY)[number];
7
- // <<< CAIJUE GENERATED END <<<
8
- // >>> USER CODE >>>
7
+ // <<< CAIJUE GENERATED END <<<// >>> USER CODE >>>
9
8
  import { resolve, dirname } from "path"
10
9
  import { existsSync } from "fs"
11
10
 
@@ -0,0 +1,16 @@
1
+ // ⚠️ 由 caijuehub/transcribe.ts 自动生成,不要手动编辑!
2
+ // 改 *-rules.toml 后重新运行: add-coder generate
3
+
4
+ // >>> CAIJUE GENERATED START >>>
5
+ export const QUANT_RECHECK_CONFIG = {
6
+ ANCHOR_WEIGHT: 3,
7
+ IDF_MIN_DOCS: 5,
8
+ RRF_K: 10,
9
+ MIN_ABS_SIMILARITY: 0.25,
10
+ RRF_MIN_CANDIDATES: 2,
11
+ EWMA_LAMBDA: 0.3,
12
+ EWMA_K: 1.5,
13
+ EWMA_COLD_START: 10,
14
+ EWMA_BASELINE: 0.16,
15
+ } as const;
16
+ // <<< CAIJUE GENERATED END <<<
@@ -0,0 +1,28 @@
1
+ /*
2
+ * @Author : xiaomingming wujixmm@gmail.com
3
+ * @Date : 2026-08-10 18:41:34
4
+ * @LastEditors : xiaomingming wujixmm@gmail.com
5
+ * @LastEditTime : 2026-08-10 18:41:35
6
+ * @FilePath : /farm-agent/home/xmm/ai/add-coder/templates/core/scripts/mcp-server/shared/redact.ts
7
+ * @Description :
8
+ */
9
+ // 敏感信息脱敏(进程层并发契约 v2:日志/错误不泄露 DATABASE_URL/密码)
10
+ // 统一入口:response.ts 的 textResponse/errorResponse 内部调用,一处封装全覆盖所有工具
11
+
12
+ const CONNECTION_URL_RE =
13
+ /(postgres(?:ql)?|mysql|redis|mongodb(?:\+srv)?):\/\/[^\s"'`]+/gi
14
+
15
+ export function redactUrl(url: string): string {
16
+ // postgres://user:pass@host:port/db → postgres://user:****@host:port/db
17
+ return url.replace(/(:\/\/[^:/\s]+:)([^@/\s]+)(@)/, "$1****$3")
18
+ }
19
+
20
+ export function redact(text: string): string {
21
+ return text.replace(CONNECTION_URL_RE, (m) => {
22
+ try {
23
+ return redactUrl(m)
24
+ } catch {
25
+ return m
26
+ }
27
+ })
28
+ }
@@ -1,9 +1,10 @@
1
1
  import type { ToolResponse } from "../types.js"
2
+ import { redact } from "./redact.js"
2
3
 
3
4
  export function textResponse(text: string): { content: ToolResponse } {
4
- return { content: [{ type: "text", text }] }
5
+ return { content: [{ type: "text", text: redact(text) }] }
5
6
  }
6
7
 
7
8
  export function errorResponse(message: string): { content: ToolResponse; isError: boolean } {
8
- return { content: [{ type: "text", text: message }], isError: true }
9
+ return { content: [{ type: "text", text: redact(message) }], isError: true }
9
10
  }
@@ -0,0 +1,77 @@
1
+ import { createHash } from "crypto"
2
+ import { realpathSync } from "fs"
3
+ import { isAbsolute, relative, resolve } from "path"
4
+
5
+ export const ADAPTER_MAGIC_DIR = Object.freeze({
6
+ add: ".add",
7
+ claude: ".claude",
8
+ codex: ".codex",
9
+ qoder: ".qoder",
10
+ trae: ".trae",
11
+ vscode: ".vscode",
12
+ } as const)
13
+
14
+ export type AdapterKey = keyof typeof ADAPTER_MAGIC_DIR
15
+ export type AdapterMagicDir = (typeof ADAPTER_MAGIC_DIR)[AdapterKey]
16
+
17
+ export interface RuntimeContextKey {
18
+ projectKey: string
19
+ adapterKey: AdapterKey
20
+ projectRoot: string
21
+ magicDir: AdapterMagicDir
22
+ contextId: string
23
+ }
24
+
25
+ const MAGIC_DIR_ADAPTER = new Map<AdapterMagicDir, AdapterKey>(
26
+ Object.entries(ADAPTER_MAGIC_DIR).map(([adapterKey, magicDir]) => [magicDir, adapterKey as AdapterKey]),
27
+ )
28
+
29
+ export function adapterKeyFromMagicDir(magicDir: string): AdapterKey {
30
+ const adapterKey = MAGIC_DIR_ADAPTER.get(magicDir as AdapterMagicDir)
31
+ if (!adapterKey) {
32
+ throw new Error(`未知 ADD adapter magicDir: ${magicDir}`)
33
+ }
34
+ return adapterKey
35
+ }
36
+
37
+ export function canonicalProjectRoot(projectRoot: string): string {
38
+ if (!projectRoot.trim()) throw new Error("PROJECT_ROOT 不能为空")
39
+ return realpathSync.native(resolve(projectRoot))
40
+ }
41
+
42
+ export function projectKeyFromRoot(projectRoot: string): string {
43
+ const canonicalRoot = canonicalProjectRoot(projectRoot)
44
+ return createHash("sha256").update(`add-project\0${canonicalRoot}`).digest("hex")
45
+ }
46
+
47
+ export function createRuntimeContext(projectRoot: string, magicDir: string): Readonly<RuntimeContextKey> {
48
+ const adapterKey = adapterKeyFromMagicDir(magicDir)
49
+ const canonicalRoot = canonicalProjectRoot(projectRoot)
50
+ const projectKey = projectKeyFromRoot(canonicalRoot)
51
+ return Object.freeze({
52
+ projectKey,
53
+ adapterKey,
54
+ projectRoot: canonicalRoot,
55
+ magicDir: ADAPTER_MAGIC_DIR[adapterKey],
56
+ contextId: `${projectKey}:${adapterKey}`,
57
+ })
58
+ }
59
+
60
+ export function runtimeScopeRoot(context: RuntimeContextKey): string {
61
+ return resolve(context.projectRoot, context.magicDir)
62
+ }
63
+
64
+ export function isPathInRuntimeScope(context: RuntimeContextKey, candidatePath: string): boolean {
65
+ const scopeRoot = runtimeScopeRoot(context)
66
+ const absoluteCandidate = isAbsolute(candidatePath) ? resolve(candidatePath) : resolve(context.projectRoot, candidatePath)
67
+ const scopedRelative = relative(scopeRoot, absoluteCandidate)
68
+ return scopedRelative === "" || (!scopedRelative.startsWith("..") && !isAbsolute(scopedRelative))
69
+ }
70
+
71
+ export function assertPathInRuntimeScope(context: RuntimeContextKey, candidatePath: string): string {
72
+ const absoluteCandidate = isAbsolute(candidatePath) ? resolve(candidatePath) : resolve(context.projectRoot, candidatePath)
73
+ if (!isPathInRuntimeScope(context, absoluteCandidate)) {
74
+ throw new Error(`路径越出当前 ADD adapter scope: ${absoluteCandidate}(scope=${runtimeScopeRoot(context)})`)
75
+ }
76
+ return absoluteCandidate
77
+ }