@brawnen/agent-harness-cli 0.1.0 → 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:
@@ -37,10 +44,12 @@ The current implementation also covers:
37
44
  - host rule injection
38
45
  - base project config generation
39
46
  - minimal `.codex/hooks.json` integration
47
+ - `.claude/settings.json` hook integration
48
+ - `.gemini/settings.json` hook integration
40
49
 
41
50
  ## Current Boundaries
42
51
 
43
- 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.
44
53
 
45
54
  It currently does not try to provide:
46
55
 
@@ -49,6 +58,12 @@ It currently does not try to provide:
49
58
  - automatic `git push`
50
59
  - a deep upgrader / migration system
51
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
+
52
67
  ## What The CLI Does Today
53
68
 
54
69
  ### `init`
@@ -144,7 +159,7 @@ Current responsibilities:
144
159
  - `docs scaffold --type design-note|adr`
145
160
  - generate minimal Markdown skeletons from task context
146
161
 
147
- ## Codex Support
162
+ ## Host Support
148
163
 
149
164
  The current repository has the most complete host integration for `Codex`.
150
165
 
@@ -152,16 +167,44 @@ Current Codex coverage includes:
152
167
 
153
168
  - `SessionStart`
154
169
  - `UserPromptSubmit`
170
+
171
+ Currently disabled by default:
172
+
155
173
  - `PreToolUse`
156
174
  - `PostToolUse`
157
175
 
158
176
  Highlights:
159
177
 
160
178
  - automatic intake / continue / clarify
161
- - pre-tool gating
162
- - automatic evidence capture
163
179
  - active task restore
164
180
 
181
+ Current Codex boundary:
182
+
183
+ - tool-level hooks remain implemented but are not enabled by default because of host visibility noise
184
+
185
+ Current Gemini CLI coverage includes:
186
+
187
+ - `SessionStart`
188
+ - `BeforeAgent`
189
+ - `BeforeTool`
190
+ - `AfterTool`
191
+ - `AfterAgent`
192
+
193
+ Highlights:
194
+
195
+ - automatic intake / continue / clarify
196
+ - before-tool gating for supported Gemini tools
197
+ - shell evidence capture through `AfterTool`
198
+ - completion gating through `AfterAgent`
199
+
200
+ Current Claude Code coverage includes:
201
+
202
+ - `SessionStart`
203
+ - `UserPromptSubmit`
204
+ - `PreToolUse`
205
+ - `PostToolUse`
206
+ - `Stop`
207
+
165
208
  ### Current `PreToolUse` Coverage For `Bash`
166
209
 
167
210
  `Bash` currently supports high-confidence path inference for common write commands such as:
@@ -196,6 +239,8 @@ Useful local checks:
196
239
  ```bash
197
240
  npm --prefix packages/cli run verify:task-core
198
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
199
244
  ```
200
245
 
201
246
  From the repository root:
@@ -203,8 +248,14 @@ From the repository root:
203
248
  ```bash
204
249
  npm run codex:hooks:check
205
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
206
255
  ```
207
256
 
257
+ `codex:hooks:check` validates the source-of-truth hook files under `.harness/hosts/codex/hooks/`, not the generated root `.codex/` shell.
258
+
208
259
  ## Local Invocation Note
209
260
 
210
261
  If your current working directory is not the repository root, prefer calling the CLI with an absolute path, for example:
@@ -221,4 +272,5 @@ Do not assume `node packages/cli/bin/agent-harness.js ...` will work from an arb
221
272
  - installing `@brawnen/agent-harness-cli` from npm should automatically pull `@brawnen/agent-harness-protocol`
222
273
  - the protocol must not depend on the CLI
223
274
  - the default npm entrypoint is `npx @brawnen/agent-harness-cli init`
224
- - 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.0",
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.0"
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
  "- 代价与风险:待补充",
@@ -67,7 +67,7 @@ function parseBeforeToolArgs(argv) {
67
67
  return { ok: true, options };
68
68
  }
69
69
 
