add-coder 0.3.16 → 0.3.19

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 (33) hide show
  1. package/dist/index.js +349 -62
  2. package/package.json +1 -1
  3. package/templates/.add-coder-src-hash.json +31 -24
  4. package/templates/adapters/claude/hooks/pre-tool-use.sh +6 -1
  5. package/templates/adapters/codex/hooks/pre-tool-use.sh +7 -1
  6. package/templates/adapters/qoder/hooks/pre-tool-use.sh +6 -1
  7. package/templates/adapters/trae/hooks/pre-tool-use.sh +7 -1
  8. package/templates/adapters/vscode/hooks/pre-tool-use.sh +6 -1
  9. package/templates/core/hooks/pre-tool-use.sh +7 -1
  10. package/templates/core/prisma/add.prisma +35 -0
  11. package/templates/core/rules/profiles/index.toml +12 -0
  12. package/templates/core/rules/profiles/machineserver-profile.md +22 -0
  13. package/templates/core/rules/profiles/webapp-profile.md +61 -0
  14. package/templates/core/rules/project_rules.md +6 -54
  15. package/templates/core/scripts/mcp-server/tools/audit.ts +23 -14
  16. package/templates/core/scripts/mcp-server/tools/context.ts +20 -2
  17. package/templates/core/scripts/mcp-server/tools/contract.ts +202 -0
  18. package/templates/core/scripts/mcp-server/tools/docs.ts +2 -2
  19. package/templates/core/scripts/mcp-server/tools/gateway/check_add_route_completeness.ts +2 -2
  20. package/templates/core/scripts/mcp-server/tools/gateway/check_add_route_status.ts +2 -2
  21. package/templates/core/scripts/mcp-server/tools/gateway/check_dps.ts +2 -2
  22. package/templates/core/scripts/mcp-server/tools/gateway/check_rahs.ts +2 -2
  23. package/templates/core/scripts/mcp-server/tools/gateway/check_spec_sync.ts +2 -2
  24. package/templates/core/scripts/mcp-server/tools/gateway/index.ts +2 -2
  25. package/templates/core/scripts/mcp-server/tools/hitl.ts +13 -5
  26. package/templates/core/scripts/mcp-server/tools/hook-event-report.ts +2 -2
  27. package/templates/core/scripts/mcp-server/tools/index.ts +29 -10
  28. package/templates/core/scripts/mcp-server/tools/plan.ts +5 -2
  29. package/templates/core/scripts/mcp-server/tools/quality.ts +2 -2
  30. package/templates/core/scripts/mcp-server/tools/registrar.ts +11 -0
  31. package/templates/core/scripts/mcp-server/tools/review.ts +2 -2
  32. package/templates/core/templates/collab-contract-template.md +203 -0
  33. package/templates/core/templates/collab-contract-template.schema.json +133 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "add-coder",
3
- "version": "0.3.16",
3
+ "version": "0.3.19",
4
4
  "private": false,
5
5
  "description": "[codein2027](https://github.com/xiaomingming92/codein2027) - A complete scaffolding for building the ADD programming paradigm, the implementation layer for AI code governance. Core principle: Audit as Infrastructure. Breaks the black-box programming process and cross-session amnesia, evolving programming paradigms into an auditable, traceable, and convergent new era. npx-ready.\n\n[codein2027](https://github.com/xiaomingming92/codein2027) 快速构建 ADD 编程范式的完整脚手架——AI 代码治理的落地方案。以「审计即基础设施」为核心,彻底打破编程过程黑盒与跨轮失忆,让编程范式进化为可审计、可追溯、可收敛的新时代。npx 即用,人人可体验。",
6
6
  "repository": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "_version": "0.3.16",
2
+ "_version": "0.3.19",
3
3
  "adapters/claude/hooks/doc-format-guard.sh": "ec59ad3f",
4
4
  "adapters/claude/hooks/lib/common.sh": "4f3e64ad",
5
5
  "adapters/claude/hooks/lib/context-inject.sh": "cfc8c65e",
@@ -15,7 +15,7 @@
15
15
  "adapters/claude/hooks/post-tool-failure.sh": "669b1b65",
16
16
  "adapters/claude/hooks/post-tool-use.sh": "f54dd009",
17
17
  "adapters/claude/hooks/pre-compact.sh": "4248f84f",
18
- "adapters/claude/hooks/pre-tool-use.sh": "e186abc8",
18
+ "adapters/claude/hooks/pre-tool-use.sh": "080f8979",
19
19
  "adapters/claude/hooks/prompt-submit.sh": "2fe9e42c",
20
20
  "adapters/claude/hooks/review-checklist.sh": "b383a983",
21
21
  "adapters/claude/hooks/session-end.sh": "777ae808",
@@ -40,7 +40,7 @@
40
40
  "adapters/codex/hooks/post-tool-failure.sh": "669b1b65",
41
41
  "adapters/codex/hooks/post-tool-use.sh": "27123aee",
42
42
  "adapters/codex/hooks/pre-compact.sh": "6164c007",
43
- "adapters/codex/hooks/pre-tool-use.sh": "8f6b77a0",
43
+ "adapters/codex/hooks/pre-tool-use.sh": "797664b8",
44
44
  "adapters/codex/hooks/prompt-submit.sh": "a41371ea",
45
45
  "adapters/codex/hooks/review-checklist.sh": "b383a983",
46
46
  "adapters/codex/hooks/session-end.sh": "3cc8c346",
@@ -64,7 +64,7 @@
64
64
  "adapters/qoder/hooks/post-tool-failure.sh": "8bc7a0e4",
65
65
  "adapters/qoder/hooks/post-tool-use.sh": "051cb6ed",
66
66
  "adapters/qoder/hooks/pre-compact.sh": "092fa6cf",
