add-coder 0.3.26 → 0.3.27

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 (132) hide show
  1. package/README.md +36 -10
  2. package/dist/index.js +481 -209
  3. package/package.json +14 -4
  4. package/templates/.add-coder-src-hash.json +129 -106
  5. package/templates/adapters/claude/hooks/doc-format-guard.sh +112 -31
  6. package/templates/adapters/claude/hooks/lib/common.sh +46 -87
  7. package/templates/adapters/claude/hooks/lib/context-inject.sh +2 -2
  8. package/templates/adapters/claude/hooks/lib/notify.sh +6 -4
  9. package/templates/adapters/claude/hooks/lib/preload-templates.sh +27 -2
  10. package/templates/adapters/claude/hooks/lib/state-detect.sh +8 -112
  11. package/templates/adapters/claude/hooks/lib/vocabulary.sh +13 -7
  12. package/templates/adapters/claude/hooks/prompt-submit.sh +1 -1
  13. package/templates/adapters/claude/hooks/stop-check.sh +9 -0
  14. package/templates/adapters/codex/config.toml.example +12 -0
  15. package/templates/adapters/codex/hooks/doc-format-guard.sh +121 -39
  16. package/templates/adapters/codex/hooks/lib/common.sh +47 -99
  17. package/templates/adapters/codex/hooks/lib/context-inject.sh +4 -4
  18. package/templates/adapters/codex/hooks/lib/notify.sh +6 -4
  19. package/templates/adapters/codex/hooks/lib/preload-templates.sh +27 -6
  20. package/templates/adapters/codex/hooks/lib/state-detect.sh +5 -114
  21. package/templates/adapters/codex/hooks/lib/vocabulary.sh +3 -8
  22. package/templates/adapters/codex/hooks/notification.sh +3 -18
  23. package/templates/adapters/codex/hooks/post-tool-use.sh +24 -119
  24. package/templates/adapters/codex/hooks/pre-tool-use.sh +97 -184
  25. package/templates/adapters/codex/hooks/prompt-submit.sh +8 -3
  26. package/templates/adapters/codex/hooks/session-start.sh +19 -9
  27. package/templates/adapters/codex/hooks/stop-check.sh +44 -18
  28. package/templates/adapters/codex/hooks.json +67 -1
  29. package/templates/adapters/qoder/hooks/doc-format-guard.sh +119 -38
  30. package/templates/adapters/qoder/hooks/lib/common.sh +49 -87
  31. package/templates/adapters/qoder/hooks/lib/context-inject.sh +2 -2
  32. package/templates/adapters/qoder/hooks/lib/notify.sh +6 -4
  33. package/templates/adapters/qoder/hooks/lib/preload-templates.sh +27 -2
  34. package/templates/adapters/qoder/hooks/lib/state-detect.sh +8 -112
  35. package/templates/adapters/qoder/hooks/lib/vocabulary.sh +13 -7
  36. package/templates/adapters/qoder/hooks/prompt-submit.sh +1 -1
  37. package/templates/adapters/qoder/hooks/session-start.sh +1 -1
  38. package/templates/adapters/qoder/hooks/stop-check.sh +11 -0
  39. package/templates/adapters/trae/hooks/doc-format-guard.sh +120 -38
  40. package/templates/adapters/trae/hooks/lib/common.sh +46 -87
  41. package/templates/adapters/trae/hooks/lib/context-inject.sh +2 -2
  42. package/templates/adapters/trae/hooks/lib/notify.sh +5 -3
  43. package/templates/adapters/trae/hooks/lib/preload-templates.sh +27 -2
  44. package/templates/adapters/trae/hooks/lib/state-detect.sh +8 -115
  45. package/templates/adapters/trae/hooks/lib/vocabulary.sh +13 -7
  46. package/templates/adapters/trae/hooks/pre-tool-use.sh +14 -8
  47. package/templates/adapters/trae/hooks/stop-check.sh +9 -0
  48. package/templates/adapters/vscode/hooks/doc-format-guard.sh +112 -31
  49. package/templates/adapters/vscode/hooks/lib/common.sh +46 -87
  50. package/templates/adapters/vscode/hooks/lib/context-inject.sh +2 -2
  51. package/templates/adapters/vscode/hooks/lib/notify.sh +6 -4
  52. package/templates/adapters/vscode/hooks/lib/preload-templates.sh +27 -2
  53. package/templates/adapters/vscode/hooks/lib/state-detect.sh +8 -112
  54. package/templates/adapters/vscode/hooks/lib/vocabulary.sh +13 -7
  55. package/templates/adapters/vscode/hooks/notification.sh +3 -5
  56. package/templates/adapters/vscode/hooks/prompt-submit.sh +1 -1
  57. package/templates/adapters/vscode/hooks/stop-check.sh +9 -0
  58. package/templates/core/docs/ADD-governance-claude-code.md +1 -1
  59. package/templates/core/docs/ADD-governance-codex.md +1 -1
  60. package/templates/core/docs/ADD-governance-qoder-cn.md +1 -1
  61. package/templates/core/docs/ADD-governance-trae.md +1 -1
  62. package/templates/core/docs/ADD-governance-vscode-copilot.md +1 -1
  63. package/templates/core/hooks/doc-format-guard.sh +120 -38
  64. package/templates/core/hooks/lib/common.sh +46 -87
  65. package/templates/core/hooks/lib/context-inject.sh +2 -2
  66. package/templates/core/hooks/lib/notify.sh +5 -3
  67. package/templates/core/hooks/lib/preload-templates.sh +27 -2
  68. package/templates/core/hooks/lib/state-detect.sh +8 -115
  69. package/templates/core/hooks/lib/vocabulary.sh +13 -7
  70. package/templates/core/hooks/pre-tool-use.sh +14 -8
  71. package/templates/core/hooks/stop-check.sh +9 -0
  72. package/templates/core/prisma/add.prisma +140 -82
  73. package/templates/core/rules/project_rules.md +27 -24
  74. package/templates/core/scripts/db-ensure.sh +8 -0
  75. package/templates/core/scripts/gen-plan-index.sh +4 -15
  76. package/templates/core/scripts/mcp-server/notifications/hitl.ts +9 -1
  77. package/templates/core/scripts/mcp-server/notifications/hook.ts +25 -9
  78. package/templates/core/scripts/mcp-server/resources/hitl-approval-widget.ts +34 -0
  79. package/templates/core/scripts/mcp-server/resources/hook-events-report.ts +14 -2
  80. package/templates/core/scripts/mcp-server/resources/index.ts +2 -0
  81. package/templates/core/scripts/mcp-server/sampling/review.ts +25 -10
  82. package/templates/core/scripts/mcp-server/shared/db-types.ts +33 -8
  83. package/templates/core/scripts/mcp-server/shared/dps-scoring.strategy.ts +1 -1
  84. package/templates/core/scripts/mcp-server/shared/env.ts +28 -8
  85. package/templates/core/scripts/mcp-server/shared/fs.ts +3 -0
  86. package/templates/core/scripts/mcp-server/shared/hitl-interaction.strategy.ts +2 -3
  87. package/templates/core/scripts/mcp-server/shared/hitl-lifecycle-mutation.ts +129 -0
  88. package/templates/core/scripts/mcp-server/shared/hitl-proposal-mutation.ts +98 -0
  89. package/templates/core/scripts/mcp-server/shared/hitl-ui.ts +3 -0
  90. package/templates/core/scripts/mcp-server/shared/plan-lifecycle-events.ts +62 -0
  91. package/templates/core/scripts/mcp-server/shared/plan-lifecycle-mutation.ts +78 -0
  92. package/templates/core/scripts/mcp-server/shared/plan-lifecycle-subscriber.ts +145 -0
  93. package/templates/core/scripts/mcp-server/shared/plan-lifecycle.ts +153 -0
  94. package/templates/core/scripts/mcp-server/shared/plan-round-events.ts +56 -0
  95. package/templates/core/scripts/mcp-server/shared/plan-round-mutation.ts +143 -0
  96. package/templates/core/scripts/mcp-server/shared/plan-round-store.ts +47 -0
  97. package/templates/core/scripts/mcp-server/shared/plan-round-subscriber.ts +99 -0
  98. package/templates/core/scripts/mcp-server/shared/plan-status-store.ts +37 -0
  99. package/templates/core/scripts/mcp-server/shared/plan-tracking-mutation.ts +71 -0
  100. package/templates/core/scripts/mcp-server/shared/prisma.ts +12 -6
  101. package/templates/core/scripts/mcp-server/shared/project-root-strategy.ts +1 -2
  102. package/templates/core/scripts/mcp-server/shared/quant-recheck.strategy.ts +16 -0
  103. package/templates/core/scripts/mcp-server/shared/redact.ts +28 -0
  104. package/templates/core/scripts/mcp-server/shared/response.ts +3 -2
  105. package/templates/core/scripts/mcp-server/shared/runtime-context.ts +77 -0
  106. package/templates/core/scripts/mcp-server/shared/schema-topology.ts +159 -0
  107. package/templates/core/scripts/mcp-server/tools/audit.ts +49 -7
  108. package/templates/core/scripts/mcp-server/tools/context.ts +117 -121
  109. package/templates/core/scripts/mcp-server/tools/contract.ts +34 -9
  110. package/templates/core/scripts/mcp-server/tools/gateway/check_add_route_status.ts +50 -3
  111. package/templates/core/scripts/mcp-server/tools/gateway/check_doc_similarity.ts +218 -0
  112. package/templates/core/scripts/mcp-server/tools/gateway/check_dps.ts +22 -7
  113. package/templates/core/scripts/mcp-server/tools/gateway/check_rahs.ts +75 -4
  114. package/templates/core/scripts/mcp-server/tools/gateway/check_spec_sync.ts +19 -6
  115. package/templates/core/scripts/mcp-server/tools/gateway/index.ts +2 -0
  116. package/templates/core/scripts/mcp-server/tools/hitl.ts +160 -75
  117. package/templates/core/scripts/mcp-server/tools/hook-event-report.ts +9 -2
  118. package/templates/core/scripts/mcp-server/tools/index.ts +69 -3
  119. package/templates/core/scripts/mcp-server/tools/plan.ts +140 -15
  120. package/templates/core/scripts/mcp-server/tools/review.ts +44 -15
  121. package/templates/core/scripts/mcp-server.ts +60 -1
  122. package/templates/core/scripts/plan-status-bridge.ts +26 -0
  123. package/templates/core/skills/add-paradigm/SKILL.md +2 -2
  124. package/templates/core/templates/add-route-template.schema.json +24 -56
  125. package/templates/core/templates/collab-contract-template.md +1 -1
  126. package/templates/core/templates/handoff-multi-round-template.schema.json +48 -0
  127. package/templates/core/templates/handoff-single-round-template.schema.json +92 -0
  128. package/templates/core/templates/handoff-single-round.schema.json +1 -1
  129. package/templates/core/templates/hitl-approval-widget.html +222 -37
  130. package/templates/core/templates/spec-template.schema.json +9 -46
  131. package/templates/core/templates/standard-plan-template.schema.json +1 -1
  132. package/templates/core/templates/tasks-template.schema.json +13 -54
