axstack 0.11.7 → 0.12.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "axstack",
3
- "version": "0.11.7",
3
+ "version": "0.12.0",
4
4
  "description": "Axstack installer and setup CLI: installs owned chat skills and role data, configures supported harness settings, and checks Orca capabilities.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -11,7 +11,7 @@ Pair A/B is retired for this contract; its artefacts remain untouched.
11
11
 
12
12
  - **driver** — every 15 minutes, fresh Opus session in the host's `root`
13
13
  folder workspace, which is not a git repository and belongs to no project. It discovers GitHub work, binds the persistent Orca
14
- Run, creates one project-local child worktree per selected PR, dispatches the
14
+ Run, checks each selected PR's head out into a free project-local slot, dispatches the
15
15
  matching Axstack agent, reconciles completions and decisions, then exits. The
16
16
  driver is the automation session itself, with no `axstack-monitor` or
17
17
  `axstack-owner` role row. It never performs review or repair in its own
@@ -97,8 +97,8 @@ The driver performs this order and exits:
97
97
  inbox. For a `worker_done` matching a live marker, verify the review id at
98
98
  the bound head, push range, or opened token. Release the worker; once its
99
99
  release receipt is settled and the process has exited, run the cleanup
100
- under "Run directory" below, which proves the worktree disposable before
101
- removing it; then clear the marker. Unverifiable delivery stays in
100
+ under "Run directory" below, which proves the slot disposable before
101
+ resetting it; then clear the marker. Unverifiable delivery stays in
102
102
  `pending_settlement[]` and blocks only that PR.
103
103
  3. Consume decisions as their sole consumer under "Decision tokens" below.
104
104
  4. Reconcile every marker older than 3 h. A live worker gets `worker-stop`; an
@@ -125,47 +125,17 @@ The driver performs this order and exits:
125
125
  ## Dispatch and repair selection
126
126
 
127
127
  Claude Code trusts a folder per git toplevel and stops at its "Quick safety
128
- check" dialog otherwise; every per-PR child worktree is a new toplevel, and
129
- the driver must never answer that dialog for a worker. The user decided on
130
- 2026-09-17 that a worktree the driver itself creates from an allowlisted
131
- clone at the pinned head is trusted by policy: immediately after creating
132
- it and before `worker-start`, the driver runs the trust helper
133
- (`docs/plans/pr-automations-trust.js`, deployed beside the precheck and run
134
- with Bun), which
135
- owns the only write the automation makes to `~/.claude.json`. It writes the path's `hasTrustDialogAccepted` entry — what a
136
- manual acceptance writes. The helper
137
- enforces the scope mechanically before it writes anything: the path must be
138
- a git worktree whose common dir is the named allowlisted clone's, must not
139
- be the clone itself, and must sit at exactly the pinned head — anything
140
- else exits 3 and is never seeded. It is one process on purpose: lock, refresher,
141
- render and rename all happen in the same process, so nothing can outlive the
142
- owner and commit after it dies. It writes under Claude Code's own config
143
- lock — the `mkdir`-based `~/.claude.json.lock` directory its sessions take —
144
- following Claude's own lease rules, with a bounded retry. Only a successful
145
- `mkdir` counts as holding it; a fresh foreign lock is never broken, and the only lock it will
146
- reclaim is one whose mtime is past Claude's 10 s stale threshold, which is
147
- exactly what Claude itself treats as abandoned. It keeps the lease alive the
148
- way Claude does: a refresher touches the lock's mtime every second for as
149
- long as it is held, so the lock cannot age into staleness under it even if a
150
- rename stalls. The refresher runs with the owner and dies with it, exits the
151
- moment the lock is no longer ours, and treats a failed refresh as a
152
- compromised lease by terminating the owner before it can commit. The helper
153
- re-reads under the lock, refuses a store that does not parse, writes a
154
- unique temp file, preserves the store's mode, re-verifies at commit that the
155
- lease is healthy — unchanged inode, refresher alive, refreshed within the
156
- last few seconds — and otherwise discards the temp and commits nothing,
157
- treats a failed chmod or rename as failure with the temp removed, and
158
- releases only a lock it still owns, stopping the refresher first. A busy or unreadable
159
- store exits 2: the worktree is retained and nothing is dispatched. The
160
- worktree cleanup removes the entry through the same helper; if that
161
- removal fails after the worktree is gone, the marker stays in
162
- `pending_settlement[]` as `untrust-pending` — which keeps the PR
163
- undispatchable, so the reused path can never inherit a dead trust entry —
164
- and the removal is retried next tick. A retained worktree keeps its entry
165
- while retained; it is the same driver-created path. This is scoped
166
- exactly there — never for any other path, never for a worktree it did not
167
- create — because that dialog is the last guard between PR content and a
168
- worker running with permissions bypassed. Trusting a folder activates the
128
+ check" dialog otherwise, and the driver never answers that dialog for a
129
+ worker. So workers run in a fixed pool: every allowlisted project has
130
+ exactly two slot worktrees, `slot-1` and `slot-2`, its Orca child worktrees
131
+ created once, parented to the project's primary worktree, and trusted once
132
+ by the user through that dialog. The driver never creates or removes a
133
+ worktree and never writes `~/.claude.json`. A slot is free when no live
134
+ dispatch marker names it, it is not in `retained_slots[]`, no terminal is
135
+ listed in it, and its tree is clean; no free slot in the project defers the
136
+ PR to `deferred[]` like a budget, not a hold. Taking a slot fetches the head into the project clone,
137
+ checks the slot out detached at the pinned head, and verifies HEAD equals
138
+ it; the marker's worktree is the slot path. Trusting a folder activates the
169
139
  full project surface: its `.claude/settings.json` and the hooks it defines,