67
- "adapters/qoder/hooks/pre-tool-use.sh": "ade1adf0",
67
+ "adapters/qoder/hooks/pre-tool-use.sh": "565cff92",
68
68
  "adapters/qoder/hooks/prompt-submit.sh": "336f262e",
69
69
  "adapters/qoder/hooks/review-checklist.sh": "666831c8",
70
70
  "adapters/qoder/hooks/session-end.sh": "c50339df",
@@ -89,7 +89,7 @@
89
89
  "adapters/trae/hooks/post-tool-failure.sh": "669b1b65",
90
90
  "adapters/trae/hooks/post-tool-use.sh": "27123aee",
91
91
  "adapters/trae/hooks/pre-compact.sh": "6164c007",
92
- "adapters/trae/hooks/pre-tool-use.sh": "63151fb0",
92
+ "adapters/trae/hooks/pre-tool-use.sh": "2d7fc7a7",
93
93
  "adapters/trae/hooks/prompt-submit.sh": "a41371ea",
94
94
  "adapters/trae/hooks/review-checklist.sh": "b383a983",
95
95
  "adapters/trae/hooks/session-end.sh": "3cc8c346",
@@ -124,7 +124,7 @@
124
124
  "adapters/vscode/hooks/post-tool-failure.sh": "355cfab1",
125
125
  "adapters/vscode/hooks/post-tool-use.sh": "5f188718",
126
126
  "adapters/vscode/hooks/pre-compact.sh": "a0908f0e",
127
- "adapters/vscode/hooks/pre-tool-use.sh": "705953bf",
127
+ "adapters/vscode/hooks/pre-tool-use.sh": "4bf878e7",
128
128
  "adapters/vscode/hooks/prompt-submit.sh": "3e56d1a9",
129
129
  "adapters/vscode/hooks/review-checklist.sh": "b383a983",
130
130
  "adapters/vscode/hooks/session-end.sh": "7b407b9f",
@@ -155,7 +155,7 @@
155
155
  "core/hooks/post-tool-failure.sh": "669b1b65",
156
156
  "core/hooks/post-tool-use.sh": "27123aee",
157
157
  "core/hooks/pre-compact.sh": "6164c007",
158
- "core/hooks/pre-tool-use.sh": "60bcf63a",
158
+ "core/hooks/pre-tool-use.sh": "f967ab24",
159
159
  "core/hooks/prompt-submit.sh": "a41371ea",
160
160
  "core/hooks/review-checklist.sh": "b383a983",
161
161
  "core/hooks/session-end.sh": "3cc8c346",
@@ -166,7 +166,7 @@
166
166
  "core/plans/2026-07/08/add-coder-npm-package-add-route-v1.md": "b05fce99",
167
167
  "core/plans/2026-07/08/add-coder-npm-package-handoff-v1.md": "1bd40a4a",
168
168
  "core/plans/2026-07/08/add-coder-npm-package-plan-v1.md": "a678d98e",
169
- "core/prisma/add.prisma": "fa818acf",
169
+ "core/prisma/add.prisma": "037bd7e9",
170
170
  "core/reports/REPORT-WORKFLOW.md": "03d39b21",
171
171
  "core/reports/boundary-runtime-report.md": "6fd8053f",
172
172
  "core/reports/code-review-combined-report.md": "195d52aa",
@@ -175,7 +175,10 @@
175
175
  "core/reports/index.md": "6348c374",
176
176
  "core/reports/runtime-report/gateway.md": "a5751665",
177
177
  "core/reviews/.gitkeep": "e3b0c442",
178
- "core/rules/project_rules.md": "fea8bfaa",
178
+ "core/rules/profiles/index.toml": "b83332d4",
179
+ "core/rules/profiles/machineserver-profile.md": "8a36cf39",
180
+ "core/rules/profiles/webapp-profile.md": "e493ae7b",
181
+ "core/rules/project_rules.md": "82b25e08",
179
182
  "core/rules/theory-practice-map.toml": "b1db99fd",
180
183
  "core/scripts/db-ensure.sh": "faeb6b92",
181
184
  "core/scripts/gen-plan-index.sh": "bfc6ad3e",
@@ -203,23 +206,25 @@
203
206
  "core/scripts/mcp-server/tasks/index.ts": "c040eb75",
204
207
  "core/scripts/mcp-server/tasks/runner.ts": "d84bd35d",
205
208
  "core/scripts/mcp-server/tasks/store.ts": "2fce1a92",
206
- "core/scripts/mcp-server/tools/audit.ts": "3c23458b",
207
- "core/scripts/mcp-server/tools/context.ts": "5a00457b",
208
- "core/scripts/mcp-server/tools/docs.ts": "a3937aec",
209
- "core/scripts/mcp-server/tools/gateway/check_add_route_completeness.ts": "c205989d",
210
- "core/scripts/mcp-server/tools/gateway/check_add_route_status.ts": "3662b6dc",
211
- "core/scripts/mcp-server/tools/gateway/check_dps.ts": "cea4cb8b",
212
- "core/scripts/mcp-server/tools/gateway/check_rahs.ts": "c37f6810",
213
- "core/scripts/mcp-server/tools/gateway/check_spec_sync.ts": "a683697f",
209
+ "core/scripts/mcp-server/tools/audit.ts": "71a38969",
210
+ "core/scripts/mcp-server/tools/context.ts": "9ce21255",
211
+ "core/scripts/mcp-server/tools/contract.ts": "c894e64a",
212
+ "core/scripts/mcp-server/tools/docs.ts": "034e473c",
213
+ "core/scripts/mcp-server/tools/gateway/check_add_route_completeness.ts": "b58e76ee",
214
+ "core/scripts/mcp-server/tools/gateway/check_add_route_status.ts": "23fbbb6b",
215
+ "core/scripts/mcp-server/tools/gateway/check_dps.ts": "0f0bedd8",
216
+ "core/scripts/mcp-server/tools/gateway/check_rahs.ts": "fe210727",
217
+ "core/scripts/mcp-server/tools/gateway/check_spec_sync.ts": "89c2338b",
214
218
  "core/scripts/mcp-server/tools/gateway/helpers.ts": "690d7891",