@@ -1,43 +1,54 @@
1
1
  // add.prisma — ADD 治理模型,独立于用户业务表
2
2
 
3
3
  model AddUser {
4
- id String @id @default(cuid())
5
- username String @unique
6
- email String?
7
- devOperations DevOperation[]
8
- auditLogs AuditLog[]
4
+ id String @id @default(cuid())
5
+ username String @unique
6
+ email String?
7
+ devOperations DevOperation[]
8
+ auditLogs AuditLog[]
9
9
  }
10
10
 
11
11
  model DevOperation {
12
- id String @id @default(cuid())
13
- userId String
14
- user AddUser @relation(fields: [userId], references: [id], onDelete: Cascade)
15
- planKeyword String
16
- action String
17
- targetType String
18
- targetId String
19
- beforeState Json?
20
- afterState Json?
21
- reason String?
22
- createdAt DateTime @default(now())
12
+ id String @id @default(cuid())
13
+ userId String
14
+ user AddUser @relation(fields: [userId], references: [id], onDelete: Cascade)
15
+ projectKey String @default("legacy-unknown")
16
+ producerAdapterKey String @default("legacy-unknown")
17
+ contextId String @default("legacy-unknown")
18
+ toolName String @default("record_dev_operation")
19
+ operationKey String @default(cuid())
20
+ planKeyword String
21
+ action String
22
+ targetType String
23
+ targetId String
24
+ beforeState Json?
25
+ afterState Json?
26
+ reason String?
27
+ createdAt DateTime @default(now())
23
28
 
29
+ @@unique([projectKey, producerAdapterKey, toolName, operationKey])
24
30
  @@index([planKeyword])
31
+ @@index([projectKey, producerAdapterKey, planKeyword])
25
32
  }
