repo-harness 0.9.0 → 0.9.1

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.es.md CHANGED
@@ -85,7 +85,7 @@ artifacts.
85
85
  ## Novedades
86
86
 
87
87
  Las notas de versión viven en [`docs/CHANGELOG.md`](docs/CHANGELOG.md). La línea
88
- actual es `0.9.0`.
88
+ actual es `0.9.1`.
89
89
 
90
90
  ## Cómo funciona
91
91
 
@@ -419,8 +419,8 @@ Guards habituales:
419
419
 
420
420
  ## Release actual
421
421
 
422
- - npm package: `repo-harness@0.9.0`
423
- - Generated workflow stamp: `repo-harness@0.9.0+template@0.9.0`
422
+ - npm package: `repo-harness@0.9.1`
423
+ - Generated workflow stamp: `repo-harness@0.9.1+template@0.9.1`
424
424
  - GitHub repository: `Ancienttwo/repo-harness`
425
425
  - Release history: [`docs/CHANGELOG.md`](docs/CHANGELOG.md)
426
426
 
package/README.fr.md CHANGED
@@ -85,7 +85,7 @@ l'emportent.
85
85
  ## Nouveautés
86
86
 
87
87
  Les notes de version vivent dans [`docs/CHANGELOG.md`](docs/CHANGELOG.md). La
88
- ligne actuelle est `0.9.0`.
88
+ ligne actuelle est `0.9.1`.
89
89
 
90
90
  ## Comment ça marche
91
91
 
@@ -424,8 +424,8 @@ Guards courants :
424
424
 
425
425
  ## Release actuelle
426
426
 
427
- - npm package : `repo-harness@0.9.0`
428
- - Generated workflow stamp : `repo-harness@0.9.0+template@0.9.0`
427
+ - npm package : `repo-harness@0.9.1`
428
+ - Generated workflow stamp : `repo-harness@0.9.1+template@0.9.1`
429
429
  - GitHub repository : `Ancienttwo/repo-harness`
430
430
  - Release history : [`docs/CHANGELOG.md`](docs/CHANGELOG.md)
431
431
 
package/README.ja.md CHANGED
@@ -75,7 +75,7 @@ review、checks、handoff と食い違う場合は、source artifacts を優先
75
75
  ## What's New
76
76
 
77
77
  リリースノートは [`docs/CHANGELOG.md`](docs/CHANGELOG.md) にあります。現在の
78
- ラインは `0.9.0` です。
78
+ ラインは `0.9.1` です。
79
79
 
80
80
  ## 仕組み
81
81
 
@@ -398,8 +398,8 @@ hook がブロックしたときは、まず terminal の構造化された出
398
398
 
399
399
  ## 現在の Release
400
400
 
401
- - npm package:`repo-harness@0.9.0`
402
- - Generated workflow stamp:`repo-harness@0.9.0+template@0.9.0`
401
+ - npm package:`repo-harness@0.9.1`
402
+ - Generated workflow stamp:`repo-harness@0.9.1+template@0.9.1`
403
403
  - GitHub repository:`Ancienttwo/repo-harness`
404
404
  - Release history:[`docs/CHANGELOG.md`](docs/CHANGELOG.md)
405
405
 
package/README.md CHANGED
@@ -85,7 +85,7 @@ active plan, contract, review, checks, or handoff, the source artifacts win.
85
85
  ## What's New
86
86
 
87
87
  Release notes live in [`docs/CHANGELOG.md`](docs/CHANGELOG.md). The current line
88
- is `0.9.0`.
88
+ is `0.9.1`.
89
89
 
90
90
  ## How It Works
91
91
 
@@ -282,15 +282,19 @@ user-level hook adapters, configures Waza runtime skills, persists a brain root
282
282
  under `~/.repo-harness/config.json`, and configures CodeGraph MCP. In an
283
283
  interactive terminal it asks Y/n before installing the external skills and