215
- "core/scripts/mcp-server/tools/gateway/index.ts": "5a9fd5ed",
219
+ "core/scripts/mcp-server/tools/gateway/index.ts": "559acb75",
216
220
  "core/scripts/mcp-server/tools/gateway.backup": "65eae393",
217
- "core/scripts/mcp-server/tools/hitl.ts": "80093743",
218
- "core/scripts/mcp-server/tools/hook-event-report.ts": "3926005b",
219
- "core/scripts/mcp-server/tools/index.ts": "b15c6753",
220
- "core/scripts/mcp-server/tools/plan.ts": "2f83b56f",
221
- "core/scripts/mcp-server/tools/quality.ts": "26ddf75d",
222
- "core/scripts/mcp-server/tools/review.ts": "c2d464ed",
221
+ "core/scripts/mcp-server/tools/hitl.ts": "f447345f",
222
+ "core/scripts/mcp-server/tools/hook-event-report.ts": "3f21836e",
223
+ "core/scripts/mcp-server/tools/index.ts": "884172ba",
224
+ "core/scripts/mcp-server/tools/plan.ts": "477fd80d",
225
+ "core/scripts/mcp-server/tools/quality.ts": "c4634453",
226
+ "core/scripts/mcp-server/tools/registrar.ts": "efd81b3a",
227
+ "core/scripts/mcp-server/tools/review.ts": "4d72ca19",
223
228
  "core/scripts/mcp-server/types.ts": "6c5e60bc",
224
229
  "core/scripts/mcp-server.ts": "99cc6833",
225
230
  "core/skills/add-paradigm/SKILL.md": "5d4fa389",
@@ -234,6 +239,8 @@
234
239
  "core/templates/add-route-template.schema.json": "867a45c1",
235
240
  "core/templates/checklist-template.md": "d6b73a5c",
236
241
  "core/templates/checklist-template.schema.json": "fb3a73e8",
242
+ "core/templates/collab-contract-template.md": "bccba0fd",
243
+ "core/templates/collab-contract-template.schema.json": "ae0f482c",
237
244
  "core/templates/fix-verification-template.md": "9c9ed175",
238
245
  "core/templates/fix-verification-template.schema.json": "a18bc6a5",
239
246
  "core/templates/handoff-multi-round-template.md": "d3a2375d",
