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.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +62 -0
  3. package/bin/flow-kit.mjs +4 -0
  4. package/modules/gates/dotnet-ca/README.md +24 -0
  5. package/modules/gates/dotnet-ca/check-architecture.sh +128 -0
  6. package/modules/gates/dotnet-ca/local-pre-commit.line +1 -0
  7. package/modules/hosts/omp/agents/implementer.md +12 -0
  8. package/modules/hosts/omp/agents/independent-reviewer.md +11 -0
  9. package/modules/hosts/omp/agents/ui-verifier.md +12 -0
  10. package/modules/hosts/opencode/agents/implementer.md +14 -0
  11. package/modules/hosts/opencode/agents/independent-reviewer.md +10 -0
  12. package/modules/hosts/opencode/agents/ui-verifier.md +10 -0
  13. package/modules/hosts/opencode/commands/build.md +13 -0
  14. package/modules/hosts/opencode/commands/deploy.md +12 -0
  15. package/modules/hosts/opencode/commands/design.md +12 -0
  16. package/modules/hosts/opencode/commands/maintain.md +12 -0
  17. package/modules/hosts/opencode/commands/new-task.md +12 -0
  18. package/modules/hosts/opencode/commands/plan.md +12 -0
  19. package/modules/hosts/opencode/commands/review.md +12 -0
  20. package/modules/hosts/opencode/commands/test.md +14 -0
  21. package/modules/hosts/trae/agents/implementer.md +9 -0
  22. package/modules/hosts/trae/agents/independent-reviewer.md +8 -0
  23. package/modules/hosts/trae/agents/ui-verifier.md +12 -0
  24. package/modules/hosts/trae/commands/wf-build.md +14 -0
  25. package/modules/hosts/trae/commands/wf-deploy.md +13 -0
  26. package/modules/hosts/trae/commands/wf-design.md +13 -0
  27. package/modules/hosts/trae/commands/wf-maintain.md +13 -0
  28. package/modules/hosts/trae/commands/wf-new-task.md +13 -0
  29. package/modules/hosts/trae/commands/wf-plan.md +13 -0
  30. package/modules/hosts/trae/commands/wf-review.md +13 -0
  31. package/modules/hosts/trae/commands/wf-test.md +14 -0
  32. package/modules/hosts/trae/hooks/post-edit-check.cjs +87 -0
  33. package/modules/hosts/trae/hooks/pre-shell-check.cjs +151 -0
  34. package/modules/hosts/trae/hooks/resolve-shell.cjs +21 -0
  35. package/modules/hosts/trae/hooks.json +29 -0
  36. package/modules/hosts/trae/rules/beautify-input-chinese.md +6 -0
  37. package/modules/hosts/trae/rules/git-commit-message.md +10 -0
  38. package/modules/hosts/trae/rules/project-workflow.md +14 -0
  39. package/modules/hosts/zcode/agents/implementer.md +11 -0
  40. package/modules/hosts/zcode/agents/independent-reviewer.md +10 -0
  41. package/modules/hosts/zcode/agents/ui-verifier.md +10 -0
  42. package/package.json +34 -0
  43. package/src/add-gate.mjs +83 -0
  44. package/src/add-host.mjs +63 -0
  45. package/src/cli.mjs +81 -0
  46. package/src/doctor.mjs +150 -0
  47. package/src/init.mjs +190 -0
  48. package/src/profiles.mjs +101 -0
  49. package/src/render.mjs +68 -0
  50. package/src/run-tests.mjs +35 -0
  51. package/src/sync.mjs +141 -0
  52. package/src/sync.test.mjs +207 -0
  53. package/templates/.gitattributes +6 -0
  54. package/templates/AGENTS.md +52 -0
  55. package/templates/_agents/board/index.html +382 -0
  56. package/templates/_agents/board/marked.min.js +6 -0
  57. package/templates/_agents/cache/kb-index.json +1 -0
  58. package/templates/_agents/commands/build.md +74 -0
  59. package/templates/_agents/commands/deploy.md +65 -0
  60. package/templates/_agents/commands/design.md +54 -0
  61. package/templates/_agents/commands/maintain.md +91 -0
  62. package/templates/_agents/commands/new-task.md +73 -0
  63. package/templates/_agents/commands/plan.md +46 -0
  64. package/templates/_agents/commands/review.md +59 -0
  65. package/templates/_agents/commands/test.md +80 -0
  66. package/templates/_agents/hooks/check-pairing-incremental.sh +29 -0
  67. package/templates/_agents/hooks/check-wiki-ledger.sh +18 -0
  68. package/templates/_agents/hooks/commit-check.cjs +140 -0
  69. package/templates/_agents/hooks/local-pre-commit +6 -0
  70. package/templates/_agents/notes/runtime-env.md +18 -0
  71. package/templates/_agents/roles/implementer.md +46 -0
  72. package/templates/_agents/roles/independent-reviewer.md +43 -0
  73. package/templates/_agents/roles/ui-verifier.md +42 -0
  74. package/templates/_agents/rule-budgets.txt +8 -0
  75. package/templates/_agents/scripts/agg-delegations.cjs +175 -0
  76. package/templates/_agents/scripts/check-loop.sh +803 -0
  77. package/templates/_agents/scripts/check-loop.test.sh +1042 -0
  78. package/templates/_agents/scripts/ensure-board.ps1 +48 -0
  79. package/templates/_agents/scripts/gen-wiki-board.mjs +197 -0
  80. package/templates/_agents/scripts/gen-wiki-board.test.mjs +136 -0
  81. package/templates/_agents/scripts/gen-workflow-index.mjs +190 -0
  82. package/templates/_agents/scripts/gen-workflow-index.test.mjs +113 -0
  83. package/templates/_agents/scripts/gen-workflow-metrics.mjs +172 -0
  84. package/templates/_agents/scripts/gen-workflow-metrics.test.mjs +101 -0
  85. package/templates/_agents/scripts/kb-cache-evict.mjs +50 -0
  86. package/templates/_agents/scripts/kb-cache-evict.test.mjs +104 -0
  87. package/templates/_agents/scripts/kb-search.mjs +307 -0
  88. package/templates/_agents/scripts/kb-search.test.mjs +153 -0
  89. package/templates/_agents/scripts/rule-budget.sh +68 -0
  90. package/templates/_agents/scripts/verify-wiki-consistency.mjs +192 -0
  91. package/templates/_agents/scripts/wiki-search.mjs +19 -0
  92. package/templates/_agents/scripts/workflow-board-server.mjs +244 -0
  93. package/templates/_agents/skills/verify-ui/SKILL.md +24 -0
  94. package/templates/_agents/skills/wiki/SKILL.md +104 -0
  95. package/templates/_agents/workflow-modules.txt +11 -0
  96. package/templates/_githooks/commit-msg +15 -0
  97. package/templates/_githooks/post-commit +10 -0
  98. package/templates/_githooks/pre-commit +58 -0
  99. package/templates/_githooks/pre-merge-commit +24 -0
  100. package/templates/_githooks/pre-push +32 -0
  101. package/templates/wiki/INDEX.md +23 -0
  102. package/templates/wiki/drafts-archive/README.md +3 -0
  103. package/templates/wiki//347/237/245/350/257/206/346/262/211/346/267/200/346/200/273/350/247/210.html +56 -0
  104. package/templates/workflow/README.md +52 -0
  105. package/templates/workflow/delegations.md +28 -0
  106. package/templates/workflow/incidents/_TEMPLATE.md +42 -0
  107. package/templates/workflow/intents/_TEMPLATE.md +40 -0
  108. package/templates/workflow/papercuts.md +7 -0
  109. package/templates/workflow/plans/_TEMPLATE.md +38 -0
  110. package/templates/workflow/regression-checklist.md +22 -0
  111. package/templates/workflow/specs/_TEMPLATE.md +41 -0
