agentic-flow-kit 0.2.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/LICENSE +21 -0
- package/README.md +62 -0
- package/bin/flow-kit.mjs +4 -0
- package/modules/gates/dotnet-ca/README.md +24 -0
- package/modules/gates/dotnet-ca/check-architecture.sh +128 -0
- package/modules/gates/dotnet-ca/local-pre-commit.line +1 -0
- package/modules/hosts/omp/agents/implementer.md +12 -0
- package/modules/hosts/omp/agents/independent-reviewer.md +11 -0
- package/modules/hosts/omp/agents/ui-verifier.md +12 -0
- package/modules/hosts/opencode/agents/implementer.md +14 -0
- package/modules/hosts/opencode/agents/independent-reviewer.md +10 -0
- package/modules/hosts/opencode/agents/ui-verifier.md +10 -0
- package/modules/hosts/opencode/commands/build.md +13 -0
- package/modules/hosts/opencode/commands/deploy.md +12 -0
- package/modules/hosts/opencode/commands/design.md +12 -0
- package/modules/hosts/opencode/commands/maintain.md +12 -0
- package/modules/hosts/opencode/commands/new-task.md +12 -0
- package/modules/hosts/opencode/commands/plan.md +12 -0
- package/modules/hosts/opencode/commands/review.md +12 -0
- package/modules/hosts/opencode/commands/test.md +14 -0
- package/modules/hosts/trae/agents/implementer.md +9 -0
- package/modules/hosts/trae/agents/independent-reviewer.md +8 -0
- package/modules/hosts/trae/agents/ui-verifier.md +12 -0
- package/modules/hosts/trae/commands/wf-build.md +14 -0
- package/modules/hosts/trae/commands/wf-deploy.md +13 -0
- package/modules/hosts/trae/commands/wf-design.md +13 -0
- package/modules/hosts/trae/commands/wf-maintain.md +13 -0
- package/modules/hosts/trae/commands/wf-new-task.md +13 -0
- package/modules/hosts/trae/commands/wf-plan.md +13 -0
- package/modules/hosts/trae/commands/wf-review.md +13 -0
- package/modules/hosts/trae/commands/wf-test.md +14 -0
- package/modules/hosts/trae/hooks/post-edit-check.cjs +87 -0
- package/modules/hosts/trae/hooks/pre-shell-check.cjs +151 -0
- package/modules/hosts/trae/hooks/resolve-shell.cjs +21 -0
- package/modules/hosts/trae/hooks.json +29 -0
- package/modules/hosts/trae/rules/beautify-input-chinese.md +6 -0
- package/modules/hosts/trae/rules/git-commit-message.md +10 -0
- package/modules/hosts/trae/rules/project-workflow.md +14 -0
- package/modules/hosts/zcode/agents/implementer.md +11 -0
- package/modules/hosts/zcode/agents/independent-reviewer.md +10 -0
- package/modules/hosts/zcode/agents/ui-verifier.md +10 -0
- package/package.json +34 -0
- package/src/add-gate.mjs +83 -0
- package/src/add-host.mjs +63 -0
- package/src/cli.mjs +81 -0
- package/src/doctor.mjs +150 -0
- package/src/init.mjs +190 -0
- package/src/profiles.mjs +101 -0
- package/src/render.mjs +68 -0
- package/src/run-tests.mjs +35 -0
- package/src/sync.mjs +141 -0
- package/src/sync.test.mjs +207 -0
- package/templates/.gitattributes +6 -0
- package/templates/AGENTS.md +52 -0
- package/templates/_agents/board/index.html +382 -0
- package/templates/_agents/board/marked.min.js +6 -0
- package/templates/_agents/cache/kb-index.json +1 -0
- package/templates/_agents/commands/build.md +74 -0
- package/templates/_agents/commands/deploy.md +65 -0
- package/templates/_agents/commands/design.md +54 -0
- package/templates/_agents/commands/maintain.md +91 -0
- package/templates/_agents/commands/new-task.md +73 -0
- package/templates/_agents/commands/plan.md +46 -0
- package/templates/_agents/commands/review.md +59 -0
- package/templates/_agents/commands/test.md +80 -0
- package/templates/_agents/hooks/check-pairing-incremental.sh +29 -0
- package/templates/_agents/hooks/check-wiki-ledger.sh +18 -0
- package/templates/_agents/hooks/commit-check.cjs +140 -0
- package/templates/_agents/hooks/local-pre-commit +6 -0
- package/templates/_agents/notes/runtime-env.md +18 -0
- package/templates/_agents/roles/implementer.md +46 -0
- package/templates/_agents/roles/independent-reviewer.md +43 -0
- package/templates/_agents/roles/ui-verifier.md +42 -0
- package/templates/_agents/rule-budgets.txt +8 -0
- package/templates/_agents/scripts/agg-delegations.cjs +175 -0
- package/templates/_agents/scripts/check-loop.sh +803 -0
- package/templates/_agents/scripts/check-loop.test.sh +1042 -0
- package/templates/_agents/scripts/ensure-board.ps1 +48 -0
- package/templates/_agents/scripts/gen-wiki-board.mjs +197 -0
- package/templates/_agents/scripts/gen-wiki-board.test.mjs +136 -0
- package/templates/_agents/scripts/gen-workflow-index.mjs +190 -0
- package/templates/_agents/scripts/gen-workflow-index.test.mjs +113 -0
- package/templates/_agents/scripts/gen-workflow-metrics.mjs +172 -0
- package/templates/_agents/scripts/gen-workflow-metrics.test.mjs +101 -0
- package/templates/_agents/scripts/kb-cache-evict.mjs +50 -0
- package/templates/_agents/scripts/kb-cache-evict.test.mjs +104 -0
- package/templates/_agents/scripts/kb-search.mjs +307 -0
- package/templates/_agents/scripts/kb-search.test.mjs +153 -0
- package/templates/_agents/scripts/rule-budget.sh +68 -0
- package/templates/_agents/scripts/verify-wiki-consistency.mjs +192 -0
- package/templates/_agents/scripts/wiki-search.mjs +19 -0
- package/templates/_agents/scripts/workflow-board-server.mjs +244 -0
- package/templates/_agents/skills/verify-ui/SKILL.md +24 -0
- package/templates/_agents/skills/wiki/SKILL.md +104 -0
- package/templates/_agents/workflow-modules.txt +11 -0
- package/templates/_githooks/commit-msg +15 -0
- package/templates/_githooks/post-commit +10 -0
- package/templates/_githooks/pre-commit +58 -0
- package/templates/_githooks/pre-merge-commit +24 -0
- package/templates/_githooks/pre-push +32 -0
- package/templates/wiki/INDEX.md +23 -0
- package/templates/wiki/drafts-archive/README.md +3 -0
- package/templates/wiki//347/237/245/350/257/206/346/262/211/346/267/200/346/200/273/350/247/210.html +56 -0
- package/templates/workflow/README.md +52 -0
- package/templates/workflow/delegations.md +28 -0
- package/templates/workflow/incidents/_TEMPLATE.md +42 -0
- package/templates/workflow/intents/_TEMPLATE.md +40 -0
- package/templates/workflow/papercuts.md +7 -0
- package/templates/workflow/plans/_TEMPLATE.md +38 -0
- package/templates/workflow/regression-checklist.md +22 -0
- package/templates/workflow/specs/_TEMPLATE.md +41 -0
package/src/sync.mjs
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// flow-kit sync:按 .agents/kit.json 台账升级 managed 文件。
|
|
2
|
+
// 三态:未改动(磁盘 sha==台账)→ 覆盖新版;本地已改 → 跳过并报告(--force 覆盖);
|
|
3
|
+
// 生成器目标(INDEX.md / wiki 看板)→ 不比对,收尾重跑生成器走锚点重写。
|
|
4
|
+
// 附带:包内新增 managed 文件 → 安装;包内已删 → 仅报告不删盘、出台账;managed 缺失 → 恢复。
|
|
5
|
+
// owned 文件永不触碰(两态模型,见 profiles.isOwned)。
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import os from 'node:os';
|
|
9
|
+
import { spawnSync } from 'node:child_process';
|
|
10
|
+
import { renderTree, sha256 } from './render.mjs';
|
|
11
|
+
import { HOSTS, pickStackVars, isOwned } from './profiles.mjs';
|
|
12
|
+
import { doctor } from './doctor.mjs';
|
|
13
|
+
|
|
14
|
+
function fail(msg) {
|
|
15
|
+
console.error(`❌ ${msg}`);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function runNode(target, script, args = []) {
|
|
20
|
+
const r = spawnSync(process.execPath, [script, ...args], { cwd: target, encoding: 'utf8' });
|
|
21
|
+
return { ok: r.status === 0, out: `${r.stdout || ''}${r.stderr || ''}` };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function sync(args, pkgRoot) {
|
|
25
|
+
let target = process.cwd();
|
|
26
|
+
let force = false;
|
|
27
|
+
for (let i = 0; i < args.length; i++) {
|
|
28
|
+
if (args[i] === '--dir') target = path.resolve(args[++i] || fail('--dir 缺少值'));
|
|
29
|
+
else if (args[i] === '--force') force = true;
|
|
30
|
+
else fail(`未知选项:${args[i]}(flow-kit sync --help 看全部)`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const kitPath = path.join(target, '.agents', 'kit.json');
|
|
34
|
+
if (!fs.existsSync(kitPath)) fail(`${target} 无 .agents/kit.json——先 flow-kit init(手动安装的旧项目无台账,不支持 sync)`);
|
|
35
|
+
let kit;
|
|
36
|
+
try {
|
|
37
|
+
kit = JSON.parse(fs.readFileSync(kitPath, 'utf8'));
|
|
38
|
+
} catch (e) {
|
|
39
|
+
fail(`kit.json 解析失败:${e.message}`);
|
|
40
|
+
}
|
|
41
|
+
const opt = kit.options || {};
|
|
42
|
+
const hosts = Array.isArray(opt.hosts) ? opt.hosts : [];
|
|
43
|
+
const vars = { BOARD_PORT: String(opt.boardPort || '8933'), ...pickStackVars(opt.stack) };
|
|
44
|
+
|
|
45
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(pkgRoot, 'package.json'), 'utf8'));
|
|
46
|
+
console.log(`▶ flow-kit sync → ${target}(台账版本 ${kit.version} → 包版本 ${pkg.version})`);
|
|
47
|
+
|
|
48
|
+
// 当前包内存渲染到临时目录(只读比对源;不直接写目标——三态判定必须先于落盘)
|
|
49
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'flow-kit-sync-'));
|
|
50
|
+
try {
|
|
51
|
+
// rel → 渲染产物(新包视角的"应有文件"清单)
|
|
52
|
+
const fresh = new Map();
|
|
53
|
+
const t = renderTree(path.join(pkgRoot, 'templates'), tmp, vars, { force: true });
|
|
54
|
+
for (const f of t.written) fresh.set(f.rel, { sha: f.sha256, abs: path.join(tmp, f.rel) });
|
|
55
|
+
for (const h of hosts) {
|
|
56
|
+
if (!HOSTS[h]) continue; // 台账里的宿主已不在包支持列表——只按模板层同步
|
|
57
|
+
const hr = renderTree(path.join(pkgRoot, 'modules', 'hosts', h), path.join(tmp, HOSTS[h].dir), vars, { force: true });
|
|
58
|
+
for (const f of hr.written) fresh.set(`${HOSTS[h].dir}/${f.rel}`, { sha: f.sha256, abs: path.join(tmp, HOSTS[h].dir, f.rel) });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const ledger = new Map((kit.managed || []).map((f) => [f.rel, f.sha256]));
|
|
62
|
+
const updated = [], skipped = [], restored = [], added = [], removed = [], newOwned = [];
|
|
63
|
+
let unchanged = 0;
|
|
64
|
+
const managedNew = [];
|
|
65
|
+
|
|
66
|
+
// 台账内文件:三方比对(台账 sha / 磁盘 sha / 新渲染 sha)
|
|
67
|
+
for (const [rel, ledgerSha] of ledger) {
|
|
68
|
+
const disk = path.join(target, rel);
|
|
69
|
+
const freshFile = fresh.get(rel);
|
|
70
|
+
if (!freshFile) {
|
|
71
|
+
removed.push(rel);
|
|
72
|
+
continue; // 包内已删:文件留在盘上,出台账(下次不再跟踪)
|
|
73
|
+
}
|
|
74
|
+
const diskSha = fs.existsSync(disk) ? sha256(fs.readFileSync(disk)) : null;
|
|
75
|
+
if (diskSha === null) {
|
|
76
|
+
fs.copyFileSync(freshFile.abs, disk);
|
|
77
|
+
restored.push(rel);
|
|
78
|
+
managedNew.push({ rel, sha256: freshFile.sha });
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (diskSha === ledgerSha) {
|
|
82
|
+
if (freshFile.sha === ledgerSha) { unchanged++; managedNew.push({ rel, sha256: ledgerSha }); }
|
|
83
|
+
else { fs.copyFileSync(freshFile.abs, disk); updated.push(rel); managedNew.push({ rel, sha256: freshFile.sha }); }
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
// 本地已改
|
|
87
|
+
if (freshFile.sha === diskSha) { unchanged++; managedNew.push({ rel, sha256: diskSha }); continue; } // 改动恰好等于新版
|
|
88
|
+
if (force) { fs.copyFileSync(freshFile.abs, disk); updated.push(`${rel}(--force 覆盖本地改动)`); managedNew.push({ rel, sha256: freshFile.sha }); }
|
|
89
|
+
else { skipped.push(rel); managedNew.push({ rel, sha256: diskSha }); } // 台账跟随磁盘,下次以当前盘面为基线
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 台账外的新文件:managed 类安装;owned 类只对「盘上缺失」的起步文档报告(已装的不动不报)
|
|
93
|
+
for (const [rel, freshFile] of fresh) {
|
|
94
|
+
if (ledger.has(rel)) continue;
|
|
95
|
+
if (isOwned(rel)) {
|
|
96
|
+
if (!fs.existsSync(path.join(target, rel))) newOwned.push(rel);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const disk = path.join(target, rel);
|
|
100
|
+
if (fs.existsSync(disk) && !force) { skipped.push(`${rel}(已存在未入台账)`); continue; }
|
|
101
|
+
fs.mkdirSync(path.dirname(disk), { recursive: true });
|
|
102
|
+
fs.copyFileSync(freshFile.abs, disk);
|
|
103
|
+
added.push(rel);
|
|
104
|
+
managedNew.push({ rel, sha256: freshFile.sha });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const list = (label, arr) => { if (arr.length) console.log(` ${label}(${arr.length}):${arr.join('、')}`); };
|
|
108
|
+
list('覆盖更新', updated);
|
|
109
|
+
list('恢复缺失', restored);
|
|
110
|
+
list('新增安装', added);
|
|
111
|
+
list('本地已改,跳过', skipped);
|
|
112
|
+
if (skipped.some((s) => !s.includes('未入台账'))) console.log(' ↑ --force 覆盖本地改动;git diff 自查差异');
|
|
113
|
+
list('包内已移除(文件保留在盘上,可手动删除)', removed);
|
|
114
|
+
list('新增 owned 起步文档(项目自持,未自动安装)', newOwned);
|
|
115
|
+
if (!updated.length && !restored.length && !added.length && !removed.length && !skipped.length && !newOwned.length) {
|
|
116
|
+
console.log(` 已是最新(${unchanged} 份 managed 无变化)`);
|
|
117
|
+
} else {
|
|
118
|
+
console.log(` 无变化 ${unchanged} 份`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// 台账重写:版本对齐当前包;removed 出册;skipped 盘面基线化
|
|
122
|
+
kit.managed = managedNew.sort((a, b) => a.rel.localeCompare(b.rel));
|
|
123
|
+
kit.version = pkg.version;
|
|
124
|
+
fs.writeFileSync(kitPath, `${JSON.stringify(kit, null, 2)}\n`);
|
|
125
|
+
|
|
126
|
+
// 生成器目标走锚点重写(不参与 sha 比对)
|
|
127
|
+
const genIndex = runNode(target, '.agents/scripts/gen-workflow-index.mjs');
|
|
128
|
+
if (genIndex.ok || fs.existsSync(path.join(target, '.agents/scripts/gen-workflow-index.mjs'))) {
|
|
129
|
+
console.log(genIndex.ok ? ' 已重生成 workflow/INDEX.md(锚点内重写)' : ` ⚠️ gen-workflow-index 失败:${genIndex.out.split('\n')[0]}`);
|
|
130
|
+
}
|
|
131
|
+
const genBoard = runNode(target, '.agents/scripts/gen-wiki-board.mjs');
|
|
132
|
+
if (genBoard.ok || fs.existsSync(path.join(target, '.agents/scripts/gen-wiki-board.mjs'))) {
|
|
133
|
+
console.log(genBoard.ok ? ' 已重生成 wiki 速览与看板 DATA(锚点内重写)' : ` ⚠️ gen-wiki-board 失败:${genBoard.out.split('\n')[0]}`);
|
|
134
|
+
}
|
|
135
|
+
} finally {
|
|
136
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
console.log('▶ flow-kit doctor');
|
|
140
|
+
doctor(['--dir', target], pkgRoot);
|
|
141
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// sync.test.mjs — flow-kit sync 三态升级 + add-host / add-gate 行为验证。
|
|
2
|
+
// 方法:迷你 fixture 包根(小模板树 + 假宿主 zcode + 假门禁 fakeg)+ 手工模拟 v1 安装态(kit.json 台账 + 盘面),
|
|
3
|
+
// 子进程 driver 调真实 sync/add-host/add-gact(隔离 process.exit 与 doctor 尾部退出),断言盘面 + 台账 + stdout 标记。
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import { spawnSync } from 'node:child_process';
|
|
8
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
9
|
+
import { sha256 } from './render.mjs';
|
|
10
|
+
|
|
11
|
+
const SRC_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)));
|
|
12
|
+
|
|
13
|
+
let pass = 0, failCount = 0;
|
|
14
|
+
function check(name, cond, detail = '') {
|
|
15
|
+
if (cond) { pass++; console.log(`PASS ${name}`); }
|
|
16
|
+
else { failCount++; console.log(`FAIL ${name}${detail ? `——${detail}` : ''}`); }
|
|
17
|
+
}
|
|
18
|
+
const W = (p, content) => { fs.mkdirSync(path.dirname(p), { recursive: true }); fs.writeFileSync(p, content); };
|
|
19
|
+
const R = (p) => fs.readFileSync(p, 'utf8');
|
|
20
|
+
|
|
21
|
+
// ---- fixture 包根(v2 形态:chg/host 变更、new 新增、gone 已删)----
|
|
22
|
+
function mkFixture() {
|
|
23
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'fk-fixture-'));
|
|
24
|
+
W(path.join(root, 'package.json'), JSON.stringify({ name: 'fixture', version: '9.9.9', type: 'module' }));
|
|
25
|
+
W(path.join(root, 'templates/_agents/scripts/keep.txt'), 'keep v2\n');
|
|
26
|
+
W(path.join(root, 'templates/_agents/scripts/chg.txt'), 'chg v2 port {{BOARD_PORT}}\n');
|
|
27
|
+
W(path.join(root, 'templates/_agents/scripts/loc.txt'), 'loc v2\n');
|
|
28
|
+
W(path.join(root, 'templates/_agents/scripts/var.txt'), 'same {{BUILD_CMD}}\n');
|
|
29
|
+
W(path.join(root, 'templates/_agents/commands/new.txt'), 'new in v2\n');
|
|
30
|
+
W(path.join(root, 'templates/AGENTS.md'), 'owned skeleton v2\n');
|
|
31
|
+
W(path.join(root, 'modules/hosts/zcode/h.txt'), 'host v2 {{BOARD_PORT}}\n');
|
|
32
|
+
W(path.join(root, 'modules/gates/fakeg/gate.sh'), 'echo gate\n');
|
|
33
|
+
W(path.join(root, 'modules/gates/fakeg/README.md'), 'docs only\n');
|
|
34
|
+
W(path.join(root, 'modules/gates/fakeg/local-pre-commit.line'), 'sh .agents/hooks/gate.sh\n');
|
|
35
|
+
return root;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ---- v1 安装态(managed 台账与盘面对应 v1 渲染产物)----
|
|
39
|
+
function mkTarget(fixtureRoot) {
|
|
40
|
+
const t = fs.mkdtempSync(path.join(os.tmpdir(), 'fk-target-'));
|
|
41
|
+
const files = {
|
|
42
|
+
'.agents/scripts/keep.txt': 'keep v2\n',
|
|
43
|
+
'.agents/scripts/chg.txt': 'chg v1 port 777\n',
|
|
44
|
+
'.agents/scripts/loc.txt': 'loc v1\n',
|
|
45
|
+
'.agents/scripts/var.txt': 'same <填写>\n',
|
|
46
|
+
'.agents/scripts/gone.txt': 'gone v1\n',
|
|
47
|
+
'.zcode/h.txt': 'host v1 777\n',
|
|
48
|
+
'AGENTS.md': 'owned skeleton v2\n',
|
|
49
|
+
};
|
|
50
|
+
for (const [rel, c] of Object.entries(files)) W(path.join(t, rel), c);
|
|
51
|
+
const managed = Object.entries(files)
|
|
52
|
+
.filter(([rel]) => rel !== 'AGENTS.md')
|
|
53
|
+
.map(([rel, c]) => ({ rel, sha256: sha256(Buffer.from(c, 'utf8')) }));
|
|
54
|
+
W(path.join(t, '.agents/kit.json'), `${JSON.stringify({
|
|
55
|
+
kit: 'agentic-flow-kit', version: '1.0.0', createdAt: '2026-09-23T00:00:00Z',
|
|
56
|
+
options: { hosts: ['zcode'], stack: 'none', boardPort: '777' },
|
|
57
|
+
managed, owned: [{ rel: 'AGENTS.md', sha256: sha256(Buffer.from('owned skeleton v2\n', 'utf8')) }],
|
|
58
|
+
}, null, 2)}\n`);
|
|
59
|
+
return t;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ---- 子进程 driver:调真实实现,pkgRoot 指向 fixture ----
|
|
63
|
+
function runCmd(cmd, fixtureRoot, target, extra = []) {
|
|
64
|
+
const driver = path.join(fixtureRoot, 'driver.mjs');
|
|
65
|
+
W(driver, `import { sync } from ${JSON.stringify(pathToFileURL(path.join(SRC_ROOT, 'sync.mjs')).href)};
|
|
66
|
+
import { addHost } from ${JSON.stringify(pathToFileURL(path.join(SRC_ROOT, 'add-host.mjs')).href)};
|
|
67
|
+
import { addGate } from ${JSON.stringify(pathToFileURL(path.join(SRC_ROOT, 'add-gate.mjs')).href)};
|
|
68
|
+
const root = ${JSON.stringify(fixtureRoot)};
|
|
69
|
+
const [cmd, ...rest] = process.argv.slice(2);
|
|
70
|
+
if (cmd === 'sync') sync(rest, root);
|
|
71
|
+
else if (cmd === 'add-host') addHost(rest, root);
|
|
72
|
+
else if (cmd === 'add-gate') addGate(rest, root);
|
|
73
|
+
`);
|
|
74
|
+
return spawnSync(process.execPath, [driver, cmd, ...extra, '--dir', target], { encoding: 'utf8' });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ============ 场景 1:sync 默认(无 --force)三态分派 ============
|
|
78
|
+
{
|
|
79
|
+
const fx = mkFixture(), t = mkTarget(fx);
|
|
80
|
+
W(path.join(t, '.agents/scripts/loc.txt'), 'loc USER\n'); // 用户本地改动
|
|
81
|
+
const r = runCmd('sync', fx, t);
|
|
82
|
+
const out = r.stdout + r.stderr;
|
|
83
|
+
// fixture 非完整安装:doctor 尾部按设计 FAIL 退出(status 1),此处只断言 sync 主流程走完且有分派报告
|
|
84
|
+
check('S1 sync 主流程走完(三态分派报告齐)', out.includes('覆盖更新') && out.includes('新增安装') && out.includes('doctor'), `status=${r.status} out=${out.slice(0, 200)}`);
|
|
85
|
+
check('S1 未改动+模板变更 → 覆盖(chg.txt → v2 且渲染端口)', R(path.join(t, '.agents/scripts/chg.txt')) === 'chg v2 port 777\n');
|
|
86
|
+
check('S1 宿主层同样覆盖(.zcode/h.txt → v2)', R(path.join(t, '.zcode/h.txt')) === 'host v2 777\n');
|
|
87
|
+
check('S1 本地改动 → 跳过(loc.txt 保持用户版)', R(path.join(t, '.agents/scripts/loc.txt')) === 'loc USER\n');
|
|
88
|
+
check('S1 跳过有报告标记', out.includes('本地已改,跳过') && out.includes('loc.txt'), out);
|
|
89
|
+
check('S1 无变化文件不动(keep/var)', R(path.join(t, '.agents/scripts/keep.txt')) === 'keep v2\n' && R(path.join(t, '.agents/scripts/var.txt')) === 'same <填写>\n');
|
|
90
|
+
check('S1 新增 managed 文件被安装', R(path.join(t, '.agents/commands/new.txt')) === 'new in v2\n');
|
|
91
|
+
check('S1 包内已删:文件留盘', fs.existsSync(path.join(t, '.agents/scripts/gone.txt')));
|
|
92
|
+
check('S1 包内已删有报告标记', out.includes('包内已移除'));
|
|
93
|
+
const kit = JSON.parse(R(path.join(t, '.agents/kit.json')));
|
|
94
|
+
const rels = kit.managed.map((f) => f.rel);
|
|
95
|
+
check('S1 台账:版本对齐包版本', kit.version === '9.9.9');
|
|
96
|
+
check('S1 台账:gone 出册、new 入册', !rels.includes('.agents/scripts/gone.txt') && rels.includes('.agents/commands/new.txt'));
|
|
97
|
+
check('S1 台账:跳过文件基线化为用户版 sha', kit.managed.find((f) => f.rel === '.agents/scripts/loc.txt').sha256 === sha256(Buffer.from('loc USER\n')));
|
|
98
|
+
check('S1 台账:覆盖文件 sha 为新版', kit.managed.find((f) => f.rel === '.agents/scripts/chg.txt').sha256 === sha256(Buffer.from('chg v2 port 777\n')));
|
|
99
|
+
check('S1 owned 不触碰(AGENTS.md 原样)', R(path.join(t, 'AGENTS.md')) === 'owned skeleton v2\n');
|
|
100
|
+
check('S1 已装 owned 不误报新增', !out.includes('新增 owned 起步文档'), out);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ============ 场景 2:sync --force 覆盖本地改动 ============
|
|
104
|
+
{
|
|
105
|
+
const fx = mkFixture(), t = mkTarget(fx);
|
|
106
|
+
W(path.join(t, '.agents/scripts/loc.txt'), 'loc USER\n');
|
|
107
|
+
const r = runCmd('sync', fx, t, ['--force']);
|
|
108
|
+
check('S2 --force 覆盖本地改动(loc.txt → v2)', R(path.join(t, '.agents/scripts/loc.txt')) === 'loc v2\n', r.stdout + r.stderr);
|
|
109
|
+
check('S2 --force 有覆盖标记', (r.stdout + r.stderr).includes('--force 覆盖本地改动'));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ============ 场景 3:managed 缺失 → 恢复 ============
|
|
113
|
+
{
|
|
114
|
+
const fx = mkFixture(), t = mkTarget(fx);
|
|
115
|
+
fs.rmSync(path.join(t, '.agents/scripts/keep.txt'));
|
|
116
|
+
const r = runCmd('sync', fx, t);
|
|
117
|
+
check('S3 缺失 managed 被恢复', fs.existsSync(path.join(t, '.agents/scripts/keep.txt')) && R(path.join(t, '.agents/scripts/keep.txt')) === 'keep v2\n', r.stdout + r.stderr);
|
|
118
|
+
check('S3 恢复有报告标记', (r.stdout + r.stderr).includes('恢复缺失'));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ============ 场景 4:本地改动恰好等于新版 → 视为无变化 ============
|
|
122
|
+
{
|
|
123
|
+
const fx = mkFixture(), t = mkTarget(fx);
|
|
124
|
+
W(path.join(t, '.agents/scripts/chg.txt'), 'chg v2 port 777\n'); // == 新版渲染,但台账还是 v1 sha
|
|
125
|
+
const r = runCmd('sync', fx, t);
|
|
126
|
+
const out = r.stdout + r.stderr;
|
|
127
|
+
check('S4 恰好等于新版不报跳过', !out.includes('本地已改,跳过'), out);
|
|
128
|
+
const kit = JSON.parse(R(path.join(t, '.agents/kit.json')));
|
|
129
|
+
check('S4 台账 sha 更新为新版', kit.managed.find((f) => f.rel === '.agents/scripts/chg.txt').sha256 === sha256(Buffer.from('chg v2 port 777\n')));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ============ 场景 5:无 kit.json → 明确报错引导 init ============
|
|
133
|
+
{
|
|
134
|
+
const fx = mkFixture();
|
|
135
|
+
const t = fs.mkdtempSync(path.join(os.tmpdir(), 'fk-empty-'));
|
|
136
|
+
const r = runCmd('sync', fx, t);
|
|
137
|
+
check('S5 无台账退出码 1', r.status === 1);
|
|
138
|
+
check('S5 报错引导 init', (r.stderr || '').includes('flow-kit init'), r.stderr);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ============ 场景 6:全新 v2 包(台账空基线)→ 全量安装 + 未装 owned 起步文档报告 ============
|
|
142
|
+
{
|
|
143
|
+
const fx = mkFixture(), t = mkTarget(fx);
|
|
144
|
+
fs.rmSync(path.join(t, 'AGENTS.md'), { force: true }); // 未装 owned 起步文档
|
|
145
|
+
const r = runCmd('sync', fx, t);
|
|
146
|
+
const out = r.stdout + r.stderr;
|
|
147
|
+
check('S6 未装 owned 起步文档有报告且未自动安装', out.includes('新增 owned 起步文档') && !fs.existsSync(path.join(t, 'AGENTS.md')), out);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ============ 场景 7:add-host 后补宿主 ============
|
|
151
|
+
{
|
|
152
|
+
const fx = mkFixture();
|
|
153
|
+
const t = fs.mkdtempSync(path.join(os.tmpdir(), 'fk-host-'));
|
|
154
|
+
W(path.join(t, '.agents/kit.json'), `${JSON.stringify({
|
|
155
|
+
kit: 'agentic-flow-kit', version: '1.0.0',
|
|
156
|
+
options: { hosts: [], stack: 'none', boardPort: '777' }, managed: [], owned: [],
|
|
157
|
+
}, null, 2)}\n`);
|
|
158
|
+
const r = runCmd('add-host', fx, t, ['zcode']);
|
|
159
|
+
const out = r.stdout + r.stderr;
|
|
160
|
+
check('S7 宿主文件就位(.zcode/h.txt 渲染)', R(path.join(t, '.zcode/h.txt')) === 'host v2 777\n', out);
|
|
161
|
+
const kit = JSON.parse(R(path.join(t, '.agents/kit.json')));
|
|
162
|
+
check('S7 managed 台账补记(项目根相对路径)', kit.managed.some((f) => f.rel === '.zcode/h.txt'), JSON.stringify(kit.managed));
|
|
163
|
+
check('S7 options.hosts 补记', Array.isArray(kit.options.hosts) && kit.options.hosts.includes('zcode'));
|
|
164
|
+
check('S7 localOnly 宿主进 .gitignore', R(path.join(t, '.gitignore')).includes('.zcode/'));
|
|
165
|
+
const r2 = runCmd('add-host', fx, t, ['zcode']);
|
|
166
|
+
check('S7 重复装未 --force → 报错退出', r2.status === 1 && (r2.stderr || '').includes('--force'), r2.stderr);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ============ 场景 8:add-gate 装门禁 + 幂等接线 ============
|
|
170
|
+
{
|
|
171
|
+
const fx = mkFixture();
|
|
172
|
+
const t = fs.mkdtempSync(path.join(os.tmpdir(), 'fk-gate-'));
|
|
173
|
+
W(path.join(t, '.agents/kit.json'), `${JSON.stringify({
|
|
174
|
+
kit: 'agentic-flow-kit', version: '1.0.0',
|
|
175
|
+
options: { hosts: [], stack: 'none', boardPort: '777' }, managed: [], owned: [],
|
|
176
|
+
}, null, 2)}\n`);
|
|
177
|
+
const r = runCmd('add-gate', fx, t, ['fakeg']);
|
|
178
|
+
const out = r.stdout + r.stderr;
|
|
179
|
+
check('S8 门禁文件落 .agents/hooks/', R(path.join(t, '.agents/hooks/gate.sh')) === 'echo gate\n', out);
|
|
180
|
+
check('S8 包内 README 不拷', !fs.existsSync(path.join(t, '.agents/hooks/README.md')));
|
|
181
|
+
check('S8 接线元数据不拷', !fs.existsSync(path.join(t, '.agents/hooks/local-pre-commit.line')));
|
|
182
|
+
check('S8 local-pre-commit 接线(缺失则建 set -e 头)', R(path.join(t, '.agents/hooks/local-pre-commit')) === '#!/bin/sh\nset -e\nsh .agents/hooks/gate.sh\n', out);
|
|
183
|
+
const kit = JSON.parse(R(path.join(t, '.agents/kit.json')));
|
|
184
|
+
check('S8 owned 台账补记(归项目所有)', kit.owned.some((f) => f.rel === '.agents/hooks/gate.sh'));
|
|
185
|
+
runCmd('add-gate', fx, t, ['fakeg']); // 再跑一遍
|
|
186
|
+
check('S8 幂等:接线行不重复', R(path.join(t, '.agents/hooks/local-pre-commit')) === '#!/bin/sh\nset -e\nsh .agents/hooks/gate.sh\n');
|
|
187
|
+
const r3 = runCmd('add-gate', fx, t, ['nope']);
|
|
188
|
+
check('S8 未知门禁报错并列出可选', r3.status === 1 && (r3.stderr || '').includes('fakeg'), r3.stderr);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ============ 场景 9:旧装态 local-pre-commit(尾部 exit 0)→ 归一化剥掉再接线 ============
|
|
192
|
+
{
|
|
193
|
+
const fx = mkFixture();
|
|
194
|
+
const t = fs.mkdtempSync(path.join(os.tmpdir(), 'fk-gate2-'));
|
|
195
|
+
W(path.join(t, '.agents/kit.json'), `${JSON.stringify({
|
|
196
|
+
kit: 'agentic-flow-kit', version: '1.0.0',
|
|
197
|
+
options: { hosts: [], stack: 'none', boardPort: '777' }, managed: [], owned: [],
|
|
198
|
+
}, null, 2)}\n`);
|
|
199
|
+
W(path.join(t, '.agents/hooks/local-pre-commit'), '#!/bin/sh\n# 旧装态注释\nexit 0\n');
|
|
200
|
+
const r = runCmd('add-gate', fx, t, ['fakeg']);
|
|
201
|
+
const out = r.stdout + r.stderr;
|
|
202
|
+
check('S9 旧装态归一化:剥掉尾部 exit 0', out.includes('归一化'), out);
|
|
203
|
+
check('S9 挂载行在 exit 0 原位之前追加', R(path.join(t, '.agents/hooks/local-pre-commit')) === '#!/bin/sh\n# 旧装态注释\nsh .agents/hooks/gate.sh\n', R(path.join(t, '.agents/hooks/local-pre-commit')));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
console.log(`\n合计: PASS ${pass} / FAIL ${failCount}`);
|
|
207
|
+
process.exit(failCount ? 1 : 0);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# AGENTS.md — AI 工作流 + Wiki
|
|
2
|
+
|
|
3
|
+
> 本文件由 agentic-flow-kit 生成:说明本仓库的 AI 工作流与 Wiki 用法。「项目适配区」由项目自行填写;工作流约定可增不可删。
|
|
4
|
+
|
|
5
|
+
## AI工作流
|
|
6
|
+
|
|
7
|
+
**闭环模型**:`workflow/` 是唯一真相源——intents(为什么做)→ specs(怎么设计)→ plans(怎么做)→ incidents(学到了什么)。单人 + AI 协作:AI 起草 / 实现 / 验证 / 自查,取舍与放行由用户拍板,确认在对话内一句话完成,追溯靠 git。文档协议(frontmatter 字段、状态枚举、命名规则)见 `workflow/README.md`。
|
|
8
|
+
|
|
9
|
+
### 开工前必读
|
|
10
|
+
|
|
11
|
+
1. **新需求 / 功能**:先立 `workflow/intents/` intent(复制 `_TEMPLATE.md`),对话内与用户确认后开工。
|
|
12
|
+
2. **修复类任务**:先查 `workflow/incidents/` 同类历史;L1 以上立 incident(修复中即 intent 等价入口,L2/L3 与 spec/plan 同名配对)。
|
|
13
|
+
3. **级别判断**(就高不就低):实现级(页面 / 交互 / 样式)→ L1;改「规则 / 契约」→ L2;改「数据与运行时结构」(schema / 迁移 SQL / DI 链 / 认证与中间件管线)→ L3。细则见 `.agents/commands/new-task.md`。
|
|
14
|
+
|
|
15
|
+
### 阶段路由
|
|
16
|
+
|
|
17
|
+
总入口 `.agents/commands/new-task.md`:Plan `.agents/commands/plan.md` → Design `.agents/commands/design.md` → Build `.agents/commands/build.md` → Test `.agents/commands/test.md` → Deploy `.agents/commands/deploy.md` → Maintain `.agents/commands/maintain.md`;`.agents/commands/review.md` 为横切评审入口。
|
|
18
|
+
|
|
19
|
+
### 子智能体
|
|
20
|
+
|
|
21
|
+
公共角色契约在 `.agents/roles/`(`implementer` / `independent-reviewer` / `ui-verifier`),各宿主经薄适配层(`.opencode/agents/`、`.trae/agents/`、`.zcode/agents/` 等)注册,宿主不支持子智能体时按命令 frontmatter 的 `fallback` 执行。子智能体不跨用户确认门、不替用户批准、不自行提交。
|
|
22
|
+
|
|
23
|
+
### 门禁与提交
|
|
24
|
+
|
|
25
|
+
- `.githooks/`(经 `core.hooksPath` 挂载,本地 clone 后执行 `git config core.hooksPath .githooks`):pre-commit(闭环配对 / wiki 台账 / 规则面预算 / 敏感信息扫描与条件构建)、pre-push(闭环断档扫描)、commit-msg、post-commit、pre-merge-commit。
|
|
26
|
+
- **`git commit` / `git merge` / `git push` 三处一律禁 `--no-verify`**——被拦说明产出不合规,按提示修完原路重试。
|
|
27
|
+
- 项目专属门禁挂 `.agents/hooks/local-pre-commit`。
|
|
28
|
+
- 提交遵循 Conventional Commits 中文(feat / fix / docs / style / refactor / perf);L1+ 入口文档 / spec / plan 随代码同一提交。
|
|
29
|
+
|
|
30
|
+
### 检索、看板与量化
|
|
31
|
+
|
|
32
|
+
- 跨语料检索:`node .agents/scripts/kb-search.mjs "<词>"`(workflow 节级定位 + wiki 全文);活跃流程读 `workflow/INDEX.md`(状态变更后 `node .agents/scripts/gen-workflow-index.mjs` 重生成)。
|
|
33
|
+
- workflow 看板:http://127.0.0.1:{{BOARD_PORT}}(任务入口幂等拉起 ensure-board;只读预警层,非门禁)。
|
|
34
|
+
- 委派 / 自做结果留痕 `workflow/delegations.md`(聚合 `node .agents/scripts/agg-delegations.cjs`);命令 / 技能卡壳记 `workflow/papercuts.md`,不当场顺手改。
|
|
35
|
+
|
|
36
|
+
### 验证与关单
|
|
37
|
+
|
|
38
|
+
验证入口 `.agents/commands/test.md`;**关单在 test**:逐条勾验入口文档「验收标准」并补证据后 intent → done,未勾验会被 check-loop 拦。上 prod 另走 `.agents/commands/deploy.md`(tag / 回滚 / 观察),不用 deploy 关单。
|
|
39
|
+
|
|
40
|
+
## Wiki
|
|
41
|
+
|
|
42
|
+
`wiki/` 是项目知识沉淀,按主题目录归位;原始 / 一次性文档归档 `wiki/drafts-archive/<日期-主题>/`(只读不增量)。
|
|
43
|
+
|
|
44
|
+
- 人工只维护两样:**磁盘文件本身 + `wiki/INDEX.md` 速览表「用途」列**;计数 / 映射表 / 看板 DATA 均为生成区,勿手改——`node .agents/scripts/gen-wiki-board.mjs` 重生成,`node .agents/scripts/verify-wiki-consistency.mjs` 三方一致性验证(触及 `wiki/` 的提交会被 pre-commit 台账门禁校验)。
|
|
45
|
+
- wiki 看板:`wiki/知识沉淀总览.html`。
|
|
46
|
+
|
|
47
|
+
## 项目适配区(项目自填)
|
|
48
|
+
|
|
49
|
+
- **构建 / 测试 / 类型检查命令**(静态门,各阶段命令引用此处口径):构建 = {{BUILD_CMD}};测试 = {{TEST_CMD}};类型检查 = {{TYPECHECK_CMD}}
|
|
50
|
+
- **运行时环境**(端口 / 进程 / 终端差异):`.agents/notes/runtime-env.md`
|
|
51
|
+
- **目录级规则**:如 `backend/AGENTS.md`、`frontend/AGENTS.md`(如有)——目录级约定不回填本文件
|
|
52
|
+
- **权限与提交验证配置**:`.agents/settings.json`(allow / deny / ask)、`.agents/hooks/commit-check.config.json`(条件构建 / 质量检测命令与密钥白名单——质量检测只放秒级确定性检查(lint / 类型检查 / vet),测试不放提交门,关单在 test 阶段门)
|