add-coder 0.3.34 → 0.3.35

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 (147) hide show
  1. package/dist/index.js +21 -3
  2. package/package.json +3 -3
  3. package/templates/.add-coder-src-hash.json +144 -90
  4. package/templates/adapters/claude/hooks/doc-format-guard.mjs +172 -88
  5. package/templates/adapters/claude/hooks/notification.mjs +0 -4
  6. package/templates/adapters/claude/hooks/permission-denied.mjs +0 -4
  7. package/templates/adapters/claude/hooks/permission-gate.mjs +0 -4
  8. package/templates/adapters/claude/hooks/post-tool-use.mjs +59 -5
  9. package/templates/adapters/claude/hooks/pre-compact.mjs +0 -4
  10. package/templates/adapters/claude/hooks/pre-tool-use.mjs +0 -4
  11. package/templates/adapters/claude/hooks/prompt-submit.mjs +72 -4
  12. package/templates/adapters/claude/hooks/session-end.mjs +0 -4
  13. package/templates/adapters/claude/hooks/session-start.mjs +65 -5
  14. package/templates/adapters/claude/hooks/stop-check.mjs +6 -87
  15. package/templates/adapters/claude/hooks/stop-failure.mjs +0 -4
  16. package/templates/adapters/claude/hooks/subagent-guard.mjs +0 -4
  17. package/templates/adapters/claude/hooks/subagent-stop.mjs +0 -4
  18. package/templates/adapters/codex/hooks/doc-format-guard.mjs +172 -88
  19. package/templates/adapters/codex/hooks/notification.mjs +0 -4
  20. package/templates/adapters/codex/hooks/permission-gate.mjs +0 -4
  21. package/templates/adapters/codex/hooks/post-tool-failure.mjs +0 -4
  22. package/templates/adapters/codex/hooks/post-tool-use.mjs +59 -5
  23. package/templates/adapters/codex/hooks/pre-compact.mjs +0 -4
  24. package/templates/adapters/codex/hooks/pre-tool-use.mjs +0 -4
  25. package/templates/adapters/codex/hooks/prompt-submit.mjs +72 -4
  26. package/templates/adapters/codex/hooks/session-end.mjs +0 -4
  27. package/templates/adapters/codex/hooks/session-start.mjs +65 -5
  28. package/templates/adapters/codex/hooks/stop-check.mjs +6 -87
  29. package/templates/adapters/codex/hooks/subagent-guard.mjs +0 -4
  30. package/templates/adapters/codex/hooks/subagent-stop.mjs +0 -4
  31. package/templates/adapters/qoder/hooks/doc-format-guard.mjs +172 -88
  32. package/templates/adapters/qoder/hooks/notification.mjs +0 -4
  33. package/templates/adapters/qoder/hooks/permission-gate.mjs +0 -4
  34. package/templates/adapters/qoder/hooks/post-tool-failure.mjs +0 -4
  35. package/templates/adapters/qoder/hooks/post-tool-use.mjs +59 -5
  36. package/templates/adapters/qoder/hooks/pre-compact.mjs +0 -4
  37. package/templates/adapters/qoder/hooks/pre-tool-use.mjs +0 -4
  38. package/templates/adapters/qoder/hooks/prompt-submit.mjs +72 -4
  39. package/templates/adapters/qoder/hooks/review-checklist.mjs +0 -4
  40. package/templates/adapters/qoder/hooks/session-end.mjs +0 -4
  41. package/templates/adapters/qoder/hooks/session-start.mjs +67 -5
  42. package/templates/adapters/qoder/hooks/stop-check.mjs +6 -87
  43. package/templates/adapters/qoder/hooks/subagent-guard.mjs +0 -4
  44. package/templates/adapters/qoder/hooks/subagent-stop.mjs +0 -4
  45. package/templates/adapters/trae/hooks/doc-format-guard.mjs +172 -88
  46. package/templates/adapters/trae/hooks/notification.mjs +0 -4
  47. package/templates/adapters/trae/hooks/permission-gate.mjs +0 -4
  48. package/templates/adapters/trae/hooks/post-tool-failure.mjs +0 -4
  49. package/templates/adapters/trae/hooks/post-tool-use.mjs +59 -5
  50. package/templates/adapters/trae/hooks/pre-compact.mjs +0 -4
  51. package/templates/adapters/trae/hooks/pre-tool-use.mjs +0 -4
  52. package/templates/adapters/trae/hooks/prompt-submit.mjs +72 -4
  53. package/templates/adapters/trae/hooks/session-end.mjs +0 -4
  54. package/templates/adapters/trae/hooks/session-start.mjs +65 -5
  55. package/templates/adapters/trae/hooks/stop-check.mjs +6 -87
  56. package/templates/adapters/trae/hooks/subagent-guard.mjs +0 -4
  57. package/templates/adapters/trae/hooks/subagent-stop.mjs +0 -4
  58. package/templates/adapters/vscode/hooks/doc-format-guard.mjs +172 -88
  59. package/templates/adapters/vscode/hooks/notification.mjs +0 -4
  60. package/templates/adapters/vscode/hooks/permission-gate.mjs +0 -4
  61. package/templates/adapters/vscode/hooks/post-tool-failure.mjs +0 -4
  62. package/templates/adapters/vscode/hooks/post-tool-use.mjs +59 -5
  63. package/templates/adapters/vscode/hooks/pre-compact.mjs +0 -4
  64. package/templates/adapters/vscode/hooks/pre-tool-use.mjs +0 -4
  65. package/templates/adapters/vscode/hooks/prompt-submit.mjs +72 -4
  66. package/templates/adapters/vscode/hooks/session-end.mjs +0 -4
  67. package/templates/adapters/vscode/hooks/session-start.mjs +65 -5
  68. package/templates/adapters/vscode/hooks/stop-check.mjs +6 -87
  69. package/templates/adapters/vscode/hooks/subagent-guard.mjs +0 -4
  70. package/templates/adapters/vscode/hooks/subagent-stop.mjs +0 -4
  71. package/templates/core/governance/doc-format-guard.ts +29 -112
  72. package/templates/core/governance/post-tool-router.ts +33 -1
  73. package/templates/core/governance/prompt-router.ts +47 -0
  74. package/templates/core/governance/rules.ts +0 -4
  75. package/templates/core/governance/session-start-guard.ts +48 -1
  76. package/templates/core/governance/stop-router.ts +7 -101
  77. package/templates/core/prisma/add.prisma +203 -0
  78. package/templates/core/scripts/db-ensure.sh +61 -2
  79. package/templates/core/scripts/mcp-server/shared/db-types.ts +119 -0
  80. package/templates/core/scripts/mcp-server/shared/hitl-create-policy.ts +27 -0
  81. package/templates/core/scripts/mcp-server/shared/hitl-proposal-content.ts +110 -0
  82. package/templates/core/scripts/mcp-server/shared/hitl-widget-instance.ts +85 -0
  83. package/templates/core/scripts/mcp-server/shared/memory/calibration/batch-fit.ts +250 -0
  84. package/templates/core/scripts/mcp-server/shared/memory/calibration/feedback-stats.ts +101 -0
  85. package/templates/core/scripts/mcp-server/shared/memory/calibration/unit-state.ts +224 -0
  86. package/templates/core/scripts/mcp-server/shared/memory/domain/conflicts.ts +59 -0
  87. package/templates/core/scripts/mcp-server/shared/memory/domain/dedup.ts +45 -0
  88. package/templates/core/scripts/mcp-server/shared/memory/domain/errors.ts +33 -0
  89. package/templates/core/scripts/mcp-server/shared/memory/domain/handoff-digest.ts +92 -0
  90. package/templates/core/scripts/mcp-server/shared/memory/domain/metric-candidate.ts +79 -0
  91. package/templates/core/scripts/mcp-server/shared/memory/domain/scope.ts +94 -0
  92. package/templates/core/scripts/mcp-server/shared/memory/domain/secrets.ts +50 -0
  93. package/templates/core/scripts/mcp-server/shared/memory/domain/state-machine.ts +90 -0
  94. package/templates/core/scripts/mcp-server/shared/memory/embedding/index.ts +117 -0
  95. package/templates/core/scripts/mcp-server/shared/memory/embedding/local-onnx.ts +105 -0
  96. package/templates/core/scripts/mcp-server/shared/memory/embedding/openai-compatible.ts +87 -0
  97. package/templates/core/scripts/mcp-server/shared/memory/jobs/consolidation.ts +226 -0
  98. package/templates/core/scripts/mcp-server/shared/memory/jobs/evidence-collector.ts +153 -0
  99. package/templates/core/scripts/mcp-server/shared/memory/jobs/snapshot.ts +114 -0
  100. package/templates/core/scripts/mcp-server/shared/memory/metrics/gate-recall.ts +134 -0
  101. package/templates/core/scripts/mcp-server/shared/memory/metrics/gate-writer.ts +217 -0
  102. package/templates/core/scripts/mcp-server/shared/memory/metrics/stage-words.ts +69 -0
  103. package/templates/core/scripts/mcp-server/shared/memory/retrieval/context-builder.ts +89 -0
  104. package/templates/core/scripts/mcp-server/shared/memory/retrieval/fts/pg.ts +139 -0
  105. package/templates/core/scripts/mcp-server/shared/memory/retrieval/fts/sqlite-fts5.sql +29 -0
  106. package/templates/core/scripts/mcp-server/shared/memory/retrieval/fts/sqlite.ts +106 -0
  107. package/templates/core/scripts/mcp-server/shared/memory/retrieval/fusion.ts +43 -0
  108. package/templates/core/scripts/mcp-server/shared/memory/retrieval/pipeline.ts +285 -0
  109. package/templates/core/scripts/mcp-server/shared/memory/retrieval/query-terms.ts +31 -0
  110. package/templates/core/scripts/mcp-server/shared/memory/retrieval/recall-writer.ts +87 -0
  111. package/templates/core/scripts/mcp-server/shared/memory/retrieval/reranker.ts +116 -0
  112. package/templates/core/scripts/mcp-server/shared/memory/retrieval/types.ts +52 -0
  113. package/templates/core/scripts/mcp-server/shared/memory/retrieval/vector/pgvector.ts +143 -0
  114. package/templates/core/scripts/mcp-server/shared/memory/retrieval/vector/sqlite-vec.ts +118 -0
  115. package/templates/core/scripts/mcp-server/shared/memory/switches.ts +39 -0
  116. package/templates/core/scripts/mcp-server/shared/review-files.ts +22 -0
  117. package/templates/core/scripts/mcp-server/shared/runtime-freshness.ts +235 -0
  118. package/templates/core/scripts/mcp-server/tools/gateway/check_dps.ts +41 -3
  119. package/templates/core/scripts/mcp-server/tools/gateway/check_rahs.ts +40 -1
  120. package/templates/core/scripts/mcp-server/tools/gateway/check_spec_sync.ts +2 -2
  121. package/templates/core/scripts/mcp-server/tools/hitl.ts +108 -42
  122. package/templates/core/scripts/mcp-server/tools/index.ts +7 -1
  123. package/templates/core/scripts/mcp-server/tools/memory-compat.ts +258 -0
  124. package/templates/core/scripts/mcp-server/tools/memory.ts +654 -0
  125. package/templates/core/scripts/mcp-server/tools/plan.ts +8 -3
  126. package/templates/core/scripts/mcp-server/tools/review.ts +10 -7
  127. package/templates/core/scripts/mcp-server.ts +36 -0
  128. package/templates/core/templates/checklist-template.md +13 -0
  129. package/templates/core/templates/review-implementation-template.md +24 -0
  130. package/templates/core/templates/review-template.md +16 -0
  131. package/templates/core/validation/index.ts +136 -0
  132. package/templates/core/validation/policy.ts +91 -0
  133. package/templates/core/validation/registry.ts +61 -0
  134. package/templates/core/validation/schema-validator.ts +277 -0
  135. package/templates/core/validation/validators/add-route.ts +32 -0
  136. package/templates/core/validation/validators/checklist.ts +48 -0
  137. package/templates/core/validation/validators/handoff.ts +46 -0
  138. package/templates/core/validation/validators/hitl.ts +22 -0
  139. package/templates/core/validation/validators/index.ts +52 -0
  140. package/templates/core/validation/validators/plan.ts +20 -0
  141. package/templates/core/validation/validators/report.ts +16 -0
  142. package/templates/core/validation/validators/review.ts +30 -0
  143. package/templates/core/validation/validators/spec.ts +25 -0
  144. package/templates/core/validation/validators/tasks.ts +40 -0
  145. package/templates/core/validation/validators/types.ts +32 -0
  146. package/templates/core/vocabulary/add-governance-vocabulary.md +18 -0
  147. package/templates/core/scripts/mcp-server/shared/dps-spec-ref.ts +0 -17
