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 +3 -3
- package/README.fr.md +3 -3
- package/README.ja.md +3 -3
- package/README.md +25 -10
- package/README.zh-CN.md +7 -6
- package/assets/AGENTS.md +1 -1
- package/assets/CLAUDE.md +1 -1
- package/assets/hooks/AGENTS.md +1 -1
- package/assets/hooks/CLAUDE.md +1 -1
- package/assets/hooks/codex-delegation-advisor.sh +30 -6
- package/assets/reference-configs/external-tooling.md +12 -8
- package/assets/skill-version.json +6 -2
- package/assets/templates/helpers/architecture-event.ts +8 -1
- package/assets/templates/helpers/capability-resolver.ts +3 -3
- package/assets/templates/helpers/check-agent-tooling.sh +5 -5
- package/assets/templates/helpers/context-contract-sync.sh +15 -1
- package/install.ps1 +9 -1
- package/install.sh +12 -1
- package/package.json +2 -1
- package/scripts/AGENTS.md +1 -1
- package/scripts/CLAUDE.md +1 -1
- package/scripts/architecture-event.ts +8 -1
- package/scripts/capability-resolver.ts +3 -3
- package/scripts/check-agent-tooling.sh +5 -5
- package/scripts/context-contract-sync.sh +15 -1
- package/scripts/init-project.sh +1 -0
- package/scripts/lib/project-init-lib.sh +2 -1
- package/scripts/migrate-project-template.sh +1 -0
- package/src/cli/commands/delegation-mode.ts +79 -0
- package/src/cli/commands/init-hook.ts +75 -0
- package/src/cli/commands/install.ts +15 -1
- package/src/cli/commands/validators.ts +15 -0
- package/src/cli/index.ts +74 -1
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.
|
|
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.
|
|
423
|
-
- Generated workflow stamp: `repo-harness@0.9.
|
|
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.
|
|
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.
|
|
428
|
-
- Generated workflow stamp : `repo-harness@0.9.
|
|
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.
|
|
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.
|
|
402
|
-
- Generated workflow stamp:`repo-harness@0.9.
|
|
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.
|
|
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.
|
|
286
|
-
|
|
287
|
-
|
|
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
|
|
291
|
-
not a runtime hook: it does not write user-level
|
|
292
|
-
|
|
293
|
-
|
|
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.
|
|
616
|
-
- Generated workflow stamp: `repo-harness@0.9.
|
|
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.
|
|
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
|
|
248
|
-
|
|
249
|
-
targets、可选 command 和 verification 的
|
|
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.
|
|
455
|
-
- Generated workflow stamp:`repo-harness@0.9.
|
|
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-
|
|
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-
|
|
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/hooks/AGENTS.md
CHANGED
|
@@ -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-
|
|
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
|
package/assets/hooks/CLAUDE.md
CHANGED
|
@@ -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-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
3
|
-
"templateVersion": "0.9.
|
|
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 || "
|
|
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
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
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("
|
|
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 = `
|
|
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
|
-
"
|
|
882
|
-
"
|
|
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: "
|
|
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: \`${
|
|
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
|
|
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
|
|
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.
|
|
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-
|
|
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-
|
|
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 || "
|
|
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
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
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("
|
|
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 = `
|
|
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
|
-
"
|
|
882
|
-
"
|
|
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: "
|
|
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: \`${
|
|
483
|
+
- Pending architecture request: \`${pending_request_file}\`
|
|
470
484
|
|
|
471
485
|
## Active Workstreams
|
|
472
486
|
|
package/scripts/init-project.sh
CHANGED
|
@@ -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
|
-
|
|
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);
|