agentic-flow-kit 0.8.0 → 0.9.0
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 +36 -12
- package/modules/gates/README.md +24 -0
- package/modules/gates/generated-readonly/README.md +13 -0
- package/modules/gates/generated-readonly/check-generated.mjs +61 -0
- package/modules/gates/generated-readonly/local-pre-commit.line +1 -0
- package/modules/gates/node-layer/README.md +9 -0
- package/modules/gates/node-layer/check-node-layer.mjs +29 -0
- package/modules/gates/node-layer/local-pre-commit.line +1 -0
- package/modules/gates/py-import/README.md +9 -0
- package/modules/gates/py-import/check-py-import.mjs +30 -0
- package/modules/gates/py-import/local-pre-commit.line +1 -0
- package/modules/hosts/claude/agents/implementer.md +52 -0
- package/modules/hosts/claude/agents/independent-reviewer.md +49 -0
- package/modules/hosts/claude/agents/ui-verifier.md +48 -0
- package/modules/hosts/claude/commands/wf-build.md +79 -0
- package/modules/hosts/claude/commands/wf-deploy.md +57 -0
- package/modules/hosts/claude/commands/wf-design.md +42 -0
- package/modules/hosts/claude/commands/wf-gate-checklist.md +77 -0
- package/modules/hosts/claude/commands/wf-maintain.md +76 -0
- package/modules/hosts/claude/commands/wf-new-task.md +65 -0
- package/modules/hosts/claude/commands/wf-plan.md +41 -0
- package/modules/hosts/claude/commands/wf-review.md +47 -0
- package/modules/hosts/claude/commands/wf-source-sync-check.md +89 -0
- package/modules/hosts/claude/commands/wf-sync-hosts.md +66 -0
- package/modules/hosts/claude/commands/wf-test.md +66 -0
- package/modules/hosts/codex/agents/implementer.md +52 -0
- package/modules/hosts/codex/agents/independent-reviewer.md +49 -0
- package/modules/hosts/codex/agents/ui-verifier.md +48 -0
- package/modules/hosts/codex/commands/wf-build.md +79 -0
- package/modules/hosts/codex/commands/wf-deploy.md +57 -0
- package/modules/hosts/codex/commands/wf-design.md +42 -0
- package/modules/hosts/codex/commands/wf-gate-checklist.md +77 -0
- package/modules/hosts/codex/commands/wf-maintain.md +76 -0
- package/modules/hosts/codex/commands/wf-new-task.md +65 -0
- package/modules/hosts/codex/commands/wf-plan.md +41 -0
- package/modules/hosts/codex/commands/wf-review.md +47 -0
- package/modules/hosts/codex/commands/wf-source-sync-check.md +89 -0
- package/modules/hosts/codex/commands/wf-sync-hosts.md +66 -0
- package/modules/hosts/codex/commands/wf-test.md +66 -0
- package/modules/hosts/codex/skills/flow-kit/SKILL.md +14 -0
- package/modules/hosts/cursor/agents/implementer.md +52 -0
- package/modules/hosts/cursor/agents/independent-reviewer.md +49 -0
- package/modules/hosts/cursor/agents/ui-verifier.md +48 -0
- package/modules/hosts/cursor/commands/wf-build.md +79 -0
- package/modules/hosts/cursor/commands/wf-deploy.md +57 -0
- package/modules/hosts/cursor/commands/wf-design.md +42 -0
- package/modules/hosts/cursor/commands/wf-gate-checklist.md +77 -0
- package/modules/hosts/cursor/commands/wf-maintain.md +76 -0
- package/modules/hosts/cursor/commands/wf-new-task.md +65 -0
- package/modules/hosts/cursor/commands/wf-plan.md +41 -0
- package/modules/hosts/cursor/commands/wf-review.md +47 -0
- package/modules/hosts/cursor/commands/wf-source-sync-check.md +89 -0
- package/modules/hosts/cursor/commands/wf-sync-hosts.md +66 -0
- package/modules/hosts/cursor/commands/wf-test.md +66 -0
- package/modules/hosts/omp/agents/implementer.md +1 -1
- package/modules/hosts/omp/agents/independent-reviewer.md +1 -1
- package/modules/hosts/omp/agents/ui-verifier.md +1 -1
- package/modules/hosts/opencode/agents/implementer.md +1 -1
- package/modules/hosts/opencode/agents/independent-reviewer.md +1 -1
- package/modules/hosts/opencode/agents/ui-verifier.md +1 -1
- package/modules/hosts/opencode/commands/wf-build.md +3 -4
- package/modules/hosts/opencode/commands/wf-design.md +2 -2
- package/modules/hosts/opencode/commands/wf-maintain.md +1 -1
- package/modules/hosts/opencode/commands/wf-plan.md +3 -3
- package/modules/hosts/opencode/commands/wf-review.md +1 -1
- package/modules/hosts/opencode/commands/wf-source-sync-check.md +1 -1
- package/modules/hosts/opencode/commands/wf-test.md +3 -1
- package/modules/hosts/trae/agents/implementer.md +1 -1
- package/modules/hosts/trae/agents/independent-reviewer.md +1 -1
- package/modules/hosts/trae/agents/ui-verifier.md +1 -1
- package/modules/hosts/trae/commands/wf-build.md +3 -4
- package/modules/hosts/trae/commands/wf-design.md +2 -2
- package/modules/hosts/trae/commands/wf-maintain.md +1 -1
- package/modules/hosts/trae/commands/wf-plan.md +3 -3
- package/modules/hosts/trae/commands/wf-review.md +1 -1
- package/modules/hosts/trae/commands/wf-source-sync-check.md +1 -1
- package/modules/hosts/trae/commands/wf-test.md +3 -1
- package/modules/hosts/zcode/agents/implementer.md +1 -1
- package/modules/hosts/zcode/agents/independent-reviewer.md +1 -1
- package/modules/hosts/zcode/agents/ui-verifier.md +1 -1
- package/package.json +1 -1
- package/src/cli.mjs +16 -4
- package/src/doctor.mjs +49 -1
- package/src/fresh-init.test.mjs +36 -0
- package/src/gates.test.mjs +126 -0
- package/src/init.mjs +8 -1
- package/src/init.test.mjs +7 -1
- package/src/profiles.mjs +10 -4
- package/src/run-tests.mjs +3 -0
- package/src/stack-profile.test.mjs +89 -0
- package/src/sync-hosts.test.mjs +26 -6
- package/templates/AGENTS.md +4 -4
- package/templates/_agents/board/index.html +3 -3
- package/templates/_agents/commands/build.md +4 -5
- package/templates/_agents/commands/design.md +4 -4
- package/templates/_agents/commands/maintain.md +1 -1
- package/templates/_agents/commands/new-task.md +13 -2
- package/templates/_agents/commands/plan.md +4 -4
- package/templates/_agents/commands/review.md +1 -1
- package/templates/_agents/commands/source-sync-check.md +1 -1
- package/templates/_agents/commands/test.md +4 -2
- package/templates/_agents/hooks/commit-check.cjs +45 -24
- package/templates/_agents/lane-surfaces.txt +14 -0
- package/templates/_agents/roles/implementer.md +1 -1
- package/templates/_agents/roles/independent-reviewer.md +1 -1
- package/templates/_agents/roles/ui-verifier.md +1 -1
- package/templates/_agents/scripts/check-lane-surface.mjs +164 -0
- package/templates/_agents/scripts/check-lane-surface.test.mjs +200 -0
- package/templates/_agents/scripts/check-loop-rev.test.mjs +337 -0
- package/templates/_agents/scripts/check-loop.mjs +460 -268
- package/templates/_agents/scripts/check-loop.test.mjs +677 -17
- package/templates/_agents/scripts/check-metric-claims.mjs +275 -0
- package/templates/_agents/scripts/commit-check-trigger.test.mjs +64 -1
- package/templates/_agents/scripts/confirm-doc.mjs +159 -15
- package/templates/_agents/scripts/confirm-doc.test.mjs +362 -10
- package/templates/_agents/scripts/doctor.test.mjs +102 -1
- package/templates/_agents/scripts/fill-intent.mjs +8 -7
- package/templates/_agents/scripts/fill-intent.test.mjs +12 -3
- package/templates/_agents/scripts/fill-plan.mjs +27 -11
- package/templates/_agents/scripts/fill-plan.test.mjs +148 -8
- package/templates/_agents/scripts/fill-spec.mjs +18 -3
- package/templates/_agents/scripts/fill-spec.test.mjs +198 -1
- package/templates/_agents/scripts/gate-checklist.mjs +4 -1
- package/templates/_agents/scripts/gen-workflow-index.mjs +6 -2
- package/templates/_agents/scripts/gen-workflow-index.test.mjs +12 -0
- package/templates/_agents/scripts/policy.mjs +46 -0
- package/templates/_agents/scripts/solidify-task.mjs +101 -0
- package/templates/_agents/scripts/stage-gates.mjs +256 -0
- package/templates/_agents/scripts/trust-mode.mjs +67 -0
- package/templates/_agents/scripts/wf-journal.mjs +71 -2
- package/templates/_agents/scripts/wf-journal.test.mjs +66 -0
- package/templates/_agents/scripts/workflow-board-server.mjs +18 -15
- package/templates/_agents/scripts/workflow-board-server.test.mjs +30 -1
- package/templates/_agents/workflows/pipeline-closing.md +1 -1
- package/templates/_githooks/pre-commit +33 -2
- package/templates/_githooks/pre-push +56 -12
- package/templates/workflow/README.md +22 -3
- package/templates/workflow/incidents/_TEMPLATE.md +4 -0
- package/templates/workflow/intents/_TEMPLATE.md +1 -0
package/src/init.test.mjs
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
// 断言:①ts/js 别名归一 node;②序号/名称/混输/全角逗号/空白分隔解析正确(去重保序);
|
|
5
5
|
// ③非法输入返回 null(就地重问信号);④空输入回默认(EOF/直接回车安全回退);⑥装户面含 runner/workflows。
|
|
6
6
|
// 用法:node src/init.test.mjs
|
|
7
|
-
import { normalizeStack, parseChoices, hasAgentsSkeleton, mergeAgents } from './init.mjs';
|
|
7
|
+
import { normalizeStack, parseChoices, hasAgentsSkeleton, mergeAgents, menuMark } from './init.mjs';
|
|
8
|
+
import { HOSTS as HOST_REG } from './profiles.mjs';
|
|
8
9
|
import { renderTree, scriptTrusted, listTree } from './render.mjs';
|
|
9
10
|
import { sha256 } from './render.mjs';
|
|
10
11
|
import fs from 'node:fs';
|
|
@@ -38,6 +39,11 @@ check('栈别名 ts + map 归一 → node', eq(parseChoices('ts', STACKS, 'none'
|
|
|
38
39
|
|
|
39
40
|
// ---- ③ 非法输入 → null(就地重问) ----
|
|
40
41
|
check('越界序号 9 → null', parseChoices('9', HOSTS, 'zcode') === null);
|
|
42
|
+
{
|
|
43
|
+
const realHosts = Object.keys(HOST_REG);
|
|
44
|
+
const n = String(realHosts.length);
|
|
45
|
+
check('菜单号覆盖全部宿主,序号 7 选中最后一项', menuMark(realHosts.length - 1) === n && eq(parseChoices(n, realHosts, 'zcode'), [realHosts[realHosts.length - 1]]));
|
|
46
|
+
}
|
|
41
47
|
check('未知名称 bad → null', parseChoices('zcode,bad', HOSTS, 'zcode') === null);
|
|
42
48
|
check('栈别名未传 map 不归一(ts → null)', parseChoices('ts', STACKS, 'none') === null);
|
|
43
49
|
|
package/src/profiles.mjs
CHANGED
|
@@ -2,13 +2,19 @@
|
|
|
2
2
|
// stack 决定门禁配置三处:commit-check 的条件构建(builds)与质量检测(checks,秒级确定性
|
|
3
3
|
// 检查——lint/类型/vet;测试不放提交门,关单在 test.md 阶段门)、settings.json 的自检验
|
|
4
4
|
// 命令权限(allow)、AGENTS.md「项目适配区」命令预填;不改工作流流程本身。
|
|
5
|
-
// checks.when
|
|
6
|
-
//
|
|
5
|
+
// checks.when / builds.when = 条件才启用。文件路径或尾部 * 通配表示配置文件存在;
|
|
6
|
+
// pkg:scripts.build 表示 package.json 里该脚本为非空字符串。没有对应配置的项目自动跳过,
|
|
7
|
+
// 不 fail-closed 误拦。none = 不配置(项目日后自填)。
|
|
7
8
|
export const HOSTS = {
|
|
8
9
|
zcode: { dir: '.zcode', localOnly: true },
|
|
9
10
|
opencode: { dir: '.opencode', localOnly: false, commandPrefix: 'wf-' },
|
|
10
11
|
trae: { dir: '.trae', localOnly: false, commandPrefix: 'wf-' },
|
|
11
12
|
omp: { dir: '.omp', localOnly: true },
|
|
13
|
+
// 2026-09-29 店面:序号仍把 zcode 放第一(交互菜单 1–4 不变)。claude / cursor 的 commands 由宿主自动加载;
|
|
14
|
+
// codex 自动读仓库根 AGENTS.md 与 .codex/skills/,commands 与 agents 是同形薄转发,供显式引用。
|
|
15
|
+
claude: { dir: '.claude', localOnly: false, commandPrefix: 'wf-' },
|
|
16
|
+
cursor: { dir: '.cursor', localOnly: false, commandPrefix: 'wf-' },
|
|
17
|
+
codex: { dir: '.codex', localOnly: false, commandPrefix: 'wf-' },
|
|
12
18
|
};
|
|
13
19
|
// commandPrefix = 该宿主的命令层薄适配文件名(= 宿主命令名)统一前缀,2026-09-28 opencode-cmd-wf-prefix
|
|
14
20
|
// 与 trae 对齐后单源:命令名不再裸占宿主顶层命名空间(撞宿主内置/第三方命令无从规避);缺该字段的宿主
|
|
@@ -33,7 +39,7 @@ export const STACKS = {
|
|
|
33
39
|
build: 'npm run build',
|
|
34
40
|
test: 'npm test',
|
|
35
41
|
typecheck: 'npx tsc --noEmit(或 npm run typecheck)',
|
|
36
|
-
builds: [{ name: 'build', command: 'npm run build', ext: ['.ts', '.tsx', '.vue', '.js', '.jsx', '.mjs', '.cjs'] }],
|
|
42
|
+
builds: [{ name: 'build', command: 'npm run build', ext: ['.ts', '.tsx', '.vue', '.js', '.jsx', '.mjs', '.cjs'], when: ['pkg:scripts.build'] }],
|
|
37
43
|
checks: [
|
|
38
44
|
{ name: 'typecheck', command: 'npx tsc --noEmit', ext: ['.ts', '.tsx'], when: ['tsconfig.json'] },
|
|
39
45
|
{ name: 'lint', command: 'npx eslint .', ext: ['.ts', '.tsx', '.js', '.jsx', '.vue'], when: ESLINT_CONFIGS },
|
|
@@ -44,7 +50,7 @@ export const STACKS = {
|
|
|
44
50
|
build: '(Python 无统一构建命令,按项目填)',
|
|
45
51
|
test: 'python -m pytest',
|
|
46
52
|
typecheck: '(可选:mypy)',
|
|
47
|
-
builds: [
|
|
53
|
+
builds: [],
|
|
48
54
|
checks: [{ name: 'lint', command: 'ruff check .', ext: ['.py'], when: ['ruff.toml', '.ruff.toml'] }],
|
|
49
55
|
allow: ['python -m pytest', 'pytest', 'ruff check'],
|
|
50
56
|
},
|
package/src/run-tests.mjs
CHANGED
|
@@ -12,6 +12,9 @@ const nodeSuites = [
|
|
|
12
12
|
'src/sync.test.mjs',
|
|
13
13
|
'src/init.test.mjs',
|
|
14
14
|
'src/pack.test.mjs',
|
|
15
|
+
'src/gates.test.mjs',
|
|
16
|
+
'src/fresh-init.test.mjs',
|
|
17
|
+
'src/stack-profile.test.mjs',
|
|
15
18
|
// 宿主门禁模块回归(2026-09-27 host-gates-p1:dotnet-ca BRE 豁免/fail-closed + trae 强推 token 化)
|
|
16
19
|
'src/gate-dotnet-ca.test.mjs',
|
|
17
20
|
'src/trae-hooks.test.mjs',
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// 栈提交门契约:node 的构建只在 package.json scripts.build 为非空字符串时跑;
|
|
3
|
+
// python 的提交门不再带 pytest;flow-kit --help 的门禁列表等于 modules/gates 目录。
|
|
4
|
+
// 集成段用临时 git 仓跑真实 init,再对装出的 commit-check.cjs 暂存一个源文件。
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { HELP } from './cli.mjs';
|
|
11
|
+
import { commitCheckConfig, settingsJson } from './profiles.mjs';
|
|
12
|
+
|
|
13
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
14
|
+
const git = process.platform === 'win32' ? 'git.exe' : 'git';
|
|
15
|
+
let pass = 0;
|
|
16
|
+
let fail = 0;
|
|
17
|
+
const check = (name, cond, detail = '') => {
|
|
18
|
+
if (cond) { pass++; console.log(`PASS ${name}`); }
|
|
19
|
+
else { fail++; console.log(`FAIL ${name}${detail ? `\n ${detail}` : ''}`); }
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const gates = fs.readdirSync(path.join(ROOT, 'modules', 'gates'), { withFileTypes: true })
|
|
23
|
+
.filter((d) => d.isDirectory())
|
|
24
|
+
.map((d) => d.name)
|
|
25
|
+
.sort();
|
|
26
|
+
const gateList = gates.join(' | ');
|
|
27
|
+
check('help 当前列表等于 modules/gates 目录', HELP.includes(`当前:${gateList}`), gateList);
|
|
28
|
+
check('help 含四套已发布门禁',
|
|
29
|
+
['dotnet-ca', 'generated-readonly', 'node-layer', 'py-import'].every((n) => HELP.includes(n)));
|
|
30
|
+
check('help 不再把当前门禁写成只有 dotnet-ca',
|
|
31
|
+
!HELP.includes('当前:dotnet-ca;') && !HELP.includes('当前:dotnet-ca)'));
|
|
32
|
+
|
|
33
|
+
const nodeCfg = JSON.parse(commitCheckConfig('node'));
|
|
34
|
+
check('node 构建命令带 pkg:scripts.build',
|
|
35
|
+
nodeCfg.builds.some((b) => b.command === 'npm run build' && Array.isArray(b.when) && b.when.includes('pkg:scripts.build')));
|
|
36
|
+
const pyText = commitCheckConfig('python');
|
|
37
|
+
const pyCfg = JSON.parse(pyText);
|
|
38
|
+
check('python builds 为空', Array.isArray(pyCfg.builds) && pyCfg.builds.length === 0, pyText);
|
|
39
|
+
check('python 提交门配置不含 pytest', !pyText.includes('pytest'), pyText);
|
|
40
|
+
check('python 质量检查仍是 ruff',
|
|
41
|
+
pyCfg.checks.some((c) => String(c.command).includes('ruff') && (c.when || []).includes('ruff.toml')));
|
|
42
|
+
check('python 的 test 阶段权限仍允许 pytest', settingsJson('python').includes('pytest'));
|
|
43
|
+
|
|
44
|
+
const dirs = [];
|
|
45
|
+
function initStack(stack, port) {
|
|
46
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), `fk-stack-${stack}-`));
|
|
47
|
+
dirs.push(dir);
|
|
48
|
+
spawnSync(git, ['init', '-q'], { cwd: dir });
|
|
49
|
+
const r = spawnSync(process.execPath, [
|
|
50
|
+
path.join(ROOT, 'bin', 'flow-kit.mjs'), 'init',
|
|
51
|
+
'--stack', stack, '--hosts', 'claude', '--board-port', String(port), '--dir', dir,
|
|
52
|
+
], { encoding: 'utf8', timeout: 180000 });
|
|
53
|
+
return { dir, status: r.status, out: `${r.stdout || ''}${r.stderr || ''}` };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function runCommitCheck(dir, rel, content) {
|
|
57
|
+
fs.writeFileSync(path.join(dir, rel), content);
|
|
58
|
+
spawnSync(git, ['add', '--', rel], { cwd: dir });
|
|
59
|
+
const r = spawnSync(process.execPath, [path.join(dir, '.agents', 'hooks', 'commit-check.cjs')], {
|
|
60
|
+
cwd: dir, encoding: 'utf8', timeout: 60000,
|
|
61
|
+
});
|
|
62
|
+
return { status: r.status, out: `${r.stdout || ''}${r.stderr || ''}` };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const nodeInit = initStack('node', 8942);
|
|
66
|
+
check('init --stack node 退出 0', nodeInit.status === 0, `exit=${nodeInit.status}\n${nodeInit.out.split('\n').slice(-25).join('\n')}`);
|
|
67
|
+
if (nodeInit.status === 0) {
|
|
68
|
+
const installed = fs.readFileSync(path.join(nodeInit.dir, '.agents', 'hooks', 'commit-check.config.json'), 'utf8');
|
|
69
|
+
check('装出的 node 配置含 pkg:scripts.build', installed.includes('pkg:scripts.build'), installed);
|
|
70
|
+
const hook = runCommitCheck(nodeInit.dir, 'a.js', 'console.log(1)\n');
|
|
71
|
+
check('无 scripts.build 时暂存 js:SKIP 且退出 0',
|
|
72
|
+
hook.status === 0 && hook.out.includes('SKIP') && hook.out.includes('pkg:scripts.build') && !hook.out.includes('BLOCK:'),
|
|
73
|
+
hook.out);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const pyInit = initStack('python', 8943);
|
|
77
|
+
check('init --stack python 退出 0', pyInit.status === 0, `exit=${pyInit.status}\n${pyInit.out.split('\n').slice(-25).join('\n')}`);
|
|
78
|
+
if (pyInit.status === 0) {
|
|
79
|
+
const installed = fs.readFileSync(path.join(pyInit.dir, '.agents', 'hooks', 'commit-check.config.json'), 'utf8');
|
|
80
|
+
check('装出的 python 配置不含 pytest', !installed.includes('pytest'), installed);
|
|
81
|
+
const hook = runCommitCheck(pyInit.dir, 'a.py', 'x = 1\n');
|
|
82
|
+
check('暂存 py:不跑 pytest 且退出 0',
|
|
83
|
+
hook.status === 0 && !hook.out.includes('pytest') && hook.out.includes('COMMIT_CHECK_OK'),
|
|
84
|
+
hook.out);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (const dir of dirs) fs.rmSync(dir, { recursive: true, force: true });
|
|
88
|
+
console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
|
|
89
|
+
process.exit(fail ? 1 : 0);
|
package/src/sync-hosts.test.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import path from 'node:path';
|
|
|
10
10
|
import os from 'node:os';
|
|
11
11
|
import { spawnSync } from 'node:child_process';
|
|
12
12
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
13
|
+
import { diffHosts } from './sync-hosts.mjs';
|
|
13
14
|
|
|
14
15
|
const SRC_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)));
|
|
15
16
|
|
|
@@ -49,6 +50,15 @@ function mkFixture() {
|
|
|
49
50
|
`---\nname: implementer\ndescription: opencode 英文风格\n---\n\n# Implementer · v1\n\n权威源正文 implementer v1\n`);
|
|
50
51
|
W(path.join(root, 'modules/hosts/trae/agents/implementer.md'),
|
|
51
52
|
`---\nname: implementer\ndescription: trae 英文风格\n---\n\n# Implementer · v1\n\n权威源正文 implementer v1\n`);
|
|
53
|
+
for (const h of ['claude', 'cursor', 'codex']) {
|
|
54
|
+
for (const cmd of ['build', 'test']) {
|
|
55
|
+
const title = cmd === 'build' ? 'Build' : 'Test';
|
|
56
|
+
W(path.join(root, 'modules/hosts', h, 'commands', `wf-${cmd}.md`),
|
|
57
|
+
`---\ndescription: ${title} · ${h}\n---\n\n# ${title} · v1\n\n权威源正文 ${cmd} v1\n`);
|
|
58
|
+
}
|
|
59
|
+
W(path.join(root, 'modules/hosts', h, 'agents/implementer.md'),
|
|
60
|
+
`---\nname: implementer\ndescription: ${h}\n---\n\n# Implementer · v1\n\n权威源正文 implementer v1\n`);
|
|
61
|
+
}
|
|
52
62
|
// 孤儿薄适配:权威源无对应文件(两种形态——带 wf- 前缀与不带前缀)
|
|
53
63
|
W(path.join(root, 'modules/hosts/opencode/commands/orphan.md'), '# orphan\n');
|
|
54
64
|
W(path.join(root, 'modules/hosts/opencode/commands/wf-legacy.md'), '# legacy\n');
|
|
@@ -70,7 +80,7 @@ syncHosts(process.argv.slice(2), root);
|
|
|
70
80
|
const fx = mkFixture();
|
|
71
81
|
const r = runSyncHosts(fx, ['--diff']);
|
|
72
82
|
const out = r.stdout + r.stderr;
|
|
73
|
-
check('S1 全对齐:正文对齐
|
|
83
|
+
check('S1 全对齐:正文对齐 17 对(2 commands×5 适配 + 1 role×7 宿主)', /正文对齐:\s*17\s*对/.test(out), out);
|
|
74
84
|
check('S1 全对齐:报告无漂移', /无漂移/.test(out), out);
|
|
75
85
|
check('S1 全对齐:exit 0', r.status === 0, `status=${r.status}`);
|
|
76
86
|
}
|
|
@@ -84,10 +94,10 @@ syncHosts(process.argv.slice(2), root);
|
|
|
84
94
|
`---\ndescription: Test 阶段\nstage: Test\n---\n\n# Test · v2\n\n权威源正文 test v2\n`);
|
|
85
95
|
const r = runSyncHosts(fx, ['--diff']);
|
|
86
96
|
const out = r.stdout + r.stderr;
|
|
87
|
-
check('S2 权威源改了 → 正文漂移
|
|
97
|
+
check('S2 权威源改了 → 正文漂移 10(build×5 + test×5)', /正文段漂移(10/.test(out), out);
|
|
88
98
|
check('S2 列出 opencode/commands/wf-build.md(opencode wf- 前缀映射)', out.includes('commands/build.md → opencode/commands/wf-build.md'), out);
|
|
89
99
|
check('S2 列出 trae/commands/wf-build.md(trae wf- 前缀映射)', out.includes('commands/build.md → trae/commands/wf-build.md'), out);
|
|
90
|
-
check('S2 正文对齐 =
|
|
100
|
+
check('S2 正文对齐 = 7(1 role × 7 宿主 implementer 未改)', /正文对齐:\s*7\s*对/.test(out), out);
|
|
91
101
|
check('S2 exit 0(diff 模式不阻断)', r.status === 0);
|
|
92
102
|
}
|
|
93
103
|
|
|
@@ -98,7 +108,7 @@ syncHosts(process.argv.slice(2), root);
|
|
|
98
108
|
`---\ndescription: Build 阶段\nstage: Build\n---\n\n# Build · v2\n\n权威源正文 build v2\n`);
|
|
99
109
|
const r = runSyncHosts(fx, ['--apply']);
|
|
100
110
|
const out = r.stdout + r.stderr;
|
|
101
|
-
check('S3 apply:报告同步
|
|
111
|
+
check('S3 apply:报告同步 5 份(带 commandPrefix 的宿主)', /按权威源正文覆盖薄适配正文段\s*5\s*份/.test(out), out);
|
|
102
112
|
check('S3 apply:opencode 正文已同步到 v2', R(path.join(fx, 'modules/hosts/opencode/commands/wf-build.md')).includes('权威源正文 build v2'));
|
|
103
113
|
check('S3 apply:trae 正文已同步到 v2', R(path.join(fx, 'modules/hosts/trae/commands/wf-build.md')).includes('权威源正文 build v2'));
|
|
104
114
|
check('S3 apply:opencode frontmatter 保留(仍无 name 字段——前缀由文件名承载)', !R(path.join(fx, 'modules/hosts/opencode/commands/wf-build.md')).match(/^name:/m));
|
|
@@ -115,7 +125,7 @@ syncHosts(process.argv.slice(2), root);
|
|
|
115
125
|
`---\nname: wf-build\ndescription: trae 自定义描述\n---\n\n# Build · v1\n\n权威源正文 build v1\n`);
|
|
116
126
|
const r = runSyncHosts(fx, ['--diff']);
|
|
117
127
|
const out = r.stdout + r.stderr;
|
|
118
|
-
check('S4 frontmatter 差异不计正文段漂移', /正文对齐:\s*
|
|
128
|
+
check('S4 frontmatter 差异不计正文段漂移', /正文对齐:\s*17\s*对/.test(out) && /无漂移/.test(out), out);
|
|
119
129
|
}
|
|
120
130
|
|
|
121
131
|
// ============ 场景 5:薄适配正文手改 → drift 列出,apply 覆盖(不污染权威源)============
|
|
@@ -153,7 +163,7 @@ syncHosts(process.argv.slice(2), root);
|
|
|
153
163
|
let parsed = null;
|
|
154
164
|
try { parsed = JSON.parse(out); } catch {}
|
|
155
165
|
check('S7 --json 输出可解析', parsed && typeof parsed === 'object' && Array.isArray(parsed.drift));
|
|
156
|
-
check('S7 --json 含 inSync =
|
|
166
|
+
check('S7 --json 含 inSync = 17', parsed && parsed.inSync === 17, parsed ? JSON.stringify({ inSync: parsed && parsed.inSync, missing: parsed && parsed.authorityMissing }) : 'null');
|
|
157
167
|
}
|
|
158
168
|
|
|
159
169
|
// ============ 场景 8:孤儿薄适配(权威源无对应文件)→ adapterOrphans 列,apply 不删 ============
|
|
@@ -169,5 +179,15 @@ syncHosts(process.argv.slice(2), root);
|
|
|
169
179
|
check('S8 apply 不删孤儿薄适配', fs.existsSync(path.join(fx, 'modules/hosts/opencode/commands/orphan.md')) && fs.existsSync(path.join(fx, 'modules/hosts/opencode/commands/wf-legacy.md')));
|
|
170
180
|
}
|
|
171
181
|
|
|
182
|
+
{
|
|
183
|
+
const pkg = path.resolve(SRC_ROOT, '..');
|
|
184
|
+
const r = diffHosts({
|
|
185
|
+
authorityRoot: path.join(pkg, 'templates', '_agents'),
|
|
186
|
+
adaptersRoot: path.join(pkg, 'modules', 'hosts'),
|
|
187
|
+
});
|
|
188
|
+
const sample = r.authorityMissing.slice(0, 3).map((x) => x.adapterRel).concat(r.drift.slice(0, 3).map((x) => x.adapterRel)).join('、');
|
|
189
|
+
check('实仓薄适配无缺失无正文漂移', r.authorityMissing.length === 0 && r.drift.length === 0, `missing=${r.authorityMissing.length} drift=${r.drift.length} ${sample}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
172
192
|
console.log(`\n合计: PASS ${pass} / FAIL ${failCount}`);
|
|
173
193
|
process.exit(failCount ? 1 : 0);
|
package/templates/AGENTS.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
1. **新需求 / 功能**:先立 `workflow/intents/` intent(复制 `_TEMPLATE.md`),对话内与用户确认后开工。
|
|
13
13
|
2. **修复类任务**:先查 `workflow/incidents/` 同类历史;L1 以上立 incident(修复中即 intent 等价入口,L2/L3 与 spec/plan 同名配对)。
|
|
14
|
-
3.
|
|
14
|
+
3. **级别判断与风险泳道**(就高不就低):L0 例行;L1 实现级;L2 规则/契约;L3 数据与运行时结构。L0/L1 协作道(轻确认+异步审计);L2/L3 防御道(同步确认门:spec/plan 确认后方可动手,L3 加独立复核)。见 `.agents/commands/new-task.md`。
|
|
15
15
|
|
|
16
16
|
### 阶段路由
|
|
17
17
|
|
|
@@ -23,11 +23,11 @@
|
|
|
23
23
|
|
|
24
24
|
### 门禁与提交
|
|
25
25
|
|
|
26
|
-
- `.githooks/`(经 `core.hooksPath` 挂载,本地 clone 后执行 `git config core.hooksPath .githooks`):pre-commit(闭环配对 / wiki 台账 / 规则面预算 / 双源一致性 / managed 台账快检 / 敏感信息扫描与条件构建)、pre-push(闭环断档扫描)、commit-msg、post-commit、pre-merge-commit。
|
|
26
|
+
- `.githooks/`(经 `core.hooksPath` 挂载,本地 clone 后执行 `git config core.hooksPath .githooks`):pre-commit(闭环配对 / 泳道完整性 / wiki 台账 / 规则面预算 / 双源一致性 / managed 台账快检 / 敏感信息扫描与条件构建)、pre-push(闭环断档扫描)、commit-msg、post-commit、pre-merge-commit。
|
|
27
27
|
- **`git commit` / `git merge` / `git push` 三处一律禁 `--no-verify`**——被拦说明产出不合规,按提示修完原路重试。
|
|
28
|
-
- 项目专属门禁挂 `.agents/hooks/local-pre-commit
|
|
28
|
+
- 项目专属门禁挂 `.agents/hooks/local-pre-commit`。装户五条硬规则(配对、验收、确认留痕、敏感信息、双源台账)与 `audit` 档见 `workflow/README.md`「硬规则」。
|
|
29
29
|
- 提交遵循 Conventional Commits 中文(feat / fix / docs / style / refactor / perf);L1+ 入口文档 / spec / plan 随代码同一提交;确认(approved)后立即 `docs(*)` 单独提交留痕。
|
|
30
|
-
- **确认门(2026-09-27 起)**:intent/spec/plan 的 approved/done 与 incident 的 fixed/closed
|
|
30
|
+
- **确认门(2026-09-27 起)**:intent/spec/plan 的 approved/done 与 incident 的 fixed/closed(两跳、无单跳)唯一入口 = `node .agents/scripts/confirm-doc.mjs <path>`,放弃态走显式 `--to superseded|cancelled`(合法前态见脚本提示;四终态同样内容绑定),两形态:① 用户终端亲手运行键入「可以」(TTY,AI 会话内被拒);② **对话委托代录**——用户在对话内明确确认后,AI 跑 `confirm-doc.mjs <path> --delegated "<用户原话>"` 逐件代录(默认一次一份,多份并录被拒),台账如实记 `source: chat-delegated` + 原话供对质,永不伪装 TTY 行(check-loop 15 指纹+台账对账拦截,与 source 无关);L0/L1 协作道可 `--batch` 多份一次代录(台账 `brief:true`,15 并录告警豁免;L2/L3 仍逐份)。
|
|
31
31
|
|
|
32
32
|
### 检索、看板与量化
|
|
33
33
|
|
|
@@ -232,7 +232,7 @@ const badge = (c) =>
|
|
|
232
232
|
acceptBadge(c) + alertBadge(c) + ageBadge(c) +
|
|
233
233
|
`<span class="date">${esc(c.date)}</span>`;
|
|
234
234
|
|
|
235
|
-
// —— 任务族聚合:同 slug
|
|
235
|
+
// —— 任务族聚合:同 slug 一卡(只用于展示;告警配对在服务端按完整文件名)。head 取入口文档(intent 优先、其次 incident),孤儿用首文档 ——
|
|
236
236
|
const TYPE_ORDER = { intents: 0, incidents: 1, plans: 2, specs: 3 };
|
|
237
237
|
const TYPE_ABBR = { intents: 'IN', incidents: 'IC', plans: 'PL', specs: 'SP' };
|
|
238
238
|
const EXPANDED = new Set(); // 展开中的任务 slug(SSE 刷新后保持)
|
|
@@ -319,7 +319,7 @@ async function refreshBoard() {
|
|
|
319
319
|
`<div class="doc-item" data-file="${esc(d.file)}" title="打开文档">` +
|
|
320
320
|
`<span class="type" style="background:${TYPE_COLOR[d.type]}">${TYPE_LABEL[d.type]}</span>` +
|
|
321
321
|
`<span class="chip">${esc(d.status)}</span>${alertBadge(d)}` +
|
|
322
|
-
`<span class="doc-title">${esc(d.title)}</span></div>`).join('')}</div></div>`;
|
|
322
|
+
`<span class="doc-title">${esc(d.date)} · ${esc(d.title)}</span></div>`).join('')}</div></div>`;
|
|
323
323
|
};
|
|
324
324
|
let html = COLS.map((c) => {
|
|
325
325
|
const items = groups[c.key].map((fam) => cardHtml(fam)).join('');
|
|
@@ -342,7 +342,7 @@ function renderDrawer() {
|
|
|
342
342
|
$('#d-title').textContent = CURRENT.title;
|
|
343
343
|
const related = CARDS.filter((c) => c.slug === CURRENT.slug && c.file !== CURRENT.file);
|
|
344
344
|
$('#d-related').innerHTML = related.map((c) =>
|
|
345
|
-
`<a data-file="${esc(c.file)}">${TYPE_LABEL[c.type]}${c.status !== CURRENT.status ? ' · ' + esc(c.status) : ''}</a>`).join('');
|
|
345
|
+
`<a data-file="${esc(c.file)}">${TYPE_LABEL[c.type]} · ${esc(c.date)}${c.status !== CURRENT.status ? ' · ' + esc(c.status) : ''}</a>`).join('');
|
|
346
346
|
document.querySelectorAll('#d-tabs button').forEach((b) => b.classList.toggle('on', b.dataset.tab === TAB));
|
|
347
347
|
if (TAB === 'doc') loadDoc(); else loadHist();
|
|
348
348
|
}
|
|
@@ -25,14 +25,13 @@ next: .agents/commands/test.md
|
|
|
25
25
|
|
|
26
26
|
### 起草 plan
|
|
27
27
|
|
|
28
|
-
1. 确认同名入口文档(approved intent 或已确认的 incident)+ spec(若 L2/L3
|
|
28
|
+
1. 确认同名入口文档(approved intent 或已确认的 incident)+ spec(若 L2/L3)均已确认;否则回上一阶段(fill/confirm 未过即拒)
|
|
29
29
|
2. 起草 plan:
|
|
30
30
|
- 复制 `workflow/plans/_TEMPLATE.md` → `workflow/plans/YYYY-MM-DD-<主题>.md`(与入口文档同名)
|
|
31
31
|
- L2/L3 按 4 节填写:任务拆解(每个任务有判据) / 风险评估 / 执行顺序 / 遗留项
|
|
32
|
-
- L1
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
**确认落态唯一入口**(2026-09-27 起):用户在终端跑 `node .agents/scripts/confirm-doc.mjs <path>` 键入「可以」(draft→approved / approved→done;对话内明确确认后 AI 可 `--delegated "<原话>"` 代录,台账如实记 source/quote——**逐件调用 + 逐件原话**(一次一份,build.md「逐件确认」口径;多份并录被 confirm-doc 拒绝。--delegated 每次传当次实际放行的措辞,不复用同句——复用同句使台账 quote 失去分辨力,且相邻同 quote 连跑会触发并录告警))。check-loop 15 如实口径:缺记录/指纹不配对 → hard-block 拦截;伪造台账本地不可机器防,留痕供事后对质;done 内容绑定(2026-09-28 起)——关单编辑(勾验/回填)先于 done 确认、confirm-doc 是最后一次写入,此后修订走 superseded 或新 intent。
|
|
32
|
+
- L1 使用 Quick-Plan 极简三节:改动方案 / 约束与风险(原 Spec 核心) / 验证计划;多文件多步骤时可补任务拆解/执行顺序
|
|
33
|
+
3. 停下,输出 plan 草稿全文给用户过目,一句"可以"即确认。**逐件确认**:入口文档 / spec / plan 各自的确认点不得并作一次,plan 草稿须全文过目(不接受摘要代替)。**逐件配逐件原话**:`--delegated` 每次传当次实际放行的措辞,不复用同句——复用同句使台账 quote 失去分辨力(事后对质看不出放行的是哪一件);原因与根治见 workflow/papercuts.md 2026-09-28
|
|
34
|
+
**确认落态唯一入口**(2026-09-27 起):用户在终端跑 `node .agents/scripts/confirm-doc.mjs <path>` 键入「可以」(draft→approved / approved→done;对话内明确确认后 AI 可 `--delegated "<原话>"` 代录,台账如实记 source/quote——**逐件调用 + 逐件原话**(一次一份,build.md「逐件确认」口径;多份并录被 confirm-doc 拒绝。--delegated 每次传当次实际放行的措辞,不复用同句——复用同句使台账 quote 失去分辨力))。check-loop 15 如实口径:缺记录/指纹不配对 → hard-block 拦截;伪造台账本地不可机器防,留痕供事后对质;done 内容绑定(2026-09-28 起)——关单编辑(勾验/回填)先于 done 确认、confirm-doc 是最后一次写入,此后修订走 superseded 或新 intent。
|
|
36
35
|
4. 确认后 → plan 状态 draft → approved,并在 plan 内补「确认与复核」节(确认结果 + 日期)。确认环节须在文档留痕:`approved` 是确认的机器可见态,`done` 只在关单出现,禁从 `draft` 直跳 `done`;确认后立即 `docs(workflow)` 单独提交留痕(根 `AGENTS.md` 提交约定;check-loop 检查 14 口径)
|
|
37
36
|
|
|
38
37
|
### 进计划模式列改动
|
|
@@ -18,13 +18,13 @@ next: .agents/commands/build.md
|
|
|
18
18
|
|
|
19
19
|
# Design · 起 spec
|
|
20
20
|
|
|
21
|
-
> L2/L3 强制;L1
|
|
21
|
+
> L2/L3 强制;L1 走快车道(直接跳 `next: build.md` 起草 Quick-Plan)。
|
|
22
22
|
|
|
23
23
|
## 执行
|
|
24
24
|
|
|
25
|
-
1. 确认同名入口文档已批准:新需求为 approved intent,修复为已确认的 incident 草稿;否则拒绝,分别回 `plan.md` / `maintain.md
|
|
25
|
+
1. 确认同名入口文档已批准:新需求为 approved intent,修复为已确认的 incident 草稿;否则拒绝,分别回 `plan.md` / `maintain.md`(2026-09-30 起机器强制:fill-spec / confirm-doc 对前置未过直接拒绝 exit 2、不落盘不记账)
|
|
26
26
|
2. 级别判断(对照 AGENTS.md 第 3 条):
|
|
27
|
-
- **L1**(实现级改动,未命中 L2/L3)→
|
|
27
|
+
- **L1**(实现级改动,未命中 L2/L3)→ 走快车道,省略 spec,直接跳 `next: build.md` 起草 Quick-Plan
|
|
28
28
|
- **L2**(改规则 / 契约:编码权威 / 共享契约 / 既有接口语义 / 全局横切口径,闭集见 `new-task.md` §级别判断)→ 必须有 spec
|
|
29
29
|
- **L3**(数据与运行时结构:schema / 迁移 SQL / DI 链 / 认证与中间件管线)→ 必须有 spec + **新会话独立复核**
|
|
30
30
|
3. 起草 spec:
|
|
@@ -51,6 +51,6 @@ next: .agents/commands/build.md
|
|
|
51
51
|
## 确认后
|
|
52
52
|
|
|
53
53
|
- spec 状态 draft → approved
|
|
54
|
-
**确认落态唯一入口**(2026-09-27 起):用户在终端跑 `node .agents/scripts/confirm-doc.mjs <path>` 键入「可以」(draft→approved / approved→done;对话内明确确认后 AI 可 `--delegated "<原话>"` 代录,台账如实记 source/quote——**逐件调用 + 逐件原话**(一次一份,build.md「逐件确认」口径;多份并录被 confirm-doc 拒绝。--delegated 每次传当次实际放行的措辞,不复用同句——复用同句使台账 quote
|
|
54
|
+
**确认落态唯一入口**(2026-09-27 起):用户在终端跑 `node .agents/scripts/confirm-doc.mjs <path>` 键入「可以」(draft→approved / approved→done;对话内明确确认后 AI 可 `--delegated "<原话>"` 代录,台账如实记 source/quote——**逐件调用 + 逐件原话**(一次一份,build.md「逐件确认」口径;多份并录被 confirm-doc 拒绝。--delegated 每次传当次实际放行的措辞,不复用同句——复用同句使台账 quote 失去分辨力))。check-loop 15 如实口径:缺记录/指纹不配对 → hard-block 拦截;伪造台账本地不可机器防,留痕供事后对质;done 内容绑定(2026-09-28 起)——关单编辑(勾验/回填)先于 done 确认、confirm-doc 是最后一次写入,此后修订走 superseded 或新 intent。
|
|
55
55
|
- 立即 `docs(workflow)` 单独提交留痕(根 `AGENTS.md` 提交约定;check-loop 检查 14 口径)
|
|
56
56
|
- 进 `next: .agents/commands/build.md`
|
|
@@ -64,7 +64,7 @@ next: .agents/commands/plan.md(若复盘需要新 intent)
|
|
|
64
64
|
|
|
65
65
|
### 3. 起草后停下
|
|
66
66
|
|
|
67
|
-
输出草稿全文给用户过目;确认后才执行三件套落地。**确认点不合并**:incident 草稿、spec、plan 各自过目确认(plan 的两道门见 `build.md`);确认后立即留痕(spec/plan 状态 `approved` + 「确认与复核」节,incident 时间线补「用户确认」条目),`done`
|
|
67
|
+
输出草稿全文给用户过目;确认后才执行三件套落地。**确认点不合并**:incident 草稿、spec、plan 各自过目确认(plan 的两道门见 `build.md`);确认后立即留痕(spec/plan 状态 `approved` + 「确认与复核」节,incident 时间线补「用户确认」条目),`done` 只在关单出现。**L2/L3 机器前置**:起草 spec 时 fill-spec / confirm-doc 校验「时间线含『用户确认』留痕」,未过即拒(exit 2)——被拒回本流程补留痕。
|
|
68
68
|
|
|
69
69
|
### 4. 确认后落地
|
|
70
70
|
|
|
@@ -26,11 +26,22 @@ next: 按阶段路由
|
|
|
26
26
|
| **Maintain** | [.agents/commands/maintain.md](.agents/commands/maintain.md) | "线上出事了" / "记一笔 incident" | `implementer`(方案确认后) | 三件套要点确认 |
|
|
27
27
|
| **Review**(横切,不占 6 阶段) | [.agents/commands/review.md](.agents/commands/review.md) | "评审一下" / "过 review 标准" | `independent-reviewer`(L2/L3 必须独立复核) | 问题定性与合入时机确认 |
|
|
28
28
|
|
|
29
|
+
## 风险定级(第 0 步,先定级再路由)
|
|
30
|
+
|
|
31
|
+
动工前先按下表定风险级 L0-L3(就高不就低;判据细则见下节「级别判断」),级别即风险级,决定走哪条泳道——确认放事前,还是审计补事后:
|
|
32
|
+
|
|
33
|
+
| 泳道 | 级别 | 确认与审计口径 |
|
|
34
|
+
|------|------|---------------|
|
|
35
|
+
| **协作道**(敏捷优先,审计异步补) | L0 例行 / L1 实现级 | L0 直接 commit,豁免 intent;L1 快车道(intent 一句话确认 + 极简 plan)即动工。独立复核不设同步前置,check-loop 与事后 review 兜底;确认/代录可 `--batch` 批量(仅 L0/L1) |
|
|
36
|
+
| **防御道**(严谨优先,确认同步拦) | L2 规则/契约 / L3 数据与运行时结构 | 同步确认门:spec 确认通过方可起草 plan,plan 确认后方可动手;L3 加新会话独立复核,未确认不放行 |
|
|
37
|
+
|
|
38
|
+
> 定级结果落 intent frontmatter `risk_level`(=级别,`fill-intent.mjs` 自动写入,机器选道用);协作道勾触达红线会被 check-loop 判「红线判低」hard 拦(就高升级 L2/L3 并补同名 spec)。
|
|
39
|
+
|
|
29
40
|
## 路由判断
|
|
30
41
|
|
|
31
42
|
- **新需求 / 功能**:从 `plan.md` 进,立 **intent**(`workflow/intents/`)
|
|
32
43
|
- **修复类任务 / 事故**:从 `maintain.md` 进,立 **incident**(`workflow/incidents/`);incident 在 L1+ 即 **intent 等价物**(check-loop 认 incident≡intent),不必另立 intent。先检索同类历史(`node .agents/scripts/kb-search.mjs "<关键词>" --scope workflow --type incidents,plans`),有同类先读其历史三件套;再按级别走:
|
|
33
|
-
|
|
44
|
+
- L0 → 直接修复;L1 → 立 incident 后进 `build.md` 起草并确认同名 Quick-Plan → `test.md`
|
|
34
45
|
- L2/L3 → 立 incident 后进 `design.md` 起草并确认同名 spec,再进 `build.md` 起草并确认同名 plan → `test.md`
|
|
35
46
|
- 仅当根因属系统性 / 门禁缺位(maintain.md 三件套「是否需要新 intent」选"是")才另立 `workflow/intents/` 同名 intent
|
|
36
47
|
- **改完代码要验证**:直接进 `test.md`(验证通过即关单;L1 到此结束)
|
|
@@ -42,7 +53,7 @@ next: 按阶段路由
|
|
|
42
53
|
| 级别 | 改什么 | 流程 |
|
|
43
54
|
|------|--------|------|
|
|
44
55
|
| L0 | 文档/样式微调(无行为影响) | 直接 commit,豁免 intent |
|
|
45
|
-
| L1 | **实现级改动**(未命中 L2/L3) |
|
|
56
|
+
| L1 | **实现级改动**(未命中 L2/L3) | 快车道:液态草稿(.zcode/drafts) $\rightarrow$ 实现 $\rightarrow$ 一键固化(`solidify-task.mjs`) |
|
|
46
57
|
| L2 | **规则 / 契约**(一处改、多处依赖) | 入口文档(intent / incident) + spec + plan 三件套 |
|
|
47
58
|
| L3 | **数据与运行时结构**(schema / 迁移 SQL / DI 链 / 认证与中间件管线) | 同 L2 + spec 新会话独立复核 |
|
|
48
59
|
|
|
@@ -7,7 +7,7 @@ triggers:
|
|
|
7
7
|
- "新需求 <主题>"
|
|
8
8
|
- "要做个 <功能>"
|
|
9
9
|
approval_required: true
|
|
10
|
-
next: L1 → .agents/commands/build.md ;L2/L3 → .agents/commands/design.md
|
|
10
|
+
next: L1 → .agents/commands/build.md (起草 Quick-Plan) ;L2/L3 → .agents/commands/design.md
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
# Plan · 立 intent
|
|
@@ -21,7 +21,7 @@ next: L1 → .agents/commands/build.md ;L2/L3 → .agents/commands/design.md
|
|
|
21
21
|
1. 若 `workflow/intents/` 已有目标 intent 且状态为 approved → 按级别跳 next(L1 → `build.md`,L2/L3 → `design.md`)
|
|
22
22
|
2. 否则起草新 intent:
|
|
23
23
|
- **先检索同类**:`node .agents/scripts/kb-search.mjs "<关键词>" --scope workflow --type intents,specs`——命中同类先读其结论与验收,新 intent 引用(防重复立项 / 重复踩坑)
|
|
24
|
-
- **填空工具先跑**:`node .agents/scripts/fill-intent.mjs --module <模块> --level <L0|L1|L2|L3> --topic <主题> --output workflow/intents/<date>-<主题>.md --notes "<备注>"`——输出含 frontmatter
|
|
24
|
+
- **填空工具先跑**:`node .agents/scripts/fill-intent.mjs --module <模块> --level <L0|L1|L2|L3> --topic <主题> --output workflow/intents/<date>-<主题>.md --notes "<备注>"`——输出含 frontmatter 6 字段(含 risk_level)+ 7 节正文(背景与问题/目标/非目标/约束/影响面/触达红线/验收标准)的草稿;AI 据此填实,模板与节标题机器保证不出错
|
|
25
25
|
- 复制 `workflow/intents/_TEMPLATE.md` → `workflow/intents/YYYY-MM-DD-<主题>.md`
|
|
26
26
|
- frontmatter 填 `模块:`(词表见 `.agents/workflow-modules.txt`,新建文档必填,check-loop 会警告)
|
|
27
27
|
- 按 6 节填写:背景与问题 / 目标(可验证) / 非目标 / 约束 / 影响面 / 触达红线(L1 微改动可省非目标/约束两节,不硬填)
|
|
@@ -29,7 +29,7 @@ next: L1 → .agents/commands/build.md ;L2/L3 → .agents/commands/design.md
|
|
|
29
29
|
3. 写不出可测试判据 = 还没想清楚 → 反问澄清,不要硬填
|
|
30
30
|
4. 起草后停下,输出草稿全文给用户过目,一句"可以"即确认
|
|
31
31
|
|
|
32
|
-
**确认落态唯一入口**(2026-09-27 起):用户在终端跑 `node .agents/scripts/confirm-doc.mjs <path>` 键入「可以」(draft→approved / approved→done;对话内明确确认后 AI 可 `--delegated "<原话>"` 代录,台账如实记 source/quote——**逐件调用 + 逐件原话**(一次一份,build.md「逐件确认」口径;多份并录被 confirm-doc 拒绝。--delegated 每次传当次实际放行的措辞,不复用同句——复用同句使台账 quote
|
|
32
|
+
**确认落态唯一入口**(2026-09-27 起):用户在终端跑 `node .agents/scripts/confirm-doc.mjs <path>` 键入「可以」(draft→approved / approved→done;对话内明确确认后 AI 可 `--delegated "<原话>"` 代录,台账如实记 source/quote——**逐件调用 + 逐件原话**(一次一份,build.md「逐件确认」口径;多份并录被 confirm-doc 拒绝。--delegated 每次传当次实际放行的措辞,不复用同句——复用同句使台账 quote 失去分辨力))。check-loop 15 如实口径:缺记录/指纹不配对 → hard-block 拦截;伪造台账本地不可机器防,留痕供事后对质;done 内容绑定(2026-09-28 起)——关单编辑(勾验/回填)先于 done 确认、confirm-doc 是最后一次写入,此后修订走 superseded 或新 intent。
|
|
33
33
|
|
|
34
34
|
## 技能辅助(可选,宿主级 skills)
|
|
35
35
|
|
|
@@ -45,5 +45,5 @@ next: L1 → .agents/commands/build.md ;L2/L3 → .agents/commands/design.md
|
|
|
45
45
|
## 确认后
|
|
46
46
|
|
|
47
47
|
- intent 状态 draft → approved
|
|
48
|
-
- 立即 `docs(workflow)` 单独提交留痕(根 `AGENTS.md` 提交约定;check-loop 检查 14 口径)
|
|
48
|
+
- 立即 `docs(workflow)` 单独提交留痕(根 `AGENTS.md` 提交约定;check-loop 检查 14 口径)——**提交最早可行点**:入口文档受 pre-commit 闭环配对(增量)门约束,同名 spec / plan 须已在工作区(先例 61ec130)
|
|
49
49
|
- L1 → `.agents/commands/build.md`;L2/L3 → `.agents/commands/design.md`
|
|
@@ -43,7 +43,7 @@ next: .agents/commands/build.md(仅 P0 / P1 未清时;清零后经用户确
|
|
|
43
43
|
- **Create/Update 校验同口径**:Create 已有校验 Update 必须同口径;被引用的标识符字段禁改
|
|
44
44
|
- **查询分页与状态语义**:分页参数范围校验 + 服务层钳制;非法状态与不存在分离(按项目统一错误口径,如 400 / 404)
|
|
45
45
|
- **spec 承诺的断言落地**:spec §测试列出的用例 / 断言须真落到测试或验证脚本;「写进 spec」不等于「跑得起来」,且常驻断言禁写成随时间增长的行数常量
|
|
46
|
-
- **跨宿主薄适配正文漂移**:改 `templates/_agents/{commands,roles}/*.md` 后必须跑 `flow-kit sync-hosts --apply`
|
|
46
|
+
- **跨宿主薄适配正文漂移**:改 `templates/_agents/{commands,roles}/*.md` 后必须跑 `flow-kit sync-hosts --apply` 单向同步注册表内全部宿主薄适配(`modules/hosts/<h>/{agents,commands}/*.md`);装户视角由 `flow-kit doctor` §6.7 检查(包源环境 §6.7 skipped)
|
|
47
47
|
|
|
48
48
|
## P2 建议(提醒,不拦合入)
|
|
49
49
|
|
|
@@ -76,7 +76,7 @@ flow-kit doctor # 10/0/0
|
|
|
76
76
|
- 不改 managed 台账跟踪逻辑(`sync.mjs` 不动)
|
|
77
77
|
- 不改 owned 漂移校验(`doctor.mjs` §6.6 不动)
|
|
78
78
|
- 不反向同步——装副本独自有文件(如 hooks/commit-check.config.json)可能是装户配置,不应被删
|
|
79
|
-
-
|
|
79
|
+
- 不检查注册表内宿主 `modules/hosts/` 薄适配漂移(那是 `flow-kit sync-hosts` 范围)
|
|
80
80
|
|
|
81
81
|
## 当前已识别的真实漂点(首次实测,2026-09-25)
|
|
82
82
|
|
|
@@ -74,10 +74,12 @@ node bin/flow-kit.mjs doctor # §6.5 delegations + §6.6 o
|
|
|
74
74
|
- 失败项已修复并复测通过
|
|
75
75
|
- **关单**(日常闭环点,不依赖是否上 prod):
|
|
76
76
|
- 关单前一键过门:`node .agents/scripts/verify.mjs`(npm test + check-loop 固定编排,任一失败非零退出,非绿不关单)
|
|
77
|
-
- 新需求:逐条勾验入口 intent
|
|
78
|
-
|
|
77
|
+
- 新需求:逐条勾验入口 intent「验收标准」,每条补「证据:」(例如:粘贴 CI 运行结果、UI 截图路径、或具体的 commit SHA),intent $\rightarrow$ done;同名 spec/Quick-Plan(若有)仍 approved $\rightarrow$ done。**注意:勾选但缺证据会被 check-loop 拦截(hard-block)**
|
|
78
|
+
- **approved 留痕提交**(2026-09-30 起):done 确认前确认**本次关单文档**的 approved 态已进 git 历史(越早越好——approved 后即应提交;若尚未,先补提交再 done;门为逐份校验,三件套均关单时逐份各自满足)——confirm-doc 的 done 前置门硬校验(历史未出现「状态: approved」→ 拒绝落账);done 后再提交 done 态与其余改动
|
|
79
|
+
**确认落态唯一入口**(2026-09-27 起):用户在终端跑 `node .agents/scripts/confirm-doc.mjs <path>` 键入「可以」(draft→approved / approved→done;对话内明确确认后 AI 可 `--delegated "<原话>"` 代录,台账如实记 source/quote——**逐件调用 + 逐件原话**(一次一份,build.md「逐件确认」口径;多份并录被 confirm-doc 拒绝。--delegated 每次传当次实际放行的措辞,不复用同句——复用同句使台账 quote 失去分辨力))。check-loop 15 如实口径:缺记录/指纹不配对 → hard-block 拦截;伪造台账本地不可机器防,留痕供事后对质;done 内容绑定(2026-09-28 起)——**关单编辑(勾验/回填确认结果)先于 done 确认、confirm-doc 是最后一次写入**,此后修订走 superseded 或新 intent。
|
|
79
80
|
- 修复:防复发验证已落地,incident → closed(经确认门 confirm-doc:open→fixed / fixed→closed 两跳、无单跳;2026-09-28 起 check-loop 15 对账——关单编辑先于 closed 确认,口径同 done 内容绑定)
|
|
80
81
|
- **同族收尾**:入口置终态时,同名 plan 一并置 `done`(spec 见上条),不留 `approved` 孤儿(口径同看板「入口已 done,本 plan 未终态」告警)
|
|
81
82
|
- 主智能体自做的 L1+ 新需求在 `workflow/delegations.md`「自做任务结果表」记一行(修复类不重复记)
|
|
83
|
+
- **委派快照回写**:关单后若 `workflow/delegations.md` §月度聚合快照缺当月行或 doctor §6.5 报陈旧 → 跑 `node .agents/scripts/agg-delegations.cjs` 并回贴当月快照行
|
|
82
84
|
- L1:本阶段结束,不进 deploy
|
|
83
85
|
- L2/L3:仅当用户确认要上 prod 时进 `.agents/commands/deploy.md`
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// commit-check: git commit 前置验证(agentic-flow-kit 通用版)
|
|
2
2
|
// 1) 敏感信息扫描(暂存区新增行含疑似密钥 -> BLOCK)
|
|
3
|
-
// 2) 条件构建验证(按 .agents/hooks/commit-check.config.json 的 builds 配置;--full
|
|
4
|
-
// 3) 质量检测(config.checks:lint / 类型检查 / vet 等秒级确定性检查;--full
|
|
3
|
+
// 2) 条件构建验证(按 .agents/hooks/commit-check.config.json 的 builds 配置;--full 不做 ext/prefix 裁剪,仍尊重 when)
|
|
4
|
+
// 3) 质量检测(config.checks:lint / 类型检查 / vet 等秒级确定性检查;--full 不做 ext/prefix 裁剪,仍尊重 when)
|
|
5
5
|
// config 由 flow-kit init 按技术栈生成初值,项目可自行增改;缺失或无 builds/checks 时跳过对应段。
|
|
6
6
|
// 配置示例(.agents/hooks/commit-check.config.json):
|
|
7
7
|
// {
|
|
@@ -23,8 +23,11 @@
|
|
|
23
23
|
// - 只有 ext:按扩展名全局触发(跨目录,适合语言型触发——仓库里该扩展名基本只属于该构建);
|
|
24
24
|
// - 只有 prefix:该前缀下任何文件触发(目录型触发——如 monorepo 子包,仓库其他位置的相同扩展名不触发);
|
|
25
25
|
// - 两者同配为 OR 并集而非 AND 交集——要「前缀内的特定扩展名」请只配 prefix(目录内文档变更多跑一次构建,方向安全)。
|
|
26
|
-
// checks
|
|
27
|
-
//
|
|
26
|
+
// when(builds 与 checks 共用):数组任一满足才启用,无 when 字段 = 无条件启用。
|
|
27
|
+
// - 路径或尾部 * 一层通配(如 "*.sln"):相对仓库根做存在性判断;
|
|
28
|
+
// - pkg:<点分路径>(如 pkg:scripts.build):读该项 cwd(缺省仓库根)的 package.json,
|
|
29
|
+
// 沿路径取值,非空字符串才算满足。
|
|
30
|
+
// 没有对应配置的项目自动跳过并提示,不 fail-closed 误拦。--full 同样尊重 when。
|
|
28
31
|
const { execSync } = require('child_process');
|
|
29
32
|
const path = require('path');
|
|
30
33
|
const fs = require('fs');
|
|
@@ -78,17 +81,50 @@ if (hits.length) {
|
|
|
78
81
|
|
|
79
82
|
// --- 2) 条件构建验证 ---
|
|
80
83
|
// --full:合并提交(pre-merge-commit / 冲突解决后手动 commit)使用——合并结果是没人验证过的新状态,
|
|
81
|
-
// 按 ext/prefix
|
|
84
|
+
// 按 ext/prefix 裁剪可能漏掉需要重跑的一侧,故不做路径裁剪。when 仍生效:没有对应能力就不跑。
|
|
82
85
|
// 引擎自身文件(.agents/、.githooks/)不参与构建匹配——工作流引擎不是项目构建产物。
|
|
83
86
|
const FULL = process.argv.includes('--full');
|
|
84
87
|
const stagedForBuild = staged.filter(f => !f.startsWith('.agents/') && !f.startsWith('.githooks/'));
|
|
85
|
-
|
|
88
|
+
|
|
89
|
+
// whenSatisfied:任一谓词满足即启用。pkg: 读 cwd 下的 package.json;其余谓词相对仓库根。
|
|
90
|
+
function whenItem(w, cwd) {
|
|
91
|
+
if (typeof w === 'string' && w.startsWith('pkg:')) {
|
|
92
|
+
const keys = w.slice(4).split('.').filter(Boolean);
|
|
93
|
+
if (!keys.length) return false;
|
|
94
|
+
const pkgPath = path.join(cwd || '.', 'package.json');
|
|
95
|
+
let pkg;
|
|
96
|
+
try { pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); } catch (e) { return false; }
|
|
97
|
+
let cur = pkg;
|
|
98
|
+
for (const k of keys) {
|
|
99
|
+
if (cur == null || typeof cur !== 'object' || !Object.prototype.hasOwnProperty.call(cur, k)) return false;
|
|
100
|
+
cur = cur[k];
|
|
101
|
+
}
|
|
102
|
+
return typeof cur === 'string' && cur.trim() !== '';
|
|
103
|
+
}
|
|
104
|
+
const star = String(w).indexOf('*');
|
|
105
|
+
if (star < 0) return fs.existsSync(w);
|
|
106
|
+
const suffix = w.slice(star + 1);
|
|
107
|
+
const dir = path.dirname(w.slice(0, star)) || '.';
|
|
108
|
+
try { return fs.readdirSync(dir).some((f) => f.endsWith(suffix)); } catch (e) { return false; }
|
|
109
|
+
}
|
|
110
|
+
function whenSatisfied(when, cwd) {
|
|
111
|
+
if (!when) return true;
|
|
112
|
+
const list = Array.isArray(when) ? when : [when];
|
|
113
|
+
return list.some((w) => whenItem(w, cwd));
|
|
114
|
+
}
|
|
115
|
+
function skipWhen(kind, item) {
|
|
116
|
+
const shown = Array.isArray(item.when) ? item.when.join(' / ') : item.when;
|
|
117
|
+
console.log(`SKIP: ${item.name}(未找到 ${shown},跳过${kind}——配置好后自动启用)`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let consideredBuild = false;
|
|
86
121
|
for (const b of (config.builds || [])) {
|
|
87
122
|
if (!b || !b.command) continue;
|
|
88
123
|
const exts = Array.isArray(b.ext) ? b.ext : [];
|
|
89
124
|
const match = FULL || stagedForBuild.some(f => exts.some(e => f.endsWith(e)) || (b.prefix ? f.startsWith(b.prefix) : false));
|
|
90
125
|
if (!match) continue;
|
|
91
|
-
|
|
126
|
+
consideredBuild = true;
|
|
127
|
+
if (!whenSatisfied(b.when, b.cwd)) { skipWhen('构建', b); continue; }
|
|
92
128
|
const r = sh(b.command, b.cwd);
|
|
93
129
|
if (!r.ok) {
|
|
94
130
|
if (b.lockPattern && new RegExp(b.lockPattern).test(r.out)) {
|
|
@@ -101,25 +137,13 @@ for (const b of (config.builds || [])) {
|
|
|
101
137
|
}
|
|
102
138
|
console.log(`OK: ${b.name} 构建通过(${b.command})`);
|
|
103
139
|
}
|
|
104
|
-
if (!
|
|
140
|
+
if (!consideredBuild) {
|
|
105
141
|
console.log(config.builds && config.builds.length
|
|
106
142
|
? 'SKIP: 无匹配的源码变更,跳过构建验证'
|
|
107
143
|
: 'SKIP: 未配置 builds(.agents/hooks/commit-check.config.json),跳过构建验证');
|
|
108
144
|
}
|
|
109
145
|
|
|
110
146
|
// --- 3) 质量检测(checks:lint / 类型检查 / vet 等秒级确定性检查;测试不放这里——关单在 test.md 阶段门) ---
|
|
111
|
-
// whenSatisfied:任一路径存在即启用;元素支持尾部 * 一层通配(如 *.sln = 仓库根存在 .sln 结尾文件)
|
|
112
|
-
function whenSatisfied(when) {
|
|
113
|
-
if (!when) return true;
|
|
114
|
-
const list = Array.isArray(when) ? when : [when];
|
|
115
|
-
return list.some((w) => {
|
|
116
|
-
const star = w.indexOf('*');
|
|
117
|
-
if (star < 0) return fs.existsSync(w);
|
|
118
|
-
const suffix = w.slice(star + 1);
|
|
119
|
-
const dir = path.dirname(w.slice(0, star)) || '.';
|
|
120
|
-
try { return fs.readdirSync(dir).some((f) => f.endsWith(suffix)); } catch (e) { return false; }
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
147
|
let ranCheck = false;
|
|
124
148
|
for (const c of (config.checks || [])) {
|
|
125
149
|
if (!c || !c.command) continue;
|
|
@@ -127,10 +151,7 @@ for (const c of (config.checks || [])) {
|
|
|
127
151
|
const match = FULL || stagedForBuild.some(f => exts.some(e => f.endsWith(e)) || (c.prefix ? f.startsWith(c.prefix) : false));
|
|
128
152
|
if (!match) continue;
|
|
129
153
|
ranCheck = true;
|
|
130
|
-
if (!whenSatisfied(c.when)) {
|
|
131
|
-
console.log(`SKIP: ${c.name}(未找到 ${Array.isArray(c.when) ? c.when.join(' / ') : c.when},跳过质量检查——配置好后自动启用)`);
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
154
|
+
if (!whenSatisfied(c.when, c.cwd)) { skipWhen('质量检查', c); continue; }
|
|
134
155
|
const r = sh(c.command, c.cwd);
|
|
135
156
|
if (!r.ok) {
|
|
136
157
|
console.log(`BLOCK: ${c.name} 质量检查未通过(${c.command})\n` + r.out.slice(0, 1200));
|