@@ -3,10 +3,12 @@
3
3
  //
4
4
  // 设计范式: OOP 守卫类(状态恢复/模板索引/代办/HITL 四职责聚合)。
5
5
 
6
- import { existsSync, readdirSync, statSync } from "node:fs"
6
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"
7
7
  import { join } from "node:path"
8
8
  import { detectActiveAdd, tryResolveMagicDir } from "./common.js"
9
9
  import { PreloadTemplates } from "./preload-templates.js"
10
+ import { L1_SNAPSHOT_FILE, L1_SNAPSHOT_TTL_MS, MEMORY_DIR_NAME, memoryMaxTokens, recallMode } from "../scripts/mcp-server/shared/memory/switches.js"
11
+ import { estimateTokens } from "../scripts/mcp-server/shared/memory/retrieval/context-builder.js"
10
12
 
11
13
  /**
12
14
  * SessionStart 守卫(① 状态恢复 → ② 模板索引 → ③ 代办 → ④ HITL 待审批检测):
@@ -41,6 +43,9 @@ export class SessionStartGuard {
41
43
  // ── ④ §HITL 待审批检测(扩展点: adapter 可 override 关闭)──
42
44
  this.emitHitlPending()
43
45
 
46
+ // ── ⑤ Memory L1 快照注入(Spec §10;fail-open,读预计算快照,无 DB)──
47
+ this.emitMemoryL1()
48
+
44
49
  return 0
45
50
  }
46
51
 
@@ -96,4 +101,46 @@ export class SessionStartGuard {
96
101
  }
97
102
  }
98
103
  }
104
+
105
+ /**
106
+ * ⑤ Memory L1 快照注入(ADD_MEMORY_RECALL_MODE 三态):
107
+ * off → 不输出
108
+ * shadow → 仅提示快照存在(召回可执行并落审计,但不注入上下文)
109
+ * inject → 读预计算快照注入(新鲜度 ≤7 天,token 预算截断,带来源边界标签)
110
+ * 同步 Hook ≤200ms 约束:只读文件,任何异常 fail-open 静默跳过。
111
+ */
112
+ protected emitMemoryL1(): void {
113
+ try {
114
+ const mode = recallMode()
115
+ if (mode === "off" || !this.magicDir) return
116
+ const file = join(this.projectDir, this.magicDir, MEMORY_DIR_NAME, L1_SNAPSHOT_FILE)
117
+ if (!existsSync(file)) return
118
+ if (mode === "shadow") {
119
+ process.stdout.write(`[Memory] L1 快照已生成(shadow 模式未注入)。需要时调用 recall_memory 显式召回: ${file}\n`)
120
+ return
121
+ }
122
+ // inject:过期快照不注入
123
+ if (Date.now() - statSync(file).mtimeMs > L1_SNAPSHOT_TTL_MS) return
124
+ const text = readFileSync(file, "utf-8")
125
+ const budget = memoryMaxTokens()
126
+ if (estimateTokens(text) <= budget) {
127
+ process.stdout.write(text)
128
+ return
129
+ }
130
+ // 超预算:按行截断,不静默吞掉——追加显式截断标记
131
+ const lines = text.split("\n")
132
+ const kept: string[] = []
133
+ let used = 0
134
+ for (const line of lines) {
135
+ const t = estimateTokens(line)
136
+ if (used + t > budget) break
137
+ kept.push(line)
138
+ used += t
139
+ }
140
+ kept.push(`[Memory L1] ⚠️ 超出预算 ${budget} tokens,已截断(完整快照: ${file})`)
141
+ process.stdout.write(kept.join("\n") + "\n")
142
+ } catch {
143
+ /* fail-open:记忆子系统故障不阻断 session-start(Plan §9.3) */
144
+ }
145
+ }
99
146
  }