@@ -103,8 +103,13 @@ if [ "$tool_name" = "Write" ] || [ "$tool_name" = "Edit" ] || [ "$tool_name" = "
103
103
 
104
104
  # §C: HITL tongyi 检查 — plans/ + PLAN_REVIEW reviews/ 写入前必须有 .hitl-tongyi-{planName} 哨兵
105
105
  # implementation/runtime review 不需要 HITL,走 §B 活跃 Plan 检查
106
+ # handoff 是 Step 8 收敛产物(多轮交接手册),不经 HITL 审批,同样豁免
106
107
  if echo "$file_path" | grep -qE '\.(qoder|claude|add|vscode|trae)/(plans)/'; then
107
- _do_hitl=true
108
+ if echo "$file_path" | grep -qE '\-handoff'; then
109
+ _do_hitl=false # handoff 不被 HITL 拦截(Step 8 产物,无独立审批)
110
+ else
111
+ _do_hitl=true
112
+ fi
108
113
  elif echo "$file_path" | grep -qE '\.(qoder|claude|add|vscode|trae)/(reviews)/'; then
109
114
  if echo "$file_path" | grep -qE '-(implementation|runtime)'; then
110
115
  _do_hitl=false # implementation/runtime review 不被 HITL 拦截
@@ -175,8 +175,14 @@ if [ "$tool_name" = "Write" ] || [ "$tool_name" = "Edit" ] || [ "$tool_name" = "
175
175
 
176
176
  # §C: HITL tongyi 检查 — plans/ + PLAN_REVIEW reviews/ 写入前必须有 .hitl-tongyi-{planName} 哨兵
177
177
  # implementation/runtime review 不需要 HITL,走 §B 活跃 Plan 检查
178
+ # handoff 是 Step 8 收敛产物(多轮交接手册),不经 HITL 审批,同样豁免
179
+ # TODO 应该caijuehub管理
178
180
  if echo "$file_path" | grep -qE '\.(qoder|claude|add|vscode|trae)/(plans)/'; then
179
- _do_hitl=true
181
+ if echo "$file_path" | grep -qE '\-handoff'; then
182
+ _do_hitl=false # handoff 不被 HITL 拦截(Step 8 产物,无独立审批)
183
+ else
184
+ _do_hitl=true
185
+ fi
180
186
  elif echo "$file_path" | grep -qE '\.(qoder|claude|add|vscode|trae)/(reviews)/'; then
181
187
  if echo "$file_path" | grep -qE '-(implementation|runtime)'; then
182
188
  _do_hitl=false # implementation/runtime review 不被 HITL 拦截
@@ -104,8 +104,13 @@ if [ "$tool_name" = "Write" ] || [ "$tool_name" = "Edit" ] || [ "$tool_name" = "
104
104
 
105
105
  # §C: HITL tongyi 检查 — plans/ + PLAN_REVIEW reviews/ 写入前必须有 .hitl-tongyi-{planName} 哨兵
106
106
  # implementation/runtime review 不需要 HITL,走 §B 活跃 Plan 检查
107
+ # handoff 是 Step 8 收敛产物(多轮交接手册),不经 HITL 审批,同样豁免
107
108
  if echo "$file_path" | grep -qE '\.(qoder|claude|add|vscode|trae)/(plans)/'; then
108
- _do_hitl=true
109
+ if echo "$file_path" | grep -qE '\-handoff'; then
110
+ _do_hitl=false # handoff 不被 HITL 拦截(Step 8 产物,无独立审批)
111
+ else
112
+ _do_hitl=true
113
+ fi
109
114
  elif echo "$file_path" | grep -qE '\.(qoder|claude|add|vscode|trae)/(reviews)/'; then
110
115
  if echo "$file_path" | grep -qE '-(implementation|runtime)'; then
111
116
  _do_hitl=false # implementation/runtime review 不被 HITL 拦截
@@ -175,8 +175,14 @@ if [ "$tool_name" = "Write" ] || [ "$tool_name" = "Edit" ] || [ "$tool_name" = "
175
175
 
176
176
  # §C: HITL tongyi 检查 — plans/ + PLAN_REVIEW reviews/ 写入前必须有 .hitl-tongyi-{planName} 哨兵
177
177
  # implementation/runtime review 不需要 HITL,走 §B 活跃 Plan 检查
178
+ # handoff 是 Step 8 收敛产物(多轮交接手册),不经 HITL 审批,同样豁免
179
+ # TODO 应该caijuehub管理
178
180
  if echo "$file_path" | grep -qE '\.(qoder|claude|add|vscode|trae)/(plans)/'; then
179
- _do_hitl=true
181
+ if echo "$file_path" | grep -qE '\-handoff'; then
182
+ _do_hitl=false # handoff 不被 HITL 拦截(Step 8 产物,无独立审批)
183
+ else
184
+ _do_hitl=true
185
+ fi
180
186
  elif echo "$file_path" | grep -qE '\.(qoder|claude|add|vscode|trae)/(reviews)/'; then
181
187
  if echo "$file_path" | grep -qE '-(implementation|runtime)'; then
182
188
  _do_hitl=false # implementation/runtime review 不被 HITL 拦截
@@ -103,8 +103,13 @@ if [ "$tool_name" = "Write" ] || [ "$tool_name" = "Edit" ] || [ "$tool_name" = "
103
103
 
104
104
  # §C: HITL tongyi 检查 — plans/ + PLAN_REVIEW reviews/ 写入前必须有 .hitl-tongyi-{planName} 哨兵
105
105
  # implementation/runtime review 不需要 HITL,走 §B 活跃 Plan 检查
106
+ # handoff 是 Step 8 收敛产物(多轮交接手册),不经 HITL 审批,同样豁免
106
107
  if echo "$file_path" | grep -qE '\.(qoder|claude|add|vscode|trae)/(plans)/'; then
107
- _do_hitl=true
108
+ if echo "$file_path" | grep -qE '\-handoff'; then
109
+ _do_hitl=false # handoff 不被 HITL 拦截(Step 8 产物,无独立审批)
110
+ else
111
+ _do_hitl=true
112
+ fi
108
113
  elif echo "$file_path" | grep -qE '\.(qoder|claude|add|vscode|trae)/(reviews)/'; then
109
114
  if echo "$file_path" | grep -qE '-(implementation|runtime)'; then
110
115
  _do_hitl=false # implementation/runtime review 不被 HITL 拦截
@@ -175,8 +175,14 @@ if [ "$tool_name" = "Write" ] || [ "$tool_name" = "Edit" ] || [ "$tool_name" = "
175
175
 
176
176
  # §C: HITL tongyi 检查 — plans/ + PLAN_REVIEW reviews/ 写入前必须有 .hitl-tongyi-{planName} 哨兵
177
177
  # implementation/runtime review 不需要 HITL,走 §B 活跃 Plan 检查
178
+ # handoff 是 Step 8 收敛产物(多轮交接手册),不经 HITL 审批,同样豁免
179
+ # TODO 应该caijuehub管理
178
180
  if echo "$file_path" | grep -qE '\.(qoder|claude|add|vscode|trae)/(plans)/'; then
179
- _do_hitl=true
181
+ if echo "$file_path" | grep -qE '\-handoff'; then
182
+ _do_hitl=false # handoff 不被 HITL 拦截(Step 8 产物,无独立审批)
183
+ else
184
+ _do_hitl=true
185
+ fi
180
186
  elif echo "$file_path" | grep -qE '\.(qoder|claude|add|vscode|trae)/(reviews)/'; then
181
187
  if echo "$file_path" | grep -qE '-(implementation|runtime)'; then
182
188
  _do_hitl=false # implementation/runtime review 不被 HITL 拦截
@@ -45,6 +45,7 @@ model AuditLog {
45
45
  enum HitlType {
46
46
  PLAN
47
47
  PLAN_REVIEW
48
+ COLLAB_CONTRACT
48
49
  }
49
50
 
50
51
  enum HitlStatus {
@@ -94,6 +95,12 @@ model PlanRecord {
94
95
  checklistTDone Int @default(0)
95
96
  checklistR Int @default(0)
96
97
  planKeyword String?
98
+ // 并发协作契约:该 Plan 在契约中的角色(MASTER=总控 / SUB=子 Plan)
99
+ contractRole ContractRole?
100
+ // 所属契约名(与 CollabContract.contractName 对应,反查用)
101
+ contractName String?
102
+ // 作为总控 Plan 持有的契约(masterPlanName 外键方向)
103
+ masterContract CollabContract?
97
104
  // DPS 四维评分历史(check_dps 调用后回写,供 FFT 自适应权重消费)
98
105
  dpsSemScore Int? // 语义相关性 0-100
99
106
  dpsEntropyScore Int? // 信息熵匹配 0-100
@@ -106,6 +113,7 @@ model PlanRecord {
106
113
  updatedAt DateTime @updatedAt
107
114
 
108
115
  @@index([planKeyword])
116
+ @@index([contractName])
109
117
  }
110
118
 
111
119
  model ReviewRecord {
@@ -123,3 +131,30 @@ model ReviewRecord {
123
131
  @@index([planName])
124
132
  @@index([type])
125
133
  }
134
+
135
+ enum ContractRole {
136
+ MASTER // 总控 Plan(契约持有者)
137
+ SUB // 子 Plan(契约实施单元)
138
+ }
139
+
140
+ // 并发协作契约(多智能体并行实施规则集,对应 collab-contract 文档)
141
+ model CollabContract {
142
+ id String @id @default(cuid())
143
+ contractName String @unique
144
+ contractPath String
145
+ // 外键:总控 Plan(契约持有者,ContractRole=MASTER,一对一)
146
+ masterPlanName String @unique
147
+ masterPlan PlanRecord @relation(fields: [masterPlanName], references: [planName], onDelete: Restrict)
148
+ participants Json // [{role, platformEntity, boundPlan, planKeyword, description}]
149
+ abilityMatrix Json? // {能力: {专家: boolean}}
150
+ stages Json // [{stage, expert, trigger, parallelism}]
151
+ dependencyGraph String? // ASCII 图 + 机器可读声明
152
+ fileBoundaries Json // [{expert, exclusiveDomain, forbidden, isolationMode}] isolationMode: file/worktree
153
+ completionCriteria Json? // {专家: 完成标志}
154
+ status String @default("ACTIVE") // ACTIVE / SUPERSEDED
155
+ version Int @default(1)
156
+ createdAt DateTime @default(now())
157
+ updatedAt DateTime @updatedAt
158
+
159
+ @@index([masterPlanName])
160
+ }
@@ -0,0 +1,12 @@
1
+ # add-coder 技术栈 profile 注册表(内置)
2
+ # 用户自定义: 在 {magicDir}/rules/profiles/ 下放置任意 *.md 即可被 `add-coder stack list` 识别
3
+
4
+ [[profile]]
5
+ name = "webapp"
6
+ description = "Web 应用(前端/全栈): LangGraph/Next.js/Prisma/TypeScript 案例栈"
7
+ file = "webapp-profile.md"
8
+
9
+ [[profile]]
10
+ name = "machineserver"
11
+ description = "后端服务(机器人→中间件→平台链路): Kestra/HTTP API/事件驱动"
12
+ file = "machineserver-profile.md"
@@ -0,0 +1,22 @@
1
+ # machineserver-profile — 后端服务技术栈约束
2
+
3
+ > 由 add-coder 分发,可通过 `add-coder stack set machineserver` 启用/切换。
4
+ > 本 profile 描述「机器人 → 中间件 → 平台」后端链路的通用约束,不绑定具体框架实现。
5
+ > 启用后,AI 必须遵守本文件中全部约束;未启用时本文件不构成约束。
6
+
7
+ ## 技术栈事实(不是约束,仅供 AI 参考)
8
+
9
+ - 典型链路: 机器人/Agent → Kestra 工作流编排 → HTTP API → 平台
10
+ - 典型形态: 事件驱动 + 任务编排 + 服务间 HTTP 集成
11
+
12
+ ## 代码质量
13
+
14
+ - 服务边界: 编排(工作流)与业务(API)分层,禁止在编排层写业务逻辑
15
+ - 幂等: 任务/接口消费必须幂等,失败可重放
16
+ - 契约: 跨服务 API 必须有显式契约(OpenAPI/JSON Schema),禁止隐式字段约定
17
+ - 可观测: 每个任务/请求链路必须有 traceId 贯穿
18
+
19
+ ## 错误处理
20
+
21
+ - 失败路径与成功路径信息密度等价
22
+ - 编排失败必须有重试/补偿策略,禁止静默失败
@@ -0,0 +1,61 @@
1
+ # webapp-profile — Web 应用技术栈约束
2
+
3
+ > 由 add-coder 分发,可通过 `add-coder stack set webapp` 启用/切换。
4
+ > 本 profile 为 ADD 案例研究(前端/全栈 Web 应用)沉淀的技术栈约束,从 project_rules.md 迁移而来。
5
+ > 启用后,AI 必须遵守本文件中全部约束;未启用时本文件不构成约束。
6
+
7
+ ## 数据库 Schema
8
+
9
+ Prisma schema 修改时:
10
+ - 新增模型必须有审计数据字段(Json 类型)
11
+ - 关联关系必须定义 `onDelete: Cascade`
12
+ - 使用 `@id @default(cuid())` 生成 ID
13
+ - 使用 `@updatedAt` 自动更新时间戳
14
+
15
+ ## Agent 节点
16
+
17
+ LangGraph 节点实现时:
18
+ - 必须通过 `wrapNodeWithAudit` 包装
19
+ - `inputSnapshot` 不能为空对象 `{}`
20
+ - 路由决策必须调用 `agentAuditRoute()`
21
+ - 检索节点必须记录证据链 diff
22
+
23
+ ## 代码质量
24
+
25
+ - TypeScript 编译必须通过(`npx tsc --noEmit`)
26
+ - ESLint 零 error(`npx eslint src/` 不得出现 error 级别问题,warning 逐步降低)
27
+ - 禁止 `any` 类型(必须显式定义)
28
+ - 禁止简化代码实现,一切以代码高质量为衡量标准
29
+ - 新增文件必须在项目已有目录结构内,遵循现有命名规范
30
+
31
+ ## 附录 A
32
+
33
+ ### A. {{projectName}} ADD-0.3 实现(AuditCallback)
34
+
35
+ {{projectName}} 通过 **LangChain `BaseCallbackHandler` 继承模式** 实现自动审计:
36
+
37
+ - `AuditCallback extends BaseCallbackHandler` — 继承 LangChain 标准回调接口
38
+ - `handleChainStart()` / `handleChainEnd()` / `handleChainError()` — 节点进入/退出/异常自动记录
39
+ - `handleLLMEnd()` — LLM 调用完成时记录 token 用量
40
+ - `handleToolStart()` / `handleToolEnd()` — Tool 调用完成时记录输入/输出
41
+
42
+ 注入方式(`src/agents/index.ts`):
43
+ ```typescript
44
+ const callback = new AuditCallback(traceId, userId)
45
+ agent.invoke(input, { callbacks: [callback] })
46
+ ```
47
+
48
+ **与 Layer 1 dev-logger 的关系**:
49
+ - Layer 1(`wrapNodeWithAudit`):console + file,仅开发环境,AI 助手消费
50
+ - Layer 2(`AuditCallback`):AuditLog 表,所有环境始终开启,最终用户消费
51
+ - 两者共存互补,互不干扰
52
+
53
+ **设计目标达成情况**:
54
+
55
+ | 目标 | 状态 |
56
+ |------|:----:|
57
+ | 自动记录 | ✅ |
58
+ | 不阻塞响应 | ✅ |
59
+ | 成功/失败等价 | ✅ |
60
+ | 节点过滤 | ✅ |
61
+ | traceId 全链追踪 | ✅ |
@@ -846,6 +846,10 @@ ADD-17 定义 HITL 如何在 guard 约束下运作(temporary.md 绕过 guard
846
846
 
847
847
  ## 项目技术约束
848
848
 
849
+ ### 技术栈约束(profile 引用)
850
+
851
+ {{stackReferenceLine}}
852
+
849
853
  ### 审计日志器模式
850
854
 
851
855
  每个业务域的审计日志器必须遵循 `audit-logger.ts` 的完整模式:
@@ -856,30 +860,6 @@ ADD-17 定义 HITL 如何在 guard 约束下运作(temporary.md 绕过 guard
856
860
  - `readRecentLogs()` / `clearLogs()` 读写函数
857
861
  - `ENABLE_FILE_LOG` 环境变量控制,开发环境默认启用
858
862
 
859
- ### 数据库 Schema
860
-
861
- Prisma schema 修改时:
862
- - 新增模型必须有审计数据字段(Json 类型)
863
- - 关联关系必须定义 `onDelete: Cascade`
864
- - 使用 `@id @default(cuid())` 生成 ID
865
- - 使用 `@updatedAt` 自动更新时间戳
866
-
867
- ### Agent 节点
868
-
869
- LangGraph 节点实现时:
870
- - 必须通过 `wrapNodeWithAudit` 包装
871
- - `inputSnapshot` 不能为空对象 `{}`
872
- - 路由决策必须调用 `agentAuditRoute()`
873
- - 检索节点必须记录证据链 diff
874
-
875
- ### 代码质量
876
-
877
- - TypeScript 编译必须通过(`npx tsc --noEmit`)
878
- - ESLint 零 error(`npx eslint src/` 不得出现 error 级别问题,warning 逐步降低)
879
- - 禁止 `any` 类型(必须显式定义)
880
- - 禁止简化代码实现,一切以代码高质量为衡量标准
881
- - 新增文件必须在项目已有目录结构内,遵循现有命名规范
882
-
883
863
  ---
884
864
 
885
865
  ## MCP 工具约束
@@ -890,7 +870,7 @@ LangGraph 节点实现时:
890
870
 
891
871
  AI 助手在生成任何代码前,必须先调用以下工具获取真实信息,不得凭记忆假设:
892
872
  - `get_project_context` — 获取项目结构、技术栈、可用脚本
893
- - `get_db_schema` — 获取 Prisma Schema 模型定义
873
+ - `get_db_schema` — 获取项目数据模型定义(如 Prisma Schema / ORM 模型)
894
874
  - `get_audit_logger_pattern` — 获取现有审计日志器模式
895
875
  - `find_related_docs` — 查找与变更相关的项目文档(ADD-0.1 文档先行)
896
876
 
@@ -961,34 +941,6 @@ Trae IDE 加载项目时自动连接 MCP 服务器。
961
941
 
962
942
  ### 附录
963
943
 
964
- #### A. {{projectName}} ADD-0.3 实现(AuditCallback
965
-
966
- {{projectName}} 通过 **LangChain `BaseCallbackHandler` 继承模式** 实现自动审计:
967
-
968
- - `AuditCallback extends BaseCallbackHandler` — 继承 LangChain 标准回调接口
969
- - `handleChainStart()` / `handleChainEnd()` / `handleChainError()` — 节点进入/退出/异常自动记录
970
- - `handleLLMEnd()` — LLM 调用完成时记录 token 用量
971
- - `handleToolStart()` / `handleToolEnd()` — Tool 调用完成时记录输入/输出
972
-
973
- 注入方式(`src/agents/index.ts`):
974
- ```typescript
975
- const callback = new AuditCallback(traceId, userId)
976
- agent.invoke(input, { callbacks: [callback] })
977
- ```
978
-
979
- **与 Layer 1 dev-logger 的关系**:
980
- - Layer 1(`wrapNodeWithAudit`):console + file,仅开发环境,AI 助手消费
981
- - Layer 2(`AuditCallback`):AuditLog 表,所有环境始终开启,最终用户消费
982
- - 两者共存互补,互不干扰
983
-
984
- **设计目标达成情况**:
985
-
986
- | 目标 | 状态 |
987
- |------|:----:|
988
- | 自动记录 | ✅ |
989
- | 不阻塞响应 | ✅ |
990
- | 成功/失败等价 | ✅ |
991
- | 节点过滤 | ✅ |
992
- | traceId 全链追踪 | ✅ |
944
+ > 附录 A{{projectName}} ADD-0.3 AuditCallback 实现,含 LangChain `BaseCallbackHandler` 模式)已移至技术栈 profile:`{{magicDir}}/rules/profiles/webapp-profile.md` §附录 A。未设置 webapp 技术栈时,该附录内容不构成约束。
993
945
 
994
946
  #### B. 历史参考(milktea 项目)
@@ -1,9 +1,13 @@
1
1
  import * as z from "zod/v4"
2
- import type { McpServer } from "@modelcontextprotocol/server"
2
+ import type { ToolRegistrar } from "./registrar.js"
3
3
  import { textResponse, errorResponse } from "../shared/response.js"
4
4
  import { prisma } from "../shared/prisma.js"
5
+ import { PROJECT_ID, PROJECT_ROOT } from "../shared/env.js"
5
6
 
6
- export function registerAuditTools(server: McpServer) {
7
+ export function registerAuditTools(server: ToolRegistrar) {
8
+
9
+ // args 窄化辅助:MCP 工具参数可能是 string | number | undefined,统一转 string
10
+ const s = (v: string | number | undefined): string => (typeof v === "number" ? String(v) : (v ?? ""))
7
11
 
8
12
  // ===== query_audit_logs (L992-1109) =====
9
13
  server.registerTool("query_audit_logs", {
@@ -21,16 +25,18 @@ export function registerAuditTools(server: McpServer) {
21
25
  try {
22
26
  const { targetType, action, targetId, planKeyword, keyword, sinceMinutes, limit = 20 } = args
23
27
  const where: Record<string, unknown> = {}
24
- if (sinceMinutes !== undefined) where.createdAt = { gte: new Date(Date.now() - sinceMinutes * 60 * 1000) }
25
- if (targetType) where.targetType = targetType; if (action) where.action = action; if (targetId) where.targetId = targetId; if (planKeyword) where.planKeyword = planKeyword
26
- if (keyword) where.OR = [{ action: { contains: keyword, mode: "insensitive" } },{ targetType: { contains: keyword, mode: "insensitive" } },{ targetId: { contains: keyword, mode: "insensitive" } },{ reason: { contains: keyword, mode: "insensitive" } },{ planKeyword: { contains: keyword, mode: "insensitive" } }]
27
- const logs = await prisma.devOperation.findMany({ where, orderBy: { createdAt: "desc" }, take: Math.min(limit, 100), include: { user: { select: { username: true } } } })
28
+ if (sinceMinutes !== undefined) where.createdAt = { gte: new Date(Date.now() - Number(sinceMinutes) * 60 * 1000) }
29
+ if (targetType) where.targetType = s(targetType); if (action) where.action = s(action); if (targetId) where.targetId = s(targetId); if (planKeyword) where.planKeyword = s(planKeyword)
30
+ const kw = s(keyword)
31
+ if (kw) where.OR = [{ action: { contains: kw, mode: "insensitive" } },{ targetType: { contains: kw, mode: "insensitive" } },{ targetId: { contains: kw, mode: "insensitive" } },{ reason: { contains: kw, mode: "insensitive" } },{ planKeyword: { contains: kw, mode: "insensitive" } }]
32
+ type AuditLogRow = { createdAt: Date; action: string; targetType: string; targetId: string | null; reason: string | null; planKeyword: string | null }
33
+ const logs = await (prisma.devOperation as Record<string, (...a: unknown[]) => unknown>).findMany({ where, orderBy: { createdAt: "desc" }, take: Math.min(Number(limit) || 20, 100), include: { user: { select: { username: true } } } }) as unknown as AuditLogRow[]
28
34
  if (logs.length === 0) {
29
35
  const f: string[] = []; if (targetType) f.push(`targetType=${targetType}`); if (action) f.push(`action=${action}`); if (targetId) f.push(`targetId=${targetId}`); if (planKeyword) f.push(`planKeyword=${planKeyword}`); if (keyword) f.push(`keyword="${keyword}"`); if (sinceMinutes !== undefined) f.push(`sinceMinutes=${sinceMinutes}`)
30
36
  return textResponse(`=== 开发操作审计日志 ===\n\n未找到匹配的审计记录(条件: ${f.length > 0 ? f.join(", ") : "无条件"})。\n\n可能原因: 数据库未运行、尚无相关开发操作记录、或 filter 过于严格。`)
31
37
  }
32
38
  const fl: string[] = []; if (targetType) fl.push(`targetType=${targetType}`); if (action) fl.push(`action=${action}`); if (targetId) fl.push(`targetId=${targetId}`); if (planKeyword) fl.push(`planKeyword=${planKeyword}`); if (keyword) fl.push(`keyword="${keyword}"`); if (sinceMinutes !== undefined) fl.push(`最近${sinceMinutes}分钟`)
33
- const lines = [`=== 开发操作审计日志 (条件: ${fl.length > 0 ? fl.join(", ") : "无过滤条件(最近全部)"}) ===`, `共 ${logs.length} 条记录`]
39
+ const lines = [`=== 开发操作审计日志 (条件: ${fl.length > 0 ? fl.join(", ") : "无过滤条件(最近全部)"}) ===`, `服务项目: ${PROJECT_ID} (${PROJECT_ROOT})`, `共 ${logs.length} 条记录`]
34
40
  for (let i = 0; i < logs.length; i++) {
35
41
  const l = logs[i]; lines.push(`[${i+1}] ${l.createdAt.toISOString()}`, ` action: ${l.action} | targetType: ${l.targetType} | targetId: ${l.targetId || "(无)"}`)
36
42
  if (l.reason) lines.push(` reason: ${l.reason}`); if (l.planKeyword) lines.push(` planKeyword: ${l.planKeyword}`)
@@ -56,16 +62,19 @@ export function registerAuditTools(server: McpServer) {
56
62
  }, async (args: Record<string, string | number | undefined>, _ctx: unknown) => {
57
63
  try {
58
64
  const { action, targetType, targetId, planKeyword, beforeState, afterState, reason } = args
65
+ const tId = s(targetId)
59
66
  const pathWarnings: string[] = []
60
- if (targetId && (targetId.startsWith("/") || /^[A-Z]:\\/.test(targetId))) pathWarnings.push(`⚠️ targetId 使用了绝对路径: "${targetId}"。请使用相对路径。`)
67
+ if (tId && (tId.startsWith("/") || /^[A-Z]:\\/.test(tId))) pathWarnings.push(`⚠️ targetId 使用了绝对路径: "${tId}"。请使用相对路径。`)
61
68
  let parsedBefore: unknown, parsedAfter: unknown
62
- try { if (beforeState) parsedBefore = JSON.parse(beforeState); if (afterState) parsedAfter = JSON.parse(afterState) } catch { return errorResponse("beforeState/afterState 必须是有效的 JSON 字符串。") }
63
- let systemUser = await (prisma.addUser as Record<string, (...a: unknown[]) => unknown>).findUnique({ where: { username: "ai-assistant" }, select: { id: true } })
64
- if (!systemUser) systemUser = await (prisma.addUser as Record<string, (...a: unknown[]) => unknown>).create({ data: { id: "ai-assistant", username: "ai-assistant", email: "ai-assistant@internal" }, select: { id: true } })
65
- const log = await (prisma.devOperation as Record<string, (...a: unknown[]) => unknown>).create({ data: { userId: systemUser.id, planKeyword: planKeyword || "unknown", action, targetType, targetId: targetId || "unknown", beforeState: parsedBefore ?? null, afterState: parsedAfter ?? null, reason: reason || null } })
66
- const lines = [`✅ 开发操作已记录`, ` ID: ${log.id}`, ` action: ${action}`, ` targetType: ${targetType}`, ` targetId: ${targetId || "unknown"}`, ` planKeyword: ${planKeyword || "unknown"}`, ` createdAt: ${log.createdAt.toISOString()}`]
69
+ try { if (beforeState) parsedBefore = JSON.parse(s(beforeState)); if (afterState) parsedAfter = JSON.parse(s(afterState)) } catch { return errorResponse("beforeState/afterState 必须是有效的 JSON 字符串。") }
70
+ type AddUserRow = { id: string }
71
+ let systemUser = await (prisma.addUser as Record<string, (...a: unknown[]) => unknown>).findUnique({ where: { username: "ai-assistant" }, select: { id: true } }) as unknown as AddUserRow | null
72
+ if (!systemUser) systemUser = await (prisma.addUser as Record<string, (...a: unknown[]) => unknown>).create({ data: { id: "ai-assistant", username: "ai-assistant", email: "ai-assistant@internal" }, select: { id: true } }) as unknown as AddUserRow
73
+ type DevLogRow = { id: string; createdAt: Date }
74
+ const log = await (prisma.devOperation as Record<string, (...a: unknown[]) => unknown>).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 } }) as unknown as DevLogRow
75
+ 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()}`]
67
76
  if (pathWarnings.length > 0) { lines.push(""); lines.push(...pathWarnings) }
68
- lines.push("", `📋 落库回查(必须执行):`, targetId ? ` query_audit_logs({ targetId: "${targetId}" }) — 确认本条记录已写入` : "")
77
+ lines.push("", `📋 落库回查(必须执行):`, tId ? ` query_audit_logs({ targetId: "${tId}" }) — 确认本条记录已写入` : "")
69
78
  return textResponse(lines.join("\n"))
70
79
  } catch (error) { return errorResponse(`记录开发操作失败: ${error instanceof Error ? error.message : String(error)}`) }
71
80
  })
@@ -1,5 +1,5 @@
1
1
  import * as z from "zod/v4"
2
- import type { McpServer } from "@modelcontextprotocol/server"
2
+ import type { ToolRegistrar } from "./registrar.js"
3
3
  import { readFile } from "fs/promises"
4
4
  import { readdir } from "fs/promises"
5
5
  import { stat } from "fs/promises"
@@ -9,7 +9,7 @@ import { textResponse, errorResponse } from "../shared/response.js"
9
9
  import { readFileSafe, readdirRecursive } from "../shared/fs.js"
10
10
  import { PROJECT_ROOT, MAGIC_DIR } from "../shared/fs.js"
11
11
 
12
- export function registerContextTools(server: McpServer) {
12
+ export function registerContextTools(server: ToolRegistrar) {
13
13
 
14
14
  // ===== get_project_context (L166-430) =====
15
15
  server.registerTool(
@@ -66,6 +66,24 @@ export function registerContextTools(server: McpServer) {
66
66
  }
67
67
  parts.push("")
68
68
  }
69
+ // 技术栈 profile 追加(D8):stack.json → profiles/{stack}-profile.md 存在 → 追加全文
70
+ const stackRaw = await readFileSafe(join(PROJECT_ROOT, MAGIC_DIR, "stack.json"))
71
+ if (stackRaw) {
72
+ try {
73
+ const stackDoc = JSON.parse(stackRaw) as { stack?: unknown }
74
+ if (typeof stackDoc.stack === "string" && stackDoc.stack) {
75
+ const profileContent = await readFileSafe(join(PROJECT_ROOT, MAGIC_DIR, "rules", "profiles", `${stackDoc.stack}-profile.md`))
76
+ if (profileContent) {
77
+ parts.push(`=== 技术栈约束 (${stackDoc.stack}-profile) ===`)
78
+ parts.push(profileContent)
79
+ parts.push("")
80
+ } else {
81
+ parts.push(`⚠️ profile 文件缺失: ${MAGIC_DIR}/rules/profiles/${stackDoc.stack}-profile.md(技术栈约束未生效)`)
82
+ parts.push("")
83
+ }
84
+ }
85
+ } catch { /* stack.json 损坏 → 中性处理,仅返回 project_rules.md */ }
86
+ }
69
87
  }
70
88
 
71
89
  if (scope === "all" || scope === "structure" || scope === "add-state") {