package/src/doctor.mjs ADDED
@@ -0,0 +1,150 @@
1
+ // flow-kit doctor:安装体检——布局 / git 钩子 / managed 台账 / 占位符残留 / 索引漂移 / check-loop
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { execSync, spawnSync } from 'node:child_process';
5
+ import net from 'node:net';
6
+ import { createHash } from 'node:crypto';
7
+
8
+ function sh(cmd, cwd) {
9
+ try {
10
+ return { ok: true, out: execSync(cmd, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }) };
11
+ } catch (e) {
12
+ return { ok: false, out: String((e.stdout || '') + (e.stderr || '')) };
13
+ }
14
+ }
15
+
16
+ function portInUse(port) {
17
+ return new Promise((resolve) => {
18
+ const s = net.connect({ port: Number(port), host: '127.0.0.1', timeout: 400 });
19
+ s.on('connect', () => { s.destroy(); resolve(true); });
20
+ s.on('error', () => resolve(false));
21
+ s.on('timeout', () => { s.destroy(); resolve(false); });
22
+ });
23
+ }
24
+
25
+ export function doctor(args, pkgRoot) {
26
+ let target = process.cwd();
27
+ for (let i = 0; i < args.length; i++) {
28
+ if (args[i] === '--dir') target = path.resolve(args[++i]);
29
+ }
30
+ const results = []; // {level:'PASS'|'WARN'|'FAIL', msg}
31
+ const add = (level, msg) => results.push({ level, msg });
32
+
33
+ // 1. Node 版本
34
+ const major = Number(process.versions.node.split('.')[0]);
35
+ if (major >= 18) add('PASS', `Node ${process.versions.node}(≥18)`);
36
+ else add('FAIL', `Node ${process.versions.node} 过低(引擎脚本需 ≥18)`);
37
+
38
+ // 2. 目录布局
39
+ const required = [
40
+ 'AGENTS.md',
41
+ '.agents/commands/new-task.md',
42
+ '.agents/roles/implementer.md',
43
+ '.agents/roles/independent-reviewer.md',
44
+ '.agents/roles/ui-verifier.md',
45
+ '.agents/scripts/check-loop.sh',
46
+ '.agents/scripts/kb-search.mjs',
47
+ '.agents/scripts/gen-workflow-index.mjs',
48
+ '.agents/scripts/gen-wiki-board.mjs',
49
+ '.agents/scripts/workflow-board-server.mjs',
50
+ '.agents/board/index.html',
51
+ '.agents/hooks/commit-check.cjs',
52
+ '.agents/workflow-modules.txt',
53
+ '.agents/rule-budgets.txt',
54
+ '.agents/settings.json',
55
+ '.githooks/pre-commit',
56
+ '.githooks/pre-push',
57
+ '.githooks/commit-msg',
58
+ '.githooks/post-commit',
59
+ '.githooks/pre-merge-commit',
60
+ 'workflow/README.md',
61
+ 'workflow/intents/_TEMPLATE.md',
62
+ 'workflow/specs/_TEMPLATE.md',
63
+ 'workflow/plans/_TEMPLATE.md',
64
+ 'workflow/incidents/_TEMPLATE.md',
65
+ 'wiki/INDEX.md',
66
+ ];
67
+ const missing = required.filter((p) => !fs.existsSync(path.join(target, p)));
68
+ if (missing.length === 0) add('PASS', `目录布局完整(${required.length} 个关键路径)`);
69
+ else add('FAIL', `布局缺失:${missing.join('、')}`);
70
+
71
+ // 3. git 仓库与钩子
72
+ const isRepo = sh('git rev-parse --git-dir', target).ok;
73
+ if (isRepo) {
74
+ const hp = sh('git config core.hooksPath', target);
75
+ if (hp.ok && hp.out.trim() === '.githooks') add('PASS', 'core.hooksPath=.githooks(钩子已挂载)');
76
+ else add('FAIL', `core.hooksPath 当前为「${(hp.ok ? hp.out : '').trim() || '未设置'}」——执行 git config core.hooksPath .githooks`);
77
+ } else {
78
+ add('WARN', '不在 git 仓库内——钩子未挂载(git init 后执行 git config core.hooksPath .githooks)');
79
+ }
80
+
81
+ // 4. kit.json managed 台账(sha 校验:本地改动 → WARN,缺失 → FAIL)
82
+ const kitPath = path.join(target, '.agents', 'kit.json');
83
+ if (!fs.existsSync(kitPath)) {
84
+ add('WARN', '无 .agents/kit.json(非 flow-kit 安装或旧版手动安装)');
85
+ } else {
86
+ try {
87
+ const kit = JSON.parse(fs.readFileSync(kitPath, 'utf8'));
88
+ let ok = 0;
89
+ let modified = 0;
90
+ const gone = [];
91
+ for (const f of kit.managed || []) {
92
+ const p = path.join(target, f.rel);
93
+ if (!fs.existsSync(p)) { gone.push(f.rel); continue; }
94
+ const h = createHash('sha256').update(fs.readFileSync(p)).digest('hex');
95
+ if (h === f.sha256) ok++; else modified++;
96
+ }
97
+ add('PASS', `kit.json v${kit.version}:managed ${ok + modified} 份校验通过`);
98
+ if (modified) add('WARN', `managed 本地改动 ${modified} 份(升级时 flow-kit sync 会要求合并)`);
99
+ if (gone.length) add('FAIL', `managed 文件缺失:${gone.join('、')}`);
100
+ } catch (e) {
101
+ add('FAIL', `kit.json 解析失败:${e.message}`);
102
+ }
103
+ }
104
+
105
+ // 5. 占位符残留
106
+ const phFiles = ['AGENTS.md', 'workflow/README.md', '.agents/notes/runtime-env.md']
107
+ .concat(fs.existsSync(path.join(target, '.agents/commands')) ? fs.readdirSync(path.join(target, '.agents/commands')).filter((f) => f.endsWith('.md')).map((f) => `.agents/commands/${f}`) : []);
108
+ const phHits = [];
109
+ for (const rel of phFiles) {
110
+ const p = path.join(target, rel);
111
+ if (!fs.existsSync(p)) continue;
112
+ const txt = fs.readFileSync(p, 'utf8');
113
+ for (const m of txt.matchAll(/\{\{([A-Z][A-Z0-9_]*)\}\}/g)) phHits.push(`${rel}: {{${m[1]}}}`);
114
+ }
115
+ if (phHits.length === 0) add('PASS', '无占位符残留');
116
+ else add('WARN', `占位符残留 ${phHits.length} 处(init 变量缺失或需手改):${phHits.slice(0, 5).join(';')}${phHits.length > 5 ? ' …' : ''}`);
117
+
118
+ // 6. workflow 索引漂移
119
+ const idx = spawnSync(process.execPath, ['.agents/scripts/gen-workflow-index.mjs', '--check'], { cwd: target, encoding: 'utf8' });
120
+ if (idx.status === 0) add('PASS', 'workflow/INDEX.md 无漂移');
121
+ else add('WARN', `workflow/INDEX.md 漂移——跑 node .agents/scripts/gen-workflow-index.mjs 重生成`);
122
+
123
+ // 7. check-loop
124
+ const cl = spawnSync('sh', ['.agents/scripts/check-loop.sh'], { cwd: target, encoding: 'utf8' });
125
+ if (cl.status === 0) {
126
+ const warnTxt = String(cl.stderr || '').trim();
127
+ add('PASS', `check-loop 干净${warnTxt ? `(${warnTxt.split('\n').filter((l) => l.includes('WARN')).length} 条 advisory 警告)` : ''}`);
128
+ } else {
129
+ add('FAIL', `check-loop 有 hard-block:\n${String(cl.stderr || '').split('\n').slice(0, 8).join('\n')}`);
130
+ }
131
+
132
+ // 8. 看板端口(信息级)
133
+ const kit = fs.existsSync(kitPath) ? JSON.parse(fs.readFileSync(kitPath, 'utf8')) : null;
134
+ const port = kit?.options?.boardPort || '8933';
135
+ portInUse(port).then((inUse) => {
136
+ print(results);
137
+ console.log(inUse
138
+ ? `ℹ️ 看板端口 ${port} 已有监听(幂等拉起会探活复用;他人进程不 kill,见 workflow/README.md)`
139
+ : `ℹ️ 看板端口 ${port} 空闲(首次访问任务入口时由 ensure-board 拉起,或手动 node .agents/scripts/workflow-board-server.mjs)`);
140
+ process.exit(results.some((r) => r.level === 'FAIL') ? 1 : 0);
141
+ });
142
+ }
143
+
144
+ function print(results) {
145
+ const icon = { PASS: '✅', WARN: '⚠️ ', FAIL: '❌' };
146
+ for (const r of results) console.log(`${icon[r.level]} ${r.msg}`);
147
+ const fails = results.filter((r) => r.level === 'FAIL').length;
148
+ const warns = results.filter((r) => r.level === 'WARN').length;
149
+ console.log(`\ndoctor:${results.length - fails - warns} PASS | ${warns} WARN | ${fails} FAIL`);
150
+ }
package/src/init.mjs ADDED
@@ -0,0 +1,190 @@
1
+ // flow-kit init:无参数 → 交互确认环节;带参数 → 直接执行。
2
+ // 渲染模板树 + 宿主适配层 → 生成 owned 基线配置 → 挂 git 钩子 → 写 kit.json → 跑生成器与 doctor
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import readline from 'node:readline';
6
+ import { execSync, spawnSync } from 'node:child_process';
7
+ import { renderTree, sha256 } from './render.mjs';
8
+ import { HOSTS, STACKS, settingsJson, commitCheckConfig, pickStackVars, isOwned } from './profiles.mjs';
9
+ import { doctor } from './doctor.mjs';
10
+
11
+ function fail(msg) {
12
+ console.error(`❌ ${msg}`);
13
+ process.exit(1);
14
+ }
15
+
16
+ function runNode(target, script, args = []) {
17
+ const r = spawnSync(process.execPath, [script, ...args], { cwd: target, encoding: 'utf8' });
18
+ return { ok: r.status === 0, out: `${r.stdout || ''}${r.stderr || ''}` };
19
+ }
20
+
21
+ // makePrompt:行队列式问答(比逐次 rl.question 稳——管道/EOF 提前关闭时不会抛错,安全回退默认值或取消)
22
+ // ask(question, def):def 为默认值(空输入 / EOF 回退);无 def 时空输入返回 ''(用于必须显式确认的问句)
23
+ function makePrompt() {
24
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false });
25
+ const lines = [];
26
+ const waiters = [];
27
+ let ended = false;
28
+ rl.on('line', (l) => { const w = waiters.shift(); if (w) w(l.trim()); else lines.push(l.trim()); });
29
+ rl.on('close', () => { ended = true; while (waiters.length) waiters.shift()(''); });
30
+ return {
31
+ ask(question, def) {
32
+ return new Promise((resolve) => {
33
+ process.stdout.write(def ? `${question}(直接回车=${def}):` : `${question}:`);
34
+ const fin = (a) => resolve(a || def || '');
35
+ const l = lines.shift();
36
+ if (l !== undefined) fin(l);
37
+ else if (ended) fin('');
38
+ else waiters.push(fin);
39
+ });
40
+ },
41
+ close() { rl.close(); },
42
+ };
43
+ }
44
+
45
+ // 交互确认环节:宿主多选 → 看板端口 → 显式确认(EOF / 非 y 一律取消,不装)
46
+ async function interactive() {
47
+ const p = makePrompt();
48
+ console.log('▶ flow-kit init(交互模式——裸跑无参数时进入;带参数则直接执行,见 flow-kit --help)');
49
+ console.log(' 将在当前目录安装:.agents/ | .githooks/ | workflow/ | wiki/ | AGENTS.md(已存在的文件保守跳过)');
50
+ try {
51
+ const hosts = await p.ask('1/3 选择 agent 宿主(逗号分隔:zcode / opencode / trae / omp)', 'zcode');
52
+ const stack = await p.ask('2/3 技术栈——决定编译检查与自检验命令初值(dotnet / node / python / go / none)', 'none');
53
+ const boardPort = await p.ask('3/3 workflow 看板端口', '8933');
54
+ const yes = await p.ask('确认安装到当前目录?(y/n)');
55
+ if (!/^(y|yes)$/i.test(yes)) {
56
+ console.log('已取消(未做任何改动)');
57
+ process.exit(0);
58
+ }
59
+ return { hosts, stack, boardPort, dir: process.cwd(), force: false };
60
+ } finally {
61
+ p.close();
62
+ }
63
+ }
64
+
65
+ export async function init(args, pkgRoot) {
66
+ let opt;
67
+ if (args.length === 0) {
68
+ opt = await interactive();
69
+ } else {
70
+ opt = { hosts: 'zcode', stack: 'none', boardPort: '8933', dir: process.cwd(), force: false };
71
+ for (let i = 0; i < args.length; i++) {
72
+ const a = args[i];
73
+ const next = () => {
74
+ const v = args[++i];
75
+ if (v === undefined) fail(`选项 ${a} 缺少值`);
76
+ return v;
77
+ };
78
+ if (a === '--hosts') opt.hosts = next();
79
+ else if (a === '--stack') opt.stack = next();
80
+ else if (a === '--board-port') opt.boardPort = next();
81
+ else if (a === '--dir') opt.dir = next();
82
+ else if (a === '--force') opt.force = true;
83
+ else fail(`未知选项:${a}(flow-kit init --help 看全部)`);
84
+ }
85
+ }
86
+
87
+ if (!STACKS[opt.stack]) fail(`未知技术栈:${opt.stack}(可选 dotnet | node | python | go | none——对应编译检查与自检验命令初值)`);
88
+ const hosts = opt.hosts.split(',').map((s) => s.trim()).filter(Boolean);
89
+ for (const h of hosts) {
90
+ if (!HOSTS[h]) fail(`未知宿主:${h}(可选 ${Object.keys(HOSTS).join(' | ')})`);
91
+ }
92
+
93
+ const target = path.resolve(opt.dir);
94
+ if (!fs.existsSync(target)) fail(`目标目录不存在:${target}`);
95
+ const kitPath = path.join(target, '.agents', 'kit.json');
96
+ if (fs.existsSync(kitPath) && !opt.force) {
97
+ fail(`${target} 已安装 agentic-flow-kit(.agents/kit.json 存在)。重装/修复用 --force;升级将由 flow-kit sync 提供。`);
98
+ }
99
+
100
+ const vars = { BOARD_PORT: String(opt.boardPort), ...pickStackVars(opt.stack) };
101
+
102
+ console.log(`▶ flow-kit init → ${target}`);
103
+ console.log(` 宿主:${hosts.join(', ')} | 技术栈:${opt.stack} | 看板端口:${opt.boardPort}`);
104
+
105
+ // 1) 模板树(保守:已存在文件跳过,--force 覆盖)
106
+ const t = renderTree(path.join(pkgRoot, 'templates'), target, vars, { force: opt.force });
107
+ // 2) 宿主适配层
108
+ const hostResults = {};
109
+ for (const h of hosts) {
110
+ hostResults[h] = renderTree(path.join(pkgRoot, 'modules', 'hosts', h), path.join(target, HOSTS[h].dir), vars, { force: opt.force });
111
+ }
112
+
113
+ // 3) 生成的 owned 基线配置(引擎按固定路径读取;存在则跳过,永不覆盖;技术栈命令由项目在文件内自填)
114
+ const generated = {
115
+ '.agents/settings.json': settingsJson(opt.stack),
116
+ '.agents/hooks/commit-check.config.json': commitCheckConfig(opt.stack),
117
+ };
118
+ const ownedGenerated = [];
119
+ for (const [rel, content] of Object.entries(generated)) {
120
+ const p = path.join(target, rel);
121
+ if (fs.existsSync(p)) { console.log(` 跳过(已存在):${rel}`); continue; }
122
+ fs.mkdirSync(path.dirname(p), { recursive: true });
123
+ fs.writeFileSync(p, content);
124
+ ownedGenerated.push({ rel, sha256: sha256(Buffer.from(content, 'utf8')) });
125
+ }
126
+
127
+ // 4) .gitignore 追加(缺哪条补哪条)
128
+ const giPath = path.join(target, '.gitignore');
129
+ const giNeed = ['.agents/cache/', ...(hosts.filter((h) => HOSTS[h].localOnly).map((h) => `${HOSTS[h].dir}/`))];
130
+ let giText = fs.existsSync(giPath) ? fs.readFileSync(giPath, 'utf8') : '';
131
+ const giAdd = giNeed.filter((l) => !giText.split(/\r?\n/).some((x) => x.trim() === l));
132
+ if (giAdd.length) {
133
+ giText = (giText.endsWith('\n') || giText === '' ? giText : giText + '\n') + '# agentic-flow-kit\n' + giAdd.join('\n') + '\n';
134
+ fs.writeFileSync(giPath, giText);
135
+ console.log(` .gitignore 追加:${giAdd.join('、')}`);
136
+ }
137
+
138
+ // 5) git 钩子挂载
139
+ try {
140
+ execSync('git rev-parse --git-dir', { cwd: target, stdio: 'pipe' });
141
+ execSync('git config core.hooksPath .githooks', { cwd: target, stdio: 'pipe' });
142
+ console.log(' 已挂载 git 钩子:core.hooksPath=.githooks(pre-commit / pre-push / commit-msg / post-commit / pre-merge-commit)');
143
+ try {
144
+ execSync('git config merge.ours.driver true', { cwd: target, stdio: 'pipe' });
145
+ console.log(' 已启用 merge.ours driver(.gitattributes 的 merge=ours 生效)');
146
+ } catch { /* 可选配置,失败不阻断 */ }
147
+ } catch {
148
+ console.log(' ⚠️ 目标不是 git 仓库——钩子未挂载;git init 后手动执行:git config core.hooksPath .githooks');
149
+ }
150
+
151
+ // 6) kit.json:managed(模板/适配层,升级可覆盖)与 owned(基线配置+起步文档,升级不动)台账
152
+ const managed = [
153
+ ...t.written.filter((f) => !isOwned(f.rel)),
154
+ // 宿主文件渲染时相对宿主根,入台账须还原为项目根相对路径
155
+ ...hosts.flatMap((h) => hostResults[h].written.map((f) => ({ ...f, rel: `${HOSTS[h].dir}/${f.rel}` }))),
156
+ ];
157
+ const owned = [...t.written.filter((f) => isOwned(f.rel)), ...ownedGenerated];
158
+ const pkg = JSON.parse(fs.readFileSync(path.join(pkgRoot, 'package.json'), 'utf8'));
159
+ fs.mkdirSync(path.dirname(kitPath), { recursive: true });
160
+ fs.writeFileSync(kitPath, `${JSON.stringify({
161
+ kit: 'agentic-flow-kit',
162
+ version: pkg.version,
163
+ createdAt: new Date().toISOString(),
164
+ options: { hosts, stack: opt.stack, boardPort: String(opt.boardPort) },
165
+ managed,
166
+ owned,
167
+ }, null, 2)}\n`);
168
+ console.log(` kit.json 台账:managed ${managed.length} 份 | owned ${owned.length} 份(版本 ${pkg.version})`);
169
+
170
+ // 7) 生成器首跑(幂等):workflow/INDEX.md 活跃层索引 + wiki 速览计数与看板 DATA
171
+ const genIndex = runNode(target, '.agents/scripts/gen-workflow-index.mjs');
172
+ console.log(genIndex.ok ? ' 已生成 workflow/INDEX.md(活跃层索引)' : ` ⚠️ gen-workflow-index 失败:${genIndex.out.split('\n')[0]}`);
173
+ const genBoard = runNode(target, '.agents/scripts/gen-wiki-board.mjs');
174
+ console.log(genBoard.ok ? ' 已生成 wiki 速览计数与知识沉淀总览 DATA' : ` ⚠️ gen-wiki-board 失败:${genBoard.out.split('\n')[0]}`);
175
+
176
+ // 8) 自检
177
+ console.log('▶ flow-kit doctor');
178
+ doctor(['--dir', target], pkgRoot);
179
+
180
+ if (t.skipped.length) console.log(` 跳过(已存在,未覆盖):${t.skipped.join('、')}`);
181
+ for (const h of hosts) if (hostResults[h].skipped.length) console.log(` 跳过(${h} 已存在):${hostResults[h].skipped.join('、')}`);
182
+ console.log('\n✅ 安装完成。下一步:');
183
+ if (opt.stack === 'none') {
184
+ console.log(' 1. 技术栈未配置(none):填 AGENTS.md「项目适配区」构建/测试命令 + .agents/hooks/commit-check.config.json 的 builds(编译检查)+ settings.json allow(自检验命令权限)');
185
+ } else {
186
+ console.log(' 1. 按需微调 AGENTS.md「项目适配区」与 .agents/hooks/commit-check.config.json(已按技术栈预填初值)');
187
+ }
188
+ console.log(' 2. 按项目模块改 .agents/workflow-modules.txt 词表与 .agents/rule-budgets.txt 预算');
189
+ console.log(' 3. 第一个任务从 .agents/commands/new-task.md 流程发起(立 intent → 与用户确认)');
190
+ }
@@ -0,0 +1,101 @@
1
+ // 宿主注册表 + 技术栈 profile + init 生成的 owned 基线配置
2
+ // stack 决定门禁配置三处:commit-check 的条件构建(builds)与质量检测(checks,秒级确定性
3
+ // 检查——lint/类型/vet;测试不放提交门,关单在 test.md 阶段门)、settings.json 的自检验
4
+ // 命令权限(allow)、AGENTS.md「项目适配区」命令预填;不改工作流流程本身。
5
+ // checks.when = 配置文件存在才启用(如 tsconfig.json / go.mod / ruff.toml)——没有对应
6
+ // 配置的项目自动跳过,不 fail-closed 误拦。none = 不配置(项目日后自填)。
7
+ export const HOSTS = {
8
+ zcode: { dir: '.zcode', localOnly: true },
9
+ opencode: { dir: '.opencode', localOnly: false },
10
+ trae: { dir: '.trae', localOnly: false },
11
+ omp: { dir: '.omp', localOnly: true },
12
+ };
13
+
14
+ const ESLINT_CONFIGS = [
15
+ 'eslint.config.js', 'eslint.config.mjs', 'eslint.config.cjs',
16
+ '.eslintrc.js', '.eslintrc.cjs', '.eslintrc.json', '.eslintrc.yml', '.eslintrc',
17
+ ];
18
+
19
+ export const STACKS = {
20
+ dotnet: {
21
+ build: 'dotnet build',
22
+ test: 'dotnet test',
23
+ typecheck: 'dotnet build(编译即类型检查)',
24
+ builds: [{ name: 'backend', command: 'dotnet build', ext: ['.cs', '.sln', '.csproj'] }],
25
+ checks: [{ name: 'format', command: 'dotnet format --verify-no-changes', ext: ['.cs'], when: ['*.sln'] }],
26
+ allow: ['dotnet build', 'dotnet run', 'dotnet test', 'dotnet format'],
27
+ },
28
+ node: {
29
+ build: 'npm run build',
30
+ test: 'npm test',
31
+ typecheck: 'npx tsc --noEmit(或 npm run typecheck)',
32
+ builds: [{ name: 'build', command: 'npm run build', ext: ['.ts', '.tsx', '.vue', '.js', '.jsx', '.mjs', '.cjs'] }],
33
+ checks: [
34
+ { name: 'typecheck', command: 'npx tsc --noEmit', ext: ['.ts', '.tsx'], when: ['tsconfig.json'] },
35
+ { name: 'lint', command: 'npx eslint .', ext: ['.ts', '.tsx', '.js', '.jsx', '.vue'], when: ESLINT_CONFIGS },
36
+ ],
37
+ allow: ['npm test'],
38
+ },
39
+ python: {
40
+ build: '(Python 无统一构建命令,按项目填)',
41
+ test: 'python -m pytest',
42
+ typecheck: '(可选:mypy)',
43
+ builds: [{ name: 'tests', command: 'python -m pytest', ext: ['.py'] }],
44
+ checks: [{ name: 'lint', command: 'ruff check .', ext: ['.py'], when: ['ruff.toml', '.ruff.toml'] }],
45
+ allow: ['python -m pytest', 'pytest', 'ruff check'],
46
+ },
47
+ go: {
48
+ build: 'go build ./...',
49
+ test: 'go test ./...',
50
+ typecheck: 'go vet',
51
+ builds: [{ name: 'build', command: 'go build ./...', ext: ['.go'] }],
52
+ checks: [{ name: 'vet', command: 'go vet ./...', ext: ['.go'], when: ['go.mod'] }],
53
+ allow: ['go build', 'go test', 'go vet'],
54
+ },
55
+ none: {
56
+ build: '<填写>',
57
+ test: '<填写>',
58
+ typecheck: '<填写>',
59
+ builds: [],
60
+ checks: [],
61
+ allow: [],
62
+ },
63
+ };
64
+
65
+ // AGENTS.md「项目适配区」的命令预填变量(技术栈未知时留 <填写> 占位)
66
+ export function pickStackVars(stackKey) {
67
+ const s = STACKS[stackKey] || STACKS.none;
68
+ return { BUILD_CMD: s.build, TEST_CMD: s.test, TYPECHECK_CMD: s.typecheck };
69
+ }
70
+
71
+ // 两态文件模型的归属判定:managed(随包升级)之外皆 owned(项目内容,sync 永不覆盖)
72
+ // local-pre-commit 是项目门禁挂载点——一旦接线即含项目内容,按 owned 起步(旧台账里若在 managed,改动后 sync 也会保守跳过)
73
+ export function isOwned(rel) {
74
+ return rel === 'AGENTS.md' || rel === '.gitattributes' || rel.startsWith('workflow/') || rel.startsWith('wiki/')
75
+ || rel.startsWith('.agents/notes/')
76
+ || rel === '.agents/workflow-modules.txt' || rel === '.agents/rule-budgets.txt'
77
+ || rel === '.agents/hooks/local-pre-commit';
78
+ }
79
+
80
+ export function settingsJson(stackKey) {
81
+ const allow = [...new Set([
82
+ 'node', 'git status', 'git diff', 'git add', 'git commit', 'git log', 'git push',
83
+ 'npm run', 'npm install', 'npm ci',
84
+ ...(STACKS[stackKey]?.allow || []),
85
+ ])];
86
+ return `${JSON.stringify({
87
+ permissions: {
88
+ allow,
89
+ deny: ['git reset --hard', 'git push --force', 'git push -f', 'drop database'],
90
+ ask: ['git checkout', 'git clean', 'git branch -D', 'rm -rf'],
91
+ },
92
+ }, null, 2)}\n`;
93
+ }
94
+
95
+ export function commitCheckConfig(stackKey) {
96
+ return `${JSON.stringify({
97
+ knownPatterns: [],
98
+ builds: STACKS[stackKey]?.builds || [],
99
+ checks: STACKS[stackKey]?.checks || [],
100
+ }, null, 2)}\n`;
101
+ }
package/src/render.mjs ADDED
@@ -0,0 +1,68 @@
1
+ // 模板树渲染:目录名前缀 _ 映射为点目录(_agents→.agents);文本文件做 {{KEY}} 占位符替换
2
+ // 返回写入清单(rel + sha256),供 kit.json managed 台账与 doctor 校验
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import crypto from 'node:crypto';
6
+
7
+ const PH_RE = /\{\{([A-Z][A-Z0-9_]*)\}\}/g;
8
+
9
+ function sha256(buf) {
10
+ return crypto.createHash('sha256').update(buf).digest('hex');
11
+ }
12
+
13
+ // relOf:模板内相对路径 → 目标相对路径(仅顶层 _ 前缀目录转点目录)
14
+ function relOf(absFile, srcRoot) {
15
+ const parts = path.relative(srcRoot, absFile).split(path.sep);
16
+ if (parts.length > 1 && parts[0].startsWith('_')) parts[0] = '.' + parts[0].slice(1);
17
+ return parts.join('/');
18
+ }
19
+
20
+ export function renderTree(srcRoot, targetRoot, vars, { force = false } = {}) {
21
+ const written = []; // { rel, sha256 }
22
+ const skipped = []; // rel(已存在,保守跳过)
23
+ const warnings = []; // 未知占位符等
24
+ const dirs = [];
25
+
26
+ (function walk(dir) {
27
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
28
+ const abs = path.join(dir, entry.name);
29
+ if (entry.isDirectory()) {
30
+ dirs.push(relOf(abs, srcRoot));
31
+ walk(abs);
32
+ continue;
33
+ }
34
+ const rel = relOf(abs, srcRoot);
35
+ const target = path.join(targetRoot, rel);
36
+ fs.mkdirSync(path.dirname(target), { recursive: true });
37
+ if (fs.existsSync(target) && !force) {
38
+ skipped.push(rel);
39
+ continue;
40
+ }
41
+ const raw = fs.readFileSync(abs);
42
+ let out = raw;
43
+ if (raw.length < 512 * 1024 && !raw.includes(0)) {
44
+ const text = raw.toString('utf8');
45
+ const used = new Set();
46
+ const replaced = text.replace(PH_RE, (m, key) => {
47
+ if (Object.prototype.hasOwnProperty.call(vars, key)) {
48
+ used.add(key);
49
+ return vars[key];
50
+ }
51
+ return m; // 未知占位符原样保留,doctor 会告警
52
+ });
53
+ if (replaced.includes('{{')) {
54
+ for (const m of replaced.matchAll(/\{\{([A-Z][A-Z0-9_]*)\}\}/g)) {
55
+ if (!Object.prototype.hasOwnProperty.call(vars, m[1])) warnings.push(`${rel}: 未知占位符 {{${m[1]}}}(原样保留)`);
56
+ }
57
+ }
58
+ out = Buffer.from(replaced, 'utf8');
59
+ }
60
+ fs.writeFileSync(target, out);
61
+ written.push({ rel: rel.split(path.sep).join('/'), sha256: sha256(out) });
62
+ }
63
+ })(srcRoot);
64
+
65
+ return { written, skipped, warnings, dirs };
66
+ }
67
+
68
+ export { sha256 };
@@ -0,0 +1,35 @@
1
+ // run-tests.mjs — 全套测试入口(npm test):CLI 单测(sync/add-host/add-gate)+ 引擎 *.test.mjs + check-loop bash 套件。
2
+ // 顺序执行、任一非零整体失败(exit 1);无 sh 环境(极简 Windows)跳过 bash 套件并显著提示。
3
+ import { spawnSync } from 'node:child_process';
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
9
+
10
+ const nodeSuites = [
11
+ 'src/sync.test.mjs',
12
+ ...fs.readdirSync(path.join(ROOT, 'templates/_agents/scripts'))
13
+ .filter((f) => f.endsWith('.test.mjs'))
14
+ .sort()
15
+ .map((f) => `templates/_agents/scripts/${f}`),
16
+ ];
17
+
18
+ let failed = 0;
19
+ for (const rel of nodeSuites) {
20
+ console.log(`\n===== ${rel} =====`);
21
+ const r = spawnSync(process.execPath, [path.join(ROOT, rel)], { cwd: ROOT, stdio: 'inherit' });
22
+ if (r.status !== 0) failed++;
23
+ }
24
+
25
+ const shSuite = 'templates/_agents/scripts/check-loop.test.sh';
26
+ if (spawnSync('sh', ['-c', 'true']).status === 0) {
27
+ console.log(`\n===== ${shSuite}(bash 套件) =====`);
28
+ const r = spawnSync('sh', [path.join(ROOT, shSuite)], { cwd: ROOT, stdio: 'inherit' });
29
+ if (r.status !== 0) failed++;
30
+ } else {
31
+ console.warn(`\n⚠️ 环境无 sh——跳过 ${shSuite}(装 Git Bash / 在 WSL 里补跑,勿静默当作通过)`);
32
+ }
33
+
34
+ console.log(`\n${failed ? `❌ ${failed} 个套件失败` : '✅ 全部套件通过'}`);
35
+ process.exit(failed ? 1 : 0);