@@ -12,11 +12,6 @@ import {
12
12
  hasDevAction,
13
13
  } from "./common.js"
14
14
  import { buildStopContext } from "./context-inject.js"
15
- import { protocol } from "./rules.js"
16
- import { createHash } from "node:crypto"
17
- import { existsSync, readFileSync, writeFileSync } from "node:fs"
18
- import { tmpdir } from "node:os"
19
- import { join } from "node:path"
20
15
 
21
16
  /**
22
17
  * Stop 四象限分流路由(Q0-Q4,与 bash stop-check.sh 同语义):
@@ -70,20 +65,14 @@ export class StopRouter {
70
65
  // ─────────────────────────── 扩展点 ───────────────────────────
71
66
 
72
67
  /**
73
- * Q4 验收决策(双维度组合——2026-08-14 Task 9.4.4④ 上提,回流: I2):
74
- * 维度 0(新增,临时方案): 弹框频控——同项目同 Plan 窗口内达上限降级放行
75
- * 维度 1(前置): DB 任务进度(step = done/total,数值且 done<total 未完成阻断提示)
76
- * 维度 2: checklist 质量(checkAddCompleteness 未闭环 阻断)
77
- * 互补非替代——codex 原 DB 进度分流语义上提 core,core checklist 质量语义保留。
78
- */
79
- protected q4Check(plan: string, rounds: string, step: string, handoff: string, addRoute: string): number {
68
+ * Q4 验收决策(双维度组合——2026-08-14 Task 9.4.4④ 上提,回流: I2):
69
+ * 维度 1(前置): DB 任务进度(step = done/total,数值且 done<total 未完成阻断提示)
70
+ * 维度 2: checklist 质量(checkAddCompleteness 未闭环阻断)
71
+ * 互补非替代——codex DB 进度分流语义上提 core,core checklist 质量语义保留。
72
+ */
73
+ protected q4Check(plan: string, rounds: string, step: string, handoff: string, addRoute: string): number {
74
+ void plan
80
75
  void rounds
81
- // 维度 0: 弹框频控(达上限 → 放行,防 Stop 阻断刷屏死循环)
82
- const limited = this.stopPromptLimited(plan)
83
- if (limited) {
84
- process.stderr.write(`[ADD Stop] 已达本 Plan 弹框上限(${this.stopPromptMax()}),本次放行。请在后续实施中完成验收闭环(devlog + handoff)后停止。\n`)
85
- return 0
86
- }
87
76
  // 维度 1: DB 任务进度(step 格式 done/total,如 11/64)
88
77
  const [donePart, totalPart] = step.split("/")
89
78
  if (
@@ -108,89 +97,6 @@ protected q4Check(plan: string, rounds: string, step: string, handoff: string, a
108
97
  return this.emitQ4Pass()
109
98
  }
110
99
 
111
- // ── 弹框频控(临时方案,规则真源: hook-protocol-rules.toml [protocol.stop])──
112
- // 计数隔离: 文件级 = PROJECT_DIR md5(跨项目不互扰);key 级 = planName(同项目跨 Plan 不互扰)
113
- // 故障降级: 哨兵读/写异常 → fail-open 放行(不阻塞主流程)
114
-
115
- /** 哨兵记录:同一项目同一 Plan 的弹框计数 */
116
- protected stopPromptMax(): number {
117
- const cfg = protocol.stop as { max_prompt_per_context?: number } | undefined
118
- return cfg?.max_prompt_per_context ?? 3
119
- }
120
-
121
- /** 哨兵窗口(毫秒) */
122
- protected stopPromptWindowMs(): number {
123
- const cfg = protocol.stop as { window_minutes?: number } | undefined
124
- return (cfg?.window_minutes ?? 30) * 60_000
125
- }
126
-
127
- /** 哨兵文件路径(项目隔离:PROJECT_DIR md5) */
128
- protected stopSentinelPath(): string {
129
- const projectDir =
130
- process.env.PROJECT_DIR ||
131
- process.env.QODER_PROJECT_DIR ||
132
- process.env.QODERCN_PROJECT_DIR ||
133
- process.env.CLAUDE_PROJECT_DIR ||
134
- process.cwd()
135
- const md5 = createHash("md5").update(projectDir).digest("hex").slice(0, 8)
136
- return join(tmpdir(), `add_stop_${md5}.json`)
137
- }
138
-
139
- /** 写哨兵(失败静默,fail-open) */
140
- protected writeStopPromptSentinel(data: Record<string, { count: number; lastPromptAt: number }>): void {
141
- try {
142
- writeFileSync(this.stopSentinelPath(), JSON.stringify(data), "utf-8")
143
- } catch {
144
- // 静默:哨兵写失败不影响主流程
145
- }
146
- }
147
-
148
- /**
149
- * 频控判定:返回 true = 已达上限应放行(不弹框)
150
- * 生成态缺失 protocol.stop(TOML 未同步)→ 跳过频控保持既有行为(生成链幂等校验兜底)
151
- */
152
- protected stopPromptLimited(plan: string): boolean {
153
- try {
154
- const stopCfg = protocol.stop as
155
- | { max_prompt_per_context?: number; window_minutes?: number }
156
- | undefined
157
- if (!stopCfg) return false // 生成态缺失 → 跳过频控
158
- const max = this.stopPromptMax()
159
- const windowMs = this.stopPromptWindowMs()
160
- const path = this.stopSentinelPath()
161
- let data: Record<string, { count: number; lastPromptAt: number }> = {}
162
- if (existsSync(path)) {
163
- try {
164
- data = JSON.parse(readFileSync(path, "utf-8")) ?? {}
165
- } catch {
166
- return true // 哨兵损坏 → fail-open 放行
167
- }
168
- }
169
- const now = Date.now()
170
- const entry = data[plan]
171
- if (!entry) {
172
- // 首次触发:计数 1,正常弹框
173
- data[plan] = { count: 1, lastPromptAt: now }
174
- this.writeStopPromptSentinel(data)
175
- return false
176
- }
177
- if (now - entry.lastPromptAt > windowMs) {
178
- // 窗口过期:重置计数 1,正常弹框(新窗口)
179
- data[plan] = { count: 1, lastPromptAt: now }
180
- this.writeStopPromptSentinel(data)
181
- return false
182
- }
183
- if (entry.count >= max) {
184
- return true // 窗口内已达上限 → 放行(不写回,保持计数)
185
- }
186
- data[plan] = { count: entry.count + 1, lastPromptAt: now }
187
- this.writeStopPromptSentinel(data)
188
- return false
189
- } catch {
190
- return true // 自身故障 → fail-open 放行
191
- }
192
- }
193
-
194
100
  /** Q0: DB 不可用(core: stderr + 2) */
195
101
  protected emitQ0(reason: string): number {
196
102
  process.stderr.write(
@@ -216,3 +216,206 @@ model CollaborationPlanBinding {
216
216
  @@unique([contractId, projectKey, adapterKey, planName])
217
217
  @@index([projectKey, adapterKey, planName])
218
218
  }
219
+
220
+ // ===== Agent Memory 知识治理层(Plan: add-coder-agent-memory-plan-v2) =====
221
+ // 数据约束(Plan §4.4):
222
+ // - ACTIVE 必须至少关联一条 Evidence;approve 时 approvedBy/approvedAt 必填
223
+ // - SUPERSEDED 必须设置 supersededById,且新旧 repository/scope 兼容
224
+ // - validUntil <= now() 不得进入默认召回;HYPOTHESIS 不得作为 Gate 强约束
225
+ // - importance/confidence ∈ [0,1];contentHash = sha256(规范化文本),幂等含 repository+scope
226
+ // - ORGANIZATION scope 首版禁用(枚举保留,写入拒绝,§17-7 定案)
227
+
228
+ enum MemoryKind {
229
+ DECISION
230
+ CONSTRAINT
231
+ PITFALL
232
+ FAILURE
233
+ LESSON
234
+ PATTERN
235
+ CONVENTION
236
+ FACT
237
+ HANDOFF_DIGEST
238
+ HYPOTHESIS
239
+ }
240
+
241
+ enum MemoryStatus {
242
+ CANDIDATE
243
+ PENDING
244
+ ACTIVE
245
+ STALE
246
+ SUPERSEDED
247
+ REJECTED
248
+ ARCHIVED
249
+ }
250
+
251
+ enum MemoryScopeType {
252
+ ORGANIZATION
253
+ REPOSITORY
254
+ BRANCH
255
+ MODULE
256
+ PATH
257
+ SYMBOL
258
+ PLAN
259
+ SPEC
260
+ }
261
+
262
+ enum MemorySourceType {
263
+ PLAN
264
+ SPEC
265
+ DPS_GATE
266
+ DEV_OPERATION
267
+ RAHS_GATE
268
+ HANDOFF
269
+ MANUAL
270
+ IMPORT
271
+ }
272
+
273
+ enum EmbeddingState {
274
+ DISABLED
275
+ PENDING
276
+ READY
277
+ FAILED
278
+ STALE
279
+ }
280
+
281
+ enum RecallOutcome {
282
+ UNKNOWN
283
+ USED
284
+ USEFUL
285
+ IRRELEVANT
286
+ OUTDATED
287
+ CONTRADICTED
288
+ HARMFUL
289
+ }
290
+
291
+ model AddMemory {
292
+ id String @id @default(cuid())
293
+ kind MemoryKind
294
+ status MemoryStatus @default(CANDIDATE)
295
+ topic String
296
+ content String
297
+ summary String?
298
+
299
+ scopeType MemoryScopeType
300
+ scopeValue String
301
+ repositoryRef String
302
+
303
+ importance Float @default(0.5)
304
+ confidence Float @default(0.5)
305
+ validFrom DateTime @default(now())
306
+ validUntil DateTime?
307
+
308
+ supersededById String?
309
+ supersededBy AddMemory? @relation("MemorySupersession", fields: [supersededById], references: [id])
310
+ supersedes AddMemory[] @relation("MemorySupersession")
311
+
312
+ contentHash String
313
+ embeddingModel String?
314
+ embeddingDim Int?
315
+ embeddingState EmbeddingState @default(DISABLED)
316
+
317
+ createdBy String?
318
+ approvedBy String?
319
+ approvedAt DateTime?
320
+ metadata Json?
321
+ createdAt DateTime @default(now())
322
+ updatedAt DateTime @updatedAt
323
+
324
+ evidenceLinks AddMemoryEvidenceLink[]
325
+ recalls AddMemoryRecallItem[]
326
+
327
+ @@index([repositoryRef, status, kind])
328
+ @@index([scopeType, scopeValue])
329
+ @@index([supersededById])
330
+ @@index([status, validUntil])
331
+ @@unique([repositoryRef, contentHash, scopeType, scopeValue])
332
+ }
333
+
334
+ model AddMemoryEvidence {
335
+ id String @id @default(cuid())
336
+ repositoryRef String
337
+ sourceType MemorySourceType
338
+ sourceRef String
339
+ planKeyword String?
340
+ excerpt String
341
+ contentHash String
342
+ occurredAt DateTime?
343
+ metadata Json?
344
+ createdAt DateTime @default(now())
345
+
346
+ memoryLinks AddMemoryEvidenceLink[]
347
+
348
+ @@index([repositoryRef, sourceType, sourceRef])
349
+ @@index([planKeyword])
350
+ @@unique([repositoryRef, sourceType, sourceRef, contentHash])
351
+ }
352
+
353
+ model AddMemoryEvidenceLink {
354
+ memoryId String
355
+ evidenceId String
356
+ relation String?
357
+ createdAt DateTime @default(now())
358
+
359
+ memory AddMemory @relation(fields: [memoryId], references: [id])
360
+ evidence AddMemoryEvidence @relation(fields: [evidenceId], references: [id])
361
+
362
+ @@id([memoryId, evidenceId])
363
+ }
364
+
365
+ // sourceRef 约定:必须包含当次运行标识(格式 {gate}:{planKeyword}:{runId}),
366
+ // 同一 Gate 对同一来源的重复测量由 runId 区分(Review P1 #2 回流)
367
+ model AddMetricSnapshot {
368
+ id String @id @default(cuid())
369
+ repositoryRef String
370
+ metricType String
371
+ value Float
372
+ baseline Float?
373
+ delta Float?
374
+ unit String?
375
+ planKeyword String?
376
+ specRef String?
377
+ commitSha String?
378
+ sourceRef String
379
+ metadata Json?
380
+ measuredAt DateTime @default(now())
381
+
382
+ @@index([repositoryRef, metricType, measuredAt])
383
+ @@unique([repositoryRef, metricType, sourceRef])
384
+ }
385
+
386
+ model AddMemoryRecall {
387
+ id String @id @default(cuid())
388
+ repositoryRef String
389
+ query String
390
+ stage String
391
+ consumerRef String?
392
+ scopeContext Json
393
+ candidateIds Json
394
+ selectedIds Json
395
+ scoreBreakdown Json
396
+ exclusionReasons Json?
397
+ rankingVersion String
398
+ tokenBudget Int
399
+ injectedTokens Int
400
+ latencyMs Int?
401
+ degradedMode String?
402
+ createdAt DateTime @default(now())
403
+ items AddMemoryRecallItem[]
404
+
405
+ @@index([repositoryRef, stage, createdAt])
406
+ }
407
+
408
+ model AddMemoryRecallItem {
409
+ recallId String
410
+ memoryId String
411
+ selected Boolean
412
+ rank Int?
413
+ outcome RecallOutcome @default(UNKNOWN)
414
+ feedback String?
415
+ updatedAt DateTime @updatedAt
416
+
417
+ recall AddMemoryRecall @relation(fields: [recallId], references: [id])
418
+ memory AddMemory @relation(fields: [memoryId], references: [id])
419
+
420
+ @@id([recallId, memoryId])
421
+ }
@@ -123,14 +123,54 @@ build_target() {
123
123
  [ -n "$tables" ] && EXCLUDE_ARGS=(--exclude "$tables")
124
124
  echo ">>> Atlas 同步(共库模式: 仅 ADD 治理表,其余 $(echo "$tables" | tr ',' '\n' | wc -l) 张表排除)..."
125
125
  fi
126
+ # Atlas 自身的版本记录 schema 不属于期望态,必须排除,否则 diff 会生成 DROP SCHEMA ... CASCADE
127
+ EXCLUDE_ARGS+=(--exclude atlas_schema_revisions)
126
128
  TARGET_URL="${TARGET_URL}?sslmode=disable"
127
129
  }
128
130
 
129
- # baseline 生成(同源:Prisma schema SQL,过滤 Prisma 7 ◇ 提示)
131
+ # ③.5 raw 对象登记(schema 表达不了的对象:trgm 索引 / 向量层;单一事实源)
132
+ # 期望态必须能表达这些对象,否则 diff 把「raw 对象」判成多余并生成 DROP(review 发现 #2)。
133
+ # 约束:Atlas dev-url 必须是干净库且具备同名扩展(否则 gin_trgm_ops / vector 类型无法解析);
134
+ # 本项目的 dev 库是一次性沙箱 → 每次 diff 前从 template1 重建(见 prepare_atlas_dev_db)。
135
+ RAW_OBJECTS_SQL="prisma/raw-objects.sql"
136
+ RAW_OBJECTS_VECTOR_SQL="prisma/raw-objects-vector.sql"
137
+
138
+ # dev 沙箱库准备:DROP + CREATE TEMPLATE template1(template1 内已装 pg_trgm/vector)
139
+ prepare_atlas_dev_db() {
140
+ local c="${PROJECT_NAME:-add-project}-dev" u="${ATLAS_DEV_USER:-admin}" d="${ATLAS_DEV_DB:-add-project-dev}"
141
+ podman exec "$c" true >/dev/null 2>&1 || { echo ">>> [dev-url] 容器 $c 不可达,跳过重建(依赖现有 dev 库)"; return 0; }
142
+ if [ "${ADD_DB_KEEP_DEV:-}" = "yes" ]; then
143
+ echo ">>> [dev-url] ADD_DB_KEEP_DEV=yes:沿用现有 dev 库"
144
+ return 0
145
+ fi
146
+ podman exec "$c" psql -U "$u" -d postgres -tAc "DROP DATABASE IF EXISTS \"$d\";" >/dev/null 2>&1 || true
147
+ if podman exec "$c" psql -U "$u" -d postgres -tAc "CREATE DATABASE \"$d\" TEMPLATE template1;" >/dev/null 2>&1; then
148
+ echo ">>> [dev-url] 已从 template1 重建沙箱库 $d(干净 + 扩展齐备)"
149
+ else
150
+ echo ">>> [dev-url] 重建 $d 失败,沿用现有库(若解析报错请检查 template1 扩展)"
151
+ fi
152
+ }
153
+
154
+ # 目标库是否具备 pgvector(决定是否把向量段并入期望态)
155
+ has_pgvector_in_target() {
156
+ local c="${PROJECT_NAME:-add-project}-postgres" u="${DATABASE_USER:-admin}" d="${PROJECT_NAME:-add-project}" has
157
+ has="$(podman exec "$c" psql -U "$u" -d "$d" -tAc "SELECT 1 FROM pg_available_extensions WHERE name='vector' LIMIT 1;" 2>/dev/null || true)"
158
+ [ "$has" = "1" ]
159
+ }
160
+
161
+ # ④ baseline 生成(同源:Prisma schema SQL + raw 对象登记段,过滤 Prisma 7 ◇ 提示)
130
162
  generate_baseline() {
131
163
  BASELINE_SQL="$(mktemp /tmp/atlas-target.XXXXXX.sql)"
132
164
  trap 'rm -f "$BASELINE_SQL"' EXIT
133
165
  npx prisma migrate diff --from-empty --to-schema "$SCHEMA_TARGET" --script 2>/dev/null | sed '/^◇/d' > "$BASELINE_SQL"
166
+ if [ -f "$RAW_OBJECTS_SQL" ]; then
167
+ printf '\n-- ===== raw objects registry =====\n' >> "$BASELINE_SQL"
168
+ cat "$RAW_OBJECTS_SQL" >> "$BASELINE_SQL"
169
+ fi
170
+ if has_pgvector_in_target && [ -f "$RAW_OBJECTS_VECTOR_SQL" ]; then
171
+ printf '\n-- ===== raw objects registry (vector) =====\n' >> "$BASELINE_SQL"
172
+ cat "$RAW_OBJECTS_VECTOR_SQL" >> "$BASELINE_SQL"
173
+ fi
134
174
  }
135
175
 
136
176
  # ⑤ diff 检测(SQL 语句特征判定:Atlas 无变更时输出 "Schemas are synced..." 非空,不算变更)
@@ -140,10 +180,28 @@ run_atlas_diff() {
140
180
  echo "$DIFF_SQL" | grep -qE "^(CREATE|ALTER|DROP|COMMENT|-- *(Create|Modify|Drop))"
141
181
  }
142
182
 
143
- # ⑥ apply(确认门槛:交互输出 SQL确认 → apply;拒绝则跳过)
183
+ # ⑥ apply(双门槛:DROP 守卫交互确认 → apply;任一不通过则跳过)
184
+ # DROP 守卫(Plan 轮 3 前置 / runtime review 发现 #2):破坏性语句一律拒绝——
185
+ # raw SQL 对象(如 pg_trgm GIN 索引、pgvector 列/索引)无法被 Prisma schema 表达,
186
+ # diff 会误判为「多余对象」并生成 DROP;若放行,索引会被静默删除且不报错。
187
+ # 显式放行需人工设 ADD_DB_ALLOW_DROP=yes,并在迁移评审中登记(禁止默认自动放行)。
144
188
  apply_atlas_diff() {
145
189
  echo "=== 待应用 diff SQL(前 60 行)==="
146
190
  echo "$DIFF_SQL" | head -60
191
+ local drops
192
+ drops="$(printf '%s\n' "$DIFF_SQL" | grep -nE '^DROP |DROP COLUMN|DROP CONSTRAINT|DROP INDEX|DROP TABLE|DROP TYPE|DROP SCHEMA' || true)"
193
+ if [ -n "$drops" ]; then
194
+ echo "!!! 检测到破坏性语句(DROP),已拒绝应用:"
195
+ printf '%s\n' "$drops" | sed 's/^/ /'
196
+ echo " 处置建议:"
197
+ echo " ① 若是无法被 schema 表达的 raw SQL 对象(索引/扩展/向量列)→ 核对登记清单 prisma/raw-objects.sql、prisma/raw-objects-vector.sql;"
198
+ echo " ② 确需删除时人工执行,并在迁移评审中登记(能力矩阵 §六 已登记同类缺口)。"
199
+ if [ "${ADD_DB_ALLOW_DROP:-}" != "yes" ]; then
200
+ echo " (如需在评审后放行:ADD_DB_ALLOW_DROP=yes 重新执行)"
201
+ return 1
202
+ fi
203
+ echo " ⚠️ ADD_DB_ALLOW_DROP=yes 已设置:放行破坏性变更(确认已完成迁移评审)"
204
+ fi
147
205
  read -rp "应用以上 schema 变更?[y/N] " ANS
148
206
  if [ "$ANS" = "y" ] || [ "$ANS" = "yes" ]; then
149
207
  atlas_cmd schema apply --url "$TARGET_URL" --to "file://$BASELINE_SQL" --dev-url "$ATLAS_DEV_URL" "${EXCLUDE_ARGS[@]}"
@@ -173,6 +231,7 @@ atlas_sync() {
173
231
  echo "!!! ATLAS_DEV_URL 未配置。请运行 add-coder init(分库引导自动创建 {project}-add-dev 常驻容器并登记)或手动配置"
174
232
  return 1
175
233
  fi
234
+ prepare_atlas_dev_db
176
235
  build_target
177
236
  generate_baseline
178
237
  if run_atlas_diff; then
@@ -138,6 +138,118 @@ export const CollabContractRowSchema = z.looseObject({
138
138
  updatedAt: z.date(),
139
139
  })
140
140
 
141
+ // ===== Agent Memory 行 schema(对齐 prisma/add.prisma,Plan: add-coder-agent-memory-plan-v2) =====
142
+
143
+ export const MemoryKindSchema = z.enum([
144
+ "DECISION", "CONSTRAINT", "PITFALL", "FAILURE", "LESSON",
145
+ "PATTERN", "CONVENTION", "FACT", "HANDOFF_DIGEST", "HYPOTHESIS",
146
+ ])
147
+ export const MemoryStatusSchema = z.enum([
148
+ "CANDIDATE", "PENDING", "ACTIVE", "STALE", "SUPERSEDED", "REJECTED", "ARCHIVED",
149
+ ])
150
+ export const MemoryScopeTypeSchema = z.enum([
151
+ "ORGANIZATION", "REPOSITORY", "BRANCH", "MODULE", "PATH", "SYMBOL", "PLAN", "SPEC",
152
+ ])
153
+ export const MemorySourceTypeSchema = z.enum([
154
+ "PLAN", "SPEC", "DPS_GATE", "DEV_OPERATION", "RAHS_GATE", "HANDOFF", "MANUAL", "IMPORT",
155
+ ])
156
+ export const EmbeddingStateSchema = z.enum(["DISABLED", "PENDING", "READY", "FAILED", "STALE"])
157
+ export const RecallOutcomeSchema = z.enum([
158
+ "UNKNOWN", "USED", "USEFUL", "IRRELEVANT", "OUTDATED", "CONTRADICTED", "HARMFUL",
159
+ ])
160
+
161
+ export const AddMemoryRowSchema = z.looseObject({
162
+ id: z.string(),
163
+ kind: MemoryKindSchema,
164
+ status: MemoryStatusSchema,
165
+ topic: z.string(),
166
+ content: z.string(),
167
+ summary: z.string().nullable(),
168
+ scopeType: MemoryScopeTypeSchema,
169
+ scopeValue: z.string(),
170
+ repositoryRef: z.string(),
171
+ importance: z.number(),
172
+ confidence: z.number(),
173
+ validFrom: z.date(),
174
+ validUntil: z.date().nullable(),
175
+ supersededById: z.string().nullable(),
176
+ contentHash: z.string(),
177
+ embeddingModel: z.string().nullable(),
178
+ embeddingDim: z.number().nullable(),
179
+ embeddingState: EmbeddingStateSchema,
180
+ createdBy: z.string().nullable(),
181
+ approvedBy: z.string().nullable(),
182
+ approvedAt: z.date().nullable(),
183
+ metadata: z.unknown().nullable(),
184
+ createdAt: z.date(),
185
+ updatedAt: z.date(),
186
+ })
187
+
188
+ export const AddMemoryEvidenceRowSchema = z.looseObject({
189
+ id: z.string(),
190
+ repositoryRef: z.string(),
191
+ sourceType: MemorySourceTypeSchema,
192
+ sourceRef: z.string(),
193
+ planKeyword: z.string().nullable(),
194
+ excerpt: z.string(),
195
+ contentHash: z.string(),
196
+ occurredAt: z.date().nullable(),
197
+ metadata: z.unknown().nullable(),
198
+ createdAt: z.date(),
199
+ })
200
+
201
+ export const AddMemoryEvidenceLinkRowSchema = z.looseObject({
202
+ memoryId: z.string(),
203
+ evidenceId: z.string(),
204
+ relation: z.string().nullable(),
205
+ createdAt: z.date(),
206
+ })
207
+
208
+ export const AddMetricSnapshotRowSchema = z.looseObject({
209
+ id: z.string(),
210
+ repositoryRef: z.string(),
211
+ metricType: z.string(),
212
+ value: z.number(),
213
+ baseline: z.number().nullable(),
214
+ delta: z.number().nullable(),
215
+ unit: z.string().nullable(),
216
+ planKeyword: z.string().nullable(),
217
+ specRef: z.string().nullable(),
218
+ commitSha: z.string().nullable(),
219
+ sourceRef: z.string(),
220
+ metadata: z.unknown().nullable(),
221
+ measuredAt: z.date(),
222
+ })
223
+
224
+ export const AddMemoryRecallRowSchema = z.looseObject({
225
+ id: z.string(),
226
+ repositoryRef: z.string(),
227
+ query: z.string(),
228
+ stage: z.string(),
229
+ consumerRef: z.string().nullable(),
230
+ scopeContext: z.unknown(),
231
+ candidateIds: z.unknown(),
232
+ selectedIds: z.unknown(),
233
+ scoreBreakdown: z.unknown(),
234
+ exclusionReasons: z.unknown().nullable(),
235
+ rankingVersion: z.string(),
236
+ tokenBudget: z.number(),
237
+ injectedTokens: z.number(),
238
+ latencyMs: z.number().nullable(),
239
+ degradedMode: z.string().nullable(),
240
+ createdAt: z.date(),
241
+ })
242
+
243
+ export const AddMemoryRecallItemRowSchema = z.looseObject({
244
+ recallId: z.string(),
245
+ memoryId: z.string(),
246
+ selected: z.boolean(),
247
+ rank: z.number().nullable(),
248
+ outcome: RecallOutcomeSchema,
249
+ feedback: z.string().nullable(),
250
+ updatedAt: z.date(),
251
+ })
252
+
141
253
  // ===== 类型派生(单一真源:schema → 类型) =====
142
254
 
143
255
  export type PlanRow = z.infer<typeof PlanRowSchema>
@@ -147,6 +259,12 @@ export type AuditLogRow = z.infer<typeof AuditLogRowSchema>
147
259
  export type DevOperationRow = z.infer<typeof DevOperationRowSchema>
148
260
  export type AddUserRow = z.infer<typeof AddUserRowSchema>
149
261
  export type CollabContractRow = z.infer<typeof CollabContractRowSchema>
262
+ export type AddMemoryRow = z.infer<typeof AddMemoryRowSchema>
263
+ export type AddMemoryEvidenceRow = z.infer<typeof AddMemoryEvidenceRowSchema>
264
+ export type AddMemoryEvidenceLinkRow = z.infer<typeof AddMemoryEvidenceLinkRowSchema>
265
+ export type AddMetricSnapshotRow = z.infer<typeof AddMetricSnapshotRowSchema>
266
+ export type AddMemoryRecallRow = z.infer<typeof AddMemoryRecallRowSchema>
267
+ export type AddMemoryRecallItemRow = z.infer<typeof AddMemoryRecallItemRowSchema>
150
268
 
151
269
  // ===== 查询参数(Prisma 最常用子集,结构化约束 + 运算符支持) =====
152
270
 
@@ -159,6 +277,7 @@ export interface QueryArgs<T> {
159
277
  orderBy?: { [K in keyof T]?: OrderDirection }
160
278
  take?: number
161
279
  skip?: number
280
+ cursor?: Record<string, unknown>
162
281
  include?: Record<string, unknown>
163
282
  }
164
283
 
@@ -0,0 +1,27 @@
1
+ /*
2
+ * create_hitl 的交互裁决(手写模块;**不要放进 hitl-interaction.strategy.ts**——
3
+ * 那是 `hitl-interaction-rules.toml` 的生成产物,任何手写逻辑都会被 generate 覆盖)。
4
+ *
5
+ * 背景(2026-09-14 修复 Codex 空转):
6
+ * 客户端按安装环境裁决交互方式:Qoder=genui、Codex=mcpApps、其余=inputRequired。
7
+ * `update_hitl` 早已有 `mcpApps` 分支(引导走 core widget),但 `create_hitl` **漏了这一支**,
8
+ * 于是 Codex 下走 elicitation 弹框 → 客户端不展示 → 一直"还在要输入" →
9
+ * 连续 8 轮后以 `inputRequired.maxRounds` 失败(实测报 `still required input after 8 rounds`)。
10
+ */
11
+
12
+ /**
13
+ * create_hitl 是否需要跳过"创建确认弹框":
14
+ * - `_fallback` / `_use_genui`:调用方显式声明的无弹框路径(既有语义);
15
+ * - **`mcpApps`(Codex)**:审批交互在 core widget(`render_hitl_approval`)里完成,
16
+ * 此处直接产出 DRAFT 提案,再由 widget 供用户拍板。
17
+ */
18
+ export function shouldSkipHitlCreateDialog(
19
+ mode: string, // 运行期取 toml 裁决值,收窄到 string 边界
20
+ flags: { fallback?: boolean; useGenui?: boolean; mcpApps?: boolean } = {},
21
+ ): boolean {
22
+ /*
23
+ * `_mcp_apps` 显式开关(2026-09-14 自 farm-agent 回灌):环境裁决之外再给调用方一个强制入口——
24
+ * 适配其它客户端/build、或需要确定性地走 widget 流程时不必依赖 toml 探测结果。
25
+ */
26
+ return Boolean(flags.useGenui || flags.fallback || flags.mcpApps) || mode === "mcpApps"
27
+ }