@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 CHANGED
@@ -2,11 +2,14 @@
2
2
 
3
3
  [中文](README.zh-CN.md)
4
4
 
5
- `@brawnen/agent-harness-cli` is the Node.js CLI for `agent-harness`.
5
+ `@brawnen/agent-harness-cli` is the compatibility CLI for `agent-harness`.
6
6
 
7
- It is the runtime layer that turns protocol rules into a working task loop with:
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 is still an evolving implementation.
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 currently treats the Node.js CLI as the only mainline implementation
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` Node.js CLI。
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
- - 当前仓库以 Node.js CLI 作为唯一主线实现
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.1",
4
- "description": "CLI for task convergence, verification, reporting, and delivery in agent-harness projects.",
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.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"
@@ -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
- context.goal ? `- ${context.goal}` : "- 待补充",
166
+ "- 问题:待补充",
166
167
  "",
167
- "## 作用范围",
168
+ "## 采用方案",
168
169
  "",
169
- ...toBulletLines(context.scope),
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
  "- 代价与风险:待补充",
@@ -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.0";
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
- queueRulesInjection(actions, cwd, hosts, options, ruleText);
294
+ queueHostLayoutActions(actions, warnings, cwd, hosts, options);
292
295
 
293
296
  if (!options.protocolOnly) {
294
297
  queueRuntimeFiles(actions, cwd);
295
- if (hosts.includes("claude-code")) {
296
- queueClaudeSettingsMerge(actions, cwd);
297
- }
298
- if (hosts.includes("gemini-cli")) {
299
- queueGeminiSettingsMerge(actions, cwd);
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
  }
@@ -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: 可先执行 \`node packages/cli/bin/agent-harness.js docs scaffold --type design-note --task-id ${taskId} --path ${suggestedPath}\``);
260
- lines.push(` 然后在 report 中补上 \`--design-note ${suggestedPath}\``);
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: 可先执行 \`node packages/cli/bin/agent-harness.js docs scaffold --type adr --task-id ${taskId} --path ${suggestedPath}\``);
267
- lines.push(` 然后在 report 中补上 \`--adr ${suggestedPath}\``);
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
 
@@ -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.some((command) =>
363
- command.includes("agent-harness gate before-tool") ||
364
- command.includes("@brawnen/agent-harness-cli gate before-tool") ||
365
- command.includes("packages/cli/bin/agent-harness.js\" gate before-tool") ||
366
- command.includes("packages/cli/bin/agent-harness.js gate before-tool")
367
- );
368
- const hasPostTool = commands.some((command) =>
369
- command.includes("agent-harness state update") ||
370
- command.includes("@brawnen/agent-harness-cli state update") ||
371
- command.includes("packages/cli/bin/agent-harness.js\" state update") ||
372
- command.includes("packages/cli/bin/agent-harness.js state update")
373
- );
374
- const hasSessionStart = commands.some((command) =>
375
- command.includes("agent-harness hook claude session-start") ||
376
- command.includes("@brawnen/agent-harness-cli hook claude session-start") ||
377
- command.includes("packages/cli/bin/agent-harness.js\" hook claude session-start") ||
378
- command.includes("packages/cli/bin/agent-harness.js hook claude session-start")
379
- );
380
- const hasUserPromptSubmit = commands.some((command) =>
381
- command.includes("agent-harness hook claude user-prompt-submit") ||
382
- command.includes("@brawnen/agent-harness-cli hook claude user-prompt-submit") ||
383
- command.includes("packages/cli/bin/agent-harness.js\" hook claude user-prompt-submit") ||
384
- command.includes("packages/cli/bin/agent-harness.js hook claude user-prompt-submit")
385
- );
386
- const hasStop = commands.some((command) =>
387
- command.includes("agent-harness hook claude stop") ||
388
- command.includes("@brawnen/agent-harness-cli hook claude stop") ||
389
- command.includes("packages/cli/bin/agent-harness.js\" hook claude stop") ||
390
- command.includes("packages/cli/bin/agent-harness.js hook claude stop")
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.some((command) =>
437
- command.includes("agent-harness hook gemini session-start") ||
438
- command.includes("@brawnen/agent-harness-cli hook gemini session-start") ||
439
- command.includes("packages/cli/bin/agent-harness.js\" hook gemini session-start") ||
440
- command.includes("packages/cli/bin/agent-harness.js hook gemini session-start")
441
- );
442
- const hasBeforeAgent = commands.some((command) =>
443
- command.includes("agent-harness hook gemini before-agent") ||
444
- command.includes("@brawnen/agent-harness-cli hook gemini before-agent") ||
445
- command.includes("packages/cli/bin/agent-harness.js\" hook gemini before-agent") ||
446
- command.includes("packages/cli/bin/agent-harness.js hook gemini before-agent")
447
- );
448
- const hasBeforeTool = commands.some((command) =>
449
- command.includes("agent-harness hook gemini before-tool") ||
450
- command.includes("@brawnen/agent-harness-cli hook gemini before-tool") ||
451
- command.includes("packages/cli/bin/agent-harness.js\" hook gemini before-tool") ||
452
- command.includes("packages/cli/bin/agent-harness.js hook gemini before-tool")
453
- );
454
- const hasAfterTool = commands.some((command) =>
455
- command.includes("agent-harness hook gemini after-tool") ||
456
- command.includes("@brawnen/agent-harness-cli hook gemini after-tool") ||
457
- command.includes("packages/cli/bin/agent-harness.js\" hook gemini after-tool") ||
458
- command.includes("packages/cli/bin/agent-harness.js hook gemini after-tool")
459
- );
460
- const hasAfterAgent = commands.some((command) =>
461
- command.includes("agent-harness hook gemini after-agent") ||
462
- command.includes("@brawnen/agent-harness-cli hook gemini after-agent") ||
463
- command.includes("packages/cli/bin/agent-harness.js\" hook gemini after-agent") ||
464
- command.includes("packages/cli/bin/agent-harness.js hook gemini after-agent")
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 CLI 做 state / verify / report";
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
+ }