26
33
 
27
34
  model AuditLog {
28
- id String @id @default(cuid())
29
- userId String
30
- user AddUser @relation(fields: [userId], references: [id], onDelete: Cascade)
31
- action String
32
- targetType String
33
- targetId String
34
- traceId String?
35
- beforeState Json?
36
- afterState Json?
37
- reason String?
38
- createdAt DateTime @default(now())
35
+ id String @id @default(cuid())
36
+ userId String
37
+ user AddUser @relation(fields: [userId], references: [id], onDelete: Cascade)
38
+ projectKey String @default("legacy-unknown")
39
+ producerAdapterKey String @default("legacy-unknown")
40
+ contextId String @default("legacy-unknown")
41
+ action String
42
+ targetType String
43
+ targetId String
44
+ traceId String?
45
+ beforeState Json?
46
+ afterState Json?
47
+ reason String?
48
+ createdAt DateTime @default(now())
39
49
 
40
50
  @@index([traceId])
51
+ @@index([projectKey, producerAdapterKey, contextId])
41
52
  }
42
53
 
43
54
  // ===== HITL 人机审核架构 =====
@@ -61,65 +72,85 @@ enum ReviewType {
61
72
  RUNTIME
62
73
  }
63
74
 
75
+ enum PlanLifecycleStatus {
76
+ DRAFT
77
+ ACTIVE
78
+ BLOCKED
79
+ REJECTED
80
+ CLOSED
81
+ ABANDONED
82
+ }
83
+
64
84
  model HitlRecord {
65
- id String @id @default(cuid())
85
+ id String @id @default(cuid())
86
+ projectKey String @default("legacy-unknown")
87
+ adapterKey String @default("legacy-unknown")
66
88
  planName String
67
- plan PlanRecord @relation(fields: [planName], references: [planName])
68
- round Int @default(1)
69
- type HitlType @default(PLAN)
70
- status HitlStatus @default(DRAFT)
89
+ plan PlanRecord @relation(fields: [projectKey, adapterKey, planName], references: [projectKey, adapterKey, planName], onDelete: Cascade)
90
+ round Int @default(1)
91
+ type HitlType @default(PLAN)
92
+ status HitlStatus @default(DRAFT)
71
93
  proposalPath String?
72
94
  approvedAt DateTime?
73
95
  rejectedAt DateTime?
74
96
  rejectReason String?
75
- createdBy String @default("ai-assistant")
76
- createdAt DateTime @default(now())
77
- updatedAt DateTime @updatedAt
97
+ createdBy String @default("ai-assistant")
98
+ createdAt DateTime @default(now())
99
+ updatedAt DateTime @updatedAt
78
100
 
79
- @@unique([planName, round])
80
- @@index([planName])
101
+ @@unique([projectKey, adapterKey, planName, type, round])
102
+ @@index([projectKey, adapterKey, planName])
81
103
  @@index([status])
82
104
  }
83
105
 
84
106
  model PlanRecord {
85
- id String @id @default(cuid())
86
- planName String @unique
87
- planPath String
88
- specPath String?
89
- tasksPath String?
90
- checklistPath String?
91
- addRoutePath String?
92
- totalTasks Int @default(0)
93
- doneTasks Int @default(0)
94
- checklistT Int @default(0)
95
- checklistTDone Int @default(0)
96
- checklistR Int @default(0)
97
- planKeyword String?
107
+ id String @id @default(cuid())
108
+ projectKey String @default("legacy-unknown")
109
+ adapterKey String @default("legacy-unknown")
110
+ planName String
111
+ lifecycle PlanLifecycleStatus @default(DRAFT)
112
+ revision Int @default(0)
113
+ planPath String
114
+ specPath String?
115
+ tasksPath String?
116
+ checklistPath String?
117
+ addRoutePath String?
118
+ totalTasks Int @default(0)
119
+ doneTasks Int @default(0)
120
+ checklistT Int @default(0)
121
+ checklistTDone Int @default(0)
122
+ checklistR Int @default(0)
123
+ planKeyword String?
98
124
  // 并发协作契约:该 Plan 在契约中的角色(MASTER=总控 / SUB=子 Plan)
99
- contractRole ContractRole?
125
+ contractRole ContractRole?
100
126
  // 所属契约名(与 CollabContract.contractName 对应,反查用)
101
- contractName String?
127
+ contractName String?
102
128
  // 作为总控 Plan 持有的契约(masterPlanName 外键方向)
103
- masterContract CollabContract?
129
+ masterContract CollabContract? @relation("MasterPlan")
130
+ contractBindings CollaborationPlanBinding[]
104
131
  // DPS 四维评分历史(check_dps 调用后回写,供 FFT 自适应权重消费)
105
- dpsSemScore Int? // 语义相关性 0-100
106
- dpsEntropyScore Int? // 信息熵匹配 0-100
107
- dpsCpmScore Int? // CPM 关键路径 0-100
108
- dpsStructScore Int? // 结构完整度 0-100
109
- dpsComposite Int? // 加权 DPS 总分 0-100
110
- hitls HitlRecord[]
111
- reviews ReviewRecord[]
112
- createdAt DateTime @default(now())
113
- updatedAt DateTime @updatedAt
114
-
132
+ dpsSemScore Int? // 语义相关性 0-100
133
+ dpsEntropyScore Int? // 信息熵匹配 0-100
134
+ dpsCpmScore Int? // CPM 关键路径 0-100
135
+ dpsStructScore Int? // 结构完整度 0-100
136
+ dpsComposite Int? // 加权 DPS 总分 0-100
137
+ hitls HitlRecord[]
138
+ reviews ReviewRecord[]
139
+ createdAt DateTime @default(now())
140
+ updatedAt DateTime @updatedAt
141
+
142
+ @@unique([projectKey, adapterKey, planName])
115
143
  @@index([planKeyword])
116
144
  @@index([contractName])
145
+ @@index([projectKey, adapterKey, lifecycle])
117
146
  }