170
140
  its `.mcp.json` servers, marketplace plugin auto-install, and `CLAUDE.md`;
171
141
  a hostile branch's hooks or MCP servers would run the moment the folder
@@ -177,12 +147,12 @@ MCP servers, custom commands or agents load from anywhere, project or user;
177
147
  built-in tools and authentication are untouched, and the brief loads the
178
148
  skill files it needs by path. The driver confirms readiness from the
179
149
  rendered frame — `wait.satisfied`, the prompt marker present, the dialog
180
- absent — before dispatching. What remains live is the repository's files as
181
- data the worker reads and the commands the worker itself chooses to run,
182
- which it already runs today; nothing from the branch loads, executes, or is
183
- offered for invocation on its own. The allowlist,
184
- the pinned head, and that reduced surface are what make pre-trust
185
- acceptable, and nothing else does.
150
+ absent — before dispatching; a dialog on a slot means the slot is not
151
+ trusted: the slot is named in a health line, the PR is deferred, nothing is
152
+ dispatched. What remains live is the repository's files as data the worker
153
+ reads and the commands the worker itself chooses to run, which it already
154
+ runs today; nothing from the branch loads, executes, or is offered for
155
+ invocation on its own.
186
156
 
187
157
  Every selected PR receives one dispatch marker with task id, dispatch id,
188
158
  worktree, head, `started_at`, reservation (`verdict` or `repair`), and trigger:
@@ -207,10 +177,10 @@ An own PR needs repair when either trigger applies:
207
177
  triggers again subject to the 24 h cap.
208
178
 
209
179
  Repair also requires no deploy-on-push head branch, no live repair cap, and
210
- selection of the lowest own PR in its stack that needs repair. Create a child
211
- worktree in that project's clone at the exact head, parented to that
212
- project's primary worktree so it appears under the project it serves, and
213
- dispatch one `axstack-watch` agent in authored repair mode. Its
180
+ selection of the lowest own PR in its stack that needs repair. Take a free
181
+ slot of that project (its slots are parented to that project's primary
182
+ worktree, so the work appears under the project it serves) at the exact
183
+ head, and dispatch one `axstack-watch` agent in authored repair mode. Its
214
184
  brief contains only the triggering checks or review findings. Each open
215
185
  descendant records one user-owned `pending restack` hold until it stops needing
216
186
  repair. The 24 h cap starts at dispatch and an abandon does not refund it.
@@ -232,8 +202,8 @@ and head. Budget exhaustion records the count and is not a hold.
232
202
 
233
203
  ## Agents and verdicts
234
204
 
235
- Every agent works in its own project-local Orca child worktree at the exact
236
- head and reports only through the Orca worker protocol.
205
+ Every agent works in a project-local slot worktree checked out detached at
206
+ the exact head and reports only through the Orca worker protocol.
237
207
 
238
208
  Peer review runs the two isolated configured reviewers on the identical brief,
239
209
  then the Luna gate. `APPROVE` requires complete exact-head/base reviews, gate
@@ -372,12 +342,12 @@ is no gate for health findings.
372
342
  ## Run directory
373
343
 
374
344
  The driver and watchdog run from the host's `root` folder workspace, not a
375
- project worktree: no project owns the automation, and every child worktree
345
+ project worktree: no project owns the automation, and every slot worktree
376
346
  belongs to the project it serves. That workspace is not a git repository, so
377
347
  the run directory is private host state, one
378
348
  `~/.local/share/axstack/runs/<run id>/` directory. Settlement leaves nothing
379
349
  behind, but never destroys work. Before any destructive step the driver
380
- proves the child is disposable: the worker is settled — on the release path
350
+ proves the slot is disposable: the worker is settled — on the release path
381
351
  a settled release receipt, on the abandon path an accepted abandon receipt,
