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
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PostToolUse(Edit|Write) hook — 编辑 .cs 文件后即时跑架构红线检查
|
|
4
|
+
*
|
|
5
|
+
* Trae 宿主事实:PostToolUse 不能撤销已完成的编辑,但可用 hookSpecificOutput.additionalContext
|
|
6
|
+
* 把违例喂回模型,让它在提交前自行修掉;硬阻断在 PreToolUse 的 pre-shell-check.cjs(走 .githooks/pre-commit)。
|
|
7
|
+
*
|
|
8
|
+
* stdin: { tool_name, llm_tool_name, tool_input: { file_path, ... }, cwd, workspace_roots, ... }
|
|
9
|
+
*/
|
|
10
|
+
const { execFileSync } = require('child_process');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const { resolveShell } = require('./resolve-shell.cjs');
|
|
13
|
+
|
|
14
|
+
// --- 读取 stdin ---
|
|
15
|
+
let raw = '';
|
|
16
|
+
try {
|
|
17
|
+
raw = fs.readFileSync(0, 'utf8');
|
|
18
|
+
} catch {
|
|
19
|
+
process.exit(0);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let data;
|
|
23
|
+
try {
|
|
24
|
+
data = JSON.parse(raw);
|
|
25
|
+
} catch {
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const toolName = `${data.tool_name || ''} ${data.llm_tool_name || ''}`;
|
|
30
|
+
if (!/edit|write/i.test(toolName)) {
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 文件路径字段名跨宿主/版本可能不同,逐个候选取第一个命中的
|
|
35
|
+
function extractFilePath(input) {
|
|
36
|
+
if (!input || typeof input !== 'object') return '';
|
|
37
|
+
for (const k of ['file_path', 'filePath', 'path', 'target_file', 'file']) {
|
|
38
|
+
if (typeof input[k] === 'string' && input[k]) return input[k];
|
|
39
|
+
}
|
|
40
|
+
return '';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const filePath = extractFilePath(data.tool_input);
|
|
44
|
+
if (!/\.cs$/i.test(filePath)) {
|
|
45
|
+
process.exit(0); // 非 .cs 文件,跳过
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const projectRoot = data.cwd || (Array.isArray(data.workspace_roots) && data.workspace_roots[0]) || process.cwd();
|
|
49
|
+
|
|
50
|
+
const shellBin = resolveShell();
|
|
51
|
+
if (!shellBin) {
|
|
52
|
+
console.log(JSON.stringify({
|
|
53
|
+
hookSpecificOutput: {
|
|
54
|
+
hookEventName: 'PostToolUse',
|
|
55
|
+
additionalContext: '⚠️ 架构红线检查未执行:未找到可用的 bash / sh。安装 Git for Windows 后重试,或提交前手动跑 .agents/hooks/check-architecture.sh。'
|
|
56
|
+
}
|
|
57
|
+
}));
|
|
58
|
+
process.exit(0);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// timeout 单位是毫秒:架构检查实测约 0.6 秒,30 秒预算够用;
|
|
62
|
+
// (2026-09-19 修复:原值 30 是 30 毫秒,必然 ETIMEDOUT,导致每次 .cs 编辑都误报「发现违例」)
|
|
63
|
+
try {
|
|
64
|
+
execFileSync(shellBin, ['.agents/hooks/check-architecture.sh'], {
|
|
65
|
+
cwd: projectRoot, stdio: ['ignore', 'pipe', 'pipe'], timeout: 30000
|
|
66
|
+
});
|
|
67
|
+
// 通过,无输出
|
|
68
|
+
} catch (e) {
|
|
69
|
+
if (e.code === 'ETIMEDOUT') {
|
|
70
|
+
console.log(JSON.stringify({
|
|
71
|
+
hookSpecificOutput: {
|
|
72
|
+
hookEventName: 'PostToolUse',
|
|
73
|
+
additionalContext: '⚠️ 架构红线检查超时(30 秒):本次编辑未验证,请在提交前手动跑 .agents/hooks/check-architecture.sh。'
|
|
74
|
+
}
|
|
75
|
+
}));
|
|
76
|
+
process.exit(0);
|
|
77
|
+
}
|
|
78
|
+
const err = String((e.stderr || '') + (e.stdout || '')).slice(0, 800);
|
|
79
|
+
console.log(JSON.stringify({
|
|
80
|
+
hookSpecificOutput: {
|
|
81
|
+
hookEventName: 'PostToolUse',
|
|
82
|
+
additionalContext: `⚠️ 架构红线检查发现违例(check-architecture.sh):\n${err}\n请在提交前修复;硬阻断在 git commit 时执行。`
|
|
83
|
+
}
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
process.exit(0);
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PreToolUse(RunCommand) hook — 两道关卡:
|
|
4
|
+
* 1) deny/ask 命令门禁(口径同 .agents/settings.json 的 deny/ask 策略)
|
|
5
|
+
* 2) git commit / git push 前置检查:直接调 .githooks/pre-commit / pre-push
|
|
6
|
+
*
|
|
7
|
+
* 为什么调 .githooks/*:那是项目唯一的门禁权威(pre-commit 六项 + pre-push 闭环断档)。
|
|
8
|
+
* 本钩子是**宿主侧冗余**——core.hooksPath 漏配时 git 侧门禁静默失效(2026-09-16 Trae 侧缺档事故),
|
|
9
|
+
* 由这里在命令执行前拦下;门禁清单只维护 .githooks/ 一份,本文件不复制。
|
|
10
|
+
*
|
|
11
|
+
* Trae 宿主事实(docs.trae.ai/ide/hook-configuration-reference):
|
|
12
|
+
* - 终端工具的 matcher 名是 RunCommand(不是 Shell/Bash);timeout 单位是**秒**。
|
|
13
|
+
* - stdin: { session_id, cwd, hook_event_name, workspace_roots, tool_use_id, tool_name, llm_tool_name, tool_input }
|
|
14
|
+
* - stdout: hookSpecificOutput.permissionDecision = allow / deny / ask;无输出 = 放行。
|
|
15
|
+
*/
|
|
16
|
+
const { execFileSync } = require('child_process');
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const { resolveShell } = require('./resolve-shell.cjs');
|
|
19
|
+
|
|
20
|
+
// --- 读取 stdin ---
|
|
21
|
+
let raw = '';
|
|
22
|
+
try {
|
|
23
|
+
raw = fs.readFileSync(0, 'utf8');
|
|
24
|
+
} catch {
|
|
25
|
+
process.exit(0); // 无 stdin,放行
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let data;
|
|
29
|
+
try {
|
|
30
|
+
data = JSON.parse(raw);
|
|
31
|
+
} catch {
|
|
32
|
+
process.exit(0); // JSON 解析失败,放行(不阻塞正常工作流)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 工具名兼容:Trae 规范化名为 RunCommand,llm_tool_name 为原始名;保留 Claude 风格别名防宿主差异
|
|
36
|
+
const toolName = `${data.tool_name || ''} ${data.llm_tool_name || ''}`;
|
|
37
|
+
if (!/runcommand|bash|shell|terminal|executecommand/i.test(toolName)) {
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 取命令文本:优先约定的 command 字段;字段名缺失时退回扫描 tool_input 的字符串值
|
|
42
|
+
// (信任边界上的宿主 payload,字段名跨版本可能不同——漏读会让门禁静默失效,故兜底扫描)
|
|
43
|
+
function extractCommand(input) {
|
|
44
|
+
if (!input || typeof input !== 'object') return '';
|
|
45
|
+
if (typeof input.command === 'string' && input.command) return input.command;
|
|
46
|
+
return Object.values(input).filter(v => typeof v === 'string').join('\n');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const command = extractCommand(data.tool_input);
|
|
50
|
+
if (!command) {
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const projectRoot = data.cwd || (Array.isArray(data.workspace_roots) && data.workspace_roots[0]) || process.cwd();
|
|
55
|
+
|
|
56
|
+
function deny(reason) {
|
|
57
|
+
console.log(JSON.stringify({
|
|
58
|
+
hookSpecificOutput: {
|
|
59
|
+
hookEventName: 'PreToolUse',
|
|
60
|
+
permissionDecision: 'deny',
|
|
61
|
+
permissionDecisionReason: reason
|
|
62
|
+
}
|
|
63
|
+
}));
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function ask(reason) {
|
|
68
|
+
console.log(JSON.stringify({
|
|
69
|
+
hookSpecificOutput: {
|
|
70
|
+
hookEventName: 'PreToolUse',
|
|
71
|
+
permissionDecision: 'ask',
|
|
72
|
+
permissionDecisionReason: reason
|
|
73
|
+
}
|
|
74
|
+
}));
|
|
75
|
+
process.exit(0);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ── 1) deny/ask 命令门禁 ──────────────────────────────
|
|
79
|
+
const denyList = [
|
|
80
|
+
{ pat: 'dotnet ef', reason: '禁 EF Migrations,Schema 由人工 SQL 维护' },
|
|
81
|
+
{ pat: 'git reset --hard', reason: '禁破坏性重置' },
|
|
82
|
+
{ pat: 'git push --force', reason: '禁强制推送' },
|
|
83
|
+
{ pat: 'git push -f', reason: '禁强制推送' },
|
|
84
|
+
{ pat: 'drop database', reason: '禁删库' }
|
|
85
|
+
];
|
|
86
|
+
const askList = [
|
|
87
|
+
{ pat: 'git checkout', reason: '可能切换/丢弃工作区改动' },
|
|
88
|
+
{ pat: 'git clean', reason: '可能删除未跟踪文件' },
|
|
89
|
+
{ pat: 'git branch -D', reason: '可能删除未合并分支' },
|
|
90
|
+
{ pat: 'rm -rf', reason: '可能递归删除文件' }
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
for (const d of denyList) {
|
|
94
|
+
if (command.includes(d.pat)) {
|
|
95
|
+
deny(`禁止执行: ${d.pat}(项目红线:${d.reason})`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
for (const a of askList) {
|
|
99
|
+
if (command.includes(a.pat)) {
|
|
100
|
+
ask(`需确认: ${a.pat}(项目 ask 策略:${a.reason})`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── 2) git commit / git push 前置检查 ──────────────────
|
|
105
|
+
const isCommit = /git\s+commit/.test(command);
|
|
106
|
+
const isPush = /git\s+push/.test(command);
|
|
107
|
+
|
|
108
|
+
if (!isCommit && !isPush) {
|
|
109
|
+
process.exit(0); // 非 commit/push,放行
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const shellBin = resolveShell();
|
|
113
|
+
if (!shellBin) {
|
|
114
|
+
deny('门禁未执行:未找到可用的 bash / sh——请安装 Git for Windows,或在 Git Bash 终端手动跑 .githooks/pre-commit');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// runGate:把 .githooks/ 门禁原始输出透传给 AI;未执行(脚本缺失)与判定失败分开报,避免把 ENOENT 误报成代码违例
|
|
118
|
+
function runGate(scriptRelPath, label, budgetMs) {
|
|
119
|
+
try {
|
|
120
|
+
execFileSync(shellBin, [scriptRelPath], {
|
|
121
|
+
cwd: projectRoot,
|
|
122
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
123
|
+
timeout: budgetMs
|
|
124
|
+
});
|
|
125
|
+
} catch (e) {
|
|
126
|
+
if (e.code === 'ETIMEDOUT') {
|
|
127
|
+
deny(`${label}超时(${scriptRelPath},${Math.round(budgetMs / 1000)} 秒未完成)——检查 dotnet build / npm run build 是否卡住`);
|
|
128
|
+
}
|
|
129
|
+
if (e.code === 'ENOENT') {
|
|
130
|
+
deny(`门禁未执行(${label}):${scriptRelPath} 不可达——确认仓库完整后重试`);
|
|
131
|
+
}
|
|
132
|
+
const err = String((e.stdout || '') + (e.stderr || '')).slice(0, 1200);
|
|
133
|
+
deny(`${label}未通过(${scriptRelPath}):\n${err}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (isCommit) {
|
|
138
|
+
// pre-commit 六项:架构红线 + 审计字段 + 删除拦截 + 闭环配对(增量)+ 敏感信息 + 条件编译
|
|
139
|
+
// 预算 5 分钟覆盖 dotnet build / npm run build(.githooks/pre-commit 内部无超时)
|
|
140
|
+
runGate('.githooks/pre-commit', '提交前门禁', 300000);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (isPush) {
|
|
144
|
+
// pre-push:check-loop.sh 扫 workflow/ 闭环断档(hard-block = 配对断裂 / 回路断档 / 新建 done 未勾验)
|
|
145
|
+
// 预算 7 分钟:2026-09-19 实测 check-loop.sh 全量扫描耗时 2 分 53 秒(231 任务卡),
|
|
146
|
+
// 原定 2 分钟会误报超时——不要按「脚本应该很快」调小
|
|
147
|
+
runGate('.githooks/pre-push', '推送前闭环扫描', 420000);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 全部通过,放行
|
|
151
|
+
process.exit(0);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// shell 探测(供 .trae/hooks/ 各钩子共用)
|
|
2
|
+
// Windows 下 bash 常不在 PATH:优先 Git Bash 绝对路径,回退 PATH 的 bash/sh。
|
|
3
|
+
// 返回 null 表示不可用——调用方须 fail-closed 报告「门禁未执行」,不要当成检查通过。
|
|
4
|
+
const { execFileSync } = require('child_process');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
|
|
7
|
+
function resolveShell() {
|
|
8
|
+
const candidates = [
|
|
9
|
+
'C:\\Program Files\\Git\\bin\\bash.exe',
|
|
10
|
+
'C:\\Program Files (x86)\\Git\\bin\\bash.exe'
|
|
11
|
+
];
|
|
12
|
+
for (const p of candidates) {
|
|
13
|
+
try { fs.accessSync(p); return p; } catch { /* 尝试下一个 */ }
|
|
14
|
+
}
|
|
15
|
+
for (const c of ['bash', 'sh']) {
|
|
16
|
+
try { execFileSync(c, ['-c', 'exit 0'], { stdio: 'pipe', timeout: 5000 }); return c; } catch { /* 尝试下一个 */ }
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = { resolveShell };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"hooks": {
|
|
4
|
+
"PreToolUse": [
|
|
5
|
+
{
|
|
6
|
+
"matcher": "RunCommand|Bash|Shell|Terminal",
|
|
7
|
+
"hooks": [
|
|
8
|
+
{
|
|
9
|
+
"type": "command",
|
|
10
|
+
"command": "node .trae/hooks/pre-shell-check.cjs",
|
|
11
|
+
"timeout": 480
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"PostToolUse": [
|
|
17
|
+
{
|
|
18
|
+
"matcher": "Edit|Write|MultiEdit",
|
|
19
|
+
"hooks": [
|
|
20
|
+
{
|
|
21
|
+
"type": "command",
|
|
22
|
+
"command": "node .trae/hooks/post-edit-check.cjs",
|
|
23
|
+
"timeout": 60
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
alwaysApply: false
|
|
3
|
+
scene: git_message
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
提交信息一律中文,遵循 Conventional Commits:`<类型>(<范围>): <说明>`,例如 `fix(采购订单): 页签切换浮层收纳`、`docs(trae): 同步工作流适配层`。
|
|
7
|
+
|
|
8
|
+
- 类型枚举:feat / fix / docs / style / refactor / perf;`.githooks/commit-msg` 按此硬校验首行,非枚举前缀会被拦下,merge / revert 提交豁免。
|
|
9
|
+
- 范围可省:`fix: 说明` 同样合法;分隔符 `:` 或 `(` 均可。
|
|
10
|
+
- 禁止 `[feat] 说明` 这类方括号写法。
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
alwaysApply: true
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
本项目工作流唯一权威是根目录 `AGENTS.md`:开工前必须完整读取并严格遵循(任务流程 / 通用 Working rules / 目录级规则 / 运行时与验证标准),本规则只做入口指针,不复制内容。
|
|
6
|
+
|
|
7
|
+
- 规则加载是惰性的:根 `AGENTS.md` 常驻;目录级 `AGENTS.md`(如有,如 `backend/AGENTS.md`、`frontend/AGENTS.md`)只在该目录文件被读取或被提及时才生效——动对应目录前先主动读,别凭记忆。
|
|
8
|
+
- 终端环境差异(shell 可用性 / 退出码取法 /「命令不存在 ⇒ 假绿」)见 `.agents/notes/runtime-env.md` §1。
|
|
9
|
+
- 闭环流程目录:`workflow/`(intents / specs / plans / incidents / delegations.md 委派台账 / papercuts.md 留痕)。
|
|
10
|
+
- 6 阶段命令:按用户触发短语路由到 `.trae/commands/wf-*.md`(new-task / plan / design / build / test / deploy / maintain / review)。
|
|
11
|
+
- 子智能体(`.trae/agents/*.md`)按适配器委派;Trae 子智能体为 Beta 功能、需在设置中开启,未开启时按权威命令的 fallback 执行——`fallback: new-session` 必须另开新会话,不得由原会话自查冒充。
|
|
12
|
+
- 闭环兜底以 `.githooks/` 为准(门禁清单以各 hook / `.agents/scripts/check-loop.sh` 头部注释为准,不在本文件复述);`.trae/hooks/` 在命令执行前再拦一次,`core.hooksPath` 漏配时它是唯一门禁。
|
|
13
|
+
- 委派完成后在 `workflow/delegations.md` 追加一行留痕;命令 / 技能卡壳误导时在 `workflow/papercuts.md` 记一行,不当场顺手改。
|
|
14
|
+
- 中文为项目唯一语言;提交遵循 Conventional Commits 中文。
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: implementer
|
|
3
|
+
description: 在已批准且边界明确的工作包内实现改动或补测试;超出授权文件范围即停
|
|
4
|
+
---
|
|
5
|
+
开工前读项目根 `AGENTS.md` 与 `.agents/roles/implementer.md`,并**完全遵循**该角色契约(必需输入 / 授权文件 / 验收与停止条件)。
|
|
6
|
+
|
|
7
|
+
宿主适配说明(本机 ZCode):
|
|
8
|
+
- 只改派单**授权文件**范围内的内容;需偏离(改公共接口 / 动未列文件 / 引新依赖 / 改验收判据)→ **停下报 blocker**,不自行扩大范围。
|
|
9
|
+
- **禁** `git commit` / `git push` / `git reset --hard`;提交与推送由主智能体在复核后执行。
|
|
10
|
+
- 不跑全量静态门(构建 / 测试 / 类型检查,命令见 `.agents/commands/test.md` 项目适配区)——由主智能体在改动面收敛后统一跑;本角色只做与工作包直接相关的最小验证。
|
|
11
|
+
- 派单缺授权文件清单或可执行验收判据时,**返回 blocker 且不动文件**。
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: independent-reviewer
|
|
3
|
+
description: 只读独立复核(spec 或实现 diff);不改动文件、不替用户批准
|
|
4
|
+
---
|
|
5
|
+
开工前读项目根 `AGENTS.md` 与 `.agents/roles/independent-reviewer.md`,并**完全遵循**该角色契约(输入 / 只读约束 / 产出格式 / 停止条件)。
|
|
6
|
+
|
|
7
|
+
宿主适配说明(本机 ZCode):
|
|
8
|
+
- 只读复核:**不改任何文件**、不跑写操作命令;结论按角色契约的分级清单(P0/P1/P2 + 位置 + 触发条件 + 影响)回报。
|
|
9
|
+
- 不替用户批准——复核结论交主智能体汇总,由用户对话内拍板。
|
|
10
|
+
- 复核范围内引用不到的必读材料(spec / diff / 关联实现)→ 报 blocker,不猜。
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ui-verifier
|
|
3
|
+
description: 浏览器实测 UI 改动(只读验证、不留脏数据);结论按页面/步骤/证据回报
|
|
4
|
+
---
|
|
5
|
+
开工前读项目根 `AGENTS.md` 与 `.agents/roles/ui-verifier.md`,并**完全遵循**该角色契约(输入 / 验证步骤 / 不留脏数据约束 / 产出格式)。
|
|
6
|
+
|
|
7
|
+
宿主适配说明(本机 ZCode):
|
|
8
|
+
- 只做只读验证与既定步骤操作;**不保存/提交业务数据**,验证产生的临时数据按契约清理。
|
|
9
|
+
- 不修代码——发现缺陷记录「页面 / 步骤 / 期望 vs 实际 / 截图或报文证据」,交主智能体定性。
|
|
10
|
+
- 验证完成后关闭起动的本地进程(按 `.agents/notes/runtime-env.md` §2 的进程清单)。
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agentic-flow-kit",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "AI-Native 闭环工作流 + wiki 知识层脚手架——flow-kit init 一条命令为新项目装好 .agents 工作流引擎(阶段命令/角色契约/门禁钩子/kb 检索/看板/规则面预算)+ workflow 文档闭环 + wiki 知识库,装完自包含、零运行时依赖",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "node src/run-tests.mjs"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"flow-kit": "bin/flow-kit.mjs"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin",
|
|
14
|
+
"src",
|
|
15
|
+
"templates",
|
|
16
|
+
"modules",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=18.0.0"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"ai-native",
|
|
24
|
+
"workflow",
|
|
25
|
+
"agent",
|
|
26
|
+
"scaffold",
|
|
27
|
+
"cli",
|
|
28
|
+
"wiki",
|
|
29
|
+
"check-loop",
|
|
30
|
+
"closed-loop"
|
|
31
|
+
],
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"private": false
|
|
34
|
+
}
|
package/src/add-gate.mjs
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// flow-kit add-gate <门禁>:装门禁模块到 .agents/hooks/ 并接线 local-pre-commit 挂载点。
|
|
2
|
+
// 模块约定:modules/gates/<gate>/ 下源文件拷入 .agents/hooks/(平铺,README.md 与 local-pre-commit.line 为包内元数据不拷);
|
|
3
|
+
// local-pre-commit.line 内容 = 需追加进 .agents/hooks/local-pre-commit 的挂载行(存在同名行则不重复追加,幂等)。
|
|
4
|
+
// 门禁装后归项目所有:入 owned 台账,sync 永不覆盖(改红线/删不适用条目自行编辑——见各模块 README)。
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { sha256 } from './render.mjs';
|
|
8
|
+
|
|
9
|
+
function fail(msg) {
|
|
10
|
+
console.error(`❌ ${msg}`);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const META = new Set(['README.md', 'local-pre-commit.line']); // 包内文档/接线元数据,不装进项目
|
|
15
|
+
|
|
16
|
+
export function addGate(args, pkgRoot) {
|
|
17
|
+
const gatesRoot = path.join(pkgRoot, 'modules', 'gates');
|
|
18
|
+
const available = fs.existsSync(gatesRoot) ? fs.readdirSync(gatesRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name) : [];
|
|
19
|
+
const gate = args[0];
|
|
20
|
+
if (!gate || gate.startsWith('--')) fail(`缺少门禁参数(可选:${available.join(' | ') || '无'})`);
|
|
21
|
+
if (!available.includes(gate)) fail(`未知门禁:${gate}(可选:${available.join(' | ') || '无'})`);
|
|
22
|
+
|
|
23
|
+
let target = process.cwd();
|
|
24
|
+
let force = false;
|
|
25
|
+
for (let i = 1; i < args.length; i++) {
|
|
26
|
+
if (args[i] === '--dir') target = path.resolve(args[++i] || fail('--dir 缺少值'));
|
|
27
|
+
else if (args[i] === '--force') force = true;
|
|
28
|
+
else fail(`未知选项:${args[i]}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const kitPath = path.join(target, '.agents', 'kit.json');
|
|
32
|
+
if (!fs.existsSync(kitPath)) fail(`${target} 无 .agents/kit.json——先 flow-kit init`);
|
|
33
|
+
const kit = JSON.parse(fs.readFileSync(kitPath, 'utf8'));
|
|
34
|
+
|
|
35
|
+
const srcRoot = path.join(gatesRoot, gate);
|
|
36
|
+
const hooksDir = path.join(target, '.agents', 'hooks');
|
|
37
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
38
|
+
console.log(`▶ flow-kit add-gate ${gate} → ${target}`);
|
|
39
|
+
|
|
40
|
+
const installed = [], skipped = [];
|
|
41
|
+
let wireLine = null;
|
|
42
|
+
for (const entry of fs.readdirSync(srcRoot, { withFileTypes: true })) {
|
|
43
|
+
if (entry.isDirectory()) fail(`门禁模块含子目录(${entry.name})——平铺约定不支持,请检查模块内容`);
|
|
44
|
+
if (entry.name === 'local-pre-commit.line') { wireLine = fs.readFileSync(path.join(srcRoot, entry.name), 'utf8').trim(); continue; }
|
|
45
|
+
if (META.has(entry.name)) continue;
|
|
46
|
+
const dst = path.join(hooksDir, entry.name);
|
|
47
|
+
if (fs.existsSync(dst) && !force) { skipped.push(entry.name); continue; }
|
|
48
|
+
fs.copyFileSync(path.join(srcRoot, entry.name), dst);
|
|
49
|
+
installed.push(entry.name);
|
|
50
|
+
}
|
|
51
|
+
console.log(installed.length ? ` 安装到 .agents/hooks/:${installed.join('、')}` : ' 无新文件');
|
|
52
|
+
if (skipped.length) console.log(` 跳过(已存在,--force 覆盖):${skipped.join('、')}`);
|
|
53
|
+
|
|
54
|
+
// 接线 local-pre-commit(幂等:挂载行已存在不重复;旧装态尾部 exit 0 会吞掉其后门禁的失败——归一化剥掉)
|
|
55
|
+
if (wireLine) {
|
|
56
|
+
const lpPath = path.join(hooksDir, 'local-pre-commit');
|
|
57
|
+
const cur = fs.existsSync(lpPath) ? fs.readFileSync(lpPath, 'utf8') : '';
|
|
58
|
+
if (cur.split(/\r?\n/).some((l) => l.trim() === wireLine)) {
|
|
59
|
+
console.log(' local-pre-commit 已接线(幂等跳过)');
|
|
60
|
+
} else {
|
|
61
|
+
let lines = cur.split(/\r?\n/);
|
|
62
|
+
while (lines.length && lines[lines.length - 1].trim() === '') lines.pop();
|
|
63
|
+
if (lines.length && lines[lines.length - 1].trim() === 'exit 0') {
|
|
64
|
+
lines.pop();
|
|
65
|
+
console.log(' local-pre-commit 归一化:剥掉尾部 exit 0(旧装态,会吞掉其后门禁的失败)');
|
|
66
|
+
}
|
|
67
|
+
const body = lines.length ? `${lines.join('\n')}\n` : '#!/bin/sh\nset -e\n';
|
|
68
|
+
fs.writeFileSync(lpPath, `${body}${wireLine}\n`);
|
|
69
|
+
console.log(` local-pre-commit 接线:+ ${wireLine}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// owned 台账补记(归项目所有,sync 永不覆盖)
|
|
74
|
+
const ownedLedger = new Map((kit.owned || []).map((f) => [f.rel, f]));
|
|
75
|
+
for (const name of installed) {
|
|
76
|
+
const rel = `.agents/hooks/${name}`;
|
|
77
|
+
ownedLedger.set(rel, { rel, sha256: sha256(fs.readFileSync(path.join(hooksDir, name))) });
|
|
78
|
+
}
|
|
79
|
+
kit.owned = [...ownedLedger.values()].sort((a, b) => a.rel.localeCompare(b.rel));
|
|
80
|
+
fs.writeFileSync(kitPath, `${JSON.stringify(kit, null, 2)}\n`);
|
|
81
|
+
|
|
82
|
+
console.log('✅ 门禁已装(归项目所有,sync 不覆盖)。装后首个 commit 前先核对脚本头部 CONFIG 的目录/类型名——门禁 fail-closed,目标缺失会拦截提交。');
|
|
83
|
+
}
|
package/src/add-host.mjs
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// flow-kit add-host <宿主>:init 后补装宿主适配层。
|
|
2
|
+
// 渲染 modules/hosts/<h> → HOSTS[h].dir → managed 台账按 rel 去重补记 → options.hosts 补记
|
|
3
|
+
// → localOnly 宿主(zcode/omp).gitignore 追加 → doctor
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { renderTree } from './render.mjs';
|
|
7
|
+
import { HOSTS, pickStackVars } from './profiles.mjs';
|
|
8
|
+
import { doctor } from './doctor.mjs';
|
|
9
|
+
|
|
10
|
+
function fail(msg) {
|
|
11
|
+
console.error(`❌ ${msg}`);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function addHost(args, pkgRoot) {
|
|
16
|
+
const host = args[0];
|
|
17
|
+
if (!host || host.startsWith('--')) fail(`缺少宿主参数(可选 ${Object.keys(HOSTS).join(' | ')})`);
|
|
18
|
+
if (!HOSTS[host]) fail(`未知宿主:${host}(可选 ${Object.keys(HOSTS).join(' | ')})`);
|
|
19
|
+
|
|
20
|
+
let target = process.cwd();
|
|
21
|
+
let force = false;
|
|
22
|
+
for (let i = 1; i < args.length; i++) {
|
|
23
|
+
if (args[i] === '--dir') target = path.resolve(args[++i] || fail('--dir 缺少值'));
|
|
24
|
+
else if (args[i] === '--force') force = true;
|
|
25
|
+
else fail(`未知选项:${args[i]}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const kitPath = path.join(target, '.agents', 'kit.json');
|
|
29
|
+
if (!fs.existsSync(kitPath)) fail(`${target} 无 .agents/kit.json——先 flow-kit init`);
|
|
30
|
+
const kit = JSON.parse(fs.readFileSync(kitPath, 'utf8'));
|
|
31
|
+
kit.options = kit.options || {};
|
|
32
|
+
const already = Array.isArray(kit.options.hosts) && kit.options.hosts.includes(host);
|
|
33
|
+
if (already && !force) fail(`${host} 已安装(options.hosts 在册)。覆盖更新用 --force`);
|
|
34
|
+
|
|
35
|
+
const vars = { BOARD_PORT: String(kit.options.boardPort || '8933'), ...pickStackVars(kit.options.stack) };
|
|
36
|
+
console.log(`▶ flow-kit add-host ${host} → ${target}${already ? '(--force 覆盖更新)' : ''}`);
|
|
37
|
+
|
|
38
|
+
// 宿主层渲染(已存在保守跳过;--force 覆盖)
|
|
39
|
+
const r = renderTree(path.join(pkgRoot, 'modules', 'hosts', host), path.join(target, HOSTS[host].dir), vars, { force });
|
|
40
|
+
if (!r.written.length && r.skipped.length) console.log(' 跳过(已存在,未覆盖):' + r.skipped.join('、'));
|
|
41
|
+
else console.log(` 安装 ${r.written.length} 份${r.skipped.length ? `,跳过已存在 ${r.skipped.length} 份` : ''}`);
|
|
42
|
+
|
|
43
|
+
// managed 台账按 rel 去重补记
|
|
44
|
+
const ledger = new Map((kit.managed || []).map((f) => [f.rel, f]));
|
|
45
|
+
for (const f of r.written) ledger.set(`${HOSTS[host].dir}/${f.rel}`, { rel: `${HOSTS[host].dir}/${f.rel}`, sha256: f.sha256 });
|
|
46
|
+
kit.managed = [...ledger.values()].sort((a, b) => a.rel.localeCompare(b.rel));
|
|
47
|
+
if (!already) kit.options.hosts = [...(kit.options.hosts || []), host];
|
|
48
|
+
fs.writeFileSync(kitPath, `${JSON.stringify(kit, null, 2)}\n`);
|
|
49
|
+
|
|
50
|
+
// localOnly 宿主进 .gitignore(缺才补)
|
|
51
|
+
if (HOSTS[host].localOnly) {
|
|
52
|
+
const giPath = path.join(target, '.gitignore');
|
|
53
|
+
const line = `${HOSTS[host].dir}/`;
|
|
54
|
+
const giText = fs.existsSync(giPath) ? fs.readFileSync(giPath, 'utf8') : '';
|
|
55
|
+
if (!giText.split(/\r?\n/).some((x) => x.trim() === line)) {
|
|
56
|
+
fs.writeFileSync(giPath, (giText.endsWith('\n') || giText === '' ? giText : giText + '\n') + line + '\n');
|
|
57
|
+
console.log(` .gitignore 追加:${line}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log('▶ flow-kit doctor');
|
|
62
|
+
doctor(['--dir', target], pkgRoot);
|
|
63
|
+
}
|
package/src/cli.mjs
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// flow-kit CLI 入口:init(安装)/ sync(升级)/ add-host(补宿主)/ add-gate(装门禁)/ doctor(体检)/ help
|
|
3
|
+
import { init } from './init.mjs';
|
|
4
|
+
import { doctor } from './doctor.mjs';
|
|
5
|
+
import { sync } from './sync.mjs';
|
|
6
|
+
import { addHost } from './add-host.mjs';
|
|
7
|
+
import { addGate } from './add-gate.mjs';
|
|
8
|
+
import { readFileSync } from 'node:fs';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
|
|
12
|
+
const PKG_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
13
|
+
|
|
14
|
+
export const HELP = `flow-kit — AI-Native 闭环工作流 + wiki 知识层脚手架(agentic-flow-kit)
|
|
15
|
+
|
|
16
|
+
用法:
|
|
17
|
+
flow-kit init 安装到当前项目(裸跑无参数 → 交互确认环节;带参数直接执行)
|
|
18
|
+
flow-kit sync 升级 managed 文件(未改动→覆盖新版;本地已改→跳过并报告,--force 才覆盖;
|
|
19
|
+
INDEX/看板等生成器目标重跑锚点重写;owned 文件永不触碰)
|
|
20
|
+
flow-kit add-host <宿主> 后补宿主适配层:zcode | opencode | trae | omp
|
|
21
|
+
flow-kit add-gate <门禁> 装门禁模块并接线 local-pre-commit(当前:dotnet-ca;装后归项目所有)
|
|
22
|
+
flow-kit doctor 体检:目录布局 / git 钩子 / managed 清单 / 索引漂移 / check-loop
|
|
23
|
+
|
|
24
|
+
init 选项(全部可选,均有默认值):
|
|
25
|
+
--stack <技术栈> dotnet | node | python | go | none(默认 none)——门禁配置三处:commit-check
|
|
26
|
+
条件编译检查(builds)与质量检测(checks:lint/类型/vet 等秒级确定性检查,
|
|
27
|
+
有对应配置文件才启用)、settings.json 自检验命令权限(allow)、
|
|
28
|
+
AGENTS.md「项目适配区」命令预填
|
|
29
|
+
--hosts <宿主列表> 逗号分隔:zcode,opencode,trae,omp(默认 zcode;zcode/omp 为本地配置,自动进 .gitignore)
|
|
30
|
+
--board-port <端口> workflow 看板端口(默认 8933)
|
|
31
|
+
--dir <目录> 目标项目根(默认当前目录)
|
|
32
|
+
--force 覆盖已存在的同名文件(默认保守跳过)
|
|
33
|
+
|
|
34
|
+
sync / add-host 选项:--dir <目录>、--force(覆盖本地已改 / 已装内容);add-gate 选项:--dir <目录>、--force。
|
|
35
|
+
|
|
36
|
+
示例:
|
|
37
|
+
npx agentic-flow-kit init # 交互模式:宿主 → 技术栈 → 端口 → 确认安装
|
|
38
|
+
npx agentic-flow-kit init --stack node --hosts zcode,opencode
|
|
39
|
+
npx agentic-flow-kit sync # 包出新版后升级(本地改过的 managed 文件会跳过并报告)
|
|
40
|
+
npx agentic-flow-kit add-host opencode # 后补宿主
|
|
41
|
+
npx agentic-flow-kit add-gate dotnet-ca # 装 Clean Architecture 门禁
|
|
42
|
+
|
|
43
|
+
装完即自包含:项目不依赖本包运行;AGENTS.md 生成「AI工作流 + Wiki」骨架,项目细节在「项目适配区」自填。`;
|
|
44
|
+
|
|
45
|
+
export function run(argv) {
|
|
46
|
+
const cmd = argv[0];
|
|
47
|
+
if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
|
|
48
|
+
console.log(HELP);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (cmd === 'version' || cmd === '--version' || cmd === '-v') {
|
|
52
|
+
const pkg = JSON.parse(readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8'));
|
|
53
|
+
console.log(pkg.version);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (cmd === 'init') {
|
|
57
|
+
init(argv.slice(1), PKG_ROOT).catch((e) => {
|
|
58
|
+
console.error(`❌ ${e?.message || e}`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
});
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (cmd === 'sync') {
|
|
64
|
+
sync(argv.slice(1), PKG_ROOT);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (cmd === 'add-host') {
|
|
68
|
+
addHost(argv.slice(1), PKG_ROOT);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (cmd === 'add-gate') {
|
|
72
|
+
addGate(argv.slice(1), PKG_ROOT);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (cmd === 'doctor') {
|
|
76
|
+
doctor(argv.slice(1), PKG_ROOT);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
console.error(`未知命令:${cmd}\n\n${HELP}`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|