118
147
 
119
148
  model ReviewRecord {
120
149
  id String @id @default(cuid())
150
+ projectKey String @default("legacy-unknown")
151
+ adapterKey String @default("legacy-unknown")
121
152
  planName String
122
- plan PlanRecord @relation(fields: [planName], references: [planName], onDelete: Cascade)
153
+ plan PlanRecord @relation(fields: [projectKey, adapterKey, planName], references: [projectKey, adapterKey, planName], onDelete: Cascade)
123
154
  type ReviewType @default(PLAN_REVIEW)
124
155
  reviewPath String?
125
156
  p0Count Int @default(0)
@@ -128,33 +159,60 @@ model ReviewRecord {
128
159
  createdAt DateTime @default(now())
129
160
  updatedAt DateTime @updatedAt
130
161
 
131
- @@index([planName])
162
+ @@index([projectKey, adapterKey, planName])
132
163
  @@index([type])
133
164
  }
134
165
 
135
166
  enum ContractRole {
136
- MASTER // 总控 Plan(契约持有者)
137
- SUB // 子 Plan(契约实施单元)
167
+ MASTER // 总控 Plan(契约持有者)
168
+ SUB // 子 Plan(契约实施单元)
169
+ }
170
+
171
+ enum CollaborationBindingRole {
172
+ MASTER
173
+ MEMBER
138
174
  }
139
175
 
140
176
  // 并发协作契约(多智能体并行实施规则集,对应 collab-contract 文档)
141
177
  model CollabContract {
142
- id String @id @default(cuid())
143
- contractName String @unique
178
+ id String @id @default(cuid())
179
+ projectKey String @default("legacy-unknown")
180
+ ownerAdapterKey String @default("legacy-unknown")
181
+ contractName String
144
182
  contractPath String
145
183
  // 外键:总控 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
184
+ masterProjectKey String @default("legacy-unknown")
185
+ masterAdapterKey String @default("legacy-unknown")
186
+ masterPlanName String
187
+ masterPlan PlanRecord @relation("MasterPlan", fields: [masterProjectKey, masterAdapterKey, masterPlanName], references: [projectKey, adapterKey, planName], onDelete: Restrict)
188
+ bindings CollaborationPlanBinding[]
189
+ participants Json // [{role, platformEntity, boundPlan, planKeyword, description}]
190
+ abilityMatrix Json? // {能力: {专家: boolean}}
191
+ stages Json // [{stage, expert, trigger, parallelism}]
192
+ dependencyGraph String? // ASCII + 机器可读声明
193
+ fileBoundaries Json // [{expert, exclusiveDomain, forbidden, isolationMode}] isolationMode: file/worktree
194
+ completionCriteria Json? // {专家: 完成标志}
195
+ status String @default("ACTIVE") // ACTIVE / SUPERSEDED
196
+ version Int @default(1)
197
+ createdAt DateTime @default(now())
198
+ updatedAt DateTime @updatedAt
199
+
200
+ @@unique([projectKey, contractName])
201
+ @@unique([masterProjectKey, masterAdapterKey, masterPlanName])
202
+ @@index([projectKey, ownerAdapterKey])
203
+ }
158
204
 
159
- @@index([masterPlanName])
205
+ model CollaborationPlanBinding {
206
+ id String @id @default(cuid())
207
+ contractId String
208
+ contract CollabContract @relation(fields: [contractId], references: [id], onDelete: Cascade)
209
+ projectKey String
210
+ adapterKey String
211
+ planName String
212
+ plan PlanRecord @relation(fields: [projectKey, adapterKey, planName], references: [projectKey, adapterKey, planName], onDelete: Cascade)
213
+ role CollaborationBindingRole @default(MEMBER)
214
+ createdAt DateTime @default(now())
215
+
216
+ @@unique([contractId, projectKey, adapterKey, planName])
217
+ @@index([projectKey, adapterKey, planName])
160
218
  }
@@ -26,8 +26,8 @@
26
26
  | P2 | ADD-13 DPS 上游文档质量闸门 | Plan 概括度 → Review 注意力稀释 → Specs 遗漏 → 实现偏差。`check_dps`(DPS ≥ {{dpsPass}})在 Step 0 末尾量化阻断 | ✅ 已实现 | 2026-06-11 引入 |
27
27
  | P2 | ADD-14 RAHS 下游执行健康度闸门 | 范围保真度 + 类型安全 + 审计完整度 + Spec 合规 + 阶段对称性。`check_rahs`(RAHS ≥ 90)在 Step 4/8 量化阻断 | ✅ 已实现 | 2026-06-11 引入 |
28
28
  | P2 | ADD-15 add-route 闭环自检 | Step 3 代码完成后必须调用 `check_add_route_completeness` 扫描 add-route Step 完成度,防止执行遗漏。返回 complete 方可进入 Step 3.5 | ✅ 已实现 | 2026-06-11 引入 |
29
- | P2 | ADD-17 HITL 磋商临时文件机制 | doc-format-guard 要求 Plan/Review 文件包含完整章节才放行,HITL 第一步只写总览表会被 guard 阻断。用 `{name}.temporary.md` 做磋商(不受 guard 检查),人类拍板后再写正式文件并删除 temporary.md | ✅ 已实现 | 2026-07-23 引入 |
30
- | P2 | ADD-18 HITL 人机审核强制规则 | Plan/Review 必须经过 HITL 审批(`create_hitl` → 人工 tongyi/bohui → `update_hitl` 确认)。无 `.hitl-tongyi-{planName}` 哨兵时 pre-tool-use hook 阻断写入正式文件。提案文件按 hitl-template.md 生成 + schema 校验 | ✅ 已实现 | 2026-07-27 引入 |
29
+ | P2 | ADD-17 HITL 提案载体与状态机 | `create_hitl` 接收 `dimensions`,创建 `HitlRecord(DRAFT)` `*.hitl.md` 提案;人工拍板后由 `update_hitl` 写入终态,正式文档仅在 `status_hitl=TONGYI` 后生成 | ✅ 已实现 | 2026-07-23 引入,2026-08-12 对齐 MCP 实现 |
30
+ | P2 | ADD-18 HITL 人机审核强制规则 | Plan、方案 Review、实现 Review 必须经过 `create_hitl` → 人工 tongyi/bohui → `update_hitl`;`TONGYI` 同步写 DB、提案状态和 `.tongyi-{planName}` 哨兵 | ✅ 已实现 | 2026-07-27 引入 |
31
31
 
32
32
 
33
33
  ### P0 规则:不可跳过
@@ -68,7 +68,7 @@ AI 助手必须先恢复到基线、找到对应的 SKILL 文件并按步骤执
68
68
  | ADD-14 | `add-paradigm` Step 4 末端 + Step 8 收敛 + MCP `check_rahs` | Step 4 合规检查 / Step 8 收敛判定 |
69
69
  | ADD-15 | `add-paradigm` Step 3.6 + MCP `check_add_route_completeness` | Step 3 代码完成后自检 |
70
70
  | ADD-16 | `add-paradigm` Step 9 | runtime-fix plan 收敛后,关闭 gateway.md 运行时发现 |
71
- | ADD-17 | `add-paradigm` 独立能力:生成 Plan / Step 0 | Plan/Review HITL 磋商时(写入 temporary.md 绕过 guard,拍板后写正式文件) |
71
+ | ADD-17 | `add-paradigm` 独立能力:生成 Plan / Step 0 / Step 3.5 | Plan/Review HITL:整理 dimensions create_hitl 生成提案 → update_hitl 拍板 → status_hitl 校验终态 |
72
72
  | ADD-18 | `add-paradigm` 独立能力:生成 Plan + Step 0 + pre-tool-use §C | HITL 审批强制:create_hitl → 人工 tongyi/bohui → update_hitl 确认;hook 哨兵检查 |
73
73
 
74
74
  ---
@@ -783,45 +783,47 @@ ADD-0.1 要求"文档先行"(先改文档再改代码)。ADD-16 是对 ADD-0
783
783
 
784
784
  ---
785
785
 
786
- ## ADD-17:HITL 磋商临时文件机制
786
+ ## ADD-17:HITL 提案载体与状态机
787
787
 
788
- **Plan Review HITL 磋商必须在写入正式文件之前完成,但 doc-format-guard 要求 `.qoder/plans/` `.qoder/reviews/` 下的文件包含完整章节才放行。HITL 第一步只写总览表、不写正文——会被 guard 阻断。**
788
+ **Plan、方案 Review 和实现 Review 在写入正式文件前,必须通过 `create_hitl` 建立结构化审批提案。`*.hitl.md` `HitlRecord` 是同一轮审批的文件/数据库双通道载体;正式文档不得替代审批提案。**
789
789
 
790
790
  ### 流程
791
791
 
792
792
  ```
793
- ① AI {name}.temporary.md(只含 HITL 总览表,放项目根目录,不受 guard 检查)
794
- 人类审阅 HITL 逐行拍板(同意/调整)
795
- AI 根据确认后的 HITL 表生成完整 Plan/Review 写入正式路径(guard 放行)
796
- 删除 temporary.md
793
+ ① AI 读取对应模板并将待拍板发现整理为 dimensions
794
+ create_hitl({ planName, type, dimensions }) HitlRecord(DRAFT) + plans/{YYYY-MM}/{DD}/{planName}.hitl.md
795
+ 人类审阅提案并逐项拍板 update_hitl({ planName, type, status })
796
+ status_hitl 确认 TONGYI → 生成完整 Plan/Review 并写入正式路径
797
+ ⑤ BOHUI → 修订 dimensions 后调用 create_hitl 创建 round+1
797
798
  ```
798
799
 
799
800
  ### 适用范围
800
801
 
801
- | 文档类型 | temporary 磋商 | 正式写入路径 |
802
+ | 文档类型 | HITL type | 正式写入路径 |
802
803
  |---------|:---:|------|
803
- | Plan(标准版/精简版) | 需要 | `.qoder/plans/{YYYY-MM}/{DD}/` |
804
- | Review(方案/实现) | 需要 | `.qoder/reviews/` |
805
- | Spec / Tasks / Checklist | 不需要 | 直接写入 `.qoder/specs/` |
806
- | Handoff | 不需要 | 直接写入 `.qoder/plans/` |
804
+ | Plan(标准版/精简版) | `PLAN` | `{{magicDir}}/plans/{YYYY-MM}/{DD}/` |
805
+ | Review(方案/实现) | `PLAN_REVIEW` | `{{magicDir}}/reviews/` |
806
+ | Runtime Review | 不需要 | `[T]` 全部通过后直接写入 `{{magicDir}}/reviews/` |
807
+ | Spec / Tasks / Checklist | 不需要 | 直接写入 `{{magicDir}}/specs/` |
808
+ | Handoff | 不需要 | 直接写入 `{{magicDir}}/plans/` |
807
809
 
808
810
  ### 与 ADD-0.1 的关系
809
811
 
810
- ADD-0.1 要求"文档先行"——HITL 磋商本身就是文档先行的一种形式(先对齐方向再产出文档)。temporary.md 机制确保 HITL 的"先拍板再展开"哲学不因 guard 的"完整章节校验"而被破坏。
812
+ ADD-0.1 要求“文档先行”。`create_hitl` 先持久化决策维度与审批状态,再展开正式文档,使“先拍板、后展开”具备可查询、可追溯的事实源。
811
813
 
812
814
  ---
813
815
 
814
816
  ## ADD-18:HITL 人机审核强制规则
815
817
 
816
- **Plan PLAN_REVIEW 类型 Review 在写入正式文件前,必须经过 HITL 审批。** 审批通过后生成 `.hitl-tongyi-{planName}` 哨兵文件,pre-tool-use hook 以此判断是否允许写入。
818
+ **Plan、方案 Review 和实现 Review 在写入正式文件前,必须经过 HITL 审批。** 审批通过后,`update_hitl` 更新数据库与 `*.hitl.md` 状态,并在 `{{magicDir}}/hitl/` 生成 `.tongyi-{planName}` 哨兵。pre-tool-use hook 对 Plan/方案 Review 执行哨兵门禁;实现 Review 由 `add-paradigm` Step 3.5 与 `status_hitl` 门禁约束。
817
819
 
818
820
  ### 强制流程
819
821
 
820
822
  ```
821
- ① create_hitl({ planName, type }) → HitlRecord DRAFT,生成 hitl.md 提案文件
822
- ② 人类审阅 hitl.md → 逐行拍板
823
- ③ update_hitl({ planName, status: "TONGYI" }) → .hitl-tongyi-{planName} 哨兵
824
- 或 update_hitl({ planName, status: "BOHUI", reason }) → 提案驳回
823
+ ① create_hitl({ planName, type, dimensions }) → HitlRecord DRAFT,生成 *.hitl.md 提案文件
824
+ ② 人类审阅 *.hitl.md → 逐行拍板
825
+ ③ update_hitl({ planName, type, status: "TONGYI" }) → 更新 DB/提案并写 .tongyi-{planName} 哨兵
826
+ 或 update_hitl({ planName, type, status: "BOHUI", reason }) → 更新 DB/提案并写 .bohui-{planName} 哨兵
825
827
  ④ TONGYI 后:正式写入 Plan/Review 文件(hook §C 放行)
826
828
  ⑤ BOHUI 后:create_hitl 新建 round+1,重新审批
827
829
  ```
@@ -830,15 +832,16 @@ ADD-0.1 要求"文档先行"——HITL 磋商本身就是文档先行的一种
830
832
 
831
833
  | 文档类型 | HITL 审批 | 依据 |
832
834
  |---------|:--------:|------|
833
- | Plan | ✅ 必须 | pre-tool-use §C 拦截 plans/ 目录 |
834
- | Review(PLAN_REVIEW) | ✅ 必须 | pre-tool-use §C 拦截 reviews/ 目录(不含 implementation/runtime) |
835
- | Review(implementation/runtime) | 不需要 | §B 活跃 Plan 检查 |
835
+ | Plan | ✅ 必须 | pre-tool-use §C 哨兵门禁 |
836
+ | 方案 Review | ✅ 必须 | pre-tool-use §C 哨兵门禁 |
837
+ | Implementation Review | 必须 | `add-paradigm` Step 3.5 + `status_hitl` |
838
+ | Runtime Review | ❌ 不需要 | `[T]` 全部通过后直接生成 |
836
839
  | Spec / Tasks / Checklist | ❌ 不需要 | 直接写 |
837
840
  | Handoff | ❌ 不需要 | 直接写 |
838
841
 
839
842
  ### 与 ADD-17 的关系
840
843
 
841
- ADD-17 定义 HITL 如何在 guard 约束下运作(temporary.md 绕过 guard 完成磋商)。ADD-18 定义 HITL 审批本身是强制的——不经过 create_hitl→update_hitl 流程,hook 阻止写入。两者互补:ADD-17 是"怎么做 HITL",ADD-18 是"必须做 HITL"。
844
+ ADD-17 定义审批提案的载体、维度和状态流转;ADD-18 定义哪些正式文档必须先取得 `TONGYI`。两者共同以 `create_hitl update_hitl status_hitl` 为事实链,文件、数据库和哨兵相互校验。
842
845
 
843
846
  ---
844
847
 
@@ -156,6 +156,14 @@ apply_atlas_diff() {
156
156
  # ⑦ Atlas 同步主流程(探测 → dev-url → 目标 → baseline → diff → apply)
157
157
  atlas_sync() {
158
158
  [ "$ENGINE" = "sqlite" ] && return 0
159
+ # 迁移锁(进程层契约 v2 §6):多 IDE 并发 init 时仅一个进程执行迁移
160
+ # 非阻塞拿锁(pg_try_advisory_lock),会话级锁——脚本退出连接断开自动释放
161
+ LOCK_KEY="0xADD001"
162
+ LOCKED="$(podman exec "${PROJECT_NAME:-add-project}-postgres" psql -U "${DATABASE_USER:-admin}" -d "${PROJECT_NAME:-add-project}" -tAc "SELECT pg_try_advisory_lock(${LOCK_KEY});" 2>/dev/null || true)"
163
+ if [ "$LOCKED" != "t" ]; then
164
+ echo "!!! 另一个进程正在迁移,请稍后重试"
165
+ return 1
166
+ fi
159
167
  if ! atlas_cmd version > /dev/null 2>&1; then
160
168
  echo "!!! Atlas 不可用。add-coder sync --patch 可自动安装 @ariga/atlas;或手动: pnpm add -D @ariga/atlas"
161
169
  echo " 降级路径: prisma-diff(免 shadow)→ db-push + 强制备份;文档: README「Atlas 数据库同步能力」"
@@ -1,24 +1,13 @@
1
1
  #!/bin/bash
2
2
  # 生成 {MAGIC_DIR}/plans/index.md 总览清单 — 按日期分层,每日自动更新
3
- # 自动探测 MAGIC_DIR(.qoder / .claude / .vscode / .trae / .codex / .add)
3
+ # MAGIC_DIR 由脚本自身的物理目录确定,各 adapter 自治。
4
4
  set -e
5
5
 
6
- # 探测 MAGIC_DIR:在当前目录向上查找包含 plans/ 的隐藏目录
7
6
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
8
- CURRENT="$SCRIPT_DIR"
9
- MAGIC_DIR=""
10
- while [ "$CURRENT" != "/" ]; do
11
- for d in "$CURRENT"/.qoder "$CURRENT"/.claude "$CURRENT"/.vscode "$CURRENT"/.trae "$CURRENT"/.codex "$CURRENT"/.add; do
12
- if [ -d "$d/plans" ]; then
13
- MAGIC_DIR="$d"
14
- break 2
15
- fi
16
- done
17
- CURRENT="$(dirname "$CURRENT")"
18
- done
7
+ MAGIC_DIR="$(dirname "$SCRIPT_DIR")"
19
8
 
20
- if [ -z "$MAGIC_DIR" ]; then
21
- echo "❌ 未找到 MAGIC_DIR(.qoder / .claude / .vscode / .trae / .codex / .add 均无 plans/ 子目录)" >&2
9
+ if [ ! -d "$MAGIC_DIR/plans" ]; then
10
+ echo "❌ 当前 adapter 不存在 plans/: $MAGIC_DIR" >&2
22
11
  exit 1
23
12
  fi
24
13
 
@@ -3,8 +3,10 @@ import { join } from "path"
3
3
  import { existsSync } from "fs"
4
4
  import { readFileSafe, readdirRecursive, PROJECT_ROOT, MAGIC_DIR } from "../shared/fs.js"
5
5
  import { prisma } from "../shared/prisma.js"
6
+ import { getRuntimeContext } from "../shared/env.js"
6
7
 
7
8
  export function registerHitlNotifications(server: McpServer) {
9
+ const runtimeContext = getRuntimeContext()
8
10
  // ── HITL 表扫描 ──
9
11
  const scanHitl = async () => {
10
12
  const plansDir = join(PROJECT_ROOT, MAGIC_DIR, "plans")
@@ -29,7 +31,13 @@ export function registerHitlNotifications(server: McpServer) {
29
31
  const ops = prisma.devOperation as Record<string, (...a: unknown[]) => unknown>
30
32
  const since = new Date(Date.now() - 24 * 60 * 60 * 1000)
31
33
  const count = await ops.count({
32
- where: { action: "HOOK_INTERCEPT", planKeyword: "no-active-plan", createdAt: { gte: since } },
34
+ where: {
35
+ action: "HOOK_INTERCEPT",
36
+ planKeyword: "no-active-plan",
37
+ projectKey: runtimeContext.projectKey,
38
+ producerAdapterKey: runtimeContext.adapterKey,
39
+ createdAt: { gte: since },
40
+ },
33
41
  }) as number
34
42
  if (count >= 10 && count !== lastWarnedCount) {
35
43
  lastWarnedCount = count
@@ -1,9 +1,21 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/server"
2
2
  import { join } from "path"
3
- import { existsSync, watch, readFileSync, statSync } from "fs"
3
+ import {
4
+ appendFileSync,
5
+ closeSync,
6
+ existsSync,
7
+ mkdirSync,
8
+ openSync,
9
+ readSync,
10
+ statSync,
11
+ watch,
12
+ writeFileSync,
13
+ } from "fs"
4
14
  import { readFile } from "fs/promises"
15
+ import { createHash } from "crypto"
5
16
  import { PROJECT_ROOT, MAGIC_DIR } from "../shared/fs.js"
6
17
  import { prisma } from "../shared/prisma.js"
18
+ import { getRuntimeContext } from "../shared/env.js"
7
19
 
8
20
  // ── 类型定义 ──
9
21
  interface HookEvent {
@@ -29,6 +41,7 @@ interface QueueState {
29
41
  const REPORT_DIR = join(PROJECT_ROOT, MAGIC_DIR, "reports")
30
42
  const JSONL_FILE = join(REPORT_DIR, "hook-events.jsonl")
31
43
  const OVERFLOW_FILE = join(REPORT_DIR, "hook-events-overflow.jsonl")
44
+ const runtimeContext = getRuntimeContext()
32
45
 
33
46
  // ── 内存缓冲队列 ──
34
47
  const MAX_QUEUE = 50
@@ -98,6 +111,13 @@ async function flushToDB(events: HookEvent[]): Promise<number> {
98
111
  const ops = prisma.devOperation as Record<string, (...a: unknown[]) => unknown>
99
112
  const data = unique.map(e => ({
100
113
  userId,
114
+ projectKey: runtimeContext.projectKey,
115
+ producerAdapterKey: runtimeContext.adapterKey,
116
+ contextId: runtimeContext.contextId,
117
+ toolName: "hook-event-notification",
118
+ operationKey: createHash("sha256")
119
+ .update(`${runtimeContext.contextId}\0${dedupKey(e)}\0${e.decision}\0${e.cmd}`)
120
+ .digest("hex"),
101
121
  action: "HOOK_INTERCEPT",
102
122
  targetType: e.hook,
103
123
  targetId: e.cmd.substring(0, 500),
@@ -106,7 +126,7 @@ async function flushToDB(events: HookEvent[]): Promise<number> {
106
126
  afterState: JSON.stringify({ planStatus: e.planStatus }),
107
127
  }))
108
128
 
109
- await ops.createMany({ data })
129
+ await ops.createMany({ data, skipDuplicates: true })
110
130
  return unique.length
111
131
  } catch (err) {
112
132
  console.error("[hook-notify] flushToDB 失败:", err instanceof Error ? err.message : String(err))
@@ -125,7 +145,6 @@ async function drainOverflowFile(): Promise<HookEvent[]> {
125
145
  if (ev) events.push(ev)
126
146
  }
127
147
  // 清空溢出文件
128
- const { writeFileSync } = await import("fs")
129
148
  writeFileSync(OVERFLOW_FILE, "", "utf-8")
130
149
  } catch {
131
150
  // 忽略,下次 flush 重试
@@ -145,7 +164,6 @@ async function doFlush(): Promise<void> {
145
164
 
146
165
  const count = await flushToDB(allEvents)
147
166
  if (count > 0 && serverRef) {
148
- const noPlanCount = allEvents.filter(e => e.planKeyword === "no-active-plan").length
149
167
  const msg = `[Hook] ${count} 条拦截事件已审计落库(计划: ${[...new Set(allEvents.map(e => e.planKeyword))].join(", ")})`
150
168
  serverRef.sendLoggingMessage({ level: "warning" as const, data: msg }).catch(() => {})
151
169
  }
@@ -156,7 +174,6 @@ function enqueue(event: HookEvent): void {
156
174
  if (q.events.length >= MAX_QUEUE) {
157
175
  // 降级写入溢出文件
158
176
  try {
159
- const { appendFileSync } = require("fs") as typeof import("fs")
160
177
  appendFileSync(OVERFLOW_FILE, JSON.stringify(event) + "\n", "utf-8")
161
178
  } catch { /* 静默 */ }
162
179
  return
@@ -191,10 +208,10 @@ function readNewLines(): void {
191
208
  if (st.size <= q.bytesRead) return
192
209
 
193
210
  // 只读增量部分
194
- const fd = require("fs").openSync(JSONL_FILE, "r")
211
+ const fd = openSync(JSONL_FILE, "r")
195
212
  const buf = Buffer.alloc(st.size - q.bytesRead)
196
- require("fs").readSync(fd, buf, 0, buf.length, q.bytesRead)
197
- require("fs").closeSync(fd)
213
+ readSync(fd, buf, 0, buf.length, q.bytesRead)
214
+ closeSync(fd)
198
215
 
199
216
  const text = buf.toString("utf-8")
200
217
  for (const line of text.split("\n")) {
@@ -242,7 +259,6 @@ export function registerHookNotifications(server: McpServer) {
242
259
  void initialScan().then(() => {
243
260
  // 确保 jsonl 文件存在(fs.watch 要求文件已存在)
244
261
  try {
245
- const { mkdirSync, writeFileSync } = require("fs") as typeof import("fs")
246
262
  mkdirSync(REPORT_DIR, { recursive: true })
247
263
  if (!existsSync(JSONL_FILE)) writeFileSync(JSONL_FILE, "", "utf-8")
248
264
  } catch { /* ok */ }
@@ -0,0 +1,34 @@
1
+ import type { McpServer } from "@modelcontextprotocol/server"
2
+ import { existsSync, readFileSync } from "fs"
3
+ import { join } from "path"
4
+ import { PROJECT_ROOT, MAGIC_DIR } from "../shared/fs.js"
5
+ import {
6
+ HITL_APPROVAL_WIDGET_FILE,
7
+ HITL_APPROVAL_WIDGET_MIME,
8
+ HITL_APPROVAL_WIDGET_URI,
9
+ } from "../shared/hitl-ui.js"
10
+
11
+ export function registerHitlApprovalWidgetResource(server: McpServer) {
12
+ server.registerResource(
13
+ "hitl-approval-widget",
14
+ HITL_APPROVAL_WIDGET_URI,
15
+ {
16
+ title: "ADD HITL Approval",
17
+ description: "逐项审核 ADD HITL 提案的 core 标准 widget",
18
+ mimeType: HITL_APPROVAL_WIDGET_MIME,
19
+ },
20
+ (uri) => {
21
+ const widgetPath = join(PROJECT_ROOT, MAGIC_DIR, "templates", HITL_APPROVAL_WIDGET_FILE)
22
+ if (!existsSync(widgetPath)) {
23
+ throw new Error(`HITL widget 缺失: ${widgetPath}。请重新执行 add-coder sync。`)
24
+ }
25
+ return {
26
+ contents: [{
27
+ uri: uri.href,
28
+ mimeType: HITL_APPROVAL_WIDGET_MIME,
29
+ text: readFileSync(widgetPath, "utf-8"),
30
+ }],
31
+ }
32
+ },
33
+ )
34
+ }
@@ -1,7 +1,9 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/server"
2
2
  import { prisma } from "../shared/prisma.js"
3
+ import { getRuntimeContext } from "../shared/env.js"
3
4
 
4
5
  export function registerHookEventResources(server: McpServer) {
6
+ const runtimeContext = getRuntimeContext()
5
7
 
6
8
  // ── 日报 Resource ──
7
9
  server.registerResource("hook-events-daily", "add-coder://report/hook-events/daily",
@@ -11,7 +13,12 @@ export function registerHookEventResources(server: McpServer) {
11
13
  const ops = prisma.devOperation as Record<string, (...a: unknown[]) => unknown>
12
14
  const since = new Date(Date.now() - 24 * 60 * 60 * 1000)
13
15
  const logs = await ops.findMany({
14
- where: { action: "HOOK_INTERCEPT", createdAt: { gte: since } },
16
+ where: {
17
+ action: "HOOK_INTERCEPT",
18
+ projectKey: runtimeContext.projectKey,
19
+ producerAdapterKey: runtimeContext.adapterKey,
20
+ createdAt: { gte: since },
21
+ },
15
22
  orderBy: { createdAt: "desc" },
16
23
  take: 500,
17
24
  }) as Array<Record<string, unknown>>
@@ -60,7 +67,12 @@ export function registerHookEventResources(server: McpServer) {
60
67
  const ops = prisma.devOperation as Record<string, (...a: unknown[]) => unknown>
61
68
  const since = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
62
69
  const logs = await ops.findMany({
63
- where: { action: "HOOK_INTERCEPT", createdAt: { gte: since } },
70
+ where: {
71
+ action: "HOOK_INTERCEPT",
72
+ projectKey: runtimeContext.projectKey,
73
+ producerAdapterKey: runtimeContext.adapterKey,
74
+ createdAt: { gte: since },
75
+ },
64
76
  orderBy: { createdAt: "desc" },
65
77
  take: 2000,
66
78
  }) as Array<Record<string, unknown>>
@@ -3,10 +3,12 @@ import { registerAddStateResources } from "./add-state.js"
3
3
  import { registerRoundTaskResources } from "./round-task.js"
4
4
  import { registerVersionResource } from "./add-coder-version.js"
5
5
  import { registerHookEventResources } from "./hook-events-report.js"
6
+ import { registerHitlApprovalWidgetResource } from "./hitl-approval-widget.js"
6
7
 
7
8
  export function registerAllResources(server: McpServer) {
8
9
  registerAddStateResources(server)
9
10
  registerRoundTaskResources(server)
10
11
  registerVersionResource(server)
11
12
  registerHookEventResources(server) // 2 resources: hook-events/{daily,weekly}
13
+ registerHitlApprovalWidgetResource(server)
12
14
  }