284
284
  CodeGraph pieces (Enter keeps today's default of installing both); non-TTY
285
- runs and `--json` stay unprompted with the same default-on behavior. It does
286
- not apply repo-local workflow files to the current directory. `repo-harness
287
- init` remains a compatibility alias for existing scripts.
285
+ runs and `--json` stay unprompted with the same default-on behavior. Passing
286
+ `--no-external-skills` or `--no-codegraph` explicitly also skips that item's
287
+ prompt unprompted, which is the escape hatch for PTY-allocating CI (for
288
+ example `docker run -t`). It does not apply repo-local workflow files to the
289
+ current directory. `repo-harness init` remains a compatibility alias for
290
+ existing scripts.
288
291
 
289
292
  For an Agent-owned, read-only bootstrap audit, run `repo-harness setup check
290
- --json` or add `--check-updates` for version advisories. `setup check` is
291
- not a runtime hook: it does not write user-level files, install updates, or
292
- register adapters. It emits `agent_actions` with the reason, risk, target files,
293
- optional command, and verification surface for the Agent to execute deliberately.
293
+ --json` or add `--check-updates` for version and adopted-repo refresh
294
+ advisories. `setup check` is not a runtime hook: it does not write user-level
295
+ files, install updates, run `adopt`, or register adapters. It emits
296
+ `agent_actions` with the reason, risk, target files, optional command, and
297
+ verification surface for the Agent to execute deliberately.
294
298
  `repo-harness init-hook` remains a compatibility alias.
295
299
 
296
300
  ### Install and refresh examples
@@ -312,6 +316,17 @@ repo-harness update --check
312
316
  repo-harness adopt --repo /path/to/repo
313
317
  ```
314
318
 
319
+ `repo-harness install --target codex|both --location global` also resolves and
320
+ persists the Codex delegation mode read by the delegation-advisor hook: pass
321
+ `--delegation-mode auto|explicit` for non-interactive use, or answer the
322
+ one-line interactive prompt shown in a TTY (Enter keeps the current choice,
323
+ defaulting to `explicit` when nothing is configured yet). The chosen mode is
324
+ written to `delegation.mode` in `~/.repo-harness/config.json`, merged with
325
+ existing keys such as `brainRoot`; this global value takes precedence over a
326
+ repo's `.ai/harness/policy.json` `delegation.mode`. The step never fires for
327
+ `--target claude`, and with no flag and no TTY it leaves the file untouched —
328
+ it never writes a silent default.
329
+
315
330
  ### 3. Preview the repo-local contract
316
331
 
317
332
  ```bash
@@ -612,8 +627,8 @@ Most common guards:
612
627
 
613
628
  ## Current Release
614
629
 
615
- - npm package: `repo-harness@0.9.0`
616
- - Generated workflow stamp: `repo-harness@0.9.0+template@0.9.0`
630
+ - npm package: `repo-harness@0.9.1`
631
+ - Generated workflow stamp: `repo-harness@0.9.1+template@0.9.1`
617
632
  - GitHub repository: `Ancienttwo/repo-harness`
618
633
  - Release history: [`docs/CHANGELOG.md`](docs/CHANGELOG.md)
619
634
 
package/README.zh-CN.md CHANGED
@@ -75,7 +75,7 @@ review、checks 或 handoff 冲突,以 source artifacts 为准。
75
75
 
76
76
  ## What's New
77
77
 
78
- Release notes 见 [`docs/CHANGELOG.md`](docs/CHANGELOG.md),当前版本线是 `0.9.0`。
78
+ Release notes 见 [`docs/CHANGELOG.md`](docs/CHANGELOG.md),当前版本线是 `0.9.1`。
79
79
 
80
80
  ## 工作原理
81
81
 
@@ -244,9 +244,10 @@ root 持久化到 `~/.repo-harness/config.json`,并配置 CodeGraph MCP。它
244
244
  默认迁移成 repo-local workflow。`repo-harness init` 保留为兼容 alias,给已有脚本用。
245
245
 
246
246
  如果要让 Agent 做只读 bootstrap audit,运行 `repo-harness setup check
247
- --json`;需要版本提示时加 `--check-updates`。`setup check` 不是 runtime hook:
248
- 它不会写 user-level files、安装更新或注册 adapters,只输出带 reason、risk、
249
- targets、可选 command 和 verification 的 `agent_actions`,由 Agent 再显式执行。
247
+ --json`;需要版本和已接入仓库刷新提示时加 `--check-updates`。`setup check`
248
+ 不是 runtime hook:它不会写 user-level files、安装更新、执行 `adopt` 或注册
249
+ adapters,只输出带 reason、risk、targets、可选 command 和 verification 的
250
+ `agent_actions`,由 Agent 再显式执行。
250
251
  `repo-harness init-hook` 保留为兼容 alias。
251
252
 
252
253
  ### 安装和刷新例子
@@ -451,8 +452,8 @@ hook block 工作时,先看 terminal 里的结构化输出。核心字段是
451
452
 
452
453
  ## 当前 Release
453
454
 
454
- - npm package:`repo-harness@0.9.0`
455
- - Generated workflow stamp:`repo-harness@0.9.0+template@0.9.0`
455
+ - npm package:`repo-harness@0.9.1`
456
+ - Generated workflow stamp:`repo-harness@0.9.1+template@0.9.1`
456
457
  - GitHub repository:`Ancienttwo/repo-harness`
457
458
  - Release history:[`docs/CHANGELOG.md`](docs/CHANGELOG.md)
458
459
 
package/assets/AGENTS.md CHANGED
@@ -37,7 +37,7 @@ Owns the workflow-engine-contract-assets capability boundary declared in .ai/con
37
37
  - Architecture domain: `workflow-engine`
38
38
  - Architecture capability: `contract-assets`
39
39
  - Architecture module: `docs/architecture/modules/workflow-engine/contract-assets.md`
40
- - Last architecture event: 2026-07-05T04:35:33+0800
40
+ - Last architecture event: 2026-07-06T13:54:27+0800
41
41
  - Last changed path: `.ai/harness/policy.json`
42
42
  - Severity: high
43
43
  - Change type: workflow-surface
package/assets/CLAUDE.md CHANGED
@@ -37,7 +37,7 @@ Owns the workflow-engine-contract-assets capability boundary declared in .ai/con
37
37
  - Architecture domain: `workflow-engine`
38
38
  - Architecture capability: `contract-assets`
39
39
  - Architecture module: `docs/architecture/modules/workflow-engine/contract-assets.md`
40
- - Last architecture event: 2026-07-05T04:35:33+0800
40
+ - Last architecture event: 2026-07-06T13:54:27+0800
41
41
  - Last changed path: `.ai/harness/policy.json`
42
42
  - Severity: high
43
43
  - Change type: workflow-surface
@@ -49,7 +49,7 @@ Owns the runtime-harness-hook-adapters capability boundary declared in .ai/conte
49
49
  - Architecture domain: `runtime-harness`
50
50
  - Architecture capability: `hook-adapters`
51
51
  - Architecture module: `docs/architecture/modules/runtime-harness/hook-adapters.md`
52
- - Last architecture event: 2026-07-05T13:45:11+0800
52
+ - Last architecture event: 2026-07-06T13:54:06+0800
53
53
  - Last changed path: `assets/hooks/codex-delegation-advisor.sh`
54
54
  - Severity: high
55
55
  - Change type: workflow-surface
@@ -49,7 +49,7 @@ Owns the runtime-harness-hook-adapters capability boundary declared in .ai/conte
49
49
  - Architecture domain: `runtime-harness`
50
50
  - Architecture capability: `hook-adapters`
51
51
  - Architecture module: `docs/architecture/modules/runtime-harness/hook-adapters.md`
52
- - Last architecture event: 2026-07-05T13:45:11+0800
52
+ - Last architecture event: 2026-07-06T13:54:06+0800
53
53
  - Last changed path: `assets/hooks/codex-delegation-advisor.sh`
54
54
  - Severity: high
55
55
  - Change type: workflow-surface
@@ -56,6 +56,19 @@ JSON_INPUT="$input" REPO_ROOT="${HOOK_REPO_ROOT:-$(pwd)}" bun -e '
56
56
  return null;
57
57
  }
58
58
 
59
+ function readGlobalDelegationMode() {
60
+ try {
61
+ const home = process.env.HOME;
62
+ if (!home) return null;
63
+ const raw = fs.readFileSync(path.join(home, ".repo-harness", "config.json"), "utf8");
64
+ const parsed = JSON.parse(raw);
65
+ const mode = parsed && parsed.delegation && parsed.delegation.mode;
66
+ return mode === "auto" || mode === "explicit" ? mode : null;
67
+ } catch {
68
+ return null;
69
+ }
70
+ }
71
+
59
72
  let input;
60
73
  try {
61
74
  input = JSON.parse(process.env.JSON_INPUT || "");
@@ -91,7 +104,6 @@ JSON_INPUT="$input" REPO_ROOT="${HOOK_REPO_ROOT:-$(pwd)}" bun -e '
91
104
  { name: "chinese-subagent", pattern: /交给\s*子代理|使用多个\s*(agent|代理)|并行(调查|研究|处理|执行|agent|代理)/i },
92
105
  ];
93
106
  const trigger = triggers.find((entry) => entry.pattern.test(prompt) && !(entry.skipDiscussion && isDelegationDiscussion(prompt)));
94
- if (!trigger) process.exit(0);
95
107
 
96
108
  const repoRoot = process.env.REPO_ROOT || process.cwd();
97
109
 
@@ -102,6 +114,16 @@ JSON_INPUT="$input" REPO_ROOT="${HOOK_REPO_ROOT:-$(pwd)}" bun -e '
102
114
  } catch {
103
115
  policyDelegation = {};
104
116
  }
117
+
118
+ const globalDelegationMode = readGlobalDelegationMode();
119
+ const effectiveDelegationMode = globalDelegationMode || (policyDelegation.mode === "auto" ? "auto" : "explicit");
120
+
121
+ const explicit = Boolean(trigger);
122
+ if (!explicit) {
123
+ if (effectiveDelegationMode !== "auto") process.exit(0);
124
+ if (isDelegationDiscussion(prompt)) process.exit(0);
125
+ }
126
+
105
127
  const maxAgents = Number.isInteger(policyDelegation.max_agents) ? policyDelegation.max_agents : 3;
106
128
  const maxDepth = Number.isInteger(policyDelegation.max_depth) ? policyDelegation.max_depth : 1;
107
129
  const preferredRunners =
