add-coder 0.3.38 → 0.3.40
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/README.en.md +16 -3
- package/README.md +16 -3
- package/dist/index.js +421 -70
- package/package.json +3 -2
- package/templates/.add-coder-src-hash.json +74 -61
- package/templates/adapters/claude/hooks/post-tool-use.mjs +1 -1
- package/templates/adapters/claude/hooks/prompt-submit.mjs +1 -1
- package/templates/adapters/claude/hooks/session-start.mjs +25 -5
- package/templates/adapters/claude/hooks/stop-check.mjs +2 -2
- package/templates/adapters/codex/hooks/post-tool-use.mjs +1 -1
- package/templates/adapters/codex/hooks/prompt-submit.mjs +1 -1
- package/templates/adapters/codex/hooks/session-start.mjs +25 -5
- package/templates/adapters/codex/hooks/stop-check.mjs +2 -2
- package/templates/adapters/qoder/hooks/post-tool-use.mjs +1 -1
- package/templates/adapters/qoder/hooks/prompt-submit.mjs +1 -1
- package/templates/adapters/qoder/hooks/session-start.mjs +25 -5
- package/templates/adapters/qoder/hooks/stop-check.mjs +2 -2
- package/templates/adapters/trae/hooks/post-tool-use.mjs +1 -1
- package/templates/adapters/trae/hooks/prompt-submit.mjs +1 -1
- package/templates/adapters/trae/hooks/session-start.mjs +25 -5
- package/templates/adapters/trae/hooks/stop-check.mjs +2 -2
- package/templates/adapters/vscode/hooks/post-tool-use.mjs +1 -1
- package/templates/adapters/vscode/hooks/prompt-submit.mjs +1 -1
- package/templates/adapters/vscode/hooks/session-start.mjs +25 -5
- package/templates/adapters/vscode/hooks/stop-check.mjs +2 -2
- package/templates/core/AGENTS.md +46 -0
- package/templates/core/docs/ADD-governance-claude-code.md +52 -0
- package/templates/core/docs/ADD-governance-codex.md +62 -0
- package/templates/core/docs/ADD-governance-qoder-cn.md +30 -0
- package/templates/core/docs/ADD-governance-trae.md +42 -0
- package/templates/core/docs/ADD-governance-vscode-copilot.md +88 -0
- package/templates/core/governance/rules.ts +2 -2
- package/templates/core/governance/session-start-guard.ts +28 -4
- package/templates/core/prisma/add.prisma +4 -0
- package/templates/core/scripts/mcp-server/resources/hitl-approval-widget.ts +4 -2
- package/templates/core/scripts/mcp-server/resources/index.ts +2 -0
- package/templates/core/scripts/mcp-server/resources/widget-probe.ts +53 -0
- package/templates/core/scripts/mcp-server/shared/db-types.ts +5 -1
- package/templates/core/scripts/mcp-server/shared/dev-operation.ts +97 -0
- package/templates/core/scripts/mcp-server/shared/hitl-ui.ts +46 -1
- package/templates/core/scripts/mcp-server/shared/memory/jobs/snapshot.ts +9 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/cjk-segmenter.ts +140 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/cjk-tokenize.ts +62 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/fts/pg.ts +68 -16
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/fts/sqlite-fts5.sql +3 -3
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/fts/sqlite.ts +23 -6
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/fts-fingerprint.ts +116 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/query-terms.ts +9 -25
- package/templates/core/scripts/mcp-server/shared/memory/switches.ts +4 -2
- package/templates/core/scripts/mcp-server/shared/plan-lifecycle.ts +16 -3
- package/templates/core/scripts/mcp-server/shared/server-capabilities.ts +65 -0
- package/templates/core/scripts/mcp-server/tools/audit.ts +16 -34
- package/templates/core/scripts/mcp-server/tools/gateway/check_add_route_completeness.ts +17 -4
- package/templates/core/scripts/mcp-server/tools/gateway/check_add_route_status.ts +34 -6
- package/templates/core/scripts/mcp-server/tools/gateway/check_dps.ts +15 -25
- package/templates/core/scripts/mcp-server/tools/gateway/check_rahs.ts +23 -26
- package/templates/core/scripts/mcp-server/tools/gateway/check_spec_sync.ts +56 -38
- package/templates/core/scripts/mcp-server/tools/gateway/plan-resolve.ts +270 -0
- package/templates/core/scripts/mcp-server/tools/hitl.ts +18 -8
- package/templates/core/scripts/mcp-server/tools/index.ts +7 -2
- package/templates/core/scripts/mcp-server/tools/memory.ts +235 -1
- package/templates/core/scripts/mcp-server/tools/plan-lifecycle.ts +219 -0
- package/templates/core/scripts/mcp-server/tools/plan.ts +2 -0
- package/templates/core/scripts/mcp-server/tools/widget-probe.ts +53 -0
- package/templates/core/scripts/mcp-server.ts +5 -1
- package/templates/core/scripts/memory/backfill-search-text.ts +132 -0
- package/templates/core/scripts/memory/memory-jobs.ts +124 -0
- package/templates/core/scripts/plan-close.ts +85 -0
- package/templates/core/skills/add-paradigm/SKILL.md +19 -4
- package/templates/core/skills/session-init/SKILL.md +27 -0
- package/templates/core/templates/add-route-template-heavyweight.md +2 -0
- package/templates/core/templates/add-route-template.md +4 -0
- package/templates/core/templates/checklist-template.md +2 -0
- package/templates/core/templates/handoff-multi-round-template.md +2 -0
- package/templates/core/templates/review-implementation-template.md +18 -0
- package/templates/core/templates/review-implementation-template.schema.json +7 -1
- package/templates/core/templates/simple-plan-template.md +4 -0
- package/templates/core/vocabulary/add-governance-vocabulary.md +2 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from "zod"
|
|
2
|
+
import type { ToolRegistrar } from "./registrar.js"
|
|
3
|
+
import { WIDGET_PROBE_MIME, WIDGET_PROBE_URI } from "../resources/widget-probe.js"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `probe_widget_render` —— 调用即在宿主里触发一次最小 widget 渲染(诊断用,2026-09-21 引入)。
|
|
7
|
+
*
|
|
8
|
+
* 判读方式(见 `resources/widget-probe.ts` 的说明):
|
|
9
|
+
* · 面板显示「HTML 已加载(内联脚本未执行)」 ⇒ 宿主默认 CSP 拦 inline script(需在资源 `_meta.ui.csp` 声明)
|
|
10
|
+
* · 面板显示「内联脚本已执行」 ⇒ 宿主能渲染 app,问题出在业务 widget 的实现(握手/DOM/数据注入)
|
|
11
|
+
* · 面板仍报 couldn't be loaded ⇒ 宿主渲染器本身的问题(提上游)
|
|
12
|
+
*/
|
|
13
|
+
export function registerWidgetProbeTools(server: ToolRegistrar) {
|
|
14
|
+
server.registerTool(
|
|
15
|
+
"probe_widget_render",
|
|
16
|
+
{
|
|
17
|
+
title: "Widget 渲染探针",
|
|
18
|
+
description:
|
|
19
|
+
"渲染一个最小 MCP Apps widget 以定位面板加载失败(诊断用):区分宿主渲染器故障、宿主 CSP 拦截内联脚本、业务 widget 自身缺陷。只读,不改任何状态。",
|
|
20
|
+
inputSchema: z.object({}),
|
|
21
|
+
outputSchema: z.object({
|
|
22
|
+
probeUri: z.string(),
|
|
23
|
+
mimeType: z.string(),
|
|
24
|
+
hint: z.string(),
|
|
25
|
+
}),
|
|
26
|
+
annotations: { readOnlyHint: true },
|
|
27
|
+
_meta: {
|
|
28
|
+
ui: { resourceUri: WIDGET_PROBE_URI },
|
|
29
|
+
"openai/outputTemplate": WIDGET_PROBE_URI,
|
|
30
|
+
"openai/toolInvocation/invoking": "正在渲染最小 widget 探针…",
|
|
31
|
+
"openai/toolInvocation/invoked": "最小 widget 探针已渲染",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
async () => ({
|
|
35
|
+
content: [
|
|
36
|
+
{
|
|
37
|
+
type: "text" as const,
|
|
38
|
+
text: [
|
|
39
|
+
`已请求渲染最小探针 widget:${WIDGET_PROBE_URI}`,
|
|
40
|
+
"判读:面板显示「内联脚本已执行」= 宿主可渲染 app(问题在业务 widget)",
|
|
41
|
+
" 面板显示「HTML 已加载(内联脚本未执行)」= 宿主 CSP 拦截 inline script",
|
|
42
|
+
" 面板报 couldn't be loaded = 宿主渲染器问题(提上游)",
|
|
43
|
+
].join("\n"),
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
structuredContent: {
|
|
47
|
+
probeUri: WIDGET_PROBE_URI,
|
|
48
|
+
mimeType: WIDGET_PROBE_MIME,
|
|
49
|
+
hint: "见 content 中的三段判读",
|
|
50
|
+
},
|
|
51
|
+
}),
|
|
52
|
+
)
|
|
53
|
+
}
|
|
@@ -14,6 +14,7 @@ import { PlanRoundSubscriber } from "./mcp-server/shared/plan-round-subscriber.j
|
|
|
14
14
|
import { queryPlanRounds, type PlanRoundReadClient } from "./mcp-server/shared/plan-round-store.js"
|
|
15
15
|
import { PROJECT_ROOT, MAGIC_DIR } from "./mcp-server/shared/env.js"
|
|
16
16
|
import { clearRestartRequiredMarker } from "./mcp-server/shared/runtime-freshness.js"
|
|
17
|
+
import { buildServerOptions } from "./mcp-server/shared/server-capabilities.js"
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* 启动后清掉本 adapter 的"需重启"标记(2026-09-14):
|
|
@@ -50,7 +51,10 @@ function watchLauncher(): void {
|
|
|
50
51
|
async function main() {
|
|
51
52
|
const server = new McpServer(
|
|
52
53
|
{ name: "add-dev-tools", version: "1.0.0" },
|
|
53
|
-
|
|
54
|
+
// 能力与 instructions 的真源在 shared/server-capabilities.ts(可被单测断言):
|
|
55
|
+
// extensions["io.modelcontextprotocol/ui"] 走官方扩展协商;legacy 位(工具 _meta.ui.resourceUri +
|
|
56
|
+
// openai/outputTemplate)在 tools/hitl.ts 中保留,两条路径并存。
|
|
57
|
+
buildServerOptions()
|
|
54
58
|
)
|
|
55
59
|
registerAll(server)
|
|
56
60
|
const transport = new StdioServerTransport()
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* backfill-search-text.ts — 存量记忆行的检索展开文本回填(真源,随模板分发到 `{magicDir}/scripts/memory/`)
|
|
3
|
+
*
|
|
4
|
+
* 为什么需要:`searchText` 是**写入期**产出的检索列(主通道索引建立在其 tsvector 表达式上)。
|
|
5
|
+
* 本次升级之前写入的存量行该列为空 ⇒ 主通道对它们完全不可见("旧数据检索不到")。
|
|
6
|
+
*
|
|
7
|
+
* 三种模式:
|
|
8
|
+
* --probe 只统计与抽样,不改库;缺失为 0 时打印收敛结论
|
|
9
|
+
* --apply 分页回填;**只处理 searchText 为空串的行**(幂等:重复执行为 no-op)
|
|
10
|
+
* --refresh 重算**所有非空行**的 searchText(分词器/用户词典变更后把历史 token 统一到当前口径)
|
|
11
|
+
* 失败行:打印清单(id + 原因)并以**非零退出码**结束;有失败/遗漏时**不写指纹标记**(避免假装同源)。
|
|
12
|
+
*
|
|
13
|
+
* 用法(工作目录 = 项目根):
|
|
14
|
+
* PROJECT_ROOT=$PWD MAGIC_DIR=.codex DATABASE_URL="postgresql://…" \
|
|
15
|
+
* npx tsx "${MAGIC_DIR}/scripts/memory/backfill-search-text.ts" --probe|--apply [--refresh] [--limit N]
|
|
16
|
+
*/
|
|
17
|
+
const args = process.argv.slice(2)
|
|
18
|
+
const hasFlag = (n: string) => args.includes(n)
|
|
19
|
+
const argValue = (n: string) => {
|
|
20
|
+
const i = args.indexOf(n)
|
|
21
|
+
return i >= 0 ? args[i + 1] : undefined
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const mode = hasFlag("--apply") ? "apply" : hasFlag("--probe") ? "probe" : null
|
|
25
|
+
const refresh = hasFlag("--refresh")
|
|
26
|
+
if (!mode) {
|
|
27
|
+
console.error("usage: backfill-search-text.ts --probe|--apply [--refresh] [--limit N]")
|
|
28
|
+
process.exit(2)
|
|
29
|
+
}
|
|
30
|
+
const pageSize = Number(argValue("--limit") ?? "500")
|
|
31
|
+
|
|
32
|
+
const { prisma } = await import("../mcp-server/shared/prisma.js")
|
|
33
|
+
const { expandForIndexWithMethod } = await import("../mcp-server/shared/memory/retrieval/cjk-segmenter.js")
|
|
34
|
+
const { computeFtsFingerprint, writeRecordedFingerprint } = await import(
|
|
35
|
+
"../mcp-server/shared/memory/retrieval/fts-fingerprint.js"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
/** 缺失 = searchText 为空串,且原始字段非空(原文为空的行本就不该有 token) */
|
|
39
|
+
const missingWhere = { searchText: "", NOT: { AND: [{ topic: "" }, { content: "" }] } }
|
|
40
|
+
|
|
41
|
+
async function probe(): Promise<void> {
|
|
42
|
+
const missing = await prisma.addMemory.count({ where: missingWhere })
|
|
43
|
+
const total = await prisma.addMemory.count()
|
|
44
|
+
const sample = await prisma.addMemory.findMany({
|
|
45
|
+
where: missingWhere,
|
|
46
|
+
select: { id: true, topic: true, kind: true },
|
|
47
|
+
take: 5,
|
|
48
|
+
})
|
|
49
|
+
console.log(JSON.stringify({ mode: "probe", total, missing, sample }, null, 2))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function apply(): Promise<number> {
|
|
53
|
+
let processed = 0
|
|
54
|
+
const failures: { id: string; reason: string }[] = []
|
|
55
|
+
for (;;) {
|
|
56
|
+
// 动态 import 的 prisma client 不带类型信息 ⇒ 显式标注行类型(此处断言是必要的,不是冗余)
|
|
57
|
+
const rows = (await prisma.addMemory.findMany({
|
|
58
|
+
where: missingWhere,
|
|
59
|
+
select: { id: true, topic: true, content: true },
|
|
60
|
+
take: pageSize,
|
|
61
|
+
orderBy: { id: "asc" },
|
|
62
|
+
})) as { id: string; topic: string; content: string }[]
|
|
63
|
+
if (rows.length === 0) break
|
|
64
|
+
for (const row of rows) {
|
|
65
|
+
try {
|
|
66
|
+
const { text, method } = expandForIndexWithMethod(`${row.topic} ${row.content}`)
|
|
67
|
+
if (!text) {
|
|
68
|
+
failures.push({ id: row.id, reason: `展开为空(分词器=${method})` })
|
|
69
|
+
continue
|
|
70
|
+
}
|
|
71
|
+
await prisma.addMemory.update({ where: { id: row.id }, data: { searchText: text } })
|
|
72
|
+
processed++
|
|
73
|
+
} catch (error) {
|
|
74
|
+
failures.push({ id: row.id, reason: error instanceof Error ? error.message : String(error) })
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
console.log(JSON.stringify({ mode: "apply", processed, failed: failures.length, failures: failures.slice(0, 20) }, null, 2))
|
|
79
|
+
return failures.length
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** `--refresh`:重算所有非空行的 searchText(分词器固定后用它把历史 token 统一到当前口径) */
|
|
83
|
+
async function refreshAll(): Promise<number> {
|
|
84
|
+
let scanned = 0
|
|
85
|
+
const failures: { id: string; reason: string }[] = []
|
|
86
|
+
for (;;) {
|
|
87
|
+
const rows = (await prisma.addMemory.findMany({
|
|
88
|
+
where: { NOT: { AND: [{ topic: "" }, { content: "" }] } },
|
|
89
|
+
select: { id: true, topic: true, content: true, searchText: true },
|
|
90
|
+
take: pageSize,
|
|
91
|
+
orderBy: { id: "asc" },
|
|
92
|
+
skip: scanned,
|
|
93
|
+
})) as { id: string; topic: string; content: string; searchText: string }[]
|
|
94
|
+
if (rows.length === 0) break
|
|
95
|
+
for (const row of rows) {
|
|
96
|
+
try {
|
|
97
|
+
const { text } = expandForIndexWithMethod(`${row.topic} ${row.content}`)
|
|
98
|
+
if (text !== row.searchText) {
|
|
99
|
+
await prisma.addMemory.update({ where: { id: row.id }, data: { searchText: text } })
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
failures.push({ id: row.id, reason: error instanceof Error ? error.message : String(error) })
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
scanned += rows.length
|
|
106
|
+
}
|
|
107
|
+
console.log(JSON.stringify({ mode: "refresh", scanned, failed: failures.length, failures: failures.slice(0, 20) }, null, 2))
|
|
108
|
+
return failures.length
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const failures = mode === "probe" ? 0 : refresh ? await refreshAll() : await apply()
|
|
112
|
+
if (mode === "probe") await probe()
|
|
113
|
+
const stillMissing = await prisma.addMemory.count({ where: missingWhere })
|
|
114
|
+
console.log(JSON.stringify({ stillMissing }))
|
|
115
|
+
|
|
116
|
+
// 指纹标记写入:回填/刷新无失败且无遗漏时,"全部 searchText 均由当前分词器产出"这一事实才成立。
|
|
117
|
+
// 有失败/遗漏则**不写**(否则 probe 会永远提示"需重索引",告警不可消除 = 噪音化)。
|
|
118
|
+
if (mode === "apply" && failures === 0 && stillMissing === 0) {
|
|
119
|
+
const projectRoot = process.env.PROJECT_ROOT ?? process.cwd()
|
|
120
|
+
const magicDir = process.env.MAGIC_DIR ?? ".codex"
|
|
121
|
+
const path = writeRecordedFingerprint(projectRoot, magicDir, computeFtsFingerprint({ projectRoot, magicDir }))
|
|
122
|
+
console.log(JSON.stringify({ fingerprintRecorded: path }))
|
|
123
|
+
} else if (mode === "apply") {
|
|
124
|
+
console.log(JSON.stringify({ fingerprintRecorded: null, reason: "仍有失败/遗漏,不写指纹(避免假装同源)" }))
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 动态 import 的类型面不含 $disconnect ⇒ 显式取方法(此处断言必要)
|
|
128
|
+
const disconnect = (prisma as unknown as { $disconnect?: () => Promise<void> }).$disconnect
|
|
129
|
+
if (typeof disconnect === "function") await disconnect.call(prisma)
|
|
130
|
+
process.exit(failures > 0 ? 1 : 0)
|
|
131
|
+
|
|
132
|
+
export {} // 标记为 ES 模块(本文件用顶层 await,需显式 export 才会被 TS 当作 module)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* memory-jobs.ts — Memory 异步任务入口(随模板分发到 `{magicDir}/scripts/memory/memory-jobs.ts`)
|
|
3
|
+
*
|
|
4
|
+
* 为什么随模板分发(2026-09-21 接线):设计契约(《Agent Memory 知识治理层架构设计》§90)
|
|
5
|
+
* 早就声明入口为 `scripts/memory/memory-jobs.ts <refresh-l1 | drain-evidence | consolidate>`,
|
|
6
|
+
* 但实现只存在于 add-coder 仓库根的开发脚本里 ⇒ 下游项目"拿到了库、拿不到入口",
|
|
7
|
+
* 于是 `${MAGIC_DIR}/memory/l1-context.md` 永远不会生成、会话注入也就没有数据可注入。
|
|
8
|
+
* 本文件是同一入口的**下游版**:只依赖 `${MAGIC_DIR}/scripts/mcp-server/**`,不依赖仓库根布局。
|
|
9
|
+
*
|
|
10
|
+
* 用法(工作目录 = 项目根):
|
|
11
|
+
* PROJECT_ROOT=$PWD MAGIC_DIR=.codex DATABASE_URL="postgresql://..." \
|
|
12
|
+
* npx tsx "${MAGIC_DIR}/scripts/memory/memory-jobs.ts" <refresh-l1 | drain-evidence | consolidate>
|
|
13
|
+
*
|
|
14
|
+
* 退出码:0 成功;1 执行失败(stderr 给出原因);2 用法/环境错误。
|
|
15
|
+
* 幂等:refresh-l1 原子写覆盖;drain-evidence 按 offset 续读(重放安全)。
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const command = process.argv[2]
|
|
19
|
+
if (!command || !["refresh-l1", "drain-evidence", "consolidate"].includes(command)) {
|
|
20
|
+
console.error("usage: memory-jobs.ts <refresh-l1 | drain-evidence | consolidate>")
|
|
21
|
+
process.exit(2)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { PROJECT_ROOT, MAGIC_DIR, DATABASE_URL } = process.env
|
|
25
|
+
const missing = [
|
|
26
|
+
["PROJECT_ROOT", PROJECT_ROOT],
|
|
27
|
+
["MAGIC_DIR", MAGIC_DIR],
|
|
28
|
+
["DATABASE_URL", DATABASE_URL],
|
|
29
|
+
].filter(([, v]) => !v).map(([k]) => k)
|
|
30
|
+
if (missing.length > 0) {
|
|
31
|
+
console.error(`缺少环境变量: ${missing.join(" / ")}`)
|
|
32
|
+
process.exit(2)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const { prisma } = await import("../mcp-server/shared/prisma.js")
|
|
36
|
+
const dbTypes = await import("../mcp-server/shared/db-types.js")
|
|
37
|
+
const { validatedDelegate } = dbTypes
|
|
38
|
+
const { createRuntimeContext } = await import("../mcp-server/shared/runtime-context.js")
|
|
39
|
+
const { createPgFtsAdapter } = await import("../mcp-server/shared/memory/retrieval/fts/pg.js")
|
|
40
|
+
const { createSqliteFtsAdapter } = await import("../mcp-server/shared/memory/retrieval/fts/sqlite.js")
|
|
41
|
+
const { refreshL1Snapshot } = await import("../mcp-server/shared/memory/jobs/snapshot.js")
|
|
42
|
+
const { drainEvidenceQueue } = await import("../mcp-server/shared/memory/jobs/evidence-collector.js")
|
|
43
|
+
const { runConsolidation } = await import("../mcp-server/shared/memory/jobs/consolidation.js")
|
|
44
|
+
|
|
45
|
+
type Row = Record<string, unknown>
|
|
46
|
+
const runtime = createRuntimeContext(PROJECT_ROOT as string, MAGIC_DIR as string)
|
|
47
|
+
const repositoryRef = runtime.projectKey
|
|
48
|
+
|
|
49
|
+
// ── deps 装配(与 tools/memory.ts 同一模式:validatedDelegate + RawQuerier)──
|
|
50
|
+
const rawQuerier = {
|
|
51
|
+
query: <T = Row>(sql: string, params: unknown[]): Promise<T[]> =>
|
|
52
|
+
(prisma.$queryRawUnsafe as unknown as (s: string, ...p: unknown[]) => Promise<unknown>)(sql, ...params) as Promise<T[]>,
|
|
53
|
+
}
|
|
54
|
+
const lexical = [
|
|
55
|
+
(DATABASE_URL as string).startsWith("postgres")
|
|
56
|
+
? createPgFtsAdapter(rawQuerier)
|
|
57
|
+
: createSqliteFtsAdapter(rawQuerier),
|
|
58
|
+
]
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
60
|
+
const dg = (delegate: unknown, schema: any, table: string) => validatedDelegate(delegate, schema, table) as any
|
|
61
|
+
|
|
62
|
+
const memoryDb = dg(prisma.addMemory, dbTypes.AddMemoryRowSchema, "AddMemory")
|
|
63
|
+
const evidenceDb = dg(prisma.addMemoryEvidence, dbTypes.AddMemoryEvidenceRowSchema, "AddMemoryEvidence")
|
|
64
|
+
const linkDb = dg(prisma.addMemoryEvidenceLink, dbTypes.AddMemoryEvidenceLinkRowSchema, "AddMemoryEvidenceLink")
|
|
65
|
+
const recallDb = dg(prisma.addMemoryRecall, dbTypes.AddMemoryRecallRowSchema, "AddMemoryRecall")
|
|
66
|
+
const itemDb = dg(prisma.addMemoryRecallItem, dbTypes.AddMemoryRecallItemRowSchema, "AddMemoryRecallItem")
|
|
67
|
+
const metricDb = dg(prisma.addMetricSnapshot, dbTypes.AddMetricSnapshotRowSchema, "AddMetricSnapshot")
|
|
68
|
+
|
|
69
|
+
const baseDeps = {
|
|
70
|
+
repositoryRef,
|
|
71
|
+
projectDir: PROJECT_ROOT as string,
|
|
72
|
+
magicDir: MAGIC_DIR as string,
|
|
73
|
+
lexical,
|
|
74
|
+
fetchByIds: async (ids: string[]) =>
|
|
75
|
+
memoryDb.findMany({ where: { id: { in: ids } }, include: { supersedes: { select: { id: true } } } }),
|
|
76
|
+
fetchEvidenceSourceRefs: async (memoryIds: string[]) => {
|
|
77
|
+
const links = await linkDb.findMany({ where: { memoryId: { in: memoryIds } } })
|
|
78
|
+
const evIds = [...new Set(links.map((l: Row) => l.evidenceId as string))]
|
|
79
|
+
const evs = evIds.length > 0 ? await evidenceDb.findMany({ where: { id: { in: evIds } } }) : []
|
|
80
|
+
const refById = new Map<string, string>(evs.map((e: Row) => [e.id as string, e.sourceRef as string]))
|
|
81
|
+
const out = new Map<string, string[]>()
|
|
82
|
+
for (const l of links as Row[]) {
|
|
83
|
+
const ref = refById.get(l.evidenceId as string)
|
|
84
|
+
if (!ref) continue
|
|
85
|
+
const arr = out.get(l.memoryId as string) ?? []
|
|
86
|
+
arr.push(ref)
|
|
87
|
+
out.set(l.memoryId as string, arr)
|
|
88
|
+
}
|
|
89
|
+
return out
|
|
90
|
+
},
|
|
91
|
+
audit: {
|
|
92
|
+
createRecall: (data: Row) => recallDb.create({ data }),
|
|
93
|
+
createRecallItem: (data: Row) => itemDb.create({ data }),
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
if (command === "refresh-l1") {
|
|
99
|
+
console.log(JSON.stringify(await refreshL1Snapshot(baseDeps), null, 2))
|
|
100
|
+
} else if (command === "drain-evidence") {
|
|
101
|
+
console.log(
|
|
102
|
+
JSON.stringify(
|
|
103
|
+
await drainEvidenceQueue({
|
|
104
|
+
projectDir: PROJECT_ROOT as string,
|
|
105
|
+
magicDir: MAGIC_DIR as string,
|
|
106
|
+
repositoryRef,
|
|
107
|
+
evidenceDb,
|
|
108
|
+
}),
|
|
109
|
+
null,
|
|
110
|
+
2,
|
|
111
|
+
),
|
|
112
|
+
)
|
|
113
|
+
} else {
|
|
114
|
+
console.log(JSON.stringify(await runConsolidation({ ...baseDeps, memoryDb, evidenceDb, linkDb, metricDb }), null, 2))
|
|
115
|
+
}
|
|
116
|
+
} catch (e) {
|
|
117
|
+
// 失败必须可见(非零退出 + 原因),不吞错、不打印堆栈给消费方
|
|
118
|
+
console.error(`memory-jobs ${command} 失败: ${e instanceof Error ? e.message : String(e)}`)
|
|
119
|
+
process.exit(1)
|
|
120
|
+
}
|
|
121
|
+
process.exit(0)
|
|
122
|
+
|
|
123
|
+
// 顶层 await 需要本文件被视为模块(tsconfig target=ES2022 / module=ESNext)
|
|
124
|
+
export {}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* plan-close.ts — Plan 生命周期关闭/重开的**脚本入口**(随模板分发到 `{magicDir}/scripts/plan-close.ts`)
|
|
3
|
+
*
|
|
4
|
+
* 与 MCP 工具 `plan_update` **共用同一实现**:`scripts/mcp-server/tools/plan-lifecycle.ts::updatePlanLifecycle()`
|
|
5
|
+
* (编排 = 前置校验 + 状态机迁移 + PUL 元数据 + 统一审计)。本文件只做参数解析与退出码,不再自己写逻辑
|
|
6
|
+
* —— 2026-09-21 决策:禁止"工具一份、脚本一份"的漂移。
|
|
7
|
+
*
|
|
8
|
+
* 用法(工作目录 = 项目根):
|
|
9
|
+
* PROJECT_ROOT=$PWD MAGIC_DIR=.codex DATABASE_URL="postgresql://..." \
|
|
10
|
+
* npx tsx "${MAGIC_DIR}/scripts/plan-close.ts" --plan <planName> [--to CLOSED] [--reason "…"] [--policy-ref "PUL:…"] [--force] [--dry-run]
|
|
11
|
+
*
|
|
12
|
+
* --to 取值即真源枚举(DRAFT/ACTIVE/BLOCKED/REJECTED/CLOSED/REOPENED/ABANDONED),默认 CLOSED。
|
|
13
|
+
* 退出码:0 成功/幂等;1 前置校验或审计失败(stderr 给原因);2 用法/环境错误。
|
|
14
|
+
* 幂等:已是目标态 → 0 并报 idempotent。
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const argv = process.argv.slice(2)
|
|
18
|
+
const argValue = (name: string): string | undefined => {
|
|
19
|
+
const i = argv.indexOf(name)
|
|
20
|
+
return i >= 0 ? argv[i + 1] : undefined
|
|
21
|
+
}
|
|
22
|
+
const hasFlag = (name: string) => argv.includes(name)
|
|
23
|
+
|
|
24
|
+
const planName = argValue("--plan")
|
|
25
|
+
const to = (argValue("--to") ?? "CLOSED").toUpperCase()
|
|
26
|
+
const reason = argValue("--reason")
|
|
27
|
+
const policyRef = argValue("--policy-ref")
|
|
28
|
+
const force = hasFlag("--force")
|
|
29
|
+
const dryRun = hasFlag("--dry-run")
|
|
30
|
+
|
|
31
|
+
const { PROJECT_ROOT, MAGIC_DIR, DATABASE_URL } = process.env
|
|
32
|
+
const missing = [["PROJECT_ROOT", PROJECT_ROOT], ["MAGIC_DIR", MAGIC_DIR], ["DATABASE_URL", DATABASE_URL]]
|
|
33
|
+
.filter(([, v]) => !v)
|
|
34
|
+
.map(([k]) => k)
|
|
35
|
+
if (!planName || missing.length > 0) {
|
|
36
|
+
console.error('usage: plan-close.ts --plan <planName> [--to CLOSED|REOPENED|ACTIVE|…] [--reason "…"] [--policy-ref "PUL:…"] [--force] [--dry-run]')
|
|
37
|
+
if (missing.length > 0) console.error(`缺少环境变量: ${missing.join(" / ")}`)
|
|
38
|
+
process.exit(2)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const { prisma } = await import("./mcp-server/shared/prisma.js")
|
|
42
|
+
const { createRuntimeContext } = await import("./mcp-server/shared/runtime-context.js")
|
|
43
|
+
const { PlanLifecycleStatusSchema } = await import("./mcp-server/shared/plan-lifecycle.js")
|
|
44
|
+
const { updatePlanLifecycle } = await import("./mcp-server/tools/plan-lifecycle.js")
|
|
45
|
+
|
|
46
|
+
// 目标状态必须来自真源枚举(zod 校验,禁止脚本里第二份字面量清单)
|
|
47
|
+
const parsed = PlanLifecycleStatusSchema.safeParse(to)
|
|
48
|
+
if (!parsed.success) {
|
|
49
|
+
console.error(`--to 非法: ${to}(可选:${PlanLifecycleStatusSchema.options.join(" | ")})`)
|
|
50
|
+
process.exit(2)
|
|
51
|
+
}
|
|
52
|
+
const target = parsed.data
|
|
53
|
+
|
|
54
|
+
const runtime = createRuntimeContext(PROJECT_ROOT as string, MAGIC_DIR as string)
|
|
55
|
+
|
|
56
|
+
if (dryRun) {
|
|
57
|
+
const current = await prisma.planRecord.findFirst({
|
|
58
|
+
where: { projectKey: runtime.projectKey, adapterKey: runtime.adapterKey, planName },
|
|
59
|
+
})
|
|
60
|
+
console.log(JSON.stringify({ planName, from: (current as unknown as { lifecycle?: string } | null)?.lifecycle ?? null, to: target, dryRun: true }, null, 2))
|
|
61
|
+
process.exit(0)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const result = await updatePlanLifecycle(prisma, runtime, {
|
|
65
|
+
planName,
|
|
66
|
+
target,
|
|
67
|
+
reason: reason ?? null,
|
|
68
|
+
policyRef: policyRef ?? null,
|
|
69
|
+
force,
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
console.log(JSON.stringify({ planName, ...result }, null, 2))
|
|
73
|
+
if (!result.ok) {
|
|
74
|
+
console.error(`✗ ${result.error ?? "更新失败"}`)
|
|
75
|
+
process.exit(1)
|
|
76
|
+
}
|
|
77
|
+
if (result.idempotent) process.exit(0)
|
|
78
|
+
if (result.audit && !result.audit.ok) {
|
|
79
|
+
console.error(`⚠️ ${result.audit.detail}`)
|
|
80
|
+
process.exit(1)
|
|
81
|
+
}
|
|
82
|
+
process.exit(0)
|
|
83
|
+
|
|
84
|
+
// 顶层 await 需本文件被视为模块(tsconfig target=ES2022 / module=ESNext)
|
|
85
|
+
export {}
|
|
@@ -109,16 +109,18 @@ description: "Audit-Driven Development paradigm workflow. Invoke when starting a
|
|
|
109
109
|
|
|
110
110
|
**检测链路**:`session-init` / `PromptSubmit` hook 检测到稀疏上下文时,自动提示 AI 执行以下流程。
|
|
111
111
|
|
|
112
|
-
当检测到稀疏上下文时,MUST
|
|
112
|
+
当检测到稀疏上下文时,MUST 调用以下**四件套**重建完整上下文(⓪ 记忆召回于 2026-09-21 前置接入:记忆里往往已有历史决策/陷阱,先召回可避免重复踩坑;未接线时据实明示,不假装有记忆):
|
|
113
113
|
|
|
114
114
|
```text
|
|
115
|
-
|
|
115
|
+
⓪ recall_memory({ query, stage: "session-start" }) / list_memories({ repositoryRef, status: "ACTIVE" })
|
|
116
|
+
→ 受治理记忆(约束/决策/陷阱/约定);未接线或档位 shadow 时输出状态说明与开启方式
|
|
117
|
+
① plan_status({ planName }) → tasks.md 进度、实施状态
|
|
116
118
|
② review_status({ planName }) → P0/P1 缺陷数、回流率(跨轮审计)
|
|
117
119
|
③ status_hitl({ planName }) → 审批是否已 tongyi/bohui
|
|
118
|
-
④
|
|
120
|
+
④ 汇总四结果 → AI 确定:历史约束与陷阱有哪些、上次做到哪个 Task、有哪些未闭环缺陷、审批是否通过
|
|
119
121
|
```
|
|
120
122
|
|
|
121
|
-
|
|
123
|
+
**调用顺序**:⓪ → ① → ② → ③,汇总后即可进入后续 ADD 步骤,无需人工确认。
|
|
122
124
|
|
|
123
125
|
**工具清单**:
|
|
124
126
|
|
|
@@ -714,6 +716,19 @@ export function agentAudit(phase: AgentAuditPhase, detail: string, extra?: Recor
|
|
|
714
716
|
|
|
715
717
|
## Step 3:业务逻辑实现与审计植入
|
|
716
718
|
|
|
719
|
+
> **执行风格(`executionMode`,写在 Plan 元信息;缺省 `stepwise`)**——两种风格**都完整遵守 ADD**,差别只在"是否逐步向用户同步进度":
|
|
720
|
+
>
|
|
721
|
+
> | 取值 | 行为 | HITL 触点 |
|
|
722
|
+
> |------|------|-----------|
|
|
723
|
+
> | `stepwise`(默认) | 逐轮逐 Task 单步:每个 Task 完成后停下等人确认(见下方「每个 Task 完成后」) | 每个 Task 后 |
|
|
724
|
+
> | `delegated`(托管) | 轮内连续实施全部 Task,**不逐步同步进度**;每个 Task 的审计落库/`tasks.md` 勾选/`[T]` 验证照做 | 仅两个停止条件触发时 |
|
|
725
|
+
>
|
|
726
|
+
> **停止条件(可判定,复用既有工具,不新增自定义判据)**:
|
|
727
|
+
> 1. **文档与代码不对齐**:`check_spec_sync` 报未归因漂移,或实现偏离 Plan/Spec 的 WHEN-THEN;
|
|
728
|
+
> 2. **代码基线低于预期**:`npx tsc --noEmit` / `pnpm test` / `npx tsx scripts/validate-docs.ts` 任一失败,或 `check_dps < 80` / `check_rahs < 90`。
|
|
729
|
+
>
|
|
730
|
+
> 触达后:停下 → 列证据清单 → HITL 裁决(继续 delegated / 切回 stepwise)。**不得**借托管跳过审计落库、跳过闸门,或跨轮直冲 Step 8。
|
|
731
|
+
|
|
717
732
|
**功能实现与审计点同步进行,不分离。**
|
|
718
733
|
|
|
719
734
|
### 3.0 前置守卫:add-route 存在性交叉校验(强制执行)
|
|
@@ -120,6 +120,32 @@ get_project_context({ scope: "add-state" })
|
|
|
120
120
|
- 待执行的 ADD 操作清单
|
|
121
121
|
- 如 MCP 工具不可用 → 跳过,不阻断流程
|
|
122
122
|
|
|
123
|
+
### 2.6 召回记忆(L1 快照 / L2 按需)
|
|
124
|
+
|
|
125
|
+
> **为什么有这一步**(2026-09-21 接线修复):设计契约的会话注入分层是——session-init 只注入小型 L1,任务相关 L2 由 `recall_memory` 按需获得。此前本 SKILL 零记忆引用,模型因此**从未想到**调用召回,记忆写进去也不会回到会话里。
|
|
126
|
+
|
|
127
|
+
**动作**:
|
|
128
|
+
|
|
129
|
+
1. 读 `${MAGIC_DIR}/memory/l1-context.md`(若 SessionStart hook 已注入,内容就在上文;未注入时直接读文件)
|
|
130
|
+
2. 需要与当前任务相关的上下文时,调 `recall_memory({ query: <意图>, stage: "session-start" })` 取 L2
|
|
131
|
+
3. 把命中的约束 / 决策 / 陷阱写入「上下文基准」(标注来源,不臆造)
|
|
132
|
+
4. **未接线或降级时明示**:若快照不存在 / 已过期 / 档位为 `shadow`,据实告知当前状态与开启方式,禁止假装已有记忆
|
|
133
|
+
|
|
134
|
+
**预期产出**:
|
|
135
|
+
- 记忆段(内容,或"未接线:`npx add-coder sync` / `refresh_memory_snapshots`"的状态说明)
|
|
136
|
+
- 与审计日志、Plan 拓扑交叉后的上下文基准
|
|
137
|
+
|
|
138
|
+
**开启与刷新**:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# 档位(默认已是 inject;退回不注入设为 shadow)
|
|
142
|
+
ADD_MEMORY_RECALL_MODE=inject
|
|
143
|
+
# 刷新快照(安装期由 add-coder init / sync 自动完成)
|
|
144
|
+
node ${MAGIC_DIR}/scripts/memory/memory-jobs.ts refresh-l1
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**MCP 工具不可用时**:读快照文件即可(只读),不阻断流程。
|
|
148
|
+
|
|
123
149
|
---
|
|
124
150
|
|
|
125
151
|
## Step 3:分析审计日志 + Plan 索引 + ADD 状态推断上下文
|
|
@@ -231,6 +257,7 @@ Plan 是否存在?
|
|
|
231
257
|
|
|
232
258
|
- [ ] Step 1: 已扫描 `{{magicDir}}/reviews/*review-runtime*` 并汇总未关闭发现
|
|
233
259
|
- [ ] Step 2: 已调用 `query_audit_logs({})`(含 `RUNTIME_ERROR` 查询)
|
|
260
|
+
- [ ] Step 2.6: 已召回记忆(L1 快照 / `recall_memory`),未接线时已明示状态与开启方式
|
|
234
261
|
- [ ] Step 3: 已分析审计日志推断上下文
|
|
235
262
|
- [ ] Step 4: 已构建上下文摘要
|
|
236
263
|
- [ ] Step 5: 已向用户展示恢复的上下文(含未关闭运行时发现,如有)
|
|
@@ -116,6 +116,8 @@ Task N ──→ Task M(说明依赖原因)
|
|
|
116
116
|
|
|
117
117
|
### 每个 Task 完成后(重型强制执行)
|
|
118
118
|
|
|
119
|
+
> **适用模式**:`stepwise`(默认)。`delegated`(托管)下**仍执行本块的三件事**(`[T]` 验证 → `record_dev_operation` → `tasks.md` 勾选),但不逐 Task 停下汇报;仅在 Step 3 定义的两个停止条件触发时打断(见 `add-paradigm` SKILL Step 3「执行风格」)。
|
|
120
|
+
|
|
119
121
|
1. 验证该 Task 的 checklist `[T]` 项
|
|
120
122
|
2. 调用 `record_dev_operation` 记录 ADD-7 审计
|
|
121
123
|
3. **验证并更新项目状态**:将该 Task 在 `tasks.md` 中逐子项勾选为 `[x]`
|
|
@@ -96,6 +96,10 @@ Task N ──→ Task M(说明依赖原因)
|
|
|
96
96
|
|
|
97
97
|
### 每个 Task 完成后
|
|
98
98
|
|
|
99
|
+
> **适用模式**:`stepwise`(默认)。`delegated`(托管)下**仍执行本块的两件事**(`[T]` 验证 → `record_dev_operation`),
|
|
100
|
+
> 但不逐 Task 停下汇报;仅在 `add-paradigm` SKILL Step 3「执行风格」定义的两个停止条件触发时打断。
|
|
101
|
+
> [2026-09-21:与重型 add-route 模板同口径 —— 节拍写在多处时,模式说明必须同步,否则托管开关只生效一半]
|
|
102
|
+
|
|
99
103
|
1. 验证该 Task 的 checklist `[T]` 项
|
|
100
104
|
2. 调用 `record_dev_operation` 记录 ADD-7 审计
|
|
101
105
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
> - `[T]` = 编译期验证—证据: 命令+结果(如 `tsc=0` / `vitest 18/18`)
|
|
5
5
|
> - `[R]` = 运行时验证—证据: 部署后确认(如 `curl 200`)
|
|
6
6
|
> - `[E]` = 静态检查—证据: grep/diff 输出
|
|
7
|
+
> - `[W]` = **接线验证(Wiring)**—证据: 生产调用点存在的 grep / 端到端一次真实产出(**实现存在 ≠ 生产可达**)
|
|
7
8
|
>
|
|
8
9
|
> **审计链(证据→devlog→checklist)**:
|
|
9
10
|
> - 初验规则: 先找证据(命令+结果)→ 调 `record_dev_operation` 落库 → 将返回的真实 cuid(25位)写入 checklist。**禁止抄写 `cmq...` 占位符**。
|
|
@@ -14,6 +15,7 @@
|
|
|
14
15
|
- [ ] [T] 检查项1 — 证据: (待填写)|审计: (初验从 record_dev_operation 返回值写入真实 cuid,**禁止写 `cmq...` 占位符**)
|
|
15
16
|
- [ ] [T] 检查项2 — 证据: (待填写)|审计: (初验从 record_dev_operation 返回值写入真实 cuid,**禁止写 `cmq...` 占位符**)
|
|
16
17
|
- [ ] [T] schema 变更后运行实例 client 新鲜度(`db:generate` + 重启校验)— 证据: (待填写)|审计: (待填写) [来源: farm-agent 9737ab4 回灌;Prisma+Atlas 项目通用]
|
|
18
|
+
- [ ] [W] 新增的"被调用方"(策略/服务/工具/证据产生者)存在**生产调用点** — 证据: `grep -rn "<symbol>" src/ --include=*.ts` 有**非测试**命中且位于真实执行路径(非仅导出)| 审计: (待填写) [来源: farm-agent 2026-09-18 回流 P0 #R9;同类三例均为「纯函数+单测齐全、生产不可达」]
|
|
17
19
|
|
|
18
20
|
## ADD 规则合规检查
|
|
19
21
|
|
|
@@ -263,6 +263,8 @@ query_audit_logs({ keyword: "{汇总关键词}" })
|
|
|
263
263
|
5. 读本轮对应 {{magicDir}}/specs/{spec-name}/checklist.md
|
|
264
264
|
6. 按 tasks.md 顺序执行代码修改
|
|
265
265
|
7. 每完成一个 Task:读 checklist.md → 逐项验证 → **附可验证证据** → 勾选
|
|
266
|
+
(**执行风格**:`stepwise` = 逐 Task 停下向用户汇报;`delegated`(托管)= 本步骤照做但**不逐 Task 停下**,
|
|
267
|
+
仅两个停止条件触发时打断 —— 见 `add-paradigm` SKILL Step 3「执行风格」。[2026-09-21:补齐模式口径])
|
|
266
268
|
8. 每完成一个文件修改:record_dev_operation 写入 ADD-7 审计
|
|
267
269
|
9. 写入审计后:query_audit_logs 按 action/targetId/keyword 回查确认落库
|
|
268
270
|
10. 全部代码完成后:按本轮 handoff 的 ADD-7 恢复关键词逐项回查,确认当前Round可被下一轮恢复
|
|
@@ -98,6 +98,24 @@
|
|
|
98
98
|
|
|
99
99
|
---
|
|
100
100
|
|
|
101
|
+
## 6.2 接线可达性核对(`[W]` 接线判据 · 2026-09-18 增补)
|
|
102
|
+
|
|
103
|
+
> **动因**:同类事故已发生**三次**(回流源:压缩通道 / 补向量 / 沉淀链),三次都是"**纯函数与单测齐全、生产不可达**",
|
|
104
|
+
> 而三项漂移指纹(范围扩散 / 类型错误 / 审计漏记)**全绿**——它们测的是"改得规不规矩",不是"能力是否可达"。
|
|
105
|
+
> 本节的判据是**可否证伪的 grep 与端到端产出**;"实现了 / 有单测 / 已导出"**不作为通过理由**。
|
|
106
|
+
> [来源: farm-agent 2026-09-18 回流 P0 #R9;域符号名已泛化为占位符]
|
|
107
|
+
|
|
108
|
+
对本次变更涉及的每个"被调用方"(策略 / 服务 / 工具 / 证据产生者 / 审计字面量)逐项核对:
|
|
109
|
+
|
|
110
|
+
| # | 被调用方(symbol) | 期望调用点 | grep 证据(非测试命中) | 端到端产出证据 | 判定 |
|
|
111
|
+
|---|-------------------|-----------|------------------------|---------------|:----:|
|
|
112
|
+
| 1 | {symbol} | {文件:行} | `grep -rn "<symbol>" src/ --include=*.ts` 命中数 = {N} | {DB 行数 / SSE 帧 / 审计记录} | ✅/❌ |
|
|
113
|
+
|
|
114
|
+
- [ ] 每个新增 symbol 有**非测试**调用点(**仅导出 / 仅被测试 import = 未接线 ⇒ 判 ❌**)
|
|
115
|
+
- [ ] 涉及审计字面量时:该字面量在本次运行时**至少被写出过一次**(`query_audit_logs` 可查,而非"代码里有这行")
|
|
116
|
+
- [ ] 端到端产出可证(落库行数 / 帧 / 文件产物),而非"单测断言通过"
|
|
117
|
+
- [ ] 本节表格已填;若本次变更**不含**新增被调用方,须显式写"本变更无新增被调用方",不得静默跳过
|
|
118
|
+
|
|
101
119
|
## 7. 关联 Checklist
|
|
102
120
|
|
|
103
121
|
- 本 review 的检查项与 `{checklist 文档路径}` 的"跨项目联调检查"章节一一对应
|
|
@@ -42,6 +42,12 @@
|
|
|
42
42
|
"heading": "## 6. E2E 逐端点 curl",
|
|
43
43
|
"required": true
|
|
44
44
|
},
|
|
45
|
+
{
|
|
46
|
+
"id": "wiring",
|
|
47
|
+
"heading": "## 6.2 接线可达性核对",
|
|
48
|
+
"required": true,
|
|
49
|
+
"note": "2026-09-18 增补(回流 farm-agent P0 #R9):逐项核对新增被调用方的生产调用点与端到端产出([W] 判据)。三次同类事故(纯函数与单测齐全、生产不可达)均在此节可被拦截。"
|
|
50
|
+
},
|
|
45
51
|
{
|
|
46
52
|
"id": "checklist",
|
|
47
53
|
"heading": "## 7. 关联 Checklist",
|
|
@@ -61,4 +67,4 @@
|
|
|
61
67
|
"阶段",
|
|
62
68
|
"步骤"
|
|
63
69
|
]
|
|
64
|
-
}
|
|
70
|
+
}
|
|
@@ -83,6 +83,10 @@
|
|
|
83
83
|
> AI 读取本段 JSON → 调用 `TodoWrite` 加载到 IDE 任务面板。
|
|
84
84
|
> 每条 content 以 `[轮次N·状态]` 前缀展示轮次分组,`id` 编码层级 `{plan-key}-r{N}-t{N}`。
|
|
85
85
|
> 每完成一个 Task 后更新对应 `status` 为 `COMPLETE`。
|
|
86
|
+
>
|
|
87
|
+
> **执行风格**:`stepwise`(默认)逐 Task 停下等人确认;`delegated`(托管)轮内连续实施,**仍**逐 Task 更新 `status`
|
|
88
|
+
> 并做 `[T]` 验证与 `record_dev_operation`,只是不逐步同步进度(停止条件见 `add-paradigm` SKILL Step 3)。
|
|
89
|
+
> [2026-09-21:补齐与重型/轻量 add-route 模板同口径的模式说明]
|
|
86
90
|
|
|
87
91
|
```json
|
|
88
92
|
{
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
| `Handoff` / `handoff` / `交接` | **二段式**: ①先读 `{{magicDir}}/plans/index.md` 按 planName 匹配路径 → 定位 handoff;②无匹配才全局 glob `*-handoff*.md` | 🔴 P0 |
|
|
28
28
|
| `add-route` / `执行路线图` | **二段式**: ①先读 `{{magicDir}}/plans/index.md` 定位 plan → 查其 add-route;②调 `check_add_route_status`;③无匹配才全局 glob | 🔴 P0 |
|
|
29
29
|
| `devlog` / `开发日志` | **双触发**: ①用户说"devlog 记录下"→ 立即写;②Step 8 收敛通过 → 自动写(无需提醒)| 🔴 P0 |
|
|
30
|
+
| `托管实施` / `托管模式` / `delegated` | Plan 元信息记 `executionMode: delegated` → Step 3 连续实施轮内全部 Task,**不逐步同步进度**;每个 Task 的审计/勾选/[T] 验证照做;仅在①文档↔代码不对齐 ②基线低于预期(tsc/test/validate-docs 失败或 DPS<80 / RAHS<90)时停下走 HITL | 🔴 P0 |
|
|
31
|
+
| `单步实施` / `逐 Task` / `stepwise` | 记 `executionMode: stepwise`(缺省值)→ 每个 Task 完成后停下等确认 | 🟡 P1 |
|
|
30
32
|
| `index.md` / `Plan索引` | 读 `{{magicDir}}/plans/index.md`(匹配依赖优先)。**当用户问及任何 doc 类型(Plan/Review/Handoff/add-route)但未给明确路径时,此条优先于上述所有 doc 类型触发词执行** | 🔴 P0 |
|
|
31
33
|
| `gateway.md` / `gateway报告` / `运行时报告` | 读 `{{magicDir}}/reports/{{projectName}}-runtime-report/gateway.md` | 🟡 P1 |
|
|
32
34
|
| `report-handoff` / `report交接` | 读 `{{magicDir}}/templates/report-handoff-template.md` | 🟡 P1 |
|