add-coder 0.3.33 → 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.
- package/dist/index.js +21 -3
- package/package.json +3 -3
- package/templates/.add-coder-src-hash.json +95 -41
- package/templates/adapters/claude/hooks/doc-format-guard.mjs +172 -84
- package/templates/adapters/claude/hooks/post-tool-use.mjs +59 -1
- package/templates/adapters/claude/hooks/prompt-submit.mjs +72 -0
- package/templates/adapters/claude/hooks/session-start.mjs +65 -1
- package/templates/adapters/codex/hooks/doc-format-guard.mjs +172 -84
- package/templates/adapters/codex/hooks/post-tool-use.mjs +59 -1
- package/templates/adapters/codex/hooks/prompt-submit.mjs +72 -0
- package/templates/adapters/codex/hooks/session-start.mjs +65 -1
- package/templates/adapters/qoder/hooks/doc-format-guard.mjs +172 -84
- package/templates/adapters/qoder/hooks/post-tool-use.mjs +59 -1
- package/templates/adapters/qoder/hooks/prompt-submit.mjs +72 -0
- package/templates/adapters/qoder/hooks/session-start.mjs +67 -1
- package/templates/adapters/trae/hooks/doc-format-guard.mjs +172 -84
- package/templates/adapters/trae/hooks/post-tool-use.mjs +59 -1
- package/templates/adapters/trae/hooks/prompt-submit.mjs +72 -0
- package/templates/adapters/trae/hooks/session-start.mjs +65 -1
- package/templates/adapters/vscode/hooks/doc-format-guard.mjs +172 -84
- package/templates/adapters/vscode/hooks/post-tool-use.mjs +59 -1
- package/templates/adapters/vscode/hooks/prompt-submit.mjs +72 -0
- package/templates/adapters/vscode/hooks/session-start.mjs +65 -1
- package/templates/core/governance/doc-format-guard.ts +29 -112
- package/templates/core/governance/post-tool-router.ts +33 -1
- package/templates/core/governance/prompt-router.ts +47 -0
- package/templates/core/governance/session-start-guard.ts +48 -1
- package/templates/core/prisma/add.prisma +203 -0
- package/templates/core/scripts/db-ensure.sh +61 -2
- package/templates/core/scripts/mcp-server/shared/db-types.ts +119 -0
- package/templates/core/scripts/mcp-server/shared/hitl-create-policy.ts +27 -0
- package/templates/core/scripts/mcp-server/shared/hitl-proposal-content.ts +110 -0
- package/templates/core/scripts/mcp-server/shared/hitl-widget-instance.ts +85 -0
- package/templates/core/scripts/mcp-server/shared/memory/calibration/batch-fit.ts +250 -0
- package/templates/core/scripts/mcp-server/shared/memory/calibration/feedback-stats.ts +101 -0
- package/templates/core/scripts/mcp-server/shared/memory/calibration/unit-state.ts +224 -0
- package/templates/core/scripts/mcp-server/shared/memory/domain/conflicts.ts +59 -0
- package/templates/core/scripts/mcp-server/shared/memory/domain/dedup.ts +45 -0
- package/templates/core/scripts/mcp-server/shared/memory/domain/errors.ts +33 -0
- package/templates/core/scripts/mcp-server/shared/memory/domain/handoff-digest.ts +92 -0
- package/templates/core/scripts/mcp-server/shared/memory/domain/metric-candidate.ts +79 -0
- package/templates/core/scripts/mcp-server/shared/memory/domain/scope.ts +94 -0
- package/templates/core/scripts/mcp-server/shared/memory/domain/secrets.ts +50 -0
- package/templates/core/scripts/mcp-server/shared/memory/domain/state-machine.ts +90 -0
- package/templates/core/scripts/mcp-server/shared/memory/embedding/index.ts +117 -0
- package/templates/core/scripts/mcp-server/shared/memory/embedding/local-onnx.ts +105 -0
- package/templates/core/scripts/mcp-server/shared/memory/embedding/openai-compatible.ts +87 -0
- package/templates/core/scripts/mcp-server/shared/memory/jobs/consolidation.ts +226 -0
- package/templates/core/scripts/mcp-server/shared/memory/jobs/evidence-collector.ts +153 -0
- package/templates/core/scripts/mcp-server/shared/memory/jobs/snapshot.ts +114 -0
- package/templates/core/scripts/mcp-server/shared/memory/metrics/gate-recall.ts +134 -0
- package/templates/core/scripts/mcp-server/shared/memory/metrics/gate-writer.ts +217 -0
- package/templates/core/scripts/mcp-server/shared/memory/metrics/stage-words.ts +69 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/context-builder.ts +89 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/fts/pg.ts +139 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/fts/sqlite-fts5.sql +29 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/fts/sqlite.ts +106 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/fusion.ts +43 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/pipeline.ts +285 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/query-terms.ts +31 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/recall-writer.ts +87 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/reranker.ts +116 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/types.ts +52 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/vector/pgvector.ts +143 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/vector/sqlite-vec.ts +118 -0
- package/templates/core/scripts/mcp-server/shared/memory/switches.ts +39 -0
- package/templates/core/scripts/mcp-server/shared/review-files.ts +22 -0
- package/templates/core/scripts/mcp-server/shared/runtime-freshness.ts +235 -0
- package/templates/core/scripts/mcp-server/tools/gateway/check_dps.ts +41 -3
- package/templates/core/scripts/mcp-server/tools/gateway/check_rahs.ts +40 -1
- package/templates/core/scripts/mcp-server/tools/gateway/check_spec_sync.ts +2 -2
- package/templates/core/scripts/mcp-server/tools/hitl.ts +108 -42
- package/templates/core/scripts/mcp-server/tools/index.ts +7 -1
- package/templates/core/scripts/mcp-server/tools/memory-compat.ts +258 -0
- package/templates/core/scripts/mcp-server/tools/memory.ts +654 -0
- package/templates/core/scripts/mcp-server/tools/plan.ts +8 -3
- package/templates/core/scripts/mcp-server/tools/review.ts +10 -7
- package/templates/core/scripts/mcp-server.ts +36 -0
- package/templates/core/templates/checklist-template.md +13 -0
- package/templates/core/templates/review-implementation-template.md +24 -0
- package/templates/core/templates/review-template.md +16 -0
- package/templates/core/validation/index.ts +136 -0
- package/templates/core/validation/policy.ts +91 -0
- package/templates/core/validation/registry.ts +61 -0
- package/templates/core/validation/schema-validator.ts +277 -0
- package/templates/core/validation/validators/add-route.ts +32 -0
- package/templates/core/validation/validators/checklist.ts +48 -0
- package/templates/core/validation/validators/handoff.ts +46 -0
- package/templates/core/validation/validators/hitl.ts +22 -0
- package/templates/core/validation/validators/index.ts +52 -0
- package/templates/core/validation/validators/plan.ts +20 -0
- package/templates/core/validation/validators/report.ts +16 -0
- package/templates/core/validation/validators/review.ts +30 -0
- package/templates/core/validation/validators/spec.ts +25 -0
- package/templates/core/validation/validators/tasks.ts +40 -0
- package/templates/core/validation/validators/types.ts +32 -0
- package/templates/core/vocabulary/add-governance-vocabulary.md +18 -0
- package/templates/core/scripts/mcp-server/shared/dps-spec-ref.ts +0 -17
|
@@ -11,22 +11,8 @@ import { spawnSync } from "node:child_process"
|
|
|
11
11
|
import { detectActiveAdd, localIsoSeconds } from "./common.js"
|
|
12
12
|
import { writeHookEvent } from "./notify.js"
|
|
13
13
|
import { doc } from "./rules.js"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
id?: string
|
|
17
|
-
heading?: string
|
|
18
|
-
required?: boolean
|
|
19
|
-
anchor?: string
|
|
20
|
-
within?: string
|
|
21
|
-
subsections?: Array<{ heading?: string }>
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
interface SchemaFile {
|
|
25
|
-
sections: SchemaSection[]
|
|
26
|
-
placeholders?: string[]
|
|
27
|
-
forbidden_terms?: string[]
|
|
28
|
-
groupColumn?: number | string
|
|
29
|
-
}
|
|
14
|
+
// 校验逻辑真源:core 校验层(Plan core-validation-lifecycle 轮 3——守卫不再内联实现)
|
|
15
|
+
import { validateAgainstSchema, type SchemaFile } from "../validation/schema-validator.js"
|
|
30
16
|
|
|
31
17
|
/** token 规则条目(rules.doc.token_rules 结构) */
|
|
32
18
|
interface TokenRule {
|
|
@@ -161,104 +147,35 @@ export class DocFormatGuard {
|
|
|
161
147
|
|
|
162
148
|
/** 章节/锚定/占位符/禁词校验,返回 struct 统计 */
|
|
163
149
|
private runSchemaChecks(schema: SchemaFile, templateName: string, content: string, isSearchReplace: boolean): { applied: number; missed: number; anchorHit: boolean } {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
? readFileSync(join(templatesDir, templateName), "utf-8")
|
|
182
|
-
: ""
|
|
183
|
-
const refLine = templateContent.split("\n").find((l) => l.includes(section.anchor as string))
|
|
184
|
-
if (!refLine) {
|
|
185
|
-
process.stderr.write(`[doc-format-guard] anchor_miss: schema ${section.id} 声明的 anchor '${section.anchor}' 在 ${templateName} 中未定位,跳过该规则(冒烟巡检兑底)\n`)
|
|
186
|
-
applied--
|
|
187
|
-
continue
|
|
188
|
-
}
|
|
189
|
-
const tokens = [...new Set(refLine.replace(/[#*`|(){]/g, " ").split(/\s+/).filter((t) => t !== "" && !t.includes("{")))]
|
|
190
|
-
if (tokens.length === 0) {
|
|
191
|
-
applied--
|
|
192
|
-
continue
|
|
193
|
-
}
|
|
194
|
-
let scope = content
|
|
195
|
-
if (section.within) {
|
|
196
|
-
if (content.includes(section.within)) {
|
|
197
|
-
const startIdx = content.indexOf(section.within)
|
|
198
|
-
const endIdx = content.indexOf("\n## ", startIdx + 1)
|
|
199
|
-
scope = content.slice(startIdx, endIdx === -1 ? undefined : endIdx)
|
|
200
|
-
} else {
|
|
201
|
-
process.stderr.write(`[doc-format-guard] within_miss: schema ${section.id} 的 within '${section.within}' 在文档中未定位,跳过该规则\n`)
|
|
202
|
-
applied--
|
|
203
|
-
continue
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
const missTokens = tokens.filter((tok) => !scope.includes(tok))
|
|
207
|
-
if (missTokens.length > 0) {
|
|
208
|
-
this.issues.push(` 缺锚点(${section.id}): ${missTokens.join(" ")}`)
|
|
209
|
-
missed++
|
|
210
|
-
anchorHit = false
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// 必选章节
|
|
215
|
-
for (const heading of requiredHeadings) {
|
|
216
|
-
applied++
|
|
217
|
-
if (!content.includes(heading)) {
|
|
218
|
-
this.issues.push(` 缺章节: ${heading}`)
|
|
219
|
-
missed++
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
// 子章节
|
|
223
|
-
for (const sub of subs) {
|
|
224
|
-
applied++
|
|
225
|
-
if (!content.includes(sub)) {
|
|
226
|
-
this.issues.push(` 缺子章节: ${sub}`)
|
|
227
|
-
missed++
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// 占位符
|
|
233
|
-
for (const ph of placeholders) {
|
|
234
|
-
if (content.includes(ph)) {
|
|
235
|
-
this.issues.push(` 未替换占位符: ${ph}`)
|
|
236
|
-
missed++
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// 结构位禁词(标题行 + groupColumn)
|
|
241
|
-
let structText = (content.match(/^#{2,}\s.*$/gm) ?? []).join("\n")
|
|
242
|
-
const col = typeof schema.groupColumn === "number" ? schema.groupColumn : Number(schema.groupColumn)
|
|
243
|
-
if (!Number.isNaN(col) && col > 0) {
|
|
244
|
-
const colLines = content
|
|
245
|
-
.split("\n")
|
|
246
|
-
.map((l) => {
|
|
247
|
-
const cells = l.split("|")
|
|
248
|
-
return cells.length > col ? (cells[col + 1] ?? "").trim() : ""
|
|
249
|
-
})
|
|
250
|
-
.filter(Boolean)
|
|
251
|
-
structText += "\n" + colLines.join("\n")
|
|
252
|
-
}
|
|
253
|
-
for (const term of terms) {
|
|
254
|
-
applied++
|
|
255
|
-
if (structText.includes(term)) {
|
|
256
|
-
this.issues.push(` 结构位禁词: ${term}`)
|
|
257
|
-
missed++
|
|
258
|
-
}
|
|
150
|
+
// ★ 校验真源 = core 校验层(Plan core-validation-lifecycle 轮 3:删除内联实现)
|
|
151
|
+
// SearchReplace 只传 patch → 标题/锚定类规则不适用,仅保留占位符与结构位禁词
|
|
152
|
+
const templatePath = join(this.projectDir, this.magicDir, "templates", templateName)
|
|
153
|
+
const templateContent = existsSync(templatePath) ? readFileSync(templatePath, "utf-8") : ""
|
|
154
|
+
const all = validateAgainstSchema(content, schema, { templateContent })
|
|
155
|
+
const relevant = isSearchReplace
|
|
156
|
+
? all.filter((i) => i.code === "PLACEHOLDER_LEFT" || i.code === "FORBIDDEN_TERM")
|
|
157
|
+
: all
|
|
158
|
+
|
|
159
|
+
for (const i of relevant) this.issues.push(` ${i.detail}`)
|
|
160
|
+
|
|
161
|
+
const missed = relevant.length
|
|
162
|
+
const declared = this.countDeclaredRules(schema, isSearchReplace)
|
|
163
|
+
return {
|
|
164
|
+
applied: Math.max(missed, declared),
|
|
165
|
+
missed,
|
|
166
|
+
anchorHit: !relevant.some((i) => i.code === "ANCHOR_MISS"),
|
|
259
167
|
}
|
|
168
|
+
}
|
|
260
169
|
|
|
261
|
-
|
|
170
|
+
/** 已声明规则数(用于 struct_score 分母;口径与原内联实现一致:按规则条数计) */
|
|
171
|
+
private countDeclaredRules(schema: SchemaFile, isSearchReplace: boolean): number {
|
|
172
|
+
const placeholders = (schema.placeholders ?? []).length
|
|
173
|
+
const terms = (schema.forbidden_terms ?? []).length
|
|
174
|
+
if (isSearchReplace) return placeholders + terms
|
|
175
|
+
const anchors = schema.sections.filter((s) => s.anchor).length
|
|
176
|
+
const required = schema.sections.filter((s) => s.required === true && s.heading).length
|
|
177
|
+
const subs = schema.sections.flatMap((s) => s.subsections ?? []).filter((s) => s.heading).length
|
|
178
|
+
return anchors + required + subs + placeholders + terms
|
|
262
179
|
}
|
|
263
180
|
|
|
264
181
|
/** 算法化规则校验(真源: [doc.anti_cheat] + HITL 表非空 + handoff 冲突)
|
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
//
|
|
4
4
|
// 设计范式: OOP 路由类(§1 DPS 哨兵自动化 / §2 Edit·Write 守卫 / §3 Bash 增强)。
|
|
5
5
|
|
|
6
|
-
import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs"
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, appendFileSync, writeFileSync } from "node:fs"
|
|
7
7
|
import { join, basename, dirname } from "node:path"
|
|
8
8
|
import { jsonGet } from "./common.js"
|
|
9
9
|
import { AuditBridge } from "./audit-bridge.js"
|
|
10
|
+
import { evidenceEnabled, EVIDENCE_QUEUE_FILE, MEMORY_DIR_NAME, recallMode } from "../scripts/mcp-server/shared/memory/switches.js"
|
|
11
|
+
import { buildEvidenceEvent, classifyEvidenceSource } from "../scripts/mcp-server/shared/memory/jobs/evidence-collector.js"
|
|
10
12
|
|
|
11
13
|
/** 纯函数:从 input 中提取 output/content 拼接文本(对齐 bash grep -o 组合) */
|
|
12
14
|
function extractOutputText(raw: string): string {
|
|
@@ -165,6 +167,9 @@ export class PostToolRouter {
|
|
|
165
167
|
|
|
166
168
|
// §2f: 审计桥接(Task 8.1,ADD-7 自动化)——文件写入事件 → jsonl → MCP 常驻消费落库
|
|
167
169
|
this.emitWriteEvent(filePath)
|
|
170
|
+
|
|
171
|
+
// §2g: Memory 采证入队(白名单 → evidence-queue.jsonl → consolidation 异步落库)
|
|
172
|
+
this.emitMemoryEvidence(filePath)
|
|
168
173
|
} else if (toolName === "Bash") {
|
|
169
174
|
// ═══════════════ §3: Bash matcher: 结果增强(扩展点: codex 文本)═══════════════
|
|
170
175
|
this.emitLine(this.emitBashDone())
|
|
@@ -200,6 +205,33 @@ export class PostToolRouter {
|
|
|
200
205
|
this.auditBridge.emit(filePath)
|
|
201
206
|
}
|
|
202
207
|
|
|
208
|
+
/**
|
|
209
|
+
* §2g Memory 采证入队(Spec §10 PostToolUse 白名单,扩展点):
|
|
210
|
+
* 白名单路径(plans/specs/reviews/handoff)→ 追加 evidence-queue.jsonl(幂等 dedupKey),
|
|
211
|
+
* 由 consolidation job 异步落库。ADD_MEMORY_EVIDENCE=off 或 RECALL_MODE=off 时关闭。
|
|
212
|
+
* fail-open:任何异常不阻断文件写入主路径(Plan §9.3)。
|
|
213
|
+
*/
|
|
214
|
+
protected emitMemoryEvidence(filePath: string): void {
|
|
215
|
+
try {
|
|
216
|
+
if (!evidenceEnabled() || recallMode() === "off") return
|
|
217
|
+
if (!classifyEvidenceSource(filePath)) return
|
|
218
|
+
if (!this.magicDir) return
|
|
219
|
+
let excerpt = ""
|
|
220
|
+
try {
|
|
221
|
+
excerpt = readFileSync(filePath, "utf-8").slice(0, 500)
|
|
222
|
+
} catch {
|
|
223
|
+
excerpt = basename(filePath)
|
|
224
|
+
}
|
|
225
|
+
const ev = buildEvidenceEvent(filePath, excerpt)
|
|
226
|
+
const dir = join(this.projectDir, this.magicDir, MEMORY_DIR_NAME)
|
|
227
|
+
mkdirSync(dir, { recursive: true })
|
|
228
|
+
appendFileSync(join(dir, EVIDENCE_QUEUE_FILE), JSON.stringify(ev) + "\n", "utf-8")
|
|
229
|
+
this.emitLine(`[ADD PostToolUse] 🧠 记忆采证入队: ${filePath}(consolidation 时落库)\n`)
|
|
230
|
+
} catch {
|
|
231
|
+
/* fail-open */
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
203
235
|
/** §3 Bash 增强(core: lint/tsc;codex 子类: lint/typecheck/test) */
|
|
204
236
|
protected emitBashDone(): string {
|
|
205
237
|
return "[ADD PostToolUse] 命令执行完成。如有 lint/tsc 错误请修复。\n"
|
|
@@ -17,6 +17,14 @@ import {
|
|
|
17
17
|
import { loadDevKeywords, matchTrigger } from "./vocabulary.js"
|
|
18
18
|
import { writeHookEvent } from "./notify.js"
|
|
19
19
|
import { event } from "./rules.js"
|
|
20
|
+
import { recallMode } from "../scripts/mcp-server/shared/memory/switches.js"
|
|
21
|
+
import {
|
|
22
|
+
buildStageRecallHint,
|
|
23
|
+
detectRecallStage,
|
|
24
|
+
} from "../scripts/mcp-server/shared/memory/metrics/stage-words.js"
|
|
25
|
+
|
|
26
|
+
/** 显式记忆回忆意图触发词(Plan §9.1:对话触发词仅作补充入口;vocabulary 文档类别 G 同步维护) */
|
|
27
|
+
const MEMORY_RECALL_HINT_RE = /之前|上次|还记得|记得吗|为什么当时|类似问题|历史决策|以前的方案|曾经怎么/i
|
|
20
28
|
|
|
21
29
|
/**
|
|
22
30
|
* UserPromptSubmit 触发词路由基类(模板方法):
|
|
@@ -108,6 +116,41 @@ export class PromptRouter {
|
|
|
108
116
|
// core 协议: 无前置注入
|
|
109
117
|
}
|
|
110
118
|
|
|
119
|
+
/**
|
|
120
|
+
* 显式记忆回忆提示(Spec §10 / Plan §9.1 补充入口):
|
|
121
|
+
* 命中回忆词且 ADD_MEMORY_RECALL_MODE != off → 提示调用 recall_memory。
|
|
122
|
+
* 不做自动召回(同步 Hook ≤200ms 无 DB);shadow 模式下提示语明示"仅显式调用"。
|
|
123
|
+
*/
|
|
124
|
+
protected maybeMemoryHint(prompt: string): void {
|
|
125
|
+
try {
|
|
126
|
+
if (recallMode() === "off") return
|
|
127
|
+
if (!MEMORY_RECALL_HINT_RE.test(prompt)) return
|
|
128
|
+
const mode = recallMode()
|
|
129
|
+
const suffix = mode === "shadow" ? "(当前 shadow 模式:召回将落审计,Hook 不自动注入)" : ""
|
|
130
|
+
process.stdout.write(
|
|
131
|
+
`[Memory] 检测到历史回忆意图${suffix}。可调用 recall_memory({ query: <你的问题>, stage: "prompt" }) 获取受治理的记忆上下文(含来源与评分)。\n`
|
|
132
|
+
)
|
|
133
|
+
} catch {
|
|
134
|
+
/* fail-open */
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 阶段确定性召回提示(Spec §2 §DeterministicRecall,Plan §3.1 方案 B1 的 Hook 侧):
|
|
140
|
+
* 命中 ADD 阶段词 → 提示到工具侧做确定性召回(plan-start/spec-start/dps/rahs/handoff)。
|
|
141
|
+
* Hook 同步路径无 DB:只输出提示文本,不召回、不写审计;任何异常 fail-open。
|
|
142
|
+
*/
|
|
143
|
+
protected maybeStageHint(prompt: string): void {
|
|
144
|
+
try {
|
|
145
|
+
const stage = detectRecallStage(prompt)
|
|
146
|
+
if (!stage) return
|
|
147
|
+
const hint = buildStageRecallHint(stage, recallMode())
|
|
148
|
+
if (hint) process.stdout.write(hint)
|
|
149
|
+
} catch {
|
|
150
|
+
/* fail-open */
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
111
154
|
/** Layer 3 输出形态(core: 纯文本逐行;qoder: hookSpecificOutput JSON 包) */
|
|
112
155
|
protected layer3Json(): boolean {
|
|
113
156
|
return false
|
|
@@ -148,6 +191,10 @@ export class PromptRouter {
|
|
|
148
191
|
return 0
|
|
149
192
|
}
|
|
150
193
|
|
|
194
|
+
// ─── Layer 1.5: 显式记忆回忆意图(补充入口;不替代确定性召回,见 Plan §9.1)───
|
|
195
|
+
this.maybeMemoryHint(prompt)
|
|
196
|
+
this.maybeStageHint(prompt)
|
|
197
|
+
|
|
151
198
|
// ─── 开发关键词检测(动态加载) ───
|
|
152
199
|
const devKw = loadDevKeywords()
|
|
153
200
|
if (devKw.length === 0) return 0
|
|
@@ -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
|
}
|
|
@@ -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
|
-
#
|
|
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
|
|
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
|