382
352
  either with proven process exit; pending or unknown stops here — the
383
353
  worktree's HEAD is either the pinned head or a candidate that is durably
@@ -387,22 +357,27 @@ targeted fetch of that exact remote branch into a per-dispatch ref, never
387
357
  shared clone can fake durability, a failed fetch retaining the worktree — or held by a
388
358
  `refs/axstack/decisions/<token>` ref in the project clone; and, on the
389
359
  abandon path, the worktree has no uncommitted changes.
390
- Only then it closes any terminal tab still listed, clears untracked
391
- artefacts, removes the child worktree and its directory, deletes the branch
392
- the worktree created, removes the Claude Code trust entry it seeded for that
393
- path, and verifies the directory is gone. On the settled path
360
+ Only then it closes any terminal tab still listed, resets the slot to the
361
+ pinned head, clears untracked artefacts, and verifies the slot is clean, so
362
+ it is back in the pool. On the settled path
394
363
  the worker has finished, so untracked files are artefacts by definition and
395
- are cleared; a candidate there is already pushed or token-held. An abandoned
396
- worktree that is dirty or holds an unproven candidate is **retained**, named
397
- in one `health[]` line with its path and SHA, and blocks only that PR with
398
- the user as owner. A worktree, directory, branch, or terminal that outlives
399
- its dispatch without such a retention record is a health finding. The run directory contains:
364
+ are cleared; a candidate there is already pushed or token-held. A slot
365
+ whose release is settled but whose HEAD cannot be proven disposable, and an
366
+ abandoned slot that is dirty or holds an unproven candidate, are both
367
+ **retained**: named in one `health[]` line with path and SHA, and blocking
368
+ only that PR with the user as owner. Retention is mechanical on both paths:
369
+ the driver appends `retained_slots[]` `{slot, pr, head, reason}`, which the
370
+ free predicate excludes for every PR — a clean, terminal-less slot holding
371
+ an unpushed candidate would otherwise look free — and an entry is cleared
372
+ only by the user after reconciling the candidate. A dirty slot or a terminal that
373
+ outlives its dispatch without such a retention record is a health finding. The run directory contains:
400
374
 
401
375
  - `cursor.json` — driver only, with these exact keys: `fingerprint`,
402
376
  `tick_started_at`, `tick_done_at`, `tick_outcome`, `prs{url: {head, base,
403
377
  draft, checks, reviews, last_self_review}}`, `dispatch_markers[]` (`pr`,
404
378
  `task_id`, `dispatch_id`, `worktree`, `head`, `started_at`, `reservation`,
405
379
  `trigger`), `deferred[]`, `pending_settlement[]`,
380
+ `retained_slots[]` (`slot`, `pr`, `head`, `reason`),
406
381
  `repair_caps{url: {expires_at}}`, `abandon_count{head: n}`,
407
382
  `processed_reviews[]` (`review_id`, `pr`, `head`, `digest`),
408
383
  `deploy_on_push{repo: [branches]}`,
@@ -410,7 +385,6 @@ its dispatch without such a retention record is a health finding. The run direct
410
385
  `runtime_refusal{code, first_seen, last_seen}` (absent when no runtime
411
386
  hold is open);
412
387
  - `pending.json`, `precheck.log` — driver precheck only;
413
- - `trust.js` — the trust transaction helper, run by the driver only;
414
388
  - `decisions/<token>.json` — writers assigned by the lifecycle table;
415
389
  - `watchdog.log` and `watchdog-state.json` (occurrence `first_observed` values
416
390
  and send receipts) — watchdog only;
@@ -445,12 +419,12 @@ delivery uses [axstack-relay](../../axstack-relay/SKILL.md).
445
419
  `runtime_refusal {code, first_seen, last_seen}`; the same code keeps the
446
420
  hold and updates `last_seen` without a new health line, a different code is
447
421
  a new finding. Prose is never the key. When `worker-start` itself is
448
- refused after the child worktree was created, there is no worker, so the
422
+ refused after the slot was checked out, there is no worker, so the
449
423
  settlement proof does not apply; the driver reads the receipt's `failedStage`
450
424
  and `residualResources` first. With no Dispatch and no residual resources
451
425
  the no-worker branch applies: the worktree's HEAD must equal the pinned
452
- head and `git status --porcelain` must be empty, and only then does the
453
- driver remove the worktree, its directory and branch in the same tick.
426
+ head and `git status --porcelain` must be empty, and then the slot is simply
427
+ free again in the same tick; there is nothing to remove.
454
428
  With a Dispatch or any residual resource the failed start owns runtime
455
429
  state, and retaining alone is not recovery: the driver follows the
456
430
  runtime's recovery guide. With a Dispatch: `worker-list` for that run, and