agentic-flow-kit 1.8.2 → 1.8.7
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.md +1 -1
- package/modules/hosts/claude/commands/wf-build.md +1 -1
- package/modules/hosts/claude/commands/wf-design.md +1 -1
- package/modules/hosts/claude/commands/wf-review.md +1 -1
- package/modules/hosts/codex/commands/wf-build.md +1 -1
- package/modules/hosts/codex/commands/wf-design.md +1 -1
- package/modules/hosts/codex/commands/wf-review.md +1 -1
- package/modules/hosts/cursor/commands/wf-build.md +1 -1
- package/modules/hosts/cursor/commands/wf-design.md +1 -1
- package/modules/hosts/cursor/commands/wf-review.md +1 -1
- package/modules/hosts/opencode/commands/wf-build.md +1 -1
- package/modules/hosts/opencode/commands/wf-design.md +1 -1
- package/modules/hosts/opencode/commands/wf-review.md +1 -1
- package/modules/hosts/trae/commands/wf-build.md +1 -1
- package/modules/hosts/trae/commands/wf-design.md +1 -1
- package/modules/hosts/trae/commands/wf-review.md +1 -1
- package/package.json +1 -1
- package/src/jscpd-drift.test.mjs +3 -2
- package/templates/AGENTS.md +1 -0
- package/templates/_agents/commands/build.md +1 -1
- package/templates/_agents/commands/design.md +1 -1
- package/templates/_agents/commands/review.md +1 -1
- package/templates/_agents/engine-lock.json +12 -12
- package/templates/_agents/mechanism-registry.md +8 -0
- package/templates/_agents/rule-budgets.txt +4 -2
- package/templates/_agents/scripts/check-lane-surface.test.mjs +1 -1
- package/templates/_agents/scripts/check-ledger-invariant.mjs +80 -21
- package/templates/_agents/scripts/check-ledger-invariant.test.mjs +1 -1
- package/templates/_agents/scripts/check-loop.mjs +32 -3
- package/templates/_agents/scripts/check-loop.test.mjs +37 -0
- package/templates/_agents/scripts/check-scope.test.mjs +4 -1
- package/templates/_agents/scripts/check-scripts-syntax.mjs +33 -10
- package/templates/_agents/scripts/confirm-doc.mjs +13 -6
- package/templates/_agents/scripts/confirm-doc.test.mjs +25 -9
- package/templates/_agents/scripts/gen-workflow-metrics.mjs +141 -9
- package/templates/_agents/scripts/gen-workflow-metrics.test.mjs +119 -0
- package/templates/_agents/scripts/lane-surface-audit.test.mjs +1 -1
- package/templates/_agents/scripts/pre-push.test.mjs +1 -1
- package/templates/_agents/scripts/solidify-task.mjs +3 -1
- package/templates/_agents/scripts/solidify-task.test.mjs +10 -10
- package/templates/_agents/scripts/trust-mode.test.mjs +1 -1
- package/templates/_agents/scripts/verify.mjs +209 -74
- package/templates/_agents/scripts/verify.test.mjs +15 -0
- package/templates/_agents/skills/review-method/SKILL.md +3 -0
- package/templates/workflow/README.md +2 -1
- package/templates/workflow/intents/_TEMPLATE.md +5 -0
- package/templates/workflow/specs/_TEMPLATE.md +1 -1
|
@@ -35,7 +35,7 @@ const mkfix = () => {
|
|
|
35
35
|
fs.mkdirSync(path.join(d, 'workflow', s), { recursive: true });
|
|
36
36
|
}
|
|
37
37
|
fs.mkdirSync(path.join(d, '.agents'), { recursive: true });
|
|
38
|
-
fs.mkdirSync(path.join(d, '.zcode', 'drafts'), { recursive: true });
|
|
38
|
+
fs.mkdirSync(path.join(d, '.zcode', 'workflow-drafts'), { recursive: true });
|
|
39
39
|
fs.writeFileSync(path.join(d, '.agents', 'workflow-enums.txt'), ENUMS_FIXTURE);
|
|
40
40
|
fs.writeFileSync(path.join(d, '.agents', 'workflow-modules.txt'), '# modules\npipeline\nui\n');
|
|
41
41
|
return d;
|
|
@@ -57,7 +57,7 @@ const migratedPath = (root) => path.join(root, 'workflow', 'intents', `${TODAY}-
|
|
|
57
57
|
// T1:默认只迁移不落账
|
|
58
58
|
{
|
|
59
59
|
const d = mkfix();
|
|
60
|
-
fs.writeFileSync(path.join(d, '.zcode', 'drafts', 'demo-task.md'), DRAFT('demo'));
|
|
60
|
+
fs.writeFileSync(path.join(d, '.zcode', 'workflow-drafts', 'demo-task.md'), DRAFT('demo'));
|
|
61
61
|
const r = run(d, ['--topic', 'demo', '--root', d]);
|
|
62
62
|
check('T1 默认迁移成功 exit 0', r.code === 0, r.out);
|
|
63
63
|
check('T1 草稿已迁移到 intents 且日期前缀正确',
|
|
@@ -68,7 +68,7 @@ const migratedPath = (root) => path.join(root, 'workflow', 'intents', `${TODAY}-
|
|
|
68
68
|
// T2:--delegated 原话原样转发
|
|
69
69
|
{
|
|
70
70
|
const d = mkfix();
|
|
71
|
-
fs.writeFileSync(path.join(d, '.zcode', 'drafts', 'demo-task.md'), DRAFT('demo'));
|
|
71
|
+
fs.writeFileSync(path.join(d, '.zcode', 'workflow-drafts', 'demo-task.md'), DRAFT('demo'));
|
|
72
72
|
const r = run(d, ['--topic', 'demo', '--root', d, '--delegated', '固化了可以']);
|
|
73
73
|
check('T2 --delegated 流程 exit 0', r.code === 0, r.out);
|
|
74
74
|
const lines = ledgerLines(d);
|
|
@@ -81,7 +81,7 @@ const migratedPath = (root) => path.join(root, 'workflow', 'intents', `${TODAY}-
|
|
|
81
81
|
// T3:--auto 在 Strict(缺 trust-mode.json)下被拒 → 失败计数并 exit 1
|
|
82
82
|
{
|
|
83
83
|
const d = mkfix();
|
|
84
|
-
fs.writeFileSync(path.join(d, '.zcode', 'drafts', 'demo-task.md'), DRAFT('demo'));
|
|
84
|
+
fs.writeFileSync(path.join(d, '.zcode', 'workflow-drafts', 'demo-task.md'), DRAFT('demo'));
|
|
85
85
|
const r = run(d, ['--topic', 'demo', '--root', d, '--auto']);
|
|
86
86
|
check('T3 --auto 在缺省 Strict 下 exit 1(不吞失败)', r.code === 1, r.out);
|
|
87
87
|
check('T3 输出含失败汇总', r.out.includes('确认汇总') && r.out.includes('失败'), r.out);
|
|
@@ -90,28 +90,28 @@ const migratedPath = (root) => path.join(root, 'workflow', 'intents', `${TODAY}-
|
|
|
90
90
|
// T4:缺草稿 → exit 1
|
|
91
91
|
{
|
|
92
92
|
const d = mkfix();
|
|
93
|
-
fs.writeFileSync(path.join(d, '.zcode', 'drafts', 'other.md'), DRAFT('other'));
|
|
93
|
+
fs.writeFileSync(path.join(d, '.zcode', 'workflow-drafts', 'other.md'), DRAFT('other'));
|
|
94
94
|
const r = run(d, ['--topic', 'nope', '--root', d]);
|
|
95
95
|
check('T4 缺主题草稿 exit 1 且带指引', r.code === 1 && r.out.includes('未找到主题'), r.out);
|
|
96
96
|
}
|
|
97
97
|
// T5:草稿非可识别文档 → exit 1
|
|
98
98
|
{
|
|
99
99
|
const d = mkfix();
|
|
100
|
-
fs.writeFileSync(path.join(d, '.zcode', 'drafts', 'demo-note.md'), '# 随手笔记\nnot a workflow doc\n');
|
|
100
|
+
fs.writeFileSync(path.join(d, '.zcode', 'workflow-drafts', 'demo-note.md'), '# 随手笔记\nnot a workflow doc\n');
|
|
101
101
|
const r = run(d, ['--topic', 'demo', '--root', d]);
|
|
102
102
|
check('T5 无可识别文档 exit 1', r.code === 1 && r.out.includes('未找到可识别'), r.out);
|
|
103
103
|
}
|
|
104
104
|
// T6:--delegated 与 --auto 互斥 → exit 1
|
|
105
105
|
{
|
|
106
106
|
const d = mkfix();
|
|
107
|
-
fs.writeFileSync(path.join(d, '.zcode', 'drafts', 'demo-task.md'), DRAFT('demo'));
|
|
107
|
+
fs.writeFileSync(path.join(d, '.zcode', 'workflow-drafts', 'demo-task.md'), DRAFT('demo'));
|
|
108
108
|
const r = run(d, ['--topic', 'demo', '--root', d, '--delegated', 'x', '--auto']);
|
|
109
109
|
check('T6 互斥 exit 1', r.code === 1 && r.out.includes('互斥'), r.out);
|
|
110
110
|
}
|
|
111
111
|
// T7:失败路径退出码传播(确认失败后 exit 1,不吞)
|
|
112
112
|
{
|
|
113
113
|
const d = mkfix();
|
|
114
|
-
fs.writeFileSync(path.join(d, '.zcode', 'drafts', 'demo-task.md'), DRAFT('demo'));
|
|
114
|
+
fs.writeFileSync(path.join(d, '.zcode', 'workflow-drafts', 'demo-task.md'), DRAFT('demo'));
|
|
115
115
|
// 让确认失败:用 --auto 触发 Strict 拒绝(缺 trust-mode.json 即 Strict)。
|
|
116
116
|
const r = run(d, ['--topic', 'demo', '--root', d, '--auto']);
|
|
117
117
|
check('T7 失败后 exit 1 且索引仍生成', r.code === 1 && fs.existsSync(path.join(d, 'workflow', 'INDEX.md')), r.out);
|
|
@@ -119,11 +119,11 @@ const migratedPath = (root) => path.join(root, 'workflow', 'intents', `${TODAY}-
|
|
|
119
119
|
// T8:索引更新失败传播退出码(独立于确认失败路径——删除 workflow/plans 目录使 gen-workflow-index 失败)
|
|
120
120
|
{
|
|
121
121
|
const d = mkfix();
|
|
122
|
-
fs.writeFileSync(path.join(d, '.zcode', 'drafts', 'demo-task.md'), DRAFT('demo'));
|
|
122
|
+
fs.writeFileSync(path.join(d, '.zcode', 'workflow-drafts', 'demo-task.md'), DRAFT('demo'));
|
|
123
123
|
fs.rmSync(path.join(d, 'workflow', 'plans'), { recursive: true });
|
|
124
124
|
const r = run(d, ['--topic', 'demo', '--root', d]);
|
|
125
125
|
check('T8 索引更新失败 exit 1 且输出指明', r.code === 1 && r.out.includes('索引更新失败'), r.out);
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
console.log(`\n${
|
|
128
|
+
console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
|
|
129
129
|
process.exit(fail ? 1 : 0);
|
|
@@ -106,5 +106,5 @@ const DRAFT = (level = 'L1') => `---\n状态: draft\n级别: ${level}\nrisk_leve
|
|
|
106
106
|
`${r.stdout}${r.stderr}\n${lines.join('|')}`);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
console.log(`\n${
|
|
109
|
+
console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
|
|
110
110
|
process.exit(fail ? 1 : 0);
|
|
@@ -19,13 +19,18 @@
|
|
|
19
19
|
// d) --test-cmd 为 fixture 测试注入缝:替换全部生产门为 [test, check-loop] 两门夹具(夹具根里没有
|
|
20
20
|
// 脚本本体,check-loop 用绝对路径直调,root 归属仍由 CHECK_LOOP_ROOT 裁决);夹具模式不触发生效规则
|
|
21
21
|
// 视图门与摘要生成(假门凭证只进本地台账,不产 committed 摘要)。
|
|
22
|
-
// 未执行门清单:fail-fast
|
|
22
|
+
// 未执行门清单:fail-fast 后序门状态未知≠通过——验证系统输出须完备(「没跑」与「过了」可区分);
|
|
23
|
+
// 并发编排(2026-10-16 自源仓 verify-parallel 回灌)后该清单按「已出裁决」算:guardian 门
|
|
24
|
+
// (scripts-syntax)串行先跑、未过即止;其余门进工作池并发(--jobs N / --serial,缺省
|
|
25
|
+
// clamp(2..4, floor(cpus/4))),各自缓冲输出、完成即整块回放;任一门红 → 停排新门 + taskkill /T
|
|
26
|
+
// 树杀在跑门(Windows shell:true 起的是 cmd.exe,kill 只杀壳不杀孙进程)。
|
|
23
27
|
// 生效规则视图门(effective-policy --check):policy 变更后必须重生成 workflow/effective-policy.md 随同
|
|
24
28
|
// 提交——新装户由 init 生成器首跑种子该文件,缺文件对 --check 是漂移(红),不静默放行。
|
|
25
29
|
// 绿凭证摘要(gen-verify-evidence.mjs):全绿尾部自动重生成 workflow/verify-evidence.md——全绿事实与
|
|
26
30
|
// 仓内摘要同刻生成,消除「本地台账已绿、committed 摘要滞后」的双真相分叉;生成失败响亮告警但不推翻绿裁决。
|
|
27
|
-
// 用法:node .agents/scripts/verify.mjs [--list] [--test-cmd "<shell 命令>"] [--stub-gate "<命令>"] [--doc <本单 workflow 路径>]
|
|
31
|
+
// 用法:node .agents/scripts/verify.mjs [--list] [--jobs N | --serial] [--test-cmd "<shell 命令>"] [--stub-gate "<命令>"] [--doc <本单 workflow 路径>]
|
|
28
32
|
// --list:按执行序打印门 id 后退出,不执行(与 --test-cmd / --stub-gate 互斥)。
|
|
33
|
+
// --jobs N / --serial:并发档位与串行回退(缺省 clamp(2..4, floor(cpus/4));--jobs 0 / 非整数 fail-loud)。
|
|
29
34
|
// --test-cmd 用于测试注入,或在无根 package.json 的装户仓指定项目测试命令;
|
|
30
35
|
// --stub-gate <命令>(2026-10-16 自源仓回灌):单门夹具但走**生产尾段**(落凭证 / 生成摘要)——
|
|
31
36
|
// 与 --test-cmd 的差异是不打 testCmd 标记、尾段按生产路径执行;**必须配 CHECK_LOOP_ROOT**(夹具根),
|
|
@@ -33,16 +38,21 @@
|
|
|
33
38
|
// --doc 把凭证绑到本单,如 --doc workflow/intents/<日期>-<主题>.md
|
|
34
39
|
// 测试:node templates/_agents/scripts/verify.test.mjs(--test-cmd 注入假命令 + CHECK_LOOP_ROOT 注入夹具)
|
|
35
40
|
import fs from 'node:fs';
|
|
36
|
-
import
|
|
41
|
+
import os from 'node:os';
|
|
42
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
37
43
|
import path from 'node:path';
|
|
44
|
+
import { performance } from 'node:perf_hooks';
|
|
38
45
|
import { fileURLToPath } from 'node:url';
|
|
39
46
|
|
|
40
47
|
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
41
48
|
const ROOT = process.env.CHECK_LOOP_ROOT || path.resolve(SCRIPT_DIR, '..', '..');
|
|
42
49
|
|
|
43
50
|
// --- 门禁定义(engine cohort 七门,序即执行序;遇红即停) ---
|
|
51
|
+
// guardian(2026-10-16 并行编排):本门未过则后序门一并不启动——引擎脚本不可解析时其余门无意义,
|
|
52
|
+
// 且夹具根(CHECK_LOOP_ROOT 指向无脚本目录)下若并行铺开,各门会在假根里乱跑。
|
|
53
|
+
// deps:门间依赖边(本门须待所列门**成功**后才启动;kit 门表当前无边——机制随编排器下发,装户增门可用)。
|
|
44
54
|
const GATES = [
|
|
45
|
-
{ id: 'scripts-syntax', name: '引擎脚本语法自检', cmd: 'node .agents/scripts/check-scripts-syntax.mjs', folder: '.', type: 'shell' },
|
|
55
|
+
{ id: 'scripts-syntax', guardian: true, name: '引擎脚本语法自检', cmd: 'node .agents/scripts/check-scripts-syntax.mjs', folder: '.', type: 'shell' },
|
|
46
56
|
{ id: 'engine-tests', name: '测试门 (npm test)', cmd: 'npm test', folder: '.', type: 'shell' },
|
|
47
57
|
{ id: 'workflows-check', name: '编排脚本检查', cmd: 'node .agents/scripts/workflows-check.mjs', folder: '.', type: 'shell' },
|
|
48
58
|
{ id: 'rule-budget', name: '常驻面预算检查', cmd: 'node .agents/scripts/rule-budget.mjs --all', folder: '.', type: 'shell' },
|
|
@@ -127,84 +137,209 @@ const recordFailure = (step, exitInfo, note) => {
|
|
|
127
137
|
}
|
|
128
138
|
};
|
|
129
139
|
|
|
130
|
-
|
|
140
|
+
// 并发档位(2026-10-16 并行编排):门之间无共享状态(各自独立进程 + 独立工作目录),串行编排下
|
|
141
|
+
// 总耗时 = 各门之和;工作池并发后墙钟 ≈ max(最长门, 各门之和 / 并发)——门自身已内部并行
|
|
142
|
+
//(npm test 套件池 / check-loop),档位越高越互相抢核。缺省 = clamp(2..4, floor(cpus/4));
|
|
143
|
+
// --serial 回退串行(排障/对比);--jobs N 显式钉档(0 / 非整数 fail-loud)。
|
|
144
|
+
const serial = args.includes('--serial');
|
|
145
|
+
const jobsRaw = args.includes('--jobs') ? Number(args[args.indexOf('--jobs') + 1]) : null;
|
|
146
|
+
if (jobsRaw !== null && (!Number.isInteger(jobsRaw) || jobsRaw < 1)) {
|
|
147
|
+
console.error(`[verify] --jobs 须为正整数(现 ${args[args.indexOf('--jobs') + 1]})`);
|
|
148
|
+
process.exit(2);
|
|
149
|
+
}
|
|
150
|
+
const JOBS = serial ? 1 : Math.min(jobsRaw ?? Math.max(2, Math.min(4, Math.floor(os.cpus().length / 4))), gates.length);
|
|
151
|
+
// 单门输出上限(与旧 spawnSync maxBuffer 同口径):超限即树杀判红,不静默截断(截断后的输出不可当证据)
|
|
152
|
+
const MAX_GATE_OUTPUT = 32 * 1024 * 1024;
|
|
153
|
+
// 树杀(fail-fast 中止在跑门):Windows 下 shell:true 起的是 cmd.exe,kill 只杀壳不杀孙进程
|
|
154
|
+
//(npm→node 会成孤儿并占住文件锁)——故走 taskkill /T /F 整树。
|
|
155
|
+
const killTree = (child) => {
|
|
156
|
+
try {
|
|
157
|
+
if (process.platform === 'win32' && child.pid) {
|
|
158
|
+
spawnSync('taskkill', ['/pid', String(child.pid), '/T', '/F'], { stdio: 'ignore' });
|
|
159
|
+
} else {
|
|
160
|
+
child.kill('SIGTERM');
|
|
161
|
+
}
|
|
162
|
+
} catch { /* best-effort:杀不动不改变裁决 */ }
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
console.log(`[verify] 🚀 开始静态门验证 (共 ${gates.length} 项,并发 ${JOBS}${serial ? '·串行' : ''})${explicitTestCmd ? '【fixture 模式】' : ''}${stubCmd ? '【stub 尾段】' : ''}\n`);
|
|
166
|
+
|
|
167
|
+
async function main() {
|
|
168
|
+
let passedCount = 0;
|
|
169
|
+
const outputs = {};
|
|
170
|
+
const durations = {};
|
|
171
|
+
const doneIdx = new Set(); // 已出裁决的门序(成功/失败/中止;未列入者 = fail-fast 后状态未知)
|
|
172
|
+
const aborted = new Set(); // 因他门失败被树杀中止的门序
|
|
173
|
+
const running = new Map(); // 门序 -> child(树杀目标)
|
|
174
|
+
let failedIdx = -1;
|
|
131
175
|
|
|
132
|
-
|
|
133
|
-
const
|
|
176
|
+
const startGate = (i) => {
|
|
177
|
+
const gate = gates[i];
|
|
178
|
+
console.log(`[verify] ▶ ${i + 1}/${gates.length} ${gate.name}...`);
|
|
179
|
+
const t0 = performance.now();
|
|
180
|
+
const child = spawn(gate.cmd, { shell: true, cwd: path.join(ROOT, gate.folder) });
|
|
181
|
+
running.set(i, child);
|
|
182
|
+
const buf = { stdout: '', stderr: '', overflow: false };
|
|
183
|
+
const pump = (src, key) => {
|
|
184
|
+
src.setEncoding('utf8');
|
|
185
|
+
src.on('data', (d) => {
|
|
186
|
+
if (buf.overflow) return;
|
|
187
|
+
buf[key] += d;
|
|
188
|
+
// 与旧 spawnSync maxBuffer 同口径:超限即中止判红(不静默截断——截断后的输出不可当证据)
|
|
189
|
+
if (buf[key].length > MAX_GATE_OUTPUT) { buf.overflow = true; killTree(child); }
|
|
190
|
+
});
|
|
191
|
+
};
|
|
192
|
+
pump(child.stdout, 'stdout');
|
|
193
|
+
pump(child.stderr, 'stderr');
|
|
194
|
+
return new Promise((resolve) => {
|
|
195
|
+
let settled = false;
|
|
196
|
+
const settle = (status, error) => {
|
|
197
|
+
if (settled) return;
|
|
198
|
+
settled = true;
|
|
199
|
+
running.delete(i);
|
|
200
|
+
resolve({ status, error, ...buf, durMs: Math.round(performance.now() - t0) });
|
|
201
|
+
};
|
|
202
|
+
child.on('error', (e) => settle(null, e));
|
|
203
|
+
child.on('close', (status) => settle(status, null));
|
|
204
|
+
});
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// 出裁决:先回放该门缓冲输出(并发下输出按门整块回放,逐门可读,与 run-tests 同口径),再打结论行。
|
|
208
|
+
// 返回 true = 该门红(调用方据此 fail-fast)。
|
|
209
|
+
const finishGate = (i, r) => {
|
|
210
|
+
const gate = gates[i];
|
|
211
|
+
doneIdx.add(i);
|
|
212
|
+
durations[gate.id] = r.durMs;
|
|
213
|
+
if (r.stdout) process.stdout.write(r.stdout);
|
|
214
|
+
if (r.stderr) process.stderr.write(r.stderr);
|
|
215
|
+
if (aborted.has(i)) {
|
|
216
|
+
console.error(`[verify] ⏹ ${i + 1}/${gates.length} ${gate.name} 已中止(他门失败触发 fail-fast,状态未知)`);
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
if (r.overflow) {
|
|
220
|
+
console.error(`[verify] ❌ ${i + 1}/${gates.length} ${gate.name} 未通过(输出超 ${MAX_GATE_OUTPUT / 1024 / 1024}MB 上限,已中止)`);
|
|
221
|
+
recordFailure(gate.id, -1, 'output-overflow');
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
if (r.status !== 0) {
|
|
225
|
+
const note = `exit ${r.status ?? r.error?.code}`;
|
|
226
|
+
console.error(`[verify] ❌ ${i + 1}/${gates.length} ${gate.name} 未通过 (${note})`);
|
|
227
|
+
recordFailure(gate.id, r.status, note);
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
outputs[gate.id] = r.stdout || '';
|
|
231
|
+
console.log(`[verify] ✅ ${i + 1}/${gates.length} ${gate.name} 通过 [${r.durMs} ms]\n`);
|
|
232
|
+
passedCount++;
|
|
233
|
+
return false;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const wallStart = performance.now();
|
|
237
|
+
|
|
238
|
+
// 依赖边(deps):本门须待所列门**成功**后才启动;依赖门红/未跑 → 本门不启动(状态未知,由尾段清单兜底)
|
|
239
|
+
const idxOf = new Map(gates.map((g, i) => [g.id, i]));
|
|
240
|
+
const gatePromise = new Map(); // 门序 -> 该门结果 Promise(依赖方 await 用)
|
|
241
|
+
|
|
242
|
+
// 守卫段(guardian):串行先跑、未过即止——后序门一并不启动
|
|
243
|
+
for (let i = 0; i < gates.length && failedIdx < 0; i++) {
|
|
244
|
+
if (!gates[i].guardian) continue;
|
|
245
|
+
const p = startGate(i);
|
|
246
|
+
gatePromise.set(i, p);
|
|
247
|
+
if (finishGate(i, await p) && failedIdx < 0) failedIdx = i;
|
|
248
|
+
}
|
|
134
249
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
250
|
+
if (failedIdx < 0) {
|
|
251
|
+
// 主体段:工作池并发(完成一个补一个);任一门红 → 停排新门 + 树杀在跑门(fail-fast 语义保留)
|
|
252
|
+
const bodyIdx = gates.map((_, i) => i).filter((i) => !gates[i].guardian);
|
|
253
|
+
let cursor = 0;
|
|
254
|
+
const worker = async () => {
|
|
255
|
+
while (failedIdx < 0 && cursor < bodyIdx.length) {
|
|
256
|
+
const i = bodyIdx[cursor++];
|
|
257
|
+
const gate = gates[i];
|
|
258
|
+
// 装户仓:engine-tests 门显式跳过——跳过≠通过,不计入 passedCount、不算绿(全绿落账段据此不落凭证)
|
|
259
|
+
if (skipTest && gate.id === 'engine-tests') {
|
|
260
|
+
console.log(`[verify] ⏭ ${i + 1}/${gates.length} 跳过 ${gate.cmd}:当前目录无 package.json(装户仓)——请按项目口径手工跑测试,或用 --test-cmd "<项目测试命令>" 指定后重跑`);
|
|
261
|
+
doneIdx.add(i);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
const deps = gate.deps || [];
|
|
265
|
+
if (deps.length) {
|
|
266
|
+
const depResults = await Promise.all(deps.map((d) => gatePromise.get(idxOf.get(d)) ?? Promise.resolve(null)));
|
|
267
|
+
if (failedIdx >= 0) return;
|
|
268
|
+
if (depResults.some((r) => !r || r.status !== 0)) continue; // 依赖未过 → 本门不启动
|
|
269
|
+
}
|
|
270
|
+
const p = startGate(i);
|
|
271
|
+
gatePromise.set(i, p);
|
|
272
|
+
if (finishGate(i, await p) && failedIdx < 0) {
|
|
273
|
+
failedIdx = i;
|
|
274
|
+
for (const [j, child] of [...running]) { aborted.add(j); killTree(child); }
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
await Promise.all(Array.from({ length: Math.min(JOBS, bodyIdx.length) }, worker));
|
|
141
279
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
maxBuffer: 32 * 1024 * 1024,
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
if (result.stdout) process.stdout.write(result.stdout);
|
|
152
|
-
if (result.stderr) process.stderr.write(result.stderr);
|
|
153
|
-
|
|
154
|
-
if (result.status !== 0) {
|
|
155
|
-
const note = `exit ${result.status ?? result.error?.code}`;
|
|
156
|
-
console.error(`[verify] ❌ ${i + 1}/${gates.length} ${gate.name} 未通过 (${note})`);
|
|
157
|
-
// 未执行门清单:fail-fast 后序门状态未知≠通过——「没跑」在台账里与「过了」不可区分即输出不完备
|
|
158
|
-
const remaining = gates.slice(i + 1).map((g) => g.id);
|
|
159
|
-
if (remaining.length) console.error(`[verify] ⏭️ 未执行门(fail-fast,状态未知):${remaining.join('、')}`);
|
|
160
|
-
recordFailure(gate.id, result.status, note);
|
|
280
|
+
|
|
281
|
+
if (failedIdx >= 0) {
|
|
282
|
+
// 未执行门清单(按「已出裁决」算,已中止的门同样状态未知——被树杀时无结论,一并点名):
|
|
283
|
+
// fail-fast 后状态未知≠通过——「没跑」在台账里与「过了」不可区分即输出不完备
|
|
284
|
+
const remaining = gates.map((g, i) => i).filter((i) => !doneIdx.has(i) || aborted.has(i)).map((i) => gates[i].id);
|
|
285
|
+
if (remaining.length) console.error(`[verify] ⏭️ 未执行门(fail-fast,状态未知;含被树杀的已中止门):${remaining.join('、')}`);
|
|
161
286
|
process.exit(1);
|
|
162
287
|
}
|
|
163
|
-
outputs[gate.id] = result.stdout || '';
|
|
164
|
-
console.log(`[verify] ✅ ${i + 1}/${gates.length} ${gate.name} 通过\n`);
|
|
165
|
-
passedCount++;
|
|
166
|
-
}
|
|
167
288
|
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
try {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
289
|
+
// ---- 全绿尾段(任一门红已在上方 exit 1 返回,能走到这里即全绿)----
|
|
290
|
+
// 凭证落账:全部门绿才落;**测试门被跳过同样不落**(测试未跑即无「测试绿」事实,落账即假绿);IO 失败出账不阻断
|
|
291
|
+
try {
|
|
292
|
+
if (skipTest) {
|
|
293
|
+
console.log('[verify] ⚠️ 测试门已跳过——本次不落测试绿凭证(凭证语义 = 测试真跑绿)');
|
|
294
|
+
} else {
|
|
295
|
+
const entry = { ts: new Date().toISOString(), exitCode: 0, suite: 'npm test' };
|
|
296
|
+
// 门数:凭证明此记录「过全量门禁全绿」是几门(fixture 模式 = 2 门夹具,仍如实记)
|
|
297
|
+
entry.gates = passedCount;
|
|
298
|
+
// HEAD 短 SHA 钉进绿行本体(源仓回灌):确定性证据此前只存在于测试夹具、绿行无 SHA 可锚定验证对象;
|
|
299
|
+
// 非 git 环境(fixture 根在 os.tmpdir)解析失败则省略该键,不伪造。
|
|
300
|
+
const rev = spawnSync('git', ['rev-parse', '--short', 'HEAD'], { encoding: 'utf8', cwd: ROOT });
|
|
301
|
+
if (rev.status === 0 && rev.stdout) entry.headShort = rev.stdout.trim();
|
|
302
|
+
// 计数行**只认编排器权威前缀**:嵌套套件同形行不回退——非本编排器口径的 --test-cmd
|
|
303
|
+
//(装户项目自有测试命令)抓不到即省略计数键,不回退到错的事实(宁缺勿错,exitCode 才是硬事实)。
|
|
304
|
+
const m = [...(outputs['engine-tests'] || outputs['test'] || '').matchAll(/\[run-tests\] 合计: SUITES (\d+) \/ FAILED (\d+)/g)].pop();
|
|
305
|
+
if (m) { entry.passed = Number(m[1]); entry.failed = Number(m[2]); }
|
|
306
|
+
// 门禁成本入账(自源仓回灌):totalMs = 各门之和(并发后已非墙钟,保留以续接历史口径);
|
|
307
|
+
// wallMs = 本次实际墙钟——「一次迭代的等待成本」是墙钟,读方以 wallMs 优先。
|
|
308
|
+
entry.totalMs = Object.values(durations).reduce((a, b) => a + b, 0);
|
|
309
|
+
entry.wallMs = Math.round(performance.now() - wallStart);
|
|
310
|
+
entry.durations = durations;
|
|
311
|
+
if (process.env.PIPELINE_RUN_ID) entry.runId = process.env.PIPELINE_RUN_ID;
|
|
312
|
+
// doc 键:未传 --doc 时**不写该键**(不是写 null)——消费方按 doc 精确匹配,undefined 与 null 都恒不命中,
|
|
313
|
+
// 但缺键让「本行没有绑定对象」这件事在台账盘面上直接可读。
|
|
314
|
+
if (docRel) entry.doc = docRel;
|
|
315
|
+
fs.mkdirSync(path.join(ROOT, '.agents'), { recursive: true });
|
|
316
|
+
fs.appendFileSync(path.join(ROOT, '.agents', 'verifications.jsonl'), `${JSON.stringify(entry)}\n`);
|
|
317
|
+
const top3 = Object.entries(durations).sort((a, b) => b[1] - a[1]).slice(0, 3)
|
|
318
|
+
.map(([id, ms]) => `${id}(${(ms / 1000).toFixed(1)}s)`).join(' ');
|
|
319
|
+
console.log(`[verify] 🧾 测试绿凭证已落账 verifications.jsonl${entry.passed !== undefined ? `(套件 ${entry.passed} / FAIL ${entry.failed})` : ''}${docRel ? ` 绑定本单 ${docRel}` : '(未带 --doc:不给任何单背书)'}`);
|
|
320
|
+
console.log(`[verify] ⏱️ 门禁成本:墙钟 ${(entry.wallMs / 1000).toFixed(1)}s|各门合计 ${(entry.totalMs / 1000).toFixed(1)}s|最慢三门 ${top3}`);
|
|
321
|
+
}
|
|
322
|
+
} catch (e) {
|
|
323
|
+
console.error(`[verify] ⚠️ 凭证落账失败(${e.code || e.message})——不阻断,关单门前置将按无凭证告警`);
|
|
192
324
|
}
|
|
193
|
-
} catch (e) {
|
|
194
|
-
console.error(`[verify] ⚠️ 凭证落账失败(${e.code || e.message})——不阻断,关单门前置将按无凭证告警`);
|
|
195
|
-
}
|
|
196
325
|
|
|
197
|
-
// 摘要事件原子化:全绿事实与仓内摘要(workflow/verify-evidence.md)同刻生成,消除「本地台账已绿、
|
|
198
|
-
// committed 摘要滞后」的双真相分叉。仅生产模式(fixture 假门凭证不产 committed 摘要;测试门跳过时
|
|
199
|
-
// 本就无凭证);stub 尾段按生产路径执行(摘要落在 CHECK_LOOP_ROOT 夹具根,不触真实仓)。
|
|
200
|
-
// 生成失败响亮告警但不推翻绿裁决(同落账口径)。
|
|
201
|
-
if (!explicitTestCmd && !skipTest) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
326
|
+
// 摘要事件原子化:全绿事实与仓内摘要(workflow/verify-evidence.md)同刻生成,消除「本地台账已绿、
|
|
327
|
+
// committed 摘要滞后」的双真相分叉。仅生产模式(fixture 假门凭证不产 committed 摘要;测试门跳过时
|
|
328
|
+
// 本就无凭证);stub 尾段按生产路径执行(摘要落在 CHECK_LOOP_ROOT 夹具根,不触真实仓)。
|
|
329
|
+
// 生成失败响亮告警但不推翻绿裁决(同落账口径)。
|
|
330
|
+
if (!explicitTestCmd && !skipTest) {
|
|
331
|
+
const gen = spawnSync(process.execPath, [path.join(SCRIPT_DIR, 'gen-verify-evidence.mjs'), '--root', ROOT], { encoding: 'utf8' });
|
|
332
|
+
if (gen.status === 0) {
|
|
333
|
+
if (gen.stdout) process.stdout.write(String(gen.stdout).trimEnd() + '\n');
|
|
334
|
+
} else {
|
|
335
|
+
console.error(`[verify] ⚠️ 绿凭证摘要生成失败(${String(gen.stderr || gen.stdout || '').trim().slice(0, 200)})——绿裁决不变,手动补跑 node .agents/scripts/gen-verify-evidence.mjs`);
|
|
336
|
+
}
|
|
207
337
|
}
|
|
338
|
+
|
|
339
|
+
console.log(`\n[verify] 🎉 全绿——可以关单(共 ${passedCount} 项通过;逐条勾验验收标准补证据后 intent/plan → done)`);
|
|
208
340
|
}
|
|
209
341
|
|
|
210
|
-
|
|
342
|
+
main().catch((e) => {
|
|
343
|
+
console.error(`[verify] ❌ 编排器异常:${e && e.stack ? e.stack : e}`);
|
|
344
|
+
process.exit(1);
|
|
345
|
+
});
|
|
@@ -350,6 +350,21 @@ const readLedger = (root) => {
|
|
|
350
350
|
fs.rmSync(root, { recursive: true, force: true });
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
+
// ---- 场景 15:并发档位(2026-10-16 verify-parallel)——--jobs 2 正常绿 / --serial 串行回退 / --jobs 0 fail-loud ----
|
|
354
|
+
{
|
|
355
|
+
const root = mkfix();
|
|
356
|
+
const rJ = run(['--test-cmd', `node ${mkCmd(0)}`, '--jobs', '2'], { env: { ...process.env, CHECK_LOOP_ROOT: root } });
|
|
357
|
+
check('场景 15a:--jobs 2 → exit 0 且头行标注并发档位',
|
|
358
|
+
rJ.status === 0 && rJ.stdout.includes('并发 2') && rJ.stdout.includes('全绿'), `exit=${rJ.status}\n${rJ.stdout}\n${rJ.stderr}`);
|
|
359
|
+
const rS = run(['--test-cmd', `node ${mkCmd(0)}`, '--serial'], { env: { ...process.env, CHECK_LOOP_ROOT: root } });
|
|
360
|
+
check('场景 15b:--serial → exit 0 且头行标注串行(并发 1)',
|
|
361
|
+
rS.status === 0 && rS.stdout.includes('并发 1·串行'), `exit=${rS.status}\n${rS.stdout}\n${rS.stderr}`);
|
|
362
|
+
const rO = run(['--test-cmd', `node ${mkCmd(0)}`, '--jobs', '0'], { env: { ...process.env, CHECK_LOOP_ROOT: root } });
|
|
363
|
+
check('场景 15c:--jobs 0 → exit 2 fail-loud(不静默按缺省跑)',
|
|
364
|
+
rO.status === 2 && /--jobs 须为正整数/.test(rO.stderr), `exit=${rO.status}\n${rO.stderr}`);
|
|
365
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
366
|
+
}
|
|
367
|
+
|
|
353
368
|
console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
|
|
354
369
|
if (fail) {
|
|
355
370
|
console.log('\n用法:node templates/_agents/scripts/verify.test.mjs');
|
|
@@ -16,6 +16,9 @@ description: 代码复核走查方法(Standards×Spec 双轴 + 坏味道清单
|
|
|
16
16
|
|
|
17
17
|
宿主有全局 `code-review` / `review-verification-protocol` 技能时可叠加;无则本文件兜底。
|
|
18
18
|
|
|
19
|
+
## 校准集(golden-set.md,装户自建)
|
|
20
|
+
校准集是独立复核质量的已知答案样本库:正例 = 应报缺陷、负例 = 应放行,每条带「为何需本仓上下文才可判」要点(外部实证:注入仓库上下文的缺陷识别率远高于通用样本——校准集必须用本仓已闭环的 incidents/返工件构造才有校准力,通用样本不算数)。建集方法:从本仓已闭环件选样,同族第二例须带来新失败形态才入集(宁缺毋滥);答案锚定闭环时定性,代码后续演进不改答案。使用(**双臂**):复核员/新会话上岗前对样本盲评后对答案(命中/误报/漏报三档)自检校准差——A 臂(默认)装载本技能复核;B 臂(对照,月度抽 5–8 条即可)把同样题面交给干净会话,只给样本内联的仓库上下文、不载技能。**Δ = A 命中差 − B 命中差**是唯一能证明「上下文注入起作用」的数:两臂同样准 → 校准力来自样本内联而非技能文档;两臂同样差 → 该样本已知答案不可判——两种情形都记进留痕表「臂」列,不互相冒充。可疑判定(与已知答案不符、或与常识相悖的轻松命中)须**方差复跑**:同会话原样再评,判定翻转 → 计评分方差(题面或 rubric 有歧义),本轮不计命中差、另开 papercut 修题面;不翻转才计校准差——别让基础设施噪声冒充复核方差。月度对账抽样与 reviews.d 当月片互参(事件级计数 vs 样本级命中差,两种粒度不混算,只在备注关联);命中差连续两月 >20% 在 papercuts 登记校准债,**前提是先剔掉方差翻转的判定**(否则评的是题面、不是复核员)。盲评结果按月落 golden-set.md「月度盲评留痕」表(列架:月份|参评样本数|臂|命中/误报/漏报|Δ|方差复跑翻转次数|与 reviews.d 互参谋注)。样本文件放本技能同目录 `golden-set.md`(装户数据,kit 不随包分发);建集后在 mechanism-registry 登记一行(数据面,退役阈值参考:连续 6 个有数据月零盲评)。
|
|
21
|
+
|
|
19
22
|
## 双轴框架(分轴报告,禁合并排序)
|
|
20
23
|
| 轴 | 问的问题 | 依据 |
|
|
21
24
|
|---|---|---|
|
|
@@ -32,8 +32,9 @@
|
|
|
32
32
|
- **验证**:`.agents/commands/test.md`(静态门 + 实测;项目自有验证脚本/门禁如有,见 `.agents/hooks/local-pre-commit` 与根 `AGENTS.md`「项目适配区」)
|
|
33
33
|
- **评审**:`.agents/commands/review.md`(按 P0 / P1 / P2 分级:机器兜底 + AI 自查出清单,用户决策定性与合入)
|
|
34
34
|
- **上线**:`.agents/commands/deploy.md`(上线前必跑清单)
|
|
35
|
-
- **量化**:委派/自做结果记 `delegations.md`,聚合跑 `node .agents/scripts/agg-delegations.cjs`(**并发扩容门槛见该文件——数字达标前不扩并发**);语料与常驻面体积的**月度快照**跑 `node .agents/scripts/gen-workflow-metrics.mjs`(每月一行落 `metrics.md`,同月重跑即更新;明细看 stdout
|
|
35
|
+
- **量化**:委派/自做结果记 `delegations.md`,聚合跑 `node .agents/scripts/agg-delegations.cjs`(**并发扩容门槛见该文件——数字达标前不扩并发**);语料与常驻面体积的**月度快照**跑 `node .agents/scripts/gen-workflow-metrics.mjs`(每月一行落 `metrics.md`,同月重跑即更新;明细看 stdout);独立复核的**意见级处置**记 `workflow/reviews.d/<YYYY-MM>.md`(一行 = 一次复核事件,八列 `日期|对象|复核员|意见数|采纳|拒绝|误报|备注`,采纳+拒绝+误报=意见数,月度聚合进 metrics「复核效果」表——口径详见 `.agents/commands/review.md` 落账子项)
|
|
36
36
|
- **归档**:终态件滚动归档跑 `node .agents/scripts/archive-sweep.mjs`(缺省 dry-run 预览;`--window 21 --execute` 执行 git mv)——归档单位 = **同名家族**(intent/spec/plan/incident 同进同退:任一成员非终态 / 未满窗 / 被活跃引用 / 无日期,整族留守);终态时间三级锚 = 确认台账终态行 ts → 内容 last-touch(frontmatter-only 提交不刷新)→ 文件名日期;落点 `workflow/archive/<家族最晚终态月>/<类型>/`
|
|
37
|
+
- **spec 生命周期口径(spec-anchored + 长期维护)**:spec 是**持续对照的事实源,交付后不失效**——代码改动触及某 spec 覆盖的行为时须对照该 spec(含归档件):行为漂移要么改代码回到 spec,要么同一提交回改 spec;有争议立 incident 裁决。**归档是容量治理不是失效**:归档只把终态件移出活跃扫描面(INDEX 活跃行 / check-loop 四目录扫描 / 月度快照采集),事实源地位与修订权不变——归档 spec 可原地修订,修订不改归档位置。体积治理先观察后设限:spec 行数/验收条数分布由 `gen-workflow-metrics.mjs` 的「spec 体积观察」表采集(advisory 上限显式延后,先让体积可见)
|
|
37
38
|
- **papercut 回路(存量债清偿,手动触发)**:命令/技能卡壳记 `workflow/papercuts.md` 攒成的未升级条目 = 存量债;要清时跑 `node .agents/scripts/papercut-debt-sensor.mjs`(测 debt)→ `node .agents/scripts/papercut-debt-controller.mjs`(确定性选本轮唯一目标;源码/数据库面条目被 scope gate 拦下等授权)→ 按 `.agents/skills/papercut-fix/SKILL.md` 执行(改完 debt 应恰好 -1)。判定与排序口径单源 `.agents/loop/papercut-debt/DESIGN.md`,改口径先改设计文件
|
|
38
39
|
- **追溯**:git 即审计——文档随代码同 commit、上线打 `release/<日期>` tag,`git log` 全链路可查;多人评审暂缓,git 历史即评审记录
|
|
39
40
|
- **级别**:L1 实现级(未命中 L2/L3;新需求的入口是 plan)| L2 规则 / 契约(编码权威 / 共享契约 / 既有接口语义 / 全局横切口径,触达面闭集见 `.agents/commands/new-task.md` §级别判断)| L3 数据与运行时结构(schema / 迁移 SQL / DI 链 / 认证与中间件管线);混合改动就高不就低
|
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
<!-- frontmatter 受限子集(2026-09-13):每行 `键: 值`;状态∈draft/approved/done/superseded/cancelled(严格枚举,附注写备注键);级别∈L1/L2/L3;check-loop 只扫 frontmatter 取机器字段,正文不再写状态/级别行 -->
|
|
13
13
|
<!-- 探索泳道(可选,2026-09-30 hybrid-governance-explore-hardening):experiment/ 前缀分支上的 PoC 任务在 frontmatter 加「阶段: exploring」——提交/推送门降级 advisory,spec/plan 可先于本入口确认起草(限 L1);转正合入 main 前须收口(本档 approved/done + 同名 plan/spec 在场,check-loop --hardening 加固门),或显式放弃(superseded/cancelled) -->
|
|
14
14
|
<!-- 预判(可选,2026-10-13 自 Shipyard 回灌 prejudgment-ledger):定级时先做结构化预判,结果如实写「预判: kind=<require|fix|verify|review|deploy> level=<L1|L2|L3>」——intents≡require、incidents≡fix;与实际落位不符时 check-loop 路由预判一致性检查(B6b 落地后编号以 check-loop.mjs 头部清单为准)出 WARN 留账。缺字段零告警(存量宽容不回填,不做强制前置)——advisory 灰度口径 -->
|
|
15
|
+
<!-- 待澄清标记(2026-10-12 needs-clarification;2026-10-18 立项跳对称化扩到本模板):起草时
|
|
16
|
+
不确定的地方照实写一行 `- 待澄清:<你卡在哪>`,不要猜一个填上去——立项是后链全部文档的种子,
|
|
17
|
+
「我不知道」是负责,猜是失职。起草门不拦(不阻塞继续写);但 draft→approved 确认时仍挂着
|
|
18
|
+
未澄清项会被 confirm-doc 拒绝落账。解决后把行首改成 `- 已澄清:…`(保留该行留痕)或直接删行。
|
|
19
|
+
判据单源:stage-gates.mjs#openClarifications / clarifyGateFor。存量 intent 无标记零回溯。 -->
|
|
15
20
|
|
|
16
21
|
## 背景与问题
|
|
17
22
|
<为什么做;写明需求来源(工单 / 沟通记录 / 待办事项)>
|
|
@@ -44,7 +44,7 @@ THEN 行是机器对账断言:声明了场景却缺「- THEN <可观测结果>
|
|
|
44
44
|
- 前端:路由 / 组件 / store / api
|
|
45
45
|
|
|
46
46
|
## 约束遵守映射(对照 AGENTS.md 触达红线)
|
|
47
|
-
|
|
|
47
|
+
| 红线 / 工程原则(按本项目 AGENTS.md 红线与「工程原则」节实际行逐行填,没有的行删除) | 本 spec 如何满足 |
|
|
48
48
|
|------|------------------|
|
|
49
49
|
| <示例:分层依赖约束 / 编码权威归属 / 数据迁移方式> | <通过 XXX,不引入 YYY;是否触及(触及即 L2 定级依据)> |
|
|
50
50
|
| <示例:schema / 迁移路径约束> | <涉及路径与处理方式> |
|