@@ -122,17 +144,17 @@ JSON_INPUT="$input" REPO_ROOT="${HOOK_REPO_ROOT:-$(pwd)}" bun -e '
122
144
  const state = {
123
145
  version: 1,
124
146
  eligible: true,
125
- explicit: true,
147
+ explicit,
126
148
  spawned: false,
127
149
  fallback_used: false,
128
- mode: "explicit",
150
+ mode: explicit ? "explicit" : "auto",
129
151
  max_agents: maxAgents,
130
152
  max_depth: maxDepth,
131
153
  allow_parallel_writers: false,
132
- stop_fallback: true,
154
+ stop_fallback: explicit,
133
155
  preferred_runners: preferredRunners,
134
156
  fallback_runner: fallbackRunner,
135
- trigger: trigger.name,
157
+ trigger: explicit ? trigger.name : "auto-mode",
136
158
  prompt_hash: crypto.createHash("sha1").update(prompt).digest("hex"),
137
159
  scope_source: scope?.source || "unscoped",
138
160
  scope_id: scope?.id || "",
@@ -151,7 +173,9 @@ JSON_INPUT="$input" REPO_ROOT="${HOOK_REPO_ROOT:-$(pwd)}" bun -e '
151
173
  const context = [
152
174
  "[repo-harness:delegation]",
153
175
  "",
154
- "The current user prompt explicitly enabled bounded delegation.",
176
+ explicit
177
+ ? "The current user prompt explicitly enabled bounded delegation."
178
+ : "Repo policy delegation.mode=auto is standing user authorization for bounded delegation, selected by the user at install time. This satisfies the user-authorization requirement for native subagents (spawn_agent) when the dispatch conditions below are met.",
155
179
  "",
156
180
  "Treat the active task contract (tasks/contracts/<active-plan-stem>.contract.md) as the authoritative execution brief: Goal, Scope, Allowed Paths, and Exit Criteria. Do not re-derive scope from this conversation.",
157
181
  "",
@@ -41,7 +41,9 @@ brain config, package-manager globals, or user-authored sibling hook entries.
41
41
  runtime by default. Third-party tooling and CodeGraph registration stay
42
42
  readiness findings from `repo-harness setup check` unless the update command is
43
43
  run with an explicit opt-in such as `--with-external-skills` or
44
- `--configure-codegraph`.
44
+ `--configure-codegraph`. Repo-local workflow refresh stays on
45
+ `repo-harness adopt`; `setup check --check-updates` reports an Agent action when
46
+ the current adopted repo's dry-run adoption plan has pending operations.
45
47
 
46
48
  The cross-review skills are **harness-owned and self-contained** — their source
47
49
  lives in `assets/skills/<skill>/` and they wrap the peer CLI (`codex exec` /
@@ -121,13 +123,13 @@ cd ~/.claude/skills/gstack && ./setup --host codex
121
123
  Both hosts:
122
124
 
123
125
  ```bash
124
- npx -y skills add tw93/Waza -g -a claude-code codex -s think hunt check health -y
126
+ bunx skills add tw93/Waza -g -a claude-code codex -s think hunt check health -y
125
127
  ```
126
128
 
127
129
  Single host:
128
130
 
129
131
  ```bash
130
- npx -y skills add tw93/Waza -g -a claude-code -s think hunt check health -y
132
+ bunx skills add tw93/Waza -g -a claude-code -s think hunt check health -y
131
133
  ```
132
134
 
133
135
  Replace `claude-code` with `codex` when installing for Codex only.
@@ -156,13 +158,13 @@ done
156
158
  Both hosts:
157
159
 
158
160
  ```bash
159
- npx -y skills add hylarucoder/hai-stack -g -a claude-code codex -s geju -y
161
+ bunx skills add hylarucoder/hai-stack -g -a claude-code codex -s geju -y
160
162
  ```
161
163
 
162
164
  Single host:
163
165
 
164
166
  ```bash
165
- npx -y skills add hylarucoder/hai-stack -g -a claude-code -s geju -y
167
+ bunx skills add hylarucoder/hai-stack -g -a claude-code -s geju -y
166
168
  ```
167
169
 
168
170
  Replace `claude-code` with `codex` when installing for Codex only.
@@ -204,7 +206,9 @@ command directly unless local policy explicitly opts into vendoring.
204
206
  ### Runtime Ownership Boundary
205
207
 
206
208
  `repo-harness setup check --target <host> --check-updates --json` reports the
207
- execution base as separate `runtime.*` checks. Keep the boundary explicit:
209
+ execution base as separate `runtime.*` checks and reports repo-local adoption
210
+ refresh as `repo.adopt-refresh` when the current repo has opted in. Keep the
211
+ boundary explicit:
208
212
 
209
213
  | Capability | Owner | Required for |
210
214
  |---|---|---|
@@ -424,7 +428,7 @@ cd ~/.claude/skills/gstack && git pull && ./setup --host codex
424
428
  ### Waza
425
429
 
426
430
  ```bash
427
- npx -y skills update
431
+ bunx skills update
428
432
  for d in think hunt check health; do
429
433
  rsync -a --delete ~/.agents/skills/$d/ ~/.codex/skills/$d/
430
434
  done
@@ -443,7 +447,7 @@ done
443
447
  ### hai-stack
444
448
 
445
449
  ```bash
446
- npx -y skills update
450
+ bunx skills update
447
451
  rsync -a --delete ~/.agents/skills/geju/ ~/.codex/skills/geju/
448
452
  diff -qr ~/.agents/skills/geju ~/.codex/skills/geju
449
453
  ```
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "0.9.0",
3
- "templateVersion": "0.9.0",
2
+ "version": "0.9.1",
3
+ "templateVersion": "0.9.1",
4
4
  "skillName": "repo-harness",
5
5
  "contractId": "tasks-first-harness-v1",
6
6
  "compatibility": {
@@ -191,6 +191,10 @@
191
191
  {
192
192
  "version": "0.9.0",
193
193
  "description": "Adds a bugfix task profile with root-cause evidence gating, distills contract-brief prompts and templates, makes the agent fleet and geju first-class dependencies, adds a frontend task profile and archcontext boundary bridge, and lands the EXECUTION_BOUNDARY anti-extras clause across delegated runners"
194
+ },
195
+ {
196
+ "version": "0.9.1",
197
+ "description": "Hardens the Bun-first installer and setup-check path with bunx skills probes, PATH readback hints, and explicit Codex delegation-mode selection"
194
198
  }
195
199
  ],
196
200
  "generatedProjectStamp": {
@@ -645,6 +645,13 @@ function findLatestMatchingFile(dir: string, token: string, extension: string):
645
645
  return files.at(-1) || "(none yet)";
646
646
  }
647
647
 
648
+ function activePendingRequestFile(file: string): string {
649
+ if (!file || file === "unknown") return "(none)";
650
+ if (file.includes("/archive/")) return "(none)";
651
+ if (!existsSync(file)) return "(none)";
652
+ return isPendingRequest(file) ? file : "(none)";
653
+ }
654
+
648
655
  function collectFiles(dir: string): string[] {
649
656
  if (!existsSync(dir)) return [];
650
657
  const files: string[] = [];
@@ -680,7 +687,7 @@ function renderContractBlock(args: Args): string {
680
687
  const severity = args.options.severity || "unknown";
681
688
  const changeType = args.options.changeType || "unknown";
682
689
  const lspProfile = args.options.lspProfile || "typescript-lsp";
683
- const requestFile = args.options.requestFile || "unknown";
690
+ const requestFile = activePendingRequestFile(args.options.requestFile || "");
684
691
 
685
692
  return [
686
693
  "<!-- BEGIN ARCHITECTURE CONTRACT -->",
@@ -27,9 +27,9 @@ export type CapabilityRegistry = {
27
27
 
28
28
  // archcontext-boundaries-v1 is a deliberately narrow, read-only export of the
29
29
  // capability registry shaped as a subset of ArchitectureNode (archcontext.node/v1).
30
- // See tests/fixtures/archcontext/architecture-node.subset.schema.json for the
31
- // vendored schema subset and docs/researches/20260705-archcontext-capability-filing-handover.md
32
- // for why this stays a read-only bridge (no archctx runtime dependency).
30
+ // Tests derive the bridge subset from archctx-contracts' authoritative schema;
31
+ // docs/researches/20260705-archcontext-capability-filing-handover.md explains
32
+ // why this stays a read-only bridge with no archctx CLI/daemon runtime dependency.
33
33
  export type ArchContextBoundaryNode = {
34
34
  schemaVersion: "archcontext.node/v1";
35
35
  id: string;
@@ -709,7 +709,7 @@ function inspectWazaSkill(host, skill, skillLock, skillItems, upstreamSkills) {
709
709
  function detectWaza() {
710
710
  const skillLockPath = path.join(HOME, ".agents", ".skill-lock.json");
711
711
  const skillLock = readJson(skillLockPath);
712
- const skillsResult = run("npx", ["-y", "skills", "ls", "-g", "--json"], { timeoutMs: 1500 });
712
+ const skillsResult = run("bunx", ["skills", "ls", "-g", "--json"], { timeoutMs: 1500 });
713
713
  const skillItems = skillsResult.ok ? parseJson(skillsResult.stdout) || [] : [];
714
714
  const wazaEntries = Object.entries(skillLock?.skills || {}).filter(([, meta]) => meta?.source === WAZA_SOURCE_REPO);
715
715
  const upstream = fetchWazaUpstreamSkills();
@@ -815,7 +815,7 @@ function detectWaza() {
815
815
  : "Local Waza SKILL.md and shared rule files match upstream GitHub raw content.";
816
816
 
817
817
  const status = summarizeWazaStatus(hostStatuses);
818
- const installCommand = `npx -y skills add tw93/Waza -g -a ${
818
+ const installCommand = `bunx skills add tw93/Waza -g -a ${
819
819
  hostMode === "both" ? "claude-code codex" : hostMode === "claude" ? "claude-code" : "codex"
820
820
  } -s think hunt check health -y`;
821
821
  const syncCommand = SELECTED_HOSTS.map((host) => buildWazaHostSyncCommand(host)).join(" && ");
@@ -878,8 +878,8 @@ function detectRuntimeCapabilities(waza) {
878
878
  ),
879
879
  npx: commandCapability(
880
880
  "npx",
881
- "external Skills CLI bootstrap/update commands for Waza and Mermaid",
882
- "external-skills-cli",
881
+ "no repo-harness usage; Skills CLI bootstrap/update for Waza and Mermaid runs through bunx instead",
882
+ "npm-registry",
883
883
  false
884
884
  ),
885
885
  skills_cli: {
@@ -889,7 +889,7 @@ function detectRuntimeCapabilities(waza) {
889
889
  owner: "external-skills-cli",
890
890
  required: false,
891
891
  required_for: "Waza/Mermaid external skill bootstrap; repo-harness reports this as an explicit exception boundary",
892
- command: "npx -y skills ls -g --json",
892
+ command: "bunx skills ls -g --json",
893
893
  },
894
894
  bash: commandCapability(
895
895
  "bash",
@@ -155,6 +155,19 @@ metadata_value() {
155
155
  ' "$file" 2>/dev/null
156
156
  }
157
157
 
158
+ active_pending_request_file() {
159
+ local file="$1"
160
+ if [[ -z "$file" || "$file" == "unknown" || "$file" == *"/archive/"* || ! -f "$file" ]]; then
161
+ echo "(none)"
162
+ return 0
163
+ fi
164
+ if [[ "$(metadata_value "$file" "Status" | tr '[:upper:]' '[:lower:]')" == "pending" ]]; then
165
+ echo "$file"
166
+ else
167
+ echo "(none)"
168
+ fi
169
+ }
170
+
158
171
  format_active_workstreams() {
159
172
  local dir="$1"
160
173
  local count=0
@@ -440,6 +453,7 @@ if [[ "$latest_snapshot" != "(none yet)" ]]; then
440
453
  semantic_diagram_source="$latest_snapshot"
441
454
  fi
442
455
  active_workstreams="$(format_active_workstreams "$workstream_dir")"
456
+ pending_request_file="$(active_pending_request_file "${request_file:-}")"
443
457
 
444
458
  block_tmp="$(mktemp)"
445
459
  cat > "$block_tmp" <<EOF_BLOCK
@@ -466,7 +480,7 @@ cat > "$block_tmp" <<EOF_BLOCK
466
480
  - Latest snapshot: \`${latest_snapshot}\`
467
481
  - Semantic diagram source: \`${semantic_diagram_source}\`
468
482
  - Latest human diagram: \`${latest_human_diagram}\`
469
- - Pending architecture request: \`${request_file:-unknown}\`
483
+ - Pending architecture request: \`${pending_request_file}\`
470
484
 
471
485
  ## Active Workstreams
472
486
 
package/install.ps1 CHANGED
@@ -4,6 +4,7 @@ $PackageName = "repo-harness"
4
4
  $PackageVersion = if ($env:REPO_HARNESS_VERSION) { $env:REPO_HARNESS_VERSION } else { "latest" }
5
5
  $BunInstall = if ($env:BUN_INSTALL) { $env:BUN_INSTALL } else { Join-Path $HOME ".bun" }
6
6
  $BunBin = Join-Path $BunInstall "bin"
7
+ $OriginalPath = $env:PATH
7
8
 
8
9
  function Add-BunToPath {
9
10
  if (Test-Path $BunBin) {
@@ -49,5 +50,12 @@ if (-not $Version) {
49
50
  Write-Host "repo-harness $Version installed."
50
51
  Write-Host ""
51
52
  Write-Host "Next:"
52
- Write-Host " repo-harness init"
53
+ Write-Host " repo-harness install"
53
54
  Write-Host " repo-harness adopt --dry-run"
55
+
56
+ $PathSep = [System.IO.Path]::PathSeparator
57
+ if ("$PathSep$OriginalPath$PathSep" -notlike "*$PathSep$BunBin$PathSep*") {
58
+ Write-Host ""
59
+ Write-Host "To use repo-harness in new shells, add to your PATH:"
60
+ Write-Host " $BunBin"
61
+ }
package/install.sh CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env sh
2
2
  set -eu
3
3
 
4
+ ORIGINAL_PATH="${PATH:-}"
5
+
4
6
  PACKAGE_NAME="repo-harness"
5
7
  PACKAGE_VERSION="${REPO_HARNESS_VERSION:-latest}"
6
8
 
@@ -67,5 +69,14 @@ verify_repo_harness
67
69
 
68
70
  log ""
69
71
  log "Next:"
70
- log " repo-harness init"
72
+ log " repo-harness install"
71
73
  log " repo-harness adopt --dry-run"
74
+
75
+ case ":$ORIGINAL_PATH:" in
76
+ *":$BUN_INSTALL_DIR/bin:"*) ;;
77
+ *)
78
+ log ""
79
+ log "To use repo-harness in new shells, add to your shell profile:"
80
+ log " export PATH=\"$BUN_INSTALL_DIR/bin:\$PATH\""
81
+ ;;
82
+ esac
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "repo-harness",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Installs, migrates, audits, and repairs repo-local agentic development harnesses",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -57,6 +57,7 @@
57
57
  "@types/bun": "latest",
58
58
  "@types/cors": "^2.8.17",
59
59
  "@types/express": "^5.0.0",
60
+ "archctx-contracts": "0.2.1",
60
61
  "typescript": "^6.0.3"
61
62
  },
62
63
  "dependencies": {
package/scripts/AGENTS.md CHANGED
@@ -37,7 +37,7 @@ Owns the workflow-engine-inspection-migration capability boundary declared in .a
37
37
  - Architecture domain: `workflow-engine`
38
38
  - Architecture capability: `inspection-migration`
39
39
  - Architecture module: `docs/architecture/modules/workflow-engine/inspection-migration.md`
40
- - Last architecture event: 2026-07-05T04:35:41+0800
40
+ - Last architecture event: 2026-07-06T13:54:43+0800
41
41
  - Last changed path: `scripts/lib/project-init-lib.sh`
42
42
  - Severity: high
43
43
  - Change type: workflow-surface
package/scripts/CLAUDE.md CHANGED
@@ -37,7 +37,7 @@ Owns the workflow-engine-inspection-migration capability boundary declared in .a
37
37
  - Architecture domain: `workflow-engine`
38
38
  - Architecture capability: `inspection-migration`
39
39
  - Architecture module: `docs/architecture/modules/workflow-engine/inspection-migration.md`
40
- - Last architecture event: 2026-07-05T04:35:41+0800
40
+ - Last architecture event: 2026-07-06T13:54:43+0800
41
41
  - Last changed path: `scripts/lib/project-init-lib.sh`
42
42
  - Severity: high
43
43
  - Change type: workflow-surface
@@ -645,6 +645,13 @@ function findLatestMatchingFile(dir: string, token: string, extension: string):
645
645
  return files.at(-1) || "(none yet)";
646
646
  }
647
647
 
648
+ function activePendingRequestFile(file: string): string {
649
+ if (!file || file === "unknown") return "(none)";
650
+ if (file.includes("/archive/")) return "(none)";
651
+ if (!existsSync(file)) return "(none)";
652
+ return isPendingRequest(file) ? file : "(none)";
653
+ }
654
+
648
655
  function collectFiles(dir: string): string[] {
649
656
  if (!existsSync(dir)) return [];
650
657
  const files: string[] = [];
@@ -680,7 +687,7 @@ function renderContractBlock(args: Args): string {
680
687
  const severity = args.options.severity || "unknown";
681
688
  const changeType = args.options.changeType || "unknown";
682
689
  const lspProfile = args.options.lspProfile || "typescript-lsp";
683
- const requestFile = args.options.requestFile || "unknown";
690
+ const requestFile = activePendingRequestFile(args.options.requestFile || "");
684
691
 
685
692
  return [
686
693
  "<!-- BEGIN ARCHITECTURE CONTRACT -->",
@@ -27,9 +27,9 @@ export type CapabilityRegistry = {
27
27
 
28
28
  // archcontext-boundaries-v1 is a deliberately narrow, read-only export of the
29
29
  // capability registry shaped as a subset of ArchitectureNode (archcontext.node/v1).
30
- // See tests/fixtures/archcontext/architecture-node.subset.schema.json for the
31
- // vendored schema subset and docs/researches/20260705-archcontext-capability-filing-handover.md
32
- // for why this stays a read-only bridge (no archctx runtime dependency).
30
+ // Tests derive the bridge subset from archctx-contracts' authoritative schema;
31
+ // docs/researches/20260705-archcontext-capability-filing-handover.md explains
32
+ // why this stays a read-only bridge with no archctx CLI/daemon runtime dependency.
33
33
  export type ArchContextBoundaryNode = {
34
34
  schemaVersion: "archcontext.node/v1";
35
35
  id: string;
@@ -709,7 +709,7 @@ function inspectWazaSkill(host, skill, skillLock, skillItems, upstreamSkills) {
709
709
  function detectWaza() {
710
710
  const skillLockPath = path.join(HOME, ".agents", ".skill-lock.json");
711
711
  const skillLock = readJson(skillLockPath);
712
- const skillsResult = run("npx", ["-y", "skills", "ls", "-g", "--json"], { timeoutMs: 1500 });
712
+ const skillsResult = run("bunx", ["skills", "ls", "-g", "--json"], { timeoutMs: 1500 });
713
713
  const skillItems = skillsResult.ok ? parseJson(skillsResult.stdout) || [] : [];
714
714
  const wazaEntries = Object.entries(skillLock?.skills || {}).filter(([, meta]) => meta?.source === WAZA_SOURCE_REPO);
715
715
  const upstream = fetchWazaUpstreamSkills();
@@ -815,7 +815,7 @@ function detectWaza() {
815
815
  : "Local Waza SKILL.md and shared rule files match upstream GitHub raw content.";
816
816
 
817
817
  const status = summarizeWazaStatus(hostStatuses);
818
- const installCommand = `npx -y skills add tw93/Waza -g -a ${
818
+ const installCommand = `bunx skills add tw93/Waza -g -a ${
819
819
  hostMode === "both" ? "claude-code codex" : hostMode === "claude" ? "claude-code" : "codex"
820
820
  } -s think hunt check health -y`;
821
821
  const syncCommand = SELECTED_HOSTS.map((host) => buildWazaHostSyncCommand(host)).join(" && ");
@@ -878,8 +878,8 @@ function detectRuntimeCapabilities(waza) {
878
878
  ),
879
879
  npx: commandCapability(
880
880
  "npx",
881
- "external Skills CLI bootstrap/update commands for Waza and Mermaid",
882
- "external-skills-cli",
881
+ "no repo-harness usage; Skills CLI bootstrap/update for Waza and Mermaid runs through bunx instead",
882
+ "npm-registry",
883
883
  false
884
884
  ),
885
885
  skills_cli: {
@@ -889,7 +889,7 @@ function detectRuntimeCapabilities(waza) {
889
889
  owner: "external-skills-cli",
890
890
  required: false,
891
891
  required_for: "Waza/Mermaid external skill bootstrap; repo-harness reports this as an explicit exception boundary",
892
- command: "npx -y skills ls -g --json",
892
+ command: "bunx skills ls -g --json",
893
893
  },
894
894
  bash: commandCapability(
895
895
  "bash",
@@ -155,6 +155,19 @@ metadata_value() {
155
155
  ' "$file" 2>/dev/null
156
156
  }
157
157
 
158
+ active_pending_request_file() {
159
+ local file="$1"
160
+ if [[ -z "$file" || "$file" == "unknown" || "$file" == *"/archive/"* || ! -f "$file" ]]; then
161
+ echo "(none)"
162
+ return 0
163
+ fi
164
+ if [[ "$(metadata_value "$file" "Status" | tr '[:upper:]' '[:lower:]')" == "pending" ]]; then
165
+ echo "$file"
166
+ else
167
+ echo "(none)"
168
+ fi
169
+ }
170
+
158
171
  format_active_workstreams() {
159
172
  local dir="$1"
160
173
  local count=0
@@ -440,6 +453,7 @@ if [[ "$latest_snapshot" != "(none yet)" ]]; then
440
453
  semantic_diagram_source="$latest_snapshot"
441
454
  fi
442
455
  active_workstreams="$(format_active_workstreams "$workstream_dir")"
456
+ pending_request_file="$(active_pending_request_file "${request_file:-}")"
443
457
 
444
458
  block_tmp="$(mktemp)"
445
459
  cat > "$block_tmp" <<EOF_BLOCK
@@ -466,7 +480,7 @@ cat > "$block_tmp" <<EOF_BLOCK
466
480
  - Latest snapshot: \`${latest_snapshot}\`
467
481
  - Semantic diagram source: \`${semantic_diagram_source}\`
468
482
  - Latest human diagram: \`${latest_human_diagram}\`
469
- - Pending architecture request: \`${request_file:-unknown}\`
483
+ - Pending architecture request: \`${pending_request_file}\`
470
484
 
471
485
  ## Active Workstreams
472
486
 
@@ -345,6 +345,7 @@ EOF
345
345
  ensure_gitignore_entry .gitignore ""
346
346
  ensure_gitignore_entry .gitignore "# External references"
347
347
  ensure_gitignore_entry .gitignore "_ref/"
348
+ ensure_gitignore_entry .gitignore ".archcontext/"
348
349
  ensure_gitignore_entry .gitignore ".codegraph/"
349
350
  ensure_gitignore_entry .gitignore ""
350
351
  ensure_gitignore_entry .gitignore "# Local operations state"
@@ -15,6 +15,7 @@ coverage/
15
15
 
16
16
  # External references
17
17
  _ref/
18
+ .archcontext/
18
19
  .codegraph/
19
20
 
20
21
  # Local operations state
@@ -1979,7 +1980,7 @@ pi_write_harness_policy() {
1979
1980
  "fallback_runner": "main-thread",
1980
1981
  "brief_source": "tasks/contracts/<stem>.contract.md",
1981
1982
  "runner_rule": "the active task contract is the authoritative execution brief consumed by contract-run; native subagent (Claude) or codex-subagent (Codex's own native subagent) is an optional parallelism accelerator, preferred first per host. Degrade to codex-exec, then sequential main-thread, on the SAME contract when spawn is unavailable, sandboxed, or unreliable. Runner-availability fallback MUST be recorded in the contract-run manifest and MUST NOT silently succeed; it is a runner-availability fallback, not a product-semantics compatibility fallback.",
1982
- "rule": "UserPromptSubmit.delegation only injects bounded subagent context after explicit user authorization such as /delegate, /parallel, spawn subagents, or parallel investigation"
1983
+ "rule": "UserPromptSubmit.delegation only injects bounded subagent context after explicit user authorization such as /delegate, /parallel, spawn subagents, or parallel investigation. Setting delegation.mode to \"auto\" makes UserPromptSubmit.delegation inject bounded subagent context on prompts without explicit trigger words, with explicit=false and stop_fallback disabled. The global ~/.repo-harness/config.json delegation.mode value, when exactly \"auto\" or \"explicit\", takes precedence over this repo policy value."
1983
1984
  },
1984
1985
  "sidecar_research": {
1985
1986
  "default": true,
@@ -1026,6 +1026,7 @@ migrate_workflow() {
1026
1026
  ensure_gitignore_entry "$repo_gitignore" "*.tgz"
1027
1027
  ensure_gitignore_entry "$repo_gitignore" "# External references"
1028
1028
  ensure_gitignore_entry "$repo_gitignore" "_ref/"
1029
+ ensure_gitignore_entry "$repo_gitignore" ".archcontext/"
1029
1030
  ensure_gitignore_entry "$repo_gitignore" ".codegraph/"
1030
1031
  ensure_gitignore_entry "$repo_gitignore" "# Local operations state"
1031
1032
  ensure_gitignore_entry "$repo_gitignore" "_ops/"
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Global Codex delegation-mode config — the write side of layer (1) in the
3
+ * delegation-advisor hook's resolution order: (1) this file's
4
+ * `delegation.mode` in `~/.repo-harness/config.json` wins; (2) repo
5
+ * `.ai/harness/policy.json` `delegation.mode`; (3) default "explicit". This
6
+ * module only owns persisting the user's global choice; the hook's read-side
7
+ * resolution logic is owned elsewhere.
8
+ *
9
+ * Shares `~/.repo-harness/config.json` with brain-root.ts
10
+ * (`repoHarnessConfigPath`), so every read/write here merges with unknown
11
+ * top-level keys (e.g. `brainRoot`) and unknown keys nested inside an
12
+ * existing `delegation` object — it never clobbers them.
13
+ */
14
+
15
+ import * as fs from 'fs';
16
+ import { repoHarnessConfigPath } from './brain-root';
17
+ import { atomicWriteFileSync, deepEqual, formatJson, readJsonOrEmpty } from '../installer/shared';
18
+
19
+ export type DelegationMode = 'auto' | 'explicit';
20
+
21
+ export const VALID_DELEGATION_MODES: readonly DelegationMode[] = ['auto', 'explicit'];
22
+
23
+ export interface RepoHarnessUserConfigWithDelegation {
24
+ delegation?: { mode?: DelegationMode; [key: string]: unknown };
25
+ [key: string]: unknown;
26
+ }
27
+
28
+ export interface ConfigureDelegationModeResult {
29
+ path: string;
30
+ mode: DelegationMode;
31
+ action: 'created' | 'updated' | 'unchanged';
32
+ }
33
+
34
+ function readUserConfig(env?: NodeJS.ProcessEnv): RepoHarnessUserConfigWithDelegation {
35
+ try {
36
+ const parsed = readJsonOrEmpty<RepoHarnessUserConfigWithDelegation>(repoHarnessConfigPath(env));
37
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
38
+ } catch (_error) {
39
+ return {};
40
+ }
41
+ }
42
+
43
+ function existingDelegationObject(config: RepoHarnessUserConfigWithDelegation): Record<string, unknown> {
44
+ const { delegation } = config;
45
+ return delegation && typeof delegation === 'object' && !Array.isArray(delegation)
46
+ ? (delegation as Record<string, unknown>)
47
+ : {};
48
+ }
49
+
50
+ /** Currently persisted global mode, if any — used as the interactive prompt's default. */
51
+ export function configuredDelegationMode(env?: NodeJS.ProcessEnv): DelegationMode | undefined {
52
+ const mode = existingDelegationObject(readUserConfig(env)).mode;
53
+ return mode === 'auto' || mode === 'explicit' ? mode : undefined;
54
+ }
55
+
56
+ /**
57
+ * Persist `delegation.mode`, merging with existing top-level keys (e.g.
58
+ * `brainRoot`) and existing keys nested inside `delegation` itself. Reports
59
+ * `unchanged` when the merged result matches what's already on disk, so
60
+ * re-installs with the same choice don't churn the file.
61
+ */
62
+ export function configureDelegationMode(
63
+ mode: DelegationMode,
64
+ env?: NodeJS.ProcessEnv,
65
+ ): ConfigureDelegationModeResult {
66
+ const configPath = repoHarnessConfigPath(env);
67
+ const current = readUserConfig(env);
68
+ const existed = fs.existsSync(configPath);
69
+ const next: RepoHarnessUserConfigWithDelegation = {
70
+ ...current,
71
+ delegation: { ...existingDelegationObject(current), mode },
72
+ };
73
+
74
+ if (existed && deepEqual(current, next)) {
75
+ return { path: configPath, mode, action: 'unchanged' };
76
+ }
77
+ atomicWriteFileSync(configPath, formatJson(next));
78
+ return { path: configPath, mode, action: existed ? 'updated' : 'created' };
79
+ }
@@ -16,6 +16,7 @@ import { Command } from 'commander';
16
16
  import { runDoctor, type CheckStatus, type DoctorReport } from './doctor';
17
17
  import { runStatus, type StatusReport } from './status';
18
18
  import type { InstallTargetSpec } from './install';
19
+ import { planAdoption } from '../../core/adoption/plan';
19
20
 
20
21
  export type InitHookTarget = InstallTargetSpec;
21
22
  export type InitHookStatus = 'ok' | 'attention' | 'blocked';
@@ -144,6 +145,10 @@ function verificationCommand(target: InitHookTarget, checkUpdates: boolean): str
144
145
  return `repo-harness setup check --target ${target}${checkUpdates ? ' --check-updates' : ''} --json`;
145
146
  }
146
147
 
148
+ function shellQuoteArg(value: string): string {
149
+ return `'${value.replace(/'/g, "'\\''")}'`;
150
+ }
151
+
147
152
  function addAction(actions: InitHookAction[], action: InitHookAction): void {
148
153
  if (actions.some((entry) => entry.id === action.id)) return;
149
154
  actions.push(action);
@@ -220,6 +225,75 @@ function statusChecks(
220
225
  return checks;
221
226
  }
222
227
 
228
+ function adoptionRefreshCheck(
229
+ report: StatusReport,
230
+ target: InitHookTarget,
231
+ checkUpdates: boolean,
232
+ actions: InitHookAction[],
233
+ ): InitHookCheck {
234
+ const id = 'repo.adopt-refresh';
235
+ const title = 'Repo-local adoption refresh';
236
+ if (!checkUpdates) {
237
+ return {
238
+ id,
239
+ title,
240
+ status: 'na',
241
+ source: 'status',
242
+ detail: 'disabled; run setup check with --check-updates to evaluate repo-local adopt refresh',
243
+ };
244
+ }
245
+
246
+ if (!report.repo.inGitRepo || !report.repo.repoRoot) {
247
+ return {
248
+ id,
249
+ title,
250
+ status: 'na',
251
+ source: 'status',
252
+ detail: 'current directory is not inside a git repo',
253
+ };
254
+ }
255
+
256
+ if (!report.repo.optIn) {
257
+ return {
258
+ id,
259
+ title,
260
+ status: 'na',
261
+ source: 'status',
262
+ detail: `repo is not repo-harness adopted (${report.repo.optInMarker} missing)`,
263
+ };
264
+ }
265
+
266
+ const plan = planAdoption({ repoRoot: report.repo.repoRoot, mode: 'standard', apply: false });
267
+ if (plan.summary.plannedTotal === 0) {
268
+ return {
269
+ id,
270
+ title,
271
+ status: 'ok',
272
+ source: 'status',
273
+ detail: `up-to-date; ${plan.summary.total} operations skipped`,
274
+ };
275
+ }
276
+
277
+ const command = `repo-harness adopt --repo ${shellQuoteArg(report.repo.repoRoot)}`;
278
+ addAction(actions, {
279
+ id: 'repo.adopt-refresh',
280
+ status: 'needs_agent',
281
+ reason: 'The current adopted repo has pending repo-harness adoption plan operations.',
282
+ requires_agent: true,
283
+ risk: 'Updates repo-local workflow files; review the adopt dry-run first and preserve user-owned content.',
284
+ command,
285
+ targets: [report.repo.repoRoot],
286
+ verification: verificationCommand(target, checkUpdates),
287
+ });
288
+ return {
289
+ id,
290
+ title,
291
+ status: 'needs_agent',
292
+ source: 'status',
293
+ detail: `planned=${plan.summary.plannedTotal}; skipped=${plan.summary.skippedTotal}; user_owned=${plan.summary.userOwnedFilesTouched}; command=${command}`,
294
+ };
295
+ }
296
+
223
297
  function parseActionCommand(detail: string): string | undefined {
224
298
  const direct = detail.match(/agent_action=([^;]+)/);
225
299
  if (direct) return direct[1].trim();
@@ -591,6 +665,7 @@ export function runInitHook(opts: InitHookOptions = {}): InitHookReport {
591
665
 
592
666
  const checks: InitHookCheck[] = [
593
667
  ...statusChecks(statusReport, target, checkUpdates, actions),
668
+ adoptionRefreshCheck(statusReport, target, checkUpdates, actions),
594
669
  ...doctorChecks(doctorReport, target, checkUpdates, actions),
595
670
  ...globalRulesChecks(target, opts.env, checkUpdates, actions),
596
671
  ...runtimeCapabilityChecks(toolingProbe.report, target, checkUpdates, actions),
@@ -17,12 +17,21 @@
17
17
 
18
18
  import type { Location } from '../installer/types';
19
19
  import { ALL_TARGETS, getTarget, listTargetIds } from '../installer/targets/registry';
20
+ import { configureDelegationMode, type DelegationMode } from './delegation-mode';
20
21
 
21
22
  export type InstallTargetSpec = 'codex' | 'claude' | 'both';
22
23
 
23
24
  export interface InstallCommandOptions {
24
25
  target: InstallTargetSpec;
25
26
  location: Location;
27
+ /**
28
+ * Codex delegation mode to persist to `~/.repo-harness/config.json`
29
+ * `delegation.mode`. Only takes effect for the codex/both targets at the
30
+ * global location; `undefined` performs no write at all (the caller
31
+ * resolved neither an explicit flag nor an interactive answer — never a
32
+ * silent default).
33
+ */
34
+ delegationMode?: DelegationMode;
26
35
  }
27
36
 
28
37
  export interface InstallCommandResult {
@@ -78,7 +87,12 @@ function runAdapterAction(action: AdapterAction, opts: InstallCommandOptions): I
78
87
  }
79
88
 
80
89
  export function runInstall(opts: InstallCommandOptions): InstallCommandResult {
81
- return runAdapterAction('install', opts);
90
+ const result = runAdapterAction('install', opts);
91
+ if (opts.delegationMode !== undefined && opts.target !== 'claude' && opts.location === 'global') {
92
+ const outcome = configureDelegationMode(opts.delegationMode);
93
+ result.lines.push(`[codex] ${outcome.action}: ${outcome.path} (delegation.mode=${outcome.mode})`);
94
+ }
95
+ return result;
82
96
  }
83
97
 
84
98
  export function runUninstall(opts: InstallCommandOptions): InstallCommandResult {
@@ -8,6 +8,7 @@
8
8
  import type { InstallTargetSpec } from './install';
9
9
  import type { Location } from '../installer/types';
10
10
  import type { AdoptionMode } from '../../core/adoption/modes';
11
+ import { VALID_DELEGATION_MODES, type DelegationMode } from './delegation-mode';
11
12
 
12
13
  // ---------------------------------------------------------------------------
13
14
  // Target
@@ -72,3 +73,17 @@ export function assertBrainMode(raw: string, commandName: string): string {
72
73
  );
73
74
  process.exit(2);
74
75
  }
76
+
77
+ // ---------------------------------------------------------------------------
78
+ // Delegation mode
79
+ // ---------------------------------------------------------------------------
80
+
81
+ export function assertDelegationMode(raw: string, commandName: string): DelegationMode {
82
+ if ((VALID_DELEGATION_MODES as readonly string[]).includes(raw)) {
83
+ return raw as DelegationMode;
84
+ }
85
+ console.error(
86
+ `repo-harness ${commandName}: invalid --delegation-mode "${raw}" (expected: ${VALID_DELEGATION_MODES.join(', ')})`,
87
+ );
88
+ process.exit(2);
89
+ }
package/src/cli/index.ts CHANGED
@@ -10,6 +10,7 @@ import { Command } from 'commander';
10
10
  import { createInterface } from 'readline/promises';
11
11
  import { askConfirm } from './tty-prompt';
12
12
  import { runInstall, runUninstall, type InstallTargetSpec } from './commands/install';
13
+ import { configuredDelegationMode, type DelegationMode } from './commands/delegation-mode';
13
14
  import { runInit, runInteractiveInit, type InitBrainMode } from './commands/init';
14
15
  import { runHook } from './commands/hook';
15
16
  import { CLI_VERSION, formatStatus, runStatus } from './commands/status';
@@ -37,8 +38,10 @@ import {
37
38
  assertLocation,
38
39
  assertAdoptionMode,
39
40
  assertBrainMode,
41
+ assertDelegationMode,
40
42
  } from './commands/validators';
41
43
  import type { HookEvent, RouteId } from './hook/route-registry';
44
+ import type { Location } from './installer/types';
42
45
 
43
46
  export const SUBCOMMANDS = [
44
47
  'init',
@@ -129,6 +132,64 @@ export async function resolveOptionalRuntimeDeps(
129
132
  return { externalSkills, codegraph };
130
133
  }
131
134
 
135
+ export interface ResolveDelegationModeOptions {
136
+ interactive: boolean;
137
+ input?: NodeJS.ReadableStream;
138
+ output?: NodeJS.WritableStream;
139
+ }
140
+
141
+ /**
142
+ * Resolve the Codex delegation-mode choice for `install --location global`.
143
+ * Only applies to the codex/both targets at the global location — every
144
+ * other combination (notably --target claude) resolves to `undefined`
145
+ * without prompting or touching `~/.repo-harness/config.json`.
146
+ *
147
+ * Non-interactive: an explicit `--delegation-mode <mode>` always wins
148
+ * (validated; exits 2 on an unknown value). With no flag and no TTY, this
149
+ * returns `undefined` so the caller skips the config write entirely — no
150
+ * silent default is ever persisted.
151
+ *
152
+ * Interactive: reuses the shared Y/n `askConfirm` helper, framed as keeping
153
+ * vs. switching away from the resolved default (the value already persisted
154
+ * in `~/.repo-harness/config.json`, else "explicit").
155
+ */
156
+ export async function resolveDelegationMode(
157
+ rawOpts: { target: InstallTargetSpec; delegationMode?: string },
158
+ location: Location,
159
+ cmd: OptionSourceLookup | undefined,
160
+ opts: ResolveDelegationModeOptions,
161
+ ): Promise<DelegationMode | undefined> {
162
+ const applies = location === 'global' && rawOpts.target !== 'claude';
163
+
164
+ if (rawOpts.delegationMode !== undefined) {
165
+ const validated = assertDelegationMode(rawOpts.delegationMode, 'install');
166
+ return applies ? validated : undefined;
167
+ }
168
+ if (!applies || !opts.interactive || cmd?.getOptionValueSource('delegationMode') === 'cli') {
169
+ return undefined;
170
+ }
171
+
172
+ const input = opts.input ?? process.stdin;
173
+ const output = opts.output ?? process.stdout;
174
+ const rl = createInterface({ input, output, terminal: false });
175
+ try {
176
+ const current = configuredDelegationMode() ?? 'explicit';
177
+ const other: DelegationMode = current === 'auto' ? 'explicit' : 'auto';
178
+ const detail = (mode: DelegationMode): string =>
179
+ mode === 'auto'
180
+ ? 'hook injects bounded-delegation context on substantive prompts; policy standing authorization'
181
+ : 'only on /parallel, /delegate, explicit delegation wording';
182
+ const keepCurrent = await askConfirm(
183
+ rl,
184
+ output,
185
+ `Codex delegation mode: keep "${current}" (${detail(current)}) instead of "${other}" (${detail(other)})?`,
186
+ );
187
+ return keepCurrent ? current : other;
188
+ } finally {
189
+ rl.close();
190
+ }
191
+ }
192
+
132
193
  async function runGlobalRuntimeBootstrap(
133
194
  commandName: 'init' | 'install',
134
195
  rawOpts: GlobalRuntimeCommandOptions,
@@ -441,6 +502,10 @@ export function buildProgram(): Command {
441
502
  .description('Install the repo-harness global runtime; with --location, install only hook adapters')
442
503
  .option('--target <target>', `Target host: ${TARGET_HELP}`, 'both')
443
504
  .option('--location <location>', `Adapter-only install location: ${LOCATION_HELP}`)
505
+ .option(
506
+ '--delegation-mode <mode>',
507
+ 'Codex delegation mode for --location global installs (codex/both targets only): auto|explicit',
508
+ )
444
509
  .option('--no-cli', 'Skip installing the repo-harness CLI globally')
445
510
  .option('--no-sync-skill', 'Skip refreshing repo-harness skill aliases under host skill roots')
446
511
  .option('--no-hooks', 'Skip global hook adapter installation during full runtime install')
@@ -448,16 +513,24 @@ export function buildProgram(): Command {
448
513
  .option('--no-codegraph', 'Skip CodeGraph CLI/MCP configuration')
449
514
  .option('--brain-root <path>', 'Brain vault root to persist for repo-harness brain commands')
450
515
  .option('--json', 'Output JSON instead of human-readable text')
451
- .action(async (rawOpts: GlobalRuntimeCommandOptions & { location?: string }, cmd: Command) => {
516
+ .action(async (rawOpts: GlobalRuntimeCommandOptions & { location?: string; delegationMode?: string }, cmd: Command) => {
452
517
  const target = assertTarget(rawOpts.target, 'install');
453
518
  if (rawOpts.location === undefined) {
454
519
  await runGlobalRuntimeBootstrap('install', rawOpts, cmd);
455
520
  return;
456
521
  }
457
522
  const location = assertLocation(rawOpts.location!, 'install');
523
+ const interactive = process.stdin.isTTY === true && process.stdout.isTTY === true && rawOpts.json !== true;
524
+ const delegationMode = await resolveDelegationMode(
525
+ { target, delegationMode: rawOpts.delegationMode },
526
+ location,
527
+ cmd,
528
+ { interactive },
529
+ );
458
530
  const result = runInstall({
459
531
  target,
460
532
  location,
533
+ delegationMode,
461
534
  });
462
535
  for (const line of result.lines) console.log(line);
463
536
  process.exit(result.exitCode);