70
- function beforeTool(cwd, options) {
70
+ export function beforeTool(cwd, options) {
71
71
  const taskId = options.taskId ?? resolveActiveTaskId(cwd);
72
72
  const taskState = taskId ? getTaskState(cwd, taskId) : null;
73
73
  const config = loadProjectConfig(cwd);
@@ -0,0 +1,43 @@
1
+ import { runClaudeHook, readHookPayload } from "../lib/claude-hooks.js";
2
+ import { runCodexHook } from "../lib/codex-hooks.js";
3
+ import { runGeminiHook } from "../lib/gemini-hooks.js";
4
+
5
+ export function runHook(argv) {
6
+ const [host, event] = argv;
7
+
8
+ if (!host || !event) {
9
+ console.error("用法: hook <claude|codex|gemini> <event>");
10
+ return 1;
11
+ }
12
+
13
+ if (!["claude", "claude-code", "codex", "gemini", "gemini-cli"].includes(host)) {
14
+ console.error(`未知 hook 宿主: ${host}`);
15
+ return 1;
16
+ }
17
+
18
+ try {
19
+ const payload = readHookPayload();
20
+ const result = runHostHook(host, event, payload);
21
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
22
+ return 0;
23
+ } catch (error) {
24
+ console.error(error.message);
25
+ return 1;
26
+ }
27
+ }
28
+
29
+ function runHostHook(host, event, payload) {
30
+ if (host === "claude" || host === "claude-code") {
31
+ return runClaudeHook(event, payload);
32
+ }
33
+
34
+ if (host === "codex") {
35
+ return runCodexHook(event, payload);
36
+ }
37
+
38
+ if (host === "gemini" || host === "gemini-cli") {
39
+ return runGeminiHook(event, payload);
40
+ }
41
+
42
+ throw new Error(`未知 hook 宿主: ${host}`);
43
+ }
@@ -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,13 +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
+ }
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
+ });
298
322
  }
299
323
  }
300
324
 
@@ -361,6 +385,25 @@ function queueClaudeSettingsMerge(actions, cwd) {
361
385
  });
362
386
  }
363
387
 
388
+ function queueGeminiSettingsMerge(actions, cwd) {
389
+ const targetPath = path.join(cwd, ".gemini", "settings.json");
390
+ const templatePath = path.join(PROTOCOL_ROOT, "adapters", "gemini-cli", "hooks.json");
391
+ const template = JSON.parse(readText(templatePath));
392
+ const existing = fs.existsSync(targetPath) ? readJson(targetPath) : {};
393
+ const merged = mergeClaudeSettings(existing, template);
394
+ const content = `${JSON.stringify(merged, null, 2)}\n`;
395
+
396
+ actions.push({
397
+ description: fs.existsSync(targetPath) ? "合并 Gemini CLI hooks" : "创建 Gemini CLI hooks 配置",
398
+ relativePath: path.relative(cwd, targetPath),
399
+ run: () => {
400
+ ensureDirectory(path.dirname(targetPath));
401
+ fs.writeFileSync(targetPath, content, "utf8");
402
+ },
403
+ skip: false
404
+ });
405
+ }
406
+
364
407
  function queueRuntimeFiles(actions, cwd) {
365
408
  const runtimeReadme = path.join(cwd, DEFAULT_RUNTIME_DIR, "README.md");
366
409
  queueWriteAction({
@@ -666,6 +709,38 @@ function printPlan(actions, dryRun, cwd, project, hosts) {
666
709
  }
667
710
  }
668
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
+
669
744
  function readText(filePath) {
670
745
  return fs.readFileSync(filePath, "utf8");
671
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
 
@@ -110,7 +110,9 @@ function runStateUpdate(argv) {
110
110
  }
111
111
 
112
112
  const result = updateTaskState(process.cwd(), taskId, changes);
113
- printJson(result);
113
+ if (parsed.options.verbose) {
114
+ printJson(result);
115
+ }
114
116
  return 0;
115
117
  } catch (error) {
116
118
  console.error(error.message);
@@ -208,7 +210,8 @@ function parseStateUpdateArgs(argv) {
208
210
  phase: null,
209
211
  state: null,
210
212
  taskId: null,
211
- tool: null
213
+ tool: null,
214
+ verbose: false
212
215
  };
213
216
 
214
217
  for (let index = 0; index < argv.length; index += 1) {
@@ -251,6 +254,11 @@ function parseStateUpdateArgs(argv) {
251
254
  continue;
252
255
  }
253
256
 
257
+ if (arg === "--verbose") {
258
+ options.verbose = true;
259
+ continue;
260
+ }
261
+
254
262
  return { ok: false, error: `未知参数: ${arg}` };
255
263
  }
256
264