@brawnen/agent-harness-cli 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -4
- package/README.zh-CN.md +16 -4
- package/package.json +12 -4
- package/src/commands/docs.js +19 -13
- package/src/commands/init.js +64 -11
- package/src/commands/report.js +4 -4
- package/src/commands/status.js +102 -61
- package/src/commands/sync.js +88 -0
- package/src/index.js +9 -3
- package/src/lib/hook-core.js +26 -3
- package/src/lib/host-layout.js +1384 -0
- package/src/lib/output-policy.js +6 -6
- package/src/lib/task-core.js +83 -19
- package/src/runtime-host/index.js +57 -0
package/README.md
CHANGED
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
[中文](README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
`@brawnen/agent-harness-cli` is the
|
|
5
|
+
`@brawnen/agent-harness-cli` is the compatibility CLI for `agent-harness`.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Its focus is no longer to be the long-term product center. It is the compatibility layer for:
|
|
8
8
|
|
|
9
9
|
- initialization
|
|
10
|
+
- host/runtime bootstrap
|
|
11
|
+
- status inspection
|
|
12
|
+
- manual fallback commands
|
|
10
13
|
- task state
|
|
11
14
|
- verification
|
|
12
15
|
- reports
|
|
@@ -15,6 +18,10 @@ It is the runtime layer that turns protocol rules into a working task loop with:
|
|
|
15
18
|
- delivery
|
|
16
19
|
- documentation scaffolding
|
|
17
20
|
|
|
21
|
+
The repo-local host runtime is becoming the primary execution surface. The CLI remains for initialization, diagnostics, and explicit manual operations.
|
|
22
|
+
|
|
23
|
+
This package is now in maintenance mode. It remains publishable and usable, but it is no longer intended to grow into the long-term product center.
|
|
24
|
+
|
|
18
25
|
## Current Coverage
|
|
19
26
|
|
|
20
27
|
The CLI currently includes these commands:
|
|
@@ -42,7 +49,7 @@ The current implementation also covers:
|
|
|
42
49
|
|
|
43
50
|
## Current Boundaries
|
|
44
51
|
|
|
45
|
-
The CLI is already usable, but it
|
|
52
|
+
The CLI is already usable, but it should now be treated as a bounded compatibility layer rather than an expanding surface.
|
|
46
53
|
|
|
47
54
|
It currently does not try to provide:
|
|
48
55
|
|
|
@@ -51,6 +58,12 @@ It currently does not try to provide:
|
|
|
51
58
|
- automatic `git push`
|
|
52
59
|
- a deep upgrader / migration system
|
|
53
60
|
|
|
61
|
+
Maintenance-mode interpretation:
|
|
62
|
+
|
|
63
|
+
- keep it usable for bootstrap, diagnostics, and manual fallback
|
|
64
|
+
- accept bug fixes, compatibility fixes, and documentation clarification
|
|
65
|
+
- do not keep expanding host-specific wrapper behavior
|
|
66
|
+
|
|
54
67
|
## What The CLI Does Today
|
|
55
68
|
|
|
56
69
|
### `init`
|
|
@@ -226,6 +239,8 @@ Useful local checks:
|
|
|
226
239
|
```bash
|
|
227
240
|
npm --prefix packages/cli run verify:task-core
|
|
228
241
|
npm --prefix packages/cli run verify:codex-e2e
|
|
242
|
+
npm --prefix packages/cli run verify:host-hooks
|
|
243
|
+
npm --prefix packages/cli run verify:init-status
|
|
229
244
|
```
|
|
230
245
|
|
|
231
246
|
From the repository root:
|
|
@@ -233,8 +248,14 @@ From the repository root:
|
|
|
233
248
|
```bash
|
|
234
249
|
npm run codex:hooks:check
|
|
235
250
|
npm run codex:e2e
|
|
251
|
+
npm run runtime:host-hooks
|
|
252
|
+
npm run runtime:init-status
|
|
253
|
+
npm run runtime:p1:check
|
|
254
|
+
node packages/cli/bin/agent-harness.js sync --check
|
|
236
255
|
```
|
|
237
256
|
|
|
257
|
+
`codex:hooks:check` validates the source-of-truth hook files under `.harness/hosts/codex/hooks/`, not the generated root `.codex/` shell.
|
|
258
|
+
|
|
238
259
|
## Local Invocation Note
|
|
239
260
|
|
|
240
261
|
If your current working directory is not the repository root, prefer calling the CLI with an absolute path, for example:
|
|
@@ -251,4 +272,5 @@ Do not assume `node packages/cli/bin/agent-harness.js ...` will work from an arb
|
|
|
251
272
|
- installing `@brawnen/agent-harness-cli` from npm should automatically pull `@brawnen/agent-harness-protocol`
|
|
252
273
|
- the protocol must not depend on the CLI
|
|
253
274
|
- the default npm entrypoint is `npx @brawnen/agent-harness-cli init`
|
|
254
|
-
- this repository
|
|
275
|
+
- this repository now treats the Node.js CLI as the compatibility layer of `Agent Harness Runtime`
|
|
276
|
+
- repo-local hooks should now consume the stable runtime entry `@brawnen/agent-harness-cli/runtime-host`
|
package/README.zh-CN.md
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md)
|
|
4
4
|
|
|
5
|
-
这是 `agent-harness`
|
|
5
|
+
这是 `agent-harness` 的兼容 CLI。
|
|
6
|
+
|
|
7
|
+
当前产品方向已经明确:宿主运行时逐步前移到 repo-local hooks,`CLI` 主要负责初始化、状态检查、人工 fallback 和显式交付动作。
|
|
8
|
+
这个包现在已经进入维护态:保持可用、可发布、可诊断,但不再继续向长期主产品方向扩张。
|
|
6
9
|
|
|
7
10
|
当前已完成:
|
|
8
11
|
|
|
@@ -22,9 +25,11 @@
|
|
|
22
25
|
- 基础项目配置生成
|
|
23
26
|
- `.codex/hooks.json` 最小方案
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
当前边界:
|
|
26
29
|
|
|
27
|
-
-
|
|
30
|
+
- 继续支持初始化、状态检查、验证、报告和人工 fallback
|
|
31
|
+
- 接受必要的 bug fix、兼容修复和文档澄清
|
|
32
|
+
- 不再继续扩大宿主专属 wrapper 行为
|
|
28
33
|
|
|
29
34
|
`init` MVP 当前负责:
|
|
30
35
|
|
|
@@ -218,7 +223,13 @@ node /abs/path/to/agent-harness/packages/cli/bin/agent-harness.js audit read --t
|
|
|
218
223
|
Codex E2E 回归:
|
|
219
224
|
|
|
220
225
|
- 可执行 `npm --prefix packages/cli run verify:codex-e2e`
|
|
226
|
+
- 可执行 `npm --prefix packages/cli run verify:host-hooks`
|
|
227
|
+
- 可执行 `npm --prefix packages/cli run verify:init-status`
|
|
228
|
+
- 可执行 `npm --prefix packages/cli run verify:status-compat`
|
|
221
229
|
- 或在仓库根目录执行 `npm run codex:e2e`
|
|
230
|
+
- 或在仓库根目录执行 `npm run runtime:host-hooks`
|
|
231
|
+
- 或在仓库根目录执行 `npm run runtime:init-status`
|
|
232
|
+
- 或在仓库根目录执行 `npm run runtime:p1:check`
|
|
222
233
|
- 当前最小回归覆盖:新任务自动 intake、follow-up 不误切、高风险确认链路、hook 降级提示
|
|
223
234
|
- 当前回归采用“真实 `codex exec` smoke + hook 主链路回归”混合方式,优先验证我们自己的接入链路
|
|
224
235
|
- 当前脚本会在**当前 trusted 仓库**里执行真实 Codex 回归,并清理自己创建的 task/audit/report 文件
|
|
@@ -229,4 +240,5 @@ Codex E2E 回归:
|
|
|
229
240
|
- CLI 依赖 `@brawnen/agent-harness-protocol`
|
|
230
241
|
- 从 npm 安装 `@brawnen/agent-harness-cli` 时,应自动带上 `@brawnen/agent-harness-protocol`
|
|
231
242
|
- 默认 npm 入口是 `npx @brawnen/agent-harness-cli init`
|
|
232
|
-
-
|
|
243
|
+
- 当前仓库将 Node.js CLI 视为 `Agent Harness Runtime` 的 compatibility layer
|
|
244
|
+
- repo-local hooks 当前应通过 `@brawnen/agent-harness-cli/runtime-host` 这一稳定入口接入 runtime
|
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brawnen/agent-harness-cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "CLI for
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Compatibility CLI for bootstrap, status, and manual fallback in agent-harness projects.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
7
7
|
"agent-harness": "./bin/agent-harness.js"
|
|
8
8
|
},
|
|
9
|
+
"exports": {
|
|
10
|
+
"./runtime-host": "./src/runtime-host/index.js"
|
|
11
|
+
},
|
|
9
12
|
"type": "module",
|
|
10
13
|
"files": [
|
|
11
14
|
"bin",
|
|
@@ -35,10 +38,15 @@
|
|
|
35
38
|
"access": "public"
|
|
36
39
|
},
|
|
37
40
|
"dependencies": {
|
|
38
|
-
"@brawnen/agent-harness-protocol": "^0.1.
|
|
41
|
+
"@brawnen/agent-harness-protocol": "^0.1.2"
|
|
39
42
|
},
|
|
40
43
|
"scripts": {
|
|
41
|
-
"start": "node ./bin/agent-harness.js --help"
|
|
44
|
+
"start": "node ./bin/agent-harness.js --help",
|
|
45
|
+
"verify:codex-e2e": "node ./scripts/verify-codex-e2e.js",
|
|
46
|
+
"verify:host-hooks": "node ./scripts/verify-host-hooks-smoke.js",
|
|
47
|
+
"verify:init-status": "node ./scripts/verify-host-init-status.js",
|
|
48
|
+
"verify:status-compat": "node ./scripts/verify-sync-status-compat.js",
|
|
49
|
+
"verify:task-core": "node ./scripts/verify-task-core-classification.js"
|
|
42
50
|
},
|
|
43
51
|
"engines": {
|
|
44
52
|
"node": ">=18"
|
package/src/commands/docs.js
CHANGED
|
@@ -154,31 +154,33 @@ function buildDesignNoteTemplate(title, context) {
|
|
|
154
154
|
return [
|
|
155
155
|
`# ${title}`,
|
|
156
156
|
"",
|
|
157
|
-
"##
|
|
157
|
+
"## 决策摘要",
|
|
158
158
|
"",
|
|
159
159
|
`- task_id: \`${context.task_id}\``,
|
|
160
160
|
`- intent: \`${context.intent}\``,
|
|
161
161
|
`- risk_level: \`${context.risk_level}\``,
|
|
162
|
+
context.goal ? `- goal: ${context.goal}` : "- goal: 待补充",
|
|
162
163
|
"",
|
|
163
|
-
"##
|
|
164
|
+
"## 要解决的问题",
|
|
164
165
|
"",
|
|
165
|
-
|
|
166
|
+
"- 问题:待补充",
|
|
166
167
|
"",
|
|
167
|
-
"##
|
|
168
|
+
"## 采用方案",
|
|
168
169
|
"",
|
|
169
|
-
|
|
170
|
+
"- 方案:待补充",
|
|
170
171
|
"",
|
|
171
|
-
"##
|
|
172
|
+
"## 为什么选这个方案",
|
|
172
173
|
"",
|
|
173
|
-
"-
|
|
174
|
+
"- 原因:待补充",
|
|
174
175
|
"",
|
|
175
|
-
"##
|
|
176
|
+
"## 边界与风险",
|
|
176
177
|
"",
|
|
177
|
-
|
|
178
|
+
...toBulletLines(context.scope),
|
|
179
|
+
"- 风险与权衡:待补充",
|
|
178
180
|
"",
|
|
179
|
-
"##
|
|
181
|
+
"## 验证",
|
|
180
182
|
"",
|
|
181
|
-
"-
|
|
183
|
+
"- 验证方式:待补充",
|
|
182
184
|
""
|
|
183
185
|
].join("\n");
|
|
184
186
|
}
|
|
@@ -200,9 +202,13 @@ function buildAdrTemplate(title, context) {
|
|
|
200
202
|
"",
|
|
201
203
|
"## 决策",
|
|
202
204
|
"",
|
|
203
|
-
"-
|
|
205
|
+
"- 决策内容:待补充",
|
|
206
|
+
"",
|
|
207
|
+
"## 备选方案",
|
|
208
|
+
"",
|
|
209
|
+
"- 备选方案及放弃原因:待补充",
|
|
204
210
|
"",
|
|
205
|
-
"##
|
|
211
|
+
"## 代价与后果",
|
|
206
212
|
"",
|
|
207
213
|
"- 正面影响:待补充",
|
|
208
214
|
"- 代价与风险:待补充",
|
package/src/commands/init.js
CHANGED
|
@@ -3,9 +3,10 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
|
|
6
|
+
import { collectHostLayoutWrites } from "../lib/host-layout.js";
|
|
6
7
|
import { DEFAULT_RUNTIME_DIR, defaultRuntimeRelativePath } from "../lib/runtime-paths.js";
|
|
7
8
|
|
|
8
|
-
const CLI_VERSION = "0.1.
|
|
9
|
+
const CLI_VERSION = "0.1.2";
|
|
9
10
|
const RULE_MODES = new Set(["base", "full"]);
|
|
10
11
|
const HOSTS = new Set(["auto", "claude-code", "codex", "gemini-cli"]);
|
|
11
12
|
const MODES = new Set(["delivery", "explore", "poc"]);
|
|
@@ -36,16 +37,19 @@ export function runInit(argv) {
|
|
|
36
37
|
const project = detectProject(cwd);
|
|
37
38
|
const hosts = resolveHosts(cwd, parsed.options.host);
|
|
38
39
|
const actions = [];
|
|
40
|
+
const warnings = [];
|
|
39
41
|
|
|
40
42
|
queueInitActions({
|
|
41
43
|
cwd,
|
|
42
44
|
project,
|
|
43
45
|
hosts,
|
|
44
46
|
options: parsed.options,
|
|
45
|
-
actions
|
|
47
|
+
actions,
|
|
48
|
+
warnings
|
|
46
49
|
});
|
|
47
50
|
|
|
48
51
|
printPlan(actions, parsed.options.dryRun, cwd, project, hosts);
|
|
52
|
+
printWarnings(warnings);
|
|
49
53
|
|
|
50
54
|
if (parsed.options.dryRun) {
|
|
51
55
|
return 0;
|
|
@@ -277,8 +281,7 @@ function resolveHosts(cwd, explicitHost) {
|
|
|
277
281
|
}
|
|
278
282
|
|
|
279
283
|
function queueInitActions(context) {
|
|
280
|
-
const { actions, cwd, hosts, options, project } = context;
|
|
281
|
-
const ruleText = readText(path.join(PROTOCOL_ROOT, "rules", `${options.rules}.md`));
|
|
284
|
+
const { actions, cwd, hosts, options, project, warnings } = context;
|
|
282
285
|
|
|
283
286
|
queueWriteAction({
|
|
284
287
|
actions,
|
|
@@ -288,16 +291,34 @@ function queueInitActions(context) {
|
|
|
288
291
|
});
|
|
289
292
|
|
|
290
293
|
queueProtocolTemplates(actions, cwd, options.force);
|
|
291
|
-
|
|
294
|
+
queueHostLayoutActions(actions, warnings, cwd, hosts, options);
|
|
292
295
|
|
|
293
296
|
if (!options.protocolOnly) {
|
|
294
297
|
queueRuntimeFiles(actions, cwd);
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function queueHostLayoutActions(actions, warnings, cwd, hosts, options) {
|
|
302
|
+
const layout = collectHostLayoutWrites(cwd, {
|
|
303
|
+
hosts,
|
|
304
|
+
includeConfigs: !options.protocolOnly,
|
|
305
|
+
includeRules: true,
|
|
306
|
+
rewrite: options.force,
|
|
307
|
+
seedMissing: true
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
warnings.push(...layout.warnings);
|
|
311
|
+
|
|
312
|
+
for (const write of layout.writes) {
|
|
313
|
+
actions.push({
|
|
314
|
+
description: describeHostLayoutWrite(write),
|
|
315
|
+
relativePath: path.relative(cwd, write.targetPath),
|
|
316
|
+
run: () => {
|
|
317
|
+
ensureDirectory(path.dirname(write.targetPath));
|
|
318
|
+
fs.writeFileSync(write.targetPath, write.content, "utf8");
|
|
319
|
+
},
|
|
320
|
+
skip: false
|
|
321
|
+
});
|
|
301
322
|
}
|
|
302
323
|
}
|
|
303
324
|
|
|
@@ -688,6 +709,38 @@ function printPlan(actions, dryRun, cwd, project, hosts) {
|
|
|
688
709
|
}
|
|
689
710
|
}
|
|
690
711
|
|
|
712
|
+
function printWarnings(warnings) {
|
|
713
|
+
if (!Array.isArray(warnings) || warnings.length === 0) {
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
console.log("");
|
|
718
|
+
console.log("提示:");
|
|
719
|
+
for (const warning of warnings) {
|
|
720
|
+
console.log(` - ${warning}`);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function describeHostLayoutWrite(write) {
|
|
725
|
+
if (write.type === "source") {
|
|
726
|
+
return "写入收敛布局源文件";
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
if (write.type === "host") {
|
|
730
|
+
return "生成宿主薄壳配置";
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
if (write.type === "rule") {
|
|
734
|
+
return "生成宿主规则文件";
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
if (write.type === "generated") {
|
|
738
|
+
return "生成布局清单";
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
return "写入布局文件";
|
|
742
|
+
}
|
|
743
|
+
|
|
691
744
|
function readText(filePath) {
|
|
692
745
|
return fs.readFileSync(filePath, "utf8");
|
|
693
746
|
}
|
package/src/commands/report.js
CHANGED
|
@@ -256,15 +256,15 @@ function buildMissingArtifactsMessage(taskId, error, outputPolicy) {
|
|
|
256
256
|
|
|
257
257
|
if (artifact === "design_note") {
|
|
258
258
|
const suggestedPath = path.posix.join(outputPolicy.design_note.directory, `${taskId}-design-note.md`);
|
|
259
|
-
lines.push(`- design_note:
|
|
260
|
-
lines.push(`
|
|
259
|
+
lines.push(`- design_note: 当前任务命中 design note 条件。若尚未沉淀设计决策,可执行 \`node packages/cli/bin/agent-harness.js docs scaffold --type design-note --task-id ${taskId} --path ${suggestedPath}\``);
|
|
260
|
+
lines.push(` 完成后在 report 中补上 \`--design-note ${suggestedPath}\``);
|
|
261
261
|
continue;
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
if (artifact === "adr") {
|
|
265
265
|
const suggestedPath = path.posix.join(outputPolicy.adr.directory, `${taskId}-adr.md`);
|
|
266
|
-
lines.push(`- adr:
|
|
267
|
-
lines.push(`
|
|
266
|
+
lines.push(`- adr: 当前任务命中 ADR 条件。若尚未记录正式决策,可执行 \`node packages/cli/bin/agent-harness.js docs scaffold --type adr --task-id ${taskId} --path ${suggestedPath}\``);
|
|
267
|
+
lines.push(` 完成后在 report 中补上 \`--adr ${suggestedPath}\``);
|
|
268
268
|
}
|
|
269
269
|
}
|
|
270
270
|
|
package/src/commands/status.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
4
|
import { evaluateTaskDeliveryReadiness, summarizeDeliveryReadiness } from "../lib/delivery-policy.js";
|
|
5
|
+
import { hasConvergedHostLayout, HOST_LAYOUT_VERSION } from "../lib/host-layout.js";
|
|
5
6
|
import { evaluateTaskArtifactPolicy, inspectOutputPolicyWorkspace, normalizeOutputPolicy } from "../lib/output-policy.js";
|
|
6
7
|
import { loadProjectConfig } from "../lib/project-config.js";
|
|
7
8
|
import {
|
|
@@ -36,6 +37,10 @@ export function runStatus(argv) {
|
|
|
36
37
|
pushCheck(checks, harnessConfig);
|
|
37
38
|
exitCode = maxExitCode(exitCode, harnessConfig.severity);
|
|
38
39
|
|
|
40
|
+
const hostLayoutCheck = inspectHostLayout(cwd);
|
|
41
|
+
pushCheck(checks, hostLayoutCheck);
|
|
42
|
+
exitCode = maxExitCode(exitCode, hostLayoutCheck.severity);
|
|
43
|
+
|
|
39
44
|
const deliveryPolicyCheck = inspectDeliveryPolicy(cwd);
|
|
40
45
|
pushCheck(checks, deliveryPolicyCheck);
|
|
41
46
|
exitCode = maxExitCode(exitCode, deliveryPolicyCheck.severity);
|
|
@@ -144,6 +149,28 @@ function inspectDeliveryPolicy(cwd) {
|
|
|
144
149
|
return ok("delivery_policy", `active_task=${activeTask.task_id};${summarizeDeliveryReadiness(readiness)}`);
|
|
145
150
|
}
|
|
146
151
|
|
|
152
|
+
function inspectHostLayout(cwd) {
|
|
153
|
+
if (!hasConvergedHostLayout(cwd)) {
|
|
154
|
+
return warn("host_layout", "未检测到 .harness/hosts 与 .harness/rules,当前仍可能处于旧布局");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const manifestPath = path.join(cwd, ".harness", "generated", "manifest.json");
|
|
158
|
+
if (!fs.existsSync(manifestPath)) {
|
|
159
|
+
return warn("host_layout", "已检测到收敛布局源目录,但缺少 .harness/generated/manifest.json");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
164
|
+
const version = manifest.version ?? HOST_LAYOUT_VERSION;
|
|
165
|
+
const hosts = Array.isArray(manifest.hosts) && manifest.hosts.length > 0
|
|
166
|
+
? manifest.hosts.join(", ")
|
|
167
|
+
: "unknown";
|
|
168
|
+
return ok("host_layout", `已启用收敛布局(version=${version}, hosts=${hosts})`);
|
|
169
|
+
} catch {
|
|
170
|
+
return warn("host_layout", ".harness/generated/manifest.json 存在,但 JSON 解析失败");
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
147
174
|
function inspectOutputPolicy(cwd) {
|
|
148
175
|
const config = loadProjectConfig(cwd);
|
|
149
176
|
if (!config) {
|
|
@@ -359,36 +386,41 @@ function inspectClaudeHooks(cwd, runtimeMode, hasClaudeHost) {
|
|
|
359
386
|
.map((hook) => hook.command)
|
|
360
387
|
.filter(Boolean);
|
|
361
388
|
|
|
362
|
-
const hasPreTool = commands
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
389
|
+
const hasPreTool = hasCommandFragment(commands, [
|
|
390
|
+
"agent-harness gate before-tool",
|
|
391
|
+
"@brawnen/agent-harness-cli gate before-tool",
|
|
392
|
+
"packages/cli/bin/agent-harness.js\" gate before-tool",
|
|
393
|
+
"packages/cli/bin/agent-harness.js gate before-tool",
|
|
394
|
+
".harness/hosts/claude/hooks/pre_tool_use.js"
|
|
395
|
+
]);
|
|
396
|
+
const hasPostTool = hasCommandFragment(commands, [
|
|
397
|
+
"agent-harness state update",
|
|
398
|
+
"@brawnen/agent-harness-cli state update",
|
|
399
|
+
"packages/cli/bin/agent-harness.js\" state update",
|
|
400
|
+
"packages/cli/bin/agent-harness.js state update",
|
|
401
|
+
".harness/hosts/claude/hooks/post_tool_use.js"
|
|
402
|
+
]);
|
|
403
|
+
const hasSessionStart = hasCommandFragment(commands, [
|
|
404
|
+
"agent-harness hook claude session-start",
|
|
405
|
+
"@brawnen/agent-harness-cli hook claude session-start",
|
|
406
|
+
"packages/cli/bin/agent-harness.js\" hook claude session-start",
|
|
407
|
+
"packages/cli/bin/agent-harness.js hook claude session-start",
|
|
408
|
+
".harness/hosts/claude/hooks/session_start.js"
|
|
409
|
+
]);
|
|
410
|
+
const hasUserPromptSubmit = hasCommandFragment(commands, [
|
|
411
|
+
"agent-harness hook claude user-prompt-submit",
|
|
412
|
+
"@brawnen/agent-harness-cli hook claude user-prompt-submit",
|
|
413
|
+
"packages/cli/bin/agent-harness.js\" hook claude user-prompt-submit",
|
|
414
|
+
"packages/cli/bin/agent-harness.js hook claude user-prompt-submit",
|
|
415
|
+
".harness/hosts/claude/hooks/user_prompt_submit.js"
|
|
416
|
+
]);
|
|
417
|
+
const hasStop = hasCommandFragment(commands, [
|
|
418
|
+
"agent-harness hook claude stop",
|
|
419
|
+
"@brawnen/agent-harness-cli hook claude stop",
|
|
420
|
+
"packages/cli/bin/agent-harness.js\" hook claude stop",
|
|
421
|
+
"packages/cli/bin/agent-harness.js hook claude stop",
|
|
422
|
+
".harness/hosts/claude/hooks/stop.js"
|
|
423
|
+
]);
|
|
392
424
|
|
|
393
425
|
if (hasSessionStart && hasUserPromptSubmit && hasPreTool && hasPostTool && hasStop) {
|
|
394
426
|
return ok(".claude/settings.json", "Claude Code hooks 已配置(SessionStart / UserPromptSubmit / PreToolUse / PostToolUse / Stop)");
|
|
@@ -433,40 +465,45 @@ function inspectGeminiAdapter(cwd, runtimeMode, hasGeminiHost) {
|
|
|
433
465
|
.map((hook) => hook.command)
|
|
434
466
|
.filter(Boolean);
|
|
435
467
|
|
|
436
|
-
const hasSessionStart = commands
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
468
|
+
const hasSessionStart = hasCommandFragment(commands, [
|
|
469
|
+
"agent-harness hook gemini session-start",
|
|
470
|
+
"@brawnen/agent-harness-cli hook gemini session-start",
|
|
471
|
+
"packages/cli/bin/agent-harness.js\" hook gemini session-start",
|
|
472
|
+
"packages/cli/bin/agent-harness.js hook gemini session-start",
|
|
473
|
+
".harness/hosts/gemini/hooks/session_start.js"
|
|
474
|
+
]);
|
|
475
|
+
const hasBeforeAgent = hasCommandFragment(commands, [
|
|
476
|
+
"agent-harness hook gemini before-agent",
|
|
477
|
+
"@brawnen/agent-harness-cli hook gemini before-agent",
|
|
478
|
+
"packages/cli/bin/agent-harness.js\" hook gemini before-agent",
|
|
479
|
+
"packages/cli/bin/agent-harness.js hook gemini before-agent",
|
|
480
|
+
".harness/hosts/gemini/hooks/before_agent.js"
|
|
481
|
+
]);
|
|
482
|
+
const hasBeforeTool = hasCommandFragment(commands, [
|
|
483
|
+
"agent-harness hook gemini before-tool",
|
|
484
|
+
"@brawnen/agent-harness-cli hook gemini before-tool",
|
|
485
|
+
"packages/cli/bin/agent-harness.js\" hook gemini before-tool",
|
|
486
|
+
"packages/cli/bin/agent-harness.js hook gemini before-tool",
|
|
487
|
+
".harness/hosts/gemini/hooks/before_tool.js"
|
|
488
|
+
]);
|
|
489
|
+
const hasAfterTool = hasCommandFragment(commands, [
|
|
490
|
+
"agent-harness hook gemini after-tool",
|
|
491
|
+
"@brawnen/agent-harness-cli hook gemini after-tool",
|
|
492
|
+
"packages/cli/bin/agent-harness.js\" hook gemini after-tool",
|
|
493
|
+
"packages/cli/bin/agent-harness.js hook gemini after-tool",
|
|
494
|
+
".harness/hosts/gemini/hooks/after_tool.js"
|
|
495
|
+
]);
|
|
496
|
+
const hasAfterAgent = hasCommandFragment(commands, [
|
|
497
|
+
"agent-harness hook gemini after-agent",
|
|
498
|
+
"@brawnen/agent-harness-cli hook gemini after-agent",
|
|
499
|
+
"packages/cli/bin/agent-harness.js\" hook gemini after-agent",
|
|
500
|
+
"packages/cli/bin/agent-harness.js hook gemini after-agent",
|
|
501
|
+
".harness/hosts/gemini/hooks/after_agent.js"
|
|
502
|
+
]);
|
|
466
503
|
|
|
467
504
|
const modeSummary = runtimeMode === "protocol-only"
|
|
468
505
|
? "当前为 protocol-only,但 hooks 已可用"
|
|
469
|
-
: "已检测到 .harness 运行时目录,可配合 hooks
|
|
506
|
+
: "已检测到 .harness 运行时目录,可配合 hooks 与兼容 CLI 做 state / verify / report";
|
|
470
507
|
|
|
471
508
|
if (hasSessionStart && hasBeforeAgent && hasBeforeTool && hasAfterTool && hasAfterAgent) {
|
|
472
509
|
return ok("Gemini adapter", `Gemini CLI hooks 已配置(SessionStart / BeforeAgent / BeforeTool / AfterTool / AfterAgent);${modeSummary}`);
|
|
@@ -486,6 +523,10 @@ function hasCodexHookCommand(parsedHooks, eventName, commandFragment) {
|
|
|
486
523
|
.some((hook) => typeof hook?.command === "string" && hook.command.includes(commandFragment));
|
|
487
524
|
}
|
|
488
525
|
|
|
526
|
+
function hasCommandFragment(commands, fragments) {
|
|
527
|
+
return commands.some((command) => fragments.some((fragment) => command.includes(fragment)));
|
|
528
|
+
}
|
|
529
|
+
|
|
489
530
|
function inspectRuntimeDirectories(cwd, runtimeMode) {
|
|
490
531
|
if (runtimeMode === "protocol-only") {
|
|
491
532
|
return skip("runtime", "protocol-only 模式,无需运行时目录");
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { applyHostLayoutWrites, collectHostLayoutWrites, HOST_LAYOUT_HOSTS } from "../lib/host-layout.js";
|
|
2
|
+
|
|
3
|
+
export function runSync(argv) {
|
|
4
|
+
const parsed = parseSyncArgs(argv);
|
|
5
|
+
if (!parsed.ok) {
|
|
6
|
+
console.error(parsed.error);
|
|
7
|
+
return 1;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const cwd = process.cwd();
|
|
11
|
+
const result = collectHostLayoutWrites(cwd, {
|
|
12
|
+
check: parsed.options.check,
|
|
13
|
+
hosts: parsed.options.hosts,
|
|
14
|
+
rewrite: parsed.options.rewrite,
|
|
15
|
+
seedMissing: true
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
for (const warning of result.warnings) {
|
|
19
|
+
console.error(`warn: ${warning}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (parsed.options.check) {
|
|
23
|
+
if (result.warnings.length === 0 && result.writes.length === 0) {
|
|
24
|
+
console.log("host layout 已同步");
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
for (const write of result.writes) {
|
|
29
|
+
console.log(`drift: ${write.relativePath}`);
|
|
30
|
+
}
|
|
31
|
+
return 1;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
applyHostLayoutWrites(result.writes);
|
|
35
|
+
|
|
36
|
+
for (const write of result.writes) {
|
|
37
|
+
console.log(`write: ${write.relativePath}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (result.warnings.length > 0) {
|
|
41
|
+
return 1;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log("sync 完成。");
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseSyncArgs(argv) {
|
|
49
|
+
const options = {
|
|
50
|
+
check: false,
|
|
51
|
+
hosts: null,
|
|
52
|
+
rewrite: false
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const hosts = [];
|
|
56
|
+
|
|
57
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
58
|
+
const arg = argv[index];
|
|
59
|
+
|
|
60
|
+
if (arg === "--check") {
|
|
61
|
+
options.check = true;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (arg === "--rewrite") {
|
|
66
|
+
options.rewrite = true;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (arg === "--host") {
|
|
71
|
+
const value = argv[index + 1];
|
|
72
|
+
if (!HOST_LAYOUT_HOSTS.includes(value)) {
|
|
73
|
+
return { ok: false, error: "无效的 --host 参数。可选值: codex, claude-code, gemini-cli" };
|
|
74
|
+
}
|
|
75
|
+
hosts.push(value);
|
|
76
|
+
index += 1;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { ok: false, error: `未知参数: ${arg}` };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (hosts.length > 0) {
|
|
84
|
+
options.hosts = hosts;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return { ok: true, options };
|
|
88
|
+
}
|