axstack 0.20.31 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -21
- package/bin/axstack.js +17 -5
- package/docs/installation.md +97 -48
- package/docs/workflows.md +165 -122
- package/package.json +3 -3
- package/profiles/presets/claude-only.json +23 -23
- package/profiles/presets/codex-only.json +10 -10
- package/profiles/presets/mixed.json +24 -24
- package/skills/axstack/references/automations.md +127 -137
- package/skills/axstack/references/autopilot.md +30 -17
- package/skills/axstack/references/candidate-publication.md +13 -8
- package/skills/axstack/references/contracts.md +10 -8
- package/skills/axstack/references/diligence.md +3 -1
- package/skills/axstack/references/evidence-archive.md +38 -33
- package/skills/axstack/references/lifecycle.md +64 -50
- package/skills/axstack/references/review-manager-prompt.md +13 -11
- package/skills/axstack/references/role-roster.md +12 -2
- package/skills/axstack/references/routing.md +29 -25
- package/skills/axstack/references/run-record.md +35 -16
- package/skills/axstack/references/t3-runtime.md +234 -0
- package/skills/axstack/references/test-audit-weekly.md +62 -0
- package/skills/axstack/references/test-value.md +120 -0
- package/skills/axstack/references/ui-verification.md +5 -1
- package/skills/axstack/references/workspace-hygiene.md +102 -156
- package/skills/axstack/scripts/pr-digest.js +120 -0
- package/skills/axstack/scripts/resolve-models.js +94 -38
- package/skills/axstack-align/SKILL.md +17 -7
- package/skills/axstack-audit/SKILL.md +12 -3
- package/skills/axstack-audit/references/record.md +1 -1
- package/skills/axstack-cleanup/SKILL.md +69 -87
- package/skills/axstack-debug/SKILL.md +1 -1
- package/skills/axstack-explain/SKILL.md +1 -1
- package/skills/axstack-explain/references/visual-qa.md +2 -0
- package/skills/axstack-implement/SKILL.md +56 -20
- package/skills/axstack-improve/SKILL.md +24 -4
- package/skills/axstack-relay/SKILL.md +8 -6
- package/skills/axstack-research/SKILL.md +11 -4
- package/skills/axstack-review/SKILL.md +34 -30
- package/skills/axstack-spec/SKILL.md +18 -13
- package/skills/axstack-tickets/SKILL.md +7 -8
- package/skills/axstack-watch/SKILL.md +97 -27
- package/skills/axstack-watch/references/watch-runtime.md +51 -66
- package/src/capabilities.js +33 -69
- package/src/installer.js +1 -1
- package/src/instructions.js +9 -4
- package/skills/axstack/references/orca-runtime.md +0 -202
- package/skills/axstack/scripts/trust-path.js +0 -123
package/src/capabilities.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
// Host capability checks.
|
|
2
|
-
// runtime. Resolve Orca exactly once and reuse it: a failed choice never
|
|
3
|
-
// triggers a fallback to another binary.
|
|
1
|
+
// Host capability checks. Injected execution keeps tests off live runtimes.
|
|
4
2
|
export const BUN_FLOOR = '1.3.14';
|
|
3
|
+
export const T3_FLOOR = '0.0.46-nightly.20261003.2610';
|
|
5
4
|
|
|
6
5
|
export const PROBE_LIMITATIONS = [
|
|
7
|
-
'
|
|
8
|
-
'A
|
|
9
|
-
'
|
|
6
|
+
'T3 orchestration MCP readiness, provider auth, models and effort are verified by driver preflight inside a T3 thread, never from the binary probe.',
|
|
7
|
+
'A binary probe cannot prove model availability or quotas.',
|
|
8
|
+
'An unavailable or exhausted model pauses affected work until the user decides.',
|
|
9
|
+
'Stored role model, effort and permission intent do not prove T3 launch parity or successful agent execution.',
|
|
10
10
|
];
|
|
11
11
|
|
|
12
12
|
const CHECK_LABELS = {
|
|
@@ -14,66 +14,34 @@ const CHECK_LABELS = {
|
|
|
14
14
|
git: 'git CLI',
|
|
15
15
|
gh: 'gh CLI',
|
|
16
16
|
'gh-stack': 'gh stack extension',
|
|
17
|
-
'
|
|
18
|
-
'orca-runtime': 'Orca runtime connection',
|
|
19
|
-
'orca-orchestration-guide': 'Orca orchestration guide capability',
|
|
20
|
-
'orca-cli-guide': 'Orca CLI guide capability',
|
|
21
|
-
'orca-linear-guide': 'Orca Linear guide capability',
|
|
17
|
+
't3-binary': `T3 Code CLI >= ${T3_FLOOR}`,
|
|
22
18
|
};
|
|
23
19
|
|
|
24
|
-
//
|
|
25
|
-
// `gh stack --help`: a "stack" substring in `gh extension list` output is not
|
|
26
|
-
// proof the extension command works.
|
|
20
|
+
// Run the extension command itself, not a substring in an extension listing.
|
|
27
21
|
export const PROBE_COMMANDS = {
|
|
28
22
|
git: ['git', ['--version']],
|
|
29
23
|
gh: ['gh', ['--version']],
|
|
30
24
|
'gh-stack': ['gh', ['stack', '--help']],
|
|
25
|
+
't3-binary': ['t3', ['--version']],
|
|
31
26
|
};
|
|
32
27
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
return
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
function orcaCommand(name, executable) {
|
|
46
|
-
if (name === 'orca-binary') return [executable, ['--version']];
|
|
47
|
-
if (name === 'orca-runtime') return [executable, ['status', '--json']];
|
|
48
|
-
if (name === 'orca-orchestration-guide') {
|
|
49
|
-
return [executable, ['skills', 'get', 'orchestration', '--json']];
|
|
50
|
-
}
|
|
51
|
-
if (name === 'orca-cli-guide') return [executable, ['skills', 'get', 'orca-cli', '--json']];
|
|
52
|
-
if (name === 'orca-linear-guide') return [executable, ['skills', 'get', 'orca-linear', '--json']];
|
|
53
|
-
return null;
|
|
28
|
+
function validT3Version(stdout) {
|
|
29
|
+
const match = /^(?:t3\s+)?v?(\d+\.\d+\.\d+)(?:-nightly\.(\d{8})(?:\.(\d+))?)?$/.exec(stdout.trim());
|
|
30
|
+
const [floorVersion, floorNightly] = T3_FLOOR.split('-nightly.');
|
|
31
|
+
const [floorDate, floorBuild] = floorNightly.split('.');
|
|
32
|
+
if (!match || !meetsFloor(match[1], floorVersion)) return false;
|
|
33
|
+
if (!match[2]) return true;
|
|
34
|
+
const date = match[2];
|
|
35
|
+
const iso = `${date.slice(0, 4)}-${date.slice(4, 6)}-${date.slice(6, 8)}`;
|
|
36
|
+
const parsed = new Date(`${iso}T00:00:00Z`);
|
|
37
|
+
return Number.isFinite(parsed.getTime()) && parsed.toISOString().slice(0, 10) === iso
|
|
38
|
+
&& (match[1] !== floorVersion || date > floorDate || (date === floorDate && match[3] !== undefined
|
|
39
|
+
&& Number(match[3]) >= Number(floorBuild)));
|
|
54
40
|
}
|
|
55
41
|
|
|
56
|
-
function
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
parsed = JSON.parse(stdout);
|
|
61
|
-
} catch {
|
|
62
|
-
return { ok: false, stdout: 'invalid JSON response' };
|
|
63
|
-
}
|
|
64
|
-
if (name === 'orca-runtime') {
|
|
65
|
-
const runtime = parsed?.result?.runtime;
|
|
66
|
-
const ready = parsed?.ok === true && runtime?.state === 'ready' &&
|
|
67
|
-
runtime?.reachable === true && runtime?.connectionState === 'connected';
|
|
68
|
-
return { ok: ready, stdout: ready ? 'ready and connected' : 'runtime is not ready and connected' };
|
|
69
|
-
}
|
|
70
|
-
const expected = name === 'orca-cli-guide'
|
|
71
|
-
? 'orca-cli'
|
|
72
|
-
: name === 'orca-linear-guide'
|
|
73
|
-
? 'orca-linear'
|
|
74
|
-
: 'orchestration';
|
|
75
|
-
const ready = parsed?.name === expected && typeof parsed?.markdown === 'string' && parsed.markdown.length > 0;
|
|
76
|
-
return { ok: ready, stdout: ready ? `${expected} guide available` : `${expected} guide unavailable` };
|
|
42
|
+
function validateT3Result(result) {
|
|
43
|
+
if (!result?.ok || validT3Version(String(result.stdout ?? ''))) return result;
|
|
44
|
+
return { ok: false, stdout: `requires T3 >= ${T3_FLOOR}; got ${String(result.stdout ?? '').trim() || 'malformed version'}` };
|
|
77
45
|
}
|
|
78
46
|
|
|
79
47
|
// Pure semver-floor comparison over numeric prefix segments ("1.3.14" style;
|
|
@@ -89,12 +57,12 @@ export function meetsFloor(version, floor = BUN_FLOOR) {
|
|
|
89
57
|
return true;
|
|
90
58
|
}
|
|
91
59
|
|
|
92
|
-
export async function runRealCheck(name
|
|
60
|
+
export async function runRealCheck(name) {
|
|
93
61
|
if (name === 'bun') {
|
|
94
62
|
const version = Bun.version;
|
|
95
63
|
return { ok: meetsFloor(version), stdout: `v${version}` };
|
|
96
64
|
}
|
|
97
|
-
const [cmd, args] =
|
|
65
|
+
const [cmd, args] = PROBE_COMMANDS[name];
|
|
98
66
|
try {
|
|
99
67
|
const result = Bun.spawnSync([cmd, ...args], {
|
|
100
68
|
stdout: 'pipe',
|
|
@@ -103,7 +71,8 @@ export async function runRealCheck(name, { orcaExecutable = resolveOrcaExecutabl
|
|
|
103
71
|
});
|
|
104
72
|
if (result.exitCode === 0) {
|
|
105
73
|
const stdout = result.stdout.toString().trim();
|
|
106
|
-
|
|
74
|
+
const checked = { ok: true, stdout };
|
|
75
|
+
return name === 't3-binary' ? validateT3Result(checked) : checked;
|
|
107
76
|
}
|
|
108
77
|
const detail = (result.stderr.toString().trim() || result.stdout.toString().trim()).slice(0, 120);
|
|
109
78
|
return { ok: false, stdout: detail || `exit ${result.exitCode}` };
|
|
@@ -112,27 +81,22 @@ export async function runRealCheck(name, { orcaExecutable = resolveOrcaExecutabl
|
|
|
112
81
|
}
|
|
113
82
|
}
|
|
114
83
|
|
|
115
|
-
export async function checkCapabilities(exec
|
|
116
|
-
const
|
|
117
|
-
const names = [
|
|
118
|
-
'bun', 'git', 'gh', 'gh-stack', 'orca-binary', 'orca-runtime',
|
|
119
|
-
'orca-orchestration-guide', 'orca-cli-guide', 'orca-linear-guide',
|
|
120
|
-
];
|
|
84
|
+
export async function checkCapabilities(exec) {
|
|
85
|
+
const names = ['bun', 'git', 'gh', 'gh-stack', 't3-binary'];
|
|
121
86
|
const checks = [];
|
|
122
87
|
for (const name of names) {
|
|
123
88
|
let result;
|
|
124
89
|
try {
|
|
125
|
-
result = await exec(name
|
|
90
|
+
result = await exec(name);
|
|
126
91
|
} catch (err) {
|
|
127
92
|
result = { ok: false, stdout: err?.message ?? 'error' };
|
|
128
93
|
}
|
|
94
|
+
if (name === 't3-binary') result = validateT3Result(result);
|
|
129
95
|
const ok = !!result?.ok;
|
|
130
96
|
const baseLabel = CHECK_LABELS[name] ?? name;
|
|
131
97
|
checks.push({
|
|
132
98
|
name,
|
|
133
|
-
label:
|
|
134
|
-
? `${baseLabel} via ${orcaExecutable}`
|
|
135
|
-
: baseLabel,
|
|
99
|
+
label: baseLabel,
|
|
136
100
|
ok,
|
|
137
101
|
detail: ok
|
|
138
102
|
? String(result?.stdout ?? '').trim().slice(0, 120) || 'found'
|
package/src/installer.js
CHANGED
|
@@ -535,7 +535,7 @@ export async function installBundle({
|
|
|
535
535
|
});
|
|
536
536
|
if (findLegacyRoutingLines(existingInstructionsRaw ?? '').length > 0) {
|
|
537
537
|
legacyInstructionNote =
|
|
538
|
-
'legacy
|
|
538
|
+
'legacy routing text remains outside the Axstack block; preserved for manual migration';
|
|
539
539
|
}
|
|
540
540
|
}
|
|
541
541
|
|
package/src/instructions.js
CHANGED
|
@@ -9,7 +9,11 @@ export function renderInstructionBlock() {
|
|
|
9
9
|
BEGIN,
|
|
10
10
|
'Use Axstack for engineering work: invoke the matching `axstack-*` skill directly.',
|
|
11
11
|
'`axstack-implement` loops author -> review -> repair until every PR is merge-ready.',
|
|
12
|
-
'Route every subagent, delegated worker, reviewer, and cross-harness dispatch through
|
|
12
|
+
'Route every subagent, delegated worker, reviewer, and cross-harness dispatch through T3 Code orchestration using the `t3-code` MCP.',
|
|
13
|
+
'Use `delegate_task` for non-writer roles.',
|
|
14
|
+
'Use `t3_thread_launch` for writers.',
|
|
15
|
+
'Follow `references/t3-runtime.md` in the installed `axstack` skill for the runtime contract.',
|
|
16
|
+
'The user authorizes Axstack drivers in T3 to run full-access and launch top-level writer threads and worktrees within approved scope.',
|
|
13
17
|
'Do not use a harness native subagent tool for delegated work.',
|
|
14
18
|
END,
|
|
15
19
|
].join('\n');
|
|
@@ -89,12 +93,13 @@ export function stripInstructionBlock(text, ownership, { force = false } = {}) {
|
|
|
89
93
|
};
|
|
90
94
|
}
|
|
91
95
|
|
|
96
|
+
// AC3 exemption: detection only, never an active runtime dependency.
|
|
97
|
+
export const LEGACY_ROUTING_PATTERN = /\bhaoshoku\b.*\b(?:rout\w*|skills?)\b|\b(?:planning-advisor|review-code|paseo-pr-review|paseo-pr-babysit)\b|\borca(?:-cli)?\b.*\b(?:orchestrat\w*|rout\w*|delegat\w*|dispatch\w*|subagents?|workers?|reviewers?)\b|\b(?:orchestrat\w*|rout\w*|delegat\w*|dispatch\w*|subagents?|workers?|reviewers?)\b.*\borca(?:-cli)?\b/i;
|
|
98
|
+
|
|
92
99
|
export function findLegacyRoutingLines(text) {
|
|
93
100
|
const located = locateInstructionBlock(text);
|
|
94
101
|
const outside = located
|
|
95
102
|
? text.slice(0, located.start) + text.slice(located.end)
|
|
96
103
|
: text;
|
|
97
|
-
return outside.split('\n').filter((line) =>
|
|
98
|
-
/\bhaoshoku\b.*\b(?:rout\w*|skills?)\b|\b(?:planning-advisor|review-code|paseo-pr-review|paseo-pr-babysit)\b/i.test(line),
|
|
99
|
-
);
|
|
104
|
+
return outside.split('\n').filter((line) => LEGACY_ROUTING_PATTERN.test(line));
|
|
100
105
|
}
|
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
# Orca runtime boundary
|
|
2
|
-
|
|
3
|
-
Read this immediately before using Orca for a role dispatch, handoff, delivery,
|
|
4
|
-
settlement, or recovery. Ordinary local reading and writing do not load it.
|
|
5
|
-
Axstack owns policy, role selection, and evidence; Orca owns Run, Task,
|
|
6
|
-
Dispatch, session, worktree, messaging, settlement, and scheduling state.
|
|
7
|
-
|
|
8
|
-
## Discover the runtime-owned guides
|
|
9
|
-
|
|
10
|
-
Resolve one Orca executable for the session and reuse it. Prefer
|
|
11
|
-
`ORCA_CLI_COMMAND` when set, then the checkout's `orca-dev` when
|
|
12
|
-
`ORCA_DEV_REPO_ROOT` is set, the Linux-safe `orca-ide` outside managed
|
|
13
|
-
terminals, and otherwise `orca`. If the selected executable fails, report that
|
|
14
|
-
exact gap; never switch binaries silently.
|
|
15
|
-
|
|
16
|
-
Load only the selected executable's version-matched guides needed by the
|
|
17
|
-
operation through `skills get orchestration --json`,
|
|
18
|
-
`skills get orca-cli --json`, and `skills get orca-linear --json`.
|
|
19
|
-
`orchestration` owns Run, Task,
|
|
20
|
-
Dispatch, messaging, supervision,
|
|
21
|
-
settlement, and recovery. `orca-cli` owns worktrees, terminals, automations,
|
|
22
|
-
handoffs, and artifact publication; load its named conditional reference at
|
|
23
|
-
the matching action gate. `orca-linear` owns Linear issue reads and writes.
|
|
24
|
-
Follow returned schemas and current command help rather than copying their
|
|
25
|
-
procedures into Axstack. Guide discovery does not prove runtime support for a
|
|
26
|
-
particular operation: preflight that operation and report an advertised gap.
|
|
27
|
-
Missing discovery never authorizes legacy runtime use, a silent integration or
|
|
28
|
-
store fallback, or an Axstack dispatcher, daemon, scheduler, database, or
|
|
29
|
-
escalation engine.
|
|
30
|
-
|
|
31
|
-
Orca artifacts publish public-by-link output. They are not private evidence
|
|
32
|
-
storage and must never receive private evidence by default; sharing requires
|
|
33
|
-
explicit publication authority and the `orca-cli` publishing reference.
|
|
34
|
-
|
|
35
|
-
## Bind the configured role
|
|
36
|
-
|
|
37
|
-
Read `roles.json` from the installed shared root `skills/axstack/`. The installed
|
|
38
|
-
shape is `{ "version": 1, "preset": "<name>", "roles": [...] }`. Bundled
|
|
39
|
-
profiles are setup inputs shaped as
|
|
40
|
-
`{ "version": 1, "roles": [...] }`. A new run records the selected preset and
|
|
41
|
-
all 32 role rows once. For each role record class, resolved exact ID, source,
|
|
42
|
-
and time. An active run keeps the exact snapshot; resume reuses it without
|
|
43
|
-
re-resolution until the user explicitly changes it.
|
|
44
|
-
|
|
45
|
-
Select the requested role by stable ID. A missing class and missing or null
|
|
46
|
-
model holds only that role; never launch a provider default. Resolve Codex
|
|
47
|
-
classes with `scripts/resolve-models.js`, passing the catalog path explicitly;
|
|
48
|
-
missing or malformed catalogs hold. The first launch of each Claude class uses
|
|
49
|
-
its alias. Read the exact ID from the first assistant turn's `message.model` in
|
|
50
|
-
that worker's own session transcript at
|
|
51
|
-
`~/.claude/projects/<worktree-path-slug>/*.jsonl`; the worktree path slug
|
|
52
|
-
replaces each non-alphanumeric character with `-`. Identify the file by the
|
|
53
|
-
worker's session ID, or use the newest file created after launch. Later launches
|
|
54
|
-
of that class use the recorded exact ID. Before read-back record `alias,
|
|
55
|
-
unresolved`; record an unknown read-back as unknown and hold
|
|
56
|
-
provenance-dependent work. A worker self-report is a labeled last
|
|
57
|
-
resort. Launch-by-agent-id routes for which Orca exposes no
|
|
58
|
-
`--model` override (today: `grok`, `antigravity`) record `model: null` with an explicit note and are
|
|
59
|
-
launchable; the run record snapshots the model the TUI reports. Validate provider, model, and effort
|
|
60
|
-
against the guide and actual launch capability. Stored `modeId` and other
|
|
61
|
-
permission fields are conservative intent, not proof of effective permission
|
|
62
|
-
parity or a security boundary. Requested settings, input acceptance, effective
|
|
63
|
-
settings, and completed work are separate evidence. An unsupported or
|
|
64
|
-
unavailable value holds affected work for the user's decision except the narrow
|
|
65
|
-
retry below.
|
|
66
|
-
The single-provider preset's null adviser and round-2 seat are intentional installation data, not
|
|
67
|
-
readiness failure; because Align and Spec require both adviser receipts, either
|
|
68
|
-
null adviser still holds those phases. The current chat is the driver and has
|
|
69
|
-
no role row in any preset.
|
|
70
|
-
|
|
71
|
-
Only explicit model rejection before the first turn permits a Codex
|
|
72
|
-
`--retry-of` with the next eligible ID in the same class, provider, and effort.
|
|
73
|
-
Fence the rejected Dispatch and record tried ID, error, and fallback ID in the
|
|
74
|
-
snapshot and reply. Timeout, quota, auth, and other failures hold; Claude
|
|
75
|
-
rejection holds. Apply this to every role, including advisers and judges.
|
|
76
|
-
|
|
77
|
-
## Materialize checkouts as worktrees of the registered repo
|
|
78
|
-
|
|
79
|
-
Every reviewer, release, or worker checkout is `ORCA worktree create --repo
|
|
80
|
-
id:<repoId> ...` under the repo Orca already registers. `ORCA repo add` is a
|
|
81
|
-
one-time import of a new repository; running it on a clone of a registered
|
|
82
|
-
repo creates a second top-level repo record, so it never materializes a
|
|
83
|
-
checkout. See [Candidate publication](candidate-publication.md) for the
|
|
84
|
-
detached immutable review checkout.
|
|
85
|
-
|
|
86
|
-
## Supervise one authoritative attempt
|
|
87
|
-
|
|
88
|
-
For supervised work, use the orchestration guide's native Run, Task, and
|
|
89
|
-
Dispatch flow. Reconcile existing attempts first. Bind the approved spec or
|
|
90
|
-
small-change intent, brief, authority, role snapshot, worktree, base, and
|
|
91
|
-
candidate to the Task; preserve the returned
|
|
92
|
-
Task, Dispatch, terminal, agent, and worktree identities. Exactly one Dispatch
|
|
93
|
-
may write a candidate at a time.
|
|
94
|
-
Put the [Safe deletion](workspace-hygiene.md#safe-deletion) rule in every
|
|
95
|
-
worker brief.
|
|
96
|
-
|
|
97
|
-
Create a worker worktree with `--parent-worktree` naming the candidate's
|
|
98
|
-
worktree when both are in the same repository; see
|
|
99
|
-
[Readable sidebar](workspace-hygiene.md#readable-sidebar) for naming, status,
|
|
100
|
-
and parentage at dispatch. `--no-parent` is for unrelated work with no parent
|
|
101
|
-
context. Where supported, correct wrong lineage in place with
|
|
102
|
-
`worktree set --parent-worktree`. Lineage is presentation, never authority: it
|
|
103
|
-
grants nothing and cannot replace Task, Dispatch, and receipt evidence.
|
|
104
|
-
`--no-parent` is never used for a review or repair checkout.
|
|
105
|
-
|
|
106
|
-
Every reviewer gets a separate Orca child worktree parented to the candidate.
|
|
107
|
-
Keep that reviewer's probes and private evidence in its separate private
|
|
108
|
-
per-Dispatch run folder under [Workspace hygiene](workspace-hygiene.md), with no
|
|
109
|
-
first-pass cross-read. Untracked files never prove a worktree disposable.
|
|
110
|
-
|
|
111
|
-
Before launching a Claude worker in a checkout, from the installed `axstack` skill directory
|
|
112
|
-
run `bun scripts/trust-path.js --path <exact checkout path>` for that
|
|
113
|
-
exact checkout path. It trusts only Orca-registered repository roots and their
|
|
114
|
-
worktrees. A failed preflight holds launch; workers never answer trust or
|
|
115
|
-
permission dialogs. A trust dialog that still appears is a hold.
|
|
116
|
-
|
|
117
|
-
An `input_accepted` stage proves only that input reached the terminal. Require
|
|
118
|
-
`turn_started` plus runtime/session inspection before treating the agent as
|
|
119
|
-
started, and verify the requested role independently before trusting its work.
|
|
120
|
-
If a worker asks to confirm its own dispatch brief, the dispatching owner
|
|
121
|
-
confirms once by typed terminal input restating the brief's authority, then
|
|
122
|
-
re-verifies `turn_started`. That confirmation never answers a trust or permission prompt;
|
|
123
|
-
it answers only the agent's own model-turn question about its brief, never a
|
|
124
|
-
harness or tool dialog, and adds no authority. A second ask is a hold.
|
|
125
|
-
A workspace trust, hook review, permission, authentication, or model prompt is
|
|
126
|
-
a visible hold. Never answer a trust or permission prompt on the worker's
|
|
127
|
-
behalf. A permission prompt or provider safety refusal is a held, incomplete
|
|
128
|
-
outcome, never consent or completion. Never bypass or retry it through another
|
|
129
|
-
model. Preserve the attempt and its evidence, then use only
|
|
130
|
-
the runtime guide's inspection and recovery procedure; reconcile before any
|
|
131
|
-
authorized retry so no duplicate writer starts.
|
|
132
|
-
|
|
133
|
-
## Reviewer workspaces and evidence
|
|
134
|
-
|
|
135
|
-
Give each reviewer a separate Orca-managed child worktree under the candidate's
|
|
136
|
-
worktree, including report-only reviews and rechecks; never share the author's
|
|
137
|
-
checkout or another reviewer's checkout. A later review gets a fresh Orca-managed
|
|
138
|
-
child worktree after the prior settled review's evidence and cleanup are
|
|
139
|
-
reconciled. Use the runtime-owned worktree guide,
|
|
140
|
-
not a raw Git worktree or temporary clone. Before dispatch, verify a detached
|
|
141
|
-
checkout of the exact candidate SHA and the pinned base in that child.
|
|
142
|
-
|
|
143
|
-
Name the private `<run dir>/evidence/<dispatch>/` folder in the brief and
|
|
144
|
-
completion receipt. Keep tracked candidate files read-only and peer folders
|
|
145
|
-
isolated. Scope `TMPDIR` to that 0700 folder for owned commands where supported.
|
|
146
|
-
Before use or temporary-file cleanup, validate that its real path equals or
|
|
147
|
-
is inside the recorded run evidence folder, is not a symbolic link, and matches the recorded
|
|
148
|
-
Dispatch owner. Worktree-local temporary paths use the same guards against
|
|
149
|
-
their recorded worktree and owner. Remove only an exact validated owned
|
|
150
|
-
path, with no glob or parent-root deletion; never wipe a general cache.
|
|
151
|
-
Uncertain temporary paths are preserved for reconciliation.
|
|
152
|
-
Before removing a reviewer worktree, read back its report and supporting
|
|
153
|
-
evidence from the private run evidence folder and record their paths. Files
|
|
154
|
-
already there need no archive step; the private evidence archive applies only
|
|
155
|
-
to legacy in-worktree evidence. Incidental caches are not evidence.
|
|
156
|
-
A settled reviewer Dispatch can be
|
|
157
|
-
cleaned before PR merge through [axstack-cleanup](../../axstack-cleanup/SKILL.md)
|
|
158
|
-
only after its classification, readback, and removal guards pass. Preserve
|
|
159
|
-
active or unknown review evidence and unique evidence whose bytes must survive;
|
|
160
|
-
uncertain ownership or evidence holds. Terminal release alone is not permission
|
|
161
|
-
to discard evidence or remove the worktree.
|
|
162
|
-
|
|
163
|
-
## Consume, settle, and recover
|
|
164
|
-
|
|
165
|
-
Process a whole delivery before acknowledgment. Accept `worker_done` only when
|
|
166
|
-
its sender, Task, and Dispatch match the expected active attempt; then verify
|
|
167
|
-
the candidate revision and evidence before advancing Axstack's derived record.
|
|
168
|
-
A valid completion for an older Dispatch never completes a newer Dispatch.
|
|
169
|
-
Duplicate messages are deduplicated by their runtime identity.
|
|
170
|
-
|
|
171
|
-
On `consumer_fenced`, stop consuming under that identity. Reconcile the active
|
|
172
|
-
coordinator and delivery through the runtime guide; never bypass the fence,
|
|
173
|
-
forge a sender, borrow a terminal identity, or partially acknowledge the
|
|
174
|
-
delivery. Settlement is also runtime-owned: reuse, retain, or release a settled
|
|
175
|
-
terminal only through the guide. A genuine `user_takeover` result requires
|
|
176
|
-
retention; do not close, release, reuse, or send commands to that terminal as
|
|
177
|
-
cleanup. Apply only the recorded
|
|
178
|
-
repair Dispatch ID exception in [Workspace hygiene](workspace-hygiene.md).
|
|
179
|
-
|
|
180
|
-
Contact loss, silence, idle state, or an absent status never proves exit or
|
|
181
|
-
transfers authority. Ordinary restart and resume reconcile the same owner,
|
|
182
|
-
author, Task, Dispatch, worktree, revisions, and pending receipts. Authorized
|
|
183
|
-
repairs return to the same author when its session and evidence remain usable;
|
|
184
|
-
uncertainty holds replacement rather than creating a second writer.
|
|
185
|
-
|
|
186
|
-
## Transfer ownership explicitly
|
|
187
|
-
|
|
188
|
-
Distinguish supervised workers from a full ownership handoff. Only the user's
|
|
189
|
-
explicit transfer request enters this branch. Record the intended recipient,
|
|
190
|
-
exact scope, revisions, authority, and pending request before following the
|
|
191
|
-
runtime-owned `orca-cli` handoff procedure.
|
|
192
|
-
|
|
193
|
-
Input acceptance or turn start is launch evidence, not ownership. Validate an
|
|
194
|
-
explicit recipient acceptance against the intended request, session, scope,
|
|
195
|
-
candidate/base, and authority before changing ownership. Until then the current
|
|
196
|
-
owner remains accountable. After a valid acceptance, record it, transfer only
|
|
197
|
-
the accepted authority, and have the prior owner stop. Ordinary resume keeps
|
|
198
|
-
the current owner and never launches a handoff.
|
|
199
|
-
|
|
200
|
-
The runtime step is complete only when its real receipts are recorded with
|
|
201
|
-
their limitations. Those receipts grant no merge, release, publication, model
|
|
202
|
-
substitution, host-configuration, or scope authority.
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
import { chmod, lstat, readFile, rename, unlink, writeFile } from 'node:fs/promises';
|
|
3
|
-
|
|
4
|
-
function fail(message) { throw new Error(message); }
|
|
5
|
-
|
|
6
|
-
function args(argv) {
|
|
7
|
-
const values = {};
|
|
8
|
-
for (let i = 0; i < argv.length; i += 2) {
|
|
9
|
-
const key = argv[i];
|
|
10
|
-
if (!['--path', '--repo-list-file', '--worktree-list-file'].includes(key) || !argv[i + 1] || values[key]) {
|
|
11
|
-
fail(`invalid argument: ${key ?? '(end)'}`);
|
|
12
|
-
}
|
|
13
|
-
values[key] = argv[i + 1];
|
|
14
|
-
}
|
|
15
|
-
const path = values['--path'];
|
|
16
|
-
if (!path || !path.startsWith('/') || path.includes('\0') || path.split('/').some((part) => part === '.' || part === '..') ||
|
|
17
|
-
(path !== '/' && (path.endsWith('/') || path.includes('//')))) fail('path must be an exact absolute path');
|
|
18
|
-
if (Boolean(values['--repo-list-file']) !== Boolean(values['--worktree-list-file'])) {
|
|
19
|
-
fail('both Orca inventory files are required together');
|
|
20
|
-
}
|
|
21
|
-
return values;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async function inventory(file, command, key) {
|
|
25
|
-
let raw;
|
|
26
|
-
if (file) raw = await readFile(file, 'utf8');
|
|
27
|
-
else {
|
|
28
|
-
const result = Bun.spawnSync(['orca', ...command, '--json'], { stdout: 'pipe', stderr: 'pipe' });
|
|
29
|
-
if (result.exitCode !== 0) fail(`Orca ${command.join(' ')} failed: ${result.stderr.toString().trim()}`);
|
|
30
|
-
raw = result.stdout.toString();
|
|
31
|
-
}
|
|
32
|
-
const response = JSON.parse(raw);
|
|
33
|
-
if (response.ok !== true || !Array.isArray(response.result?.[key])) fail(`invalid Orca ${key} inventory`);
|
|
34
|
-
if (response.result.truncated === true) fail(`truncated Orca ${key} inventory`);
|
|
35
|
-
return response.result[key];
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async function configStat(path) {
|
|
39
|
-
const stat = await lstat(path).catch((err) => err?.code === 'ENOENT' ? null : Promise.reject(err));
|
|
40
|
-
if (stat?.isSymbolicLink()) fail(`refusing symlinked Claude config: ${path}`);
|
|
41
|
-
if (stat && !stat.isFile()) fail(`Claude config is not a regular file: ${path}`);
|
|
42
|
-
return stat;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async function snapshot(path) {
|
|
46
|
-
const stat = await configStat(path);
|
|
47
|
-
return { stat, raw: stat ? await readFile(path, 'utf8') : null };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async function withLock(path, action) {
|
|
51
|
-
// The lock serializes this helper's writers; byte checks also catch other writers.
|
|
52
|
-
const lock = `${path}.axstack-lock`;
|
|
53
|
-
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
54
|
-
try {
|
|
55
|
-
await writeFile(lock, `${process.pid}\n`, { flag: 'wx', mode: 0o600 });
|
|
56
|
-
try { return await action(); }
|
|
57
|
-
finally { await unlink(lock); }
|
|
58
|
-
} catch (err) {
|
|
59
|
-
if (err?.code !== 'EEXIST') throw err;
|
|
60
|
-
await Bun.sleep(5 + Math.floor(Math.random() * 10));
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
fail('Claude config is busy; trust preflight held');
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async function trust(configPath, path) {
|
|
67
|
-
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
68
|
-
const { stat, raw } = await snapshot(configPath);
|
|
69
|
-
const current = raw === null ? {} : JSON.parse(raw);
|
|
70
|
-
if (!current || typeof current !== 'object' || Array.isArray(current) ||
|
|
71
|
-
(current.projects !== undefined && (!current.projects || typeof current.projects !== 'object' || Array.isArray(current.projects)))) {
|
|
72
|
-
fail('invalid Claude config shape');
|
|
73
|
-
}
|
|
74
|
-
if (current.projects?.[path]?.hasTrustDialogAccepted === true) {
|
|
75
|
-
console.log(JSON.stringify({ status: 'already-trusted', path }));
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
current.projects ??= {};
|
|
79
|
-
if (current.projects[path] !== undefined && (!current.projects[path] || typeof current.projects[path] !== 'object' || Array.isArray(current.projects[path]))) {
|
|
80
|
-
fail('invalid Claude project entry');
|
|
81
|
-
}
|
|
82
|
-
current.projects[path] ??= {};
|
|
83
|
-
current.projects[path].hasTrustDialogAccepted = true;
|
|
84
|
-
|
|
85
|
-
const temp = `${configPath}.${crypto.randomUUID()}.tmp`;
|
|
86
|
-
try {
|
|
87
|
-
await writeFile(temp, `${JSON.stringify(current, null, 2)}\n`, { flag: 'wx', mode: stat ? stat.mode & 0o777 : 0o600 });
|
|
88
|
-
if (stat) await chmod(temp, stat.mode & 0o777);
|
|
89
|
-
if ((await snapshot(configPath)).raw !== raw) {
|
|
90
|
-
await Bun.sleep(5 + Math.floor(Math.random() * 10));
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
await rename(temp, configPath);
|
|
94
|
-
const after = await snapshot(configPath);
|
|
95
|
-
if (JSON.parse(after.raw).projects?.[path]?.hasTrustDialogAccepted === true) {
|
|
96
|
-
console.log(JSON.stringify({ status: 'trusted', path }));
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
} finally {
|
|
100
|
-
await unlink(temp).catch((err) => { if (err?.code !== 'ENOENT') throw err; });
|
|
101
|
-
}
|
|
102
|
-
await Bun.sleep(5 + Math.floor(Math.random() * 10));
|
|
103
|
-
}
|
|
104
|
-
fail('Claude config changed during trust preflight');
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async function main() {
|
|
108
|
-
const options = args(process.argv.slice(2));
|
|
109
|
-
const path = options['--path'];
|
|
110
|
-
const repos = await inventory(options['--repo-list-file'], ['repo', 'list'], 'repos');
|
|
111
|
-
const worktrees = await inventory(options['--worktree-list-file'], ['worktree', 'list'], 'worktrees');
|
|
112
|
-
const repoIds = new Set(repos.filter((repo) => repo.kind === 'git').map((repo) => repo.id));
|
|
113
|
-
const registered = repos.some((repo) => repo.kind === 'git' && repo.path === path) ||
|
|
114
|
-
worktrees.some((worktree) => repoIds.has(worktree.repoId) && worktree.hostId === 'local' && worktree.path === path);
|
|
115
|
-
if (!registered) fail(`path is not an Orca-registered repository or worktree: ${path}`);
|
|
116
|
-
|
|
117
|
-
const home = process.env.HOME;
|
|
118
|
-
if (!home?.startsWith('/')) fail('HOME must be absolute');
|
|
119
|
-
const configPath = `${home.replace(/\/$/, '')}/.claude.json`;
|
|
120
|
-
await withLock(configPath, () => trust(configPath, path));
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
main().catch((err) => { console.error(err.message); process.exitCode = 1; });
|