axstack 0.32.0 → 0.34.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 CHANGED
@@ -15,7 +15,7 @@ coordination. You can start at the phase you need.
15
15
  | Failure mode | How Axstack responds |
16
16
  | --- | --- |
17
17
  | Wrong thing built | Align rounds clarify the request; every brainstorm runs a light arena across configured families. Use judges only for Rung 2 hard-to-reverse choices. |
18
- | Nobody really reviewed it | Strict TDD checks behavior first; mixed authored review is cross-provider, and mixed peer review pairs Sol high with Opus low in isolated sessions at the exact revision. |
18
+ | Nobody really reviewed it | Strict TDD checks behavior first; mixed authored review is cross-provider, and mixed peer review pairs Sol high with Opus medium in isolated sessions at the exact revision. |
19
19
  | Design rot | The design lens sketches boundaries before a build; Improve surfaces evidenced changes later. |
20
20
  | Agents left a mess | T3 makes delegation visible, one writer owns each PR, cleanup stays bounded, and watched own PRs merge under guarded rules. |
21
21
 
package/docs/workflows.md CHANGED
@@ -119,11 +119,11 @@ role.
119
119
 
120
120
  | Preset | Author | Ordered peer reviewers | Astra / Opus advisers | Auditor |
121
121
  | --- | --- | --- | --- | --- |
122
- | `mixed` | Sol high | Sol high; Opus low | Astra high / Opus xhigh | Sonnet high + Sol high |
122
+ | `mixed` | Sol high | Sol high; Opus medium | Astra high / Opus xhigh | Haiku xhigh + Sol high |
123
123
  | `codex-only` | Sol high | Sol high; Luna xhigh | Astra high / unavailable | Luna xhigh + Sol high |
124
124
  | `claude-only` | Opus medium | Opus medium; Sonnet high | unavailable / Opus xhigh | Sonnet high (Sol absent) |
125
125
 
126
- In `mixed` and `claude-only`, `axstack-auditor`, `axstack-research-requirements`,
126
+ In `mixed` and `claude-only`, `axstack-research-requirements`,
127
127
  `axstack-research-code`, `axstack-research-web`, and `axstack-explore-execution`
128
128
  use the Claude Sonnet class at high effort. `axstack-explore-codebase` and
129
129
  `axstack-monitor` adopt Haiku 5.5 high, with class `haiku` and exact pin
@@ -131,6 +131,9 @@ use the Claude Sonnet class at high effort. `axstack-explore-codebase` and
131
131
  holds those roles without substitution. `codex-only` keeps its Codex
132
132
  assignments for those roles. Mixed web-google and X retain their source-specific
133
133
  Antigravity and Grok routes.
134
+ The mixed `axstack-auditor` uses Haiku 5.5 xhigh, pinned to `claude-haiku-5-5`;
135
+ missing that ID or xhigh effort holds without substitution. The mixed
136
+ `axstack-explainer-review` uses Sol medium; `axstack-explainer` stays Sonnet high.
134
137
  The new `-sol` auditor, research-code, and explore-execution seats use Sol high
135
138
  in `mixed` and `codex-only`; `claude-only` records each as intentionally absent.
136
139
  Dispatch the base and Sol seats independently on the same brief, then reconcile
@@ -148,7 +151,7 @@ later installation changes without re-resolution.
148
151
  Peer roles use the stable IDs `axstack-reviewer-primary` and
149
152
  `axstack-reviewer-peer`; their provider/class mappings come only from the
150
153
  selected preset. The mixed cross-provider peer pair is Sol high followed by
151
- Opus low. The mixed peer coordinator and review-lane binding
154
+ Opus medium. The mixed peer coordinator and review-lane binding
152
155
  `axstack-owner` uses Sol high and never reviews. `axstack-reviewer-secondary`
153
156
  is used in the authored mapping and, alongside `axstack-reviewer-primary`,
154
157
  in codebase findings mode.
@@ -289,22 +292,36 @@ keeps the current owner and a resumable record.
289
292
 
290
293
  Serious security, downtime, data-loss, and major-design risks are raised in a
291
294
  prompt immediately and hold dependent dangerous work. This is not a runtime
292
- gate. An applicable `Notification policy` may use `axstack-relay` only for a
295
+ gate. An applicable `Notification policy` may use `axstack-relay` for a
293
296
  user-decision hold (including spec or npm approval and a genuine blocker after
294
- bounded safe recovery), a serious-risk hold immediately, or at most two merge-ready/merged milestones per run.
297
+ bounded safe recovery) or a serious-risk hold immediately.
295
298
  Routine questions stay in the T3 driver thread. Progress, CI pending, and
296
299
  completion always stay in the T3 driver thread.
297
- Only the bounded categories—user-decision holds (including spec approval),
298
- serious-risk holds, and at most two merge-ready/merged milestones per run—may
299
- be relayed under the recorded Notification policy. See [Relay operations](host-operations.md#notifications-and-relay) for native
300
+ Proactive relay eligibility is limited to user-decision holds, serious-risk holds, the
301
+ 60-minute blocked ping, unresolved-rating notice and capped peer PR milestones
302
+ under the Notification policy.
303
+ Under the recorded Notification policy, `verification not proven` is a
304
+ user-decision hold. The 60-minute blocked ping is a notification-only category
305
+ under the Notification policy. The unresolved-rating notice is a notification-only
306
+ category under the Notification policy, deduplicated per comment and head.
307
+ The unresolved-rating notice grants no reply authority.
308
+ The unresolved-rating notice never clears the hold.
309
+ Never send routine merge-ready or merged relays
310
+ for own PRs. Notifications for peer PRs are unchanged, including at most two
311
+ merge-ready/merged milestones per run, deduplicated across implementation and release.
312
+ See [Relay operations](host-operations.md#notifications-and-relay) for native
300
313
  delivery, deduplication, and verified reply handling.
301
314
 
315
+ Decision responses must remain separate from proactive Notification policy
316
+ categories.
302
317
  Decision responses must be authorized by the user's own reply, outside the
303
318
  two-milestone cap and needing no Notification policy entry. Send one
304
319
  acknowledgement per forwarded reply before any side effect and one outcome
305
320
  per decided card. An explain answer, confirmation card or fresh superseding
306
321
  card serves as that reply's acknowledgement. Identical repeat replies get T3-only
307
322
  acknowledgement; different later choices get "already decided as N" in Telegram.
323
+ For own PRs, the driver must send the required decision outcome as resolution
324
+ of the bound cause, rather than a routine merge-ready or merged milestone.
308
325
  An outcome that is itself merge-ready or merged counts as that milestone and
309
326
  is sent once. Queued, acknowledged and done are distinct.
310
327
 
@@ -323,7 +340,7 @@ read-only observer for standalone watches and never sends.
323
340
 
324
341
  For authorized engineering delivery, [Autopilot](../skills/axstack/references/autopilot.md)
325
342
  continues from Align through the eligible phase sequence in the same chat.
326
- The human approves substantial specs, peer and deploying-base merges, and the
343
+ The human approves substantial specs, peer and work deploying-base merges, and the
327
344
  npm stage. The recorded owning watch thread is the merge actor,
328
345
  including `axstack-owner` for standalone authorized maintenance and small or
329
346
  adopted work. Apply the full
@@ -358,6 +375,13 @@ review before driver publication and remote readback. Watch §5 governs merges.
358
375
 
359
376
  ## Automatic merge boundaries
360
377
 
378
+ Record approval mode from the collaborator readback.
379
+ A repository is personal (`solo`) only when it is outside `defi-com` and a
380
+ complete collaborator readback lists the user alone with write, maintain, or
381
+ admin permission. A `defi-com` repository, an extra writer, or an incomplete or
382
+ failed readback classes as work (`team`).
383
+ Every other repository is work (`team`).
384
+
361
385
  Solo approval is current-head-and-base cross-provider authored-review `APPROVE`
362
386
  plus diligence `PASS`. The reviewer differs from every receipt-recorded provider
363
387
  that authored or repaired `merge-base..head` commits. Team approval also needs a
@@ -367,36 +391,156 @@ merge card. New heads need fresh authored review, diligence, and CI. A collabora
367
391
  approval carries over only across an unchanged stable patch-id rebase, recorded
368
392
  for both heads, while the forge still counts it.
369
393
 
370
- Team auto-merge targets only documented non-production `dev`; solo targets `main`
371
- or another documented `integration` base. Unknown classification means
372
- `deploying`. Re-read approval mode and classification before merging and on resume.
373
- For a stack, only the bottom base must qualify; every upper base is its next-lower
374
- branch at the reviewed head. Every planned member must be published and satisfy
375
- the remaining terms and exclusions. Never retarget reviewed members for eligibility.
376
-
377
- Merge cards report cases that fail approval, base, exclusion, or `Auto-merge: off`
378
- rules. On own integration-base PRs, a user card reply permits the guarded actor to
379
- merge, subject to watch §5's exceptions.
380
- In solo mode the user's merge-card reply authorizes the guarded merge of
394
+ Base classification uses repository docs and workflows, never the branch name
395
+ alone, and unknown means `deploying`. A base is `integration` only when
396
+ repository docs or workflows show it does not deploy to production.
397
+ Re-read approval mode and base classification immediately before each automated
398
+ merge and at every watch resume.
399
+
400
+ ### Proven verification
401
+
402
+ Proven verification is required for every own PR. Use the authored receipt:
403
+
404
+ - Verification requires `Verdict: APPROVE`, `Coverage: COMPLETE`, and
405
+ `Escalate to user: no`.
406
+ - Verification requires a `Safety fact` about the change that is not `unproven`.
407
+ - Verification requires diligence `PASS` at the exact head and base.
408
+ - Verification requires `Final-head check: <command> -> <log path>` for a check
409
+ the reviewer ran at the final head, with its log kept in the run's evidence folder.
410
+ - Each acceptance check requires passing evidence from the ticket or spec
411
+ criteria, or the PR body for adopted and ticketless PRs.
412
+ Earlier-head red-to-green evidence in the PR's patch lineage counts only when
413
+ the reviewer records that it still applies to the final change.
414
+ An applicable `verify-<app>` passing result is required for the changed behavior.
415
+ - Verification requires no limitation tagged `changed-behavior: unproven`.
416
+ The reviewer tags every limitation leaving changed behavior unexercised or
417
+ unverified, in whatever words, `changed-behavior: unproven`.
418
+ Other limitations do not hold.
419
+ - When the PR removes or weakens a check, test-runner setting or ruleset that
420
+ gates its own merge, verification requires the reviewer ran the base's checks
421
+ against the head.
422
+
423
+ The driver first closes a verification gap itself: run the check, re-dispatch
424
+ review, or return the work to the author. Only a gap the driver cannot close
425
+ becomes a `verification not proven` card bound to head and base.
426
+ The card names what is unproven, why, and the action that would settle it.
427
+
428
+ For `verification not proven`, the driver must follow [Relay](../skills/axstack-relay/SKILL.md)'s decision-card procedure,
429
+ binding the sent run receipt and each option's scope to the exact PR head and base.
430
+ For a verification decision, a forwarded choice must pass [Relay](../skills/axstack-relay/SKILL.md)'s four reply checks
431
+ and acknowledgement-before-action rule before settling only its bound cause.
432
+ A changed PR head or base must supersede its open verification decision card,
433
+ reissuing only if a decision is still needed.
434
+
435
+ The user's reply in the driver thread or on the forge clears only that
436
+ verification cause. Record `user-accepted unproven: <item>`, never as a pass.
437
+ Treat the accepted item as settled for this head and base and merge when every
438
+ other gate and hold permits it. A reply never waives `APPROVE`, diligence `PASS`
439
+ or green CI. A new head or base voids the acceptance.
440
+ Settle an `Escalate to user` for the user-accepted verification cause at its
441
+ accepted head and base while other escalation holds still apply.
442
+ In a work repository a reply never replaces counted collaborator approval.
443
+
444
+ Only comments and threads whose IDs are recorded in an agent receipt count as agent-authored.
445
+ The authored reviewer rates each non-agent review thread, review body, or
446
+ top-level comment from a human or a bot at the current head as `above low`,
447
+ `low`, `addressed` or `uncertain`. Re-rate every non-agent item at each new head.
448
+ Reviewer-rated `low` or `addressed` items do not hold automatic merge.
449
+ An `above low`, unrated or `uncertain` item holds automatic merge.
450
+ Human `CHANGES_REQUESTED` holds automatic merge until resolved.
451
+ A comment arriving after the receipt triggers a fresh review dispatch for rating
452
+ as gap closing.
453
+ An item still unrated or uncertain after one fresh rating dispatch keeps holding
454
+ the merge and posts a report-only decision-style card bound to head and base.
455
+ The rating card names the comment, why it remains unrated or uncertain, and the
456
+ action that settles it: re-rating by the reviewer, the commenter resolving or
457
+ replying, or the author addressing it.
458
+ The report-only rating card is a notification of an unsettled rating,
459
+ deduplicated per comment and head.
460
+ A report-only rating card never offers a user-acceptance path.
461
+ A user reply never clears a comment rating hold.
462
+ Agents never resolve or dismiss non-agent items.
463
+ Agent-authored threads with only `low` findings can stay open.
464
+ Apply any repository rule that requires conversation resolution.
465
+
466
+ ### Escalation
467
+
468
+ Personal PRs merge when the gates pass and verification is proven or
469
+ user-accepted at this head and base. Personal PRs on any base, including
470
+ promotion, `deploying` and unknown bases, are eligible under the watch predicate.
471
+ Work PRs merge when the gates pass, verification is proven or user-accepted,
472
+ and a counted collaborator approval exists, on an `integration` base.
473
+ Work promotion PRs and `deploying` or unknown bases are merged by the user on
474
+ the forge. The existing serious-risk hold applies at once to both personal and
475
+ work PRs.
476
+ Personal and work PRs remain eligible whatever files they change, including
477
+ `.github/`, workflow-invoked files, package.json, lockfiles, test-runner config,
478
+ branch-protection or ruleset config, `CODEOWNERS`, `AGENTS.md`, and non-clean
479
+ revert lines such as `Revert: steps`.
480
+
481
+ For a `gh stack`, only the bottom member's base must be eligible.
482
+ Each other member's base must be the next-lower member's branch at its reviewed head.
483
+ Every member must meet every other term.
484
+ A stack holds until every member of its approved plan (the ticket map or the
485
+ adopted stack's recorded members) is published.
486
+ Reviewed members are never retargeted to become eligible.
487
+
488
+ Never auto-merge PRs authored by anyone other than the user or the user's agents.
489
+ Test sources stay eligible.
490
+ Axstack skill and merge-rule text are eligible under the watch predicate.
491
+ Changes to package.json are eligible under the watch predicate.
492
+ Release PRs are eligible under the watch predicate.
493
+ Read the rollback declaration whose line starts with `Revert:`
494
+ at line start in the PR description.
495
+ A quoted format inside a bullet never counts as the declaration.
496
+ `--admin` and rule bypass are never used.
497
+ `Auto-merge: off` for a run or PR makes the merge card wait, and it waits for the user.
498
+
499
+ Post a merge card for every nonqualifying approval, base, verification, or off case.
500
+ Bind it to the PR head and base SHA; list CI, authored review and diligence at
501
+ those SHAs, counted collaborator approvals and bot votes with each vote's SHA
502
+ and stale flag, and the causes holding this merge.
503
+ A card that needs the user's decision (work promotion or work deploying/unknown base,
504
+ `Auto-merge: off`, provenance, or an unclosable verification gap) is a decision
505
+ hold under the recorded Notification policy with one deduplicated relay;
506
+ relay text never supplies approval. Person-wait cards follow the clock below.
507
+ A changed head or base requires a refreshed card.
508
+ For an own PR on an `integration` base, the user's reply to the card authorizes
509
+ the merge actor to merge under the guarded path, subject to the exceptions below.
510
+ In `solo` mode the user's merge-card reply authorizes the guarded merge of
381
511
  user-written PRs or PRs with unknown or mixed provenance.
382
- In team mode it clears only an ineligible base, auto-merge turned off, and an open
383
- human or bot comment; it never replaces collaborator approval.
384
- PRs changing `.github/`, files a workflow step invokes by path, package.json
385
- beyond `version` and `files`, lockfiles, test-runner config, branch-protection or
386
- ruleset config, `CODEOWNERS`, or `AGENTS.md` are user-merged on the forge.
387
- PRs with a non-`clean` revert line are also user-merged on the forge.
388
- Promotion, deploying-base, unknown-base, and peer PRs are also user-merged.
389
- These categories are excluded from auto-merge.
390
- Test sources stay eligible. Axstack skill and merge-rule text are eligible
391
- under the watch predicate. Changes to package.json limited to `version` and
392
- `files` are eligible under the watch predicate. Release PRs are eligible
393
- under the watch predicate. Npm publication still requires human stage approval;
394
- agents never run `npm stage approve`.
395
- Non-agent comments hold auto-merge
396
- until human clearance under the packaged comment rules. The revert gate reads the declaration starting
397
- with `Revert:` at line start; a quoted format inside a bullet is not a declaration.
398
-
512
+ In `team` mode a reply never replaces counted collaborator approval.
513
+ A reply can clear auto-merge turned off; verification acceptance follows the
514
+ head-and-base-bound rule above. It never clears an ineligible work base or a
515
+ reviewer-rated `above low` finding. A user reply never clears a comment rating hold.
516
+ Peer PRs are merged by the user on the forge,
517
+ and the card only reports readiness.
518
+ Work promotion, deploying-base and unknown-base cards only report readiness.
399
519
  User merges are bottom-up for a stack.
520
+
521
+ ### Blocked on a person
522
+
523
+ A PR is ready-except-human when every term other than collaborator approval,
524
+ human `CHANGES_REQUESTED` and open findings from a person holds.
525
+ Each blocker has a key: `approval:<PR>`, `changes:<review id>` or
526
+ `comment:<comment id>`. A missing approval's clock starts at the first observed
527
+ ready-except-human time, including no review requested.
528
+ Other clocks start at the later of that ready-except-human time and the item's
529
+ forge time. A `comment:` clock starts only once the reviewer rates the item
530
+ `above low`. An unrated or uncertain item waits on the reviewer and does not
531
+ count as ready-except-human.
532
+ A clock resets when the PR stops being ready-except-human or its patch-id
533
+ changes. A same-patch-id rebase keeps the clock.
534
+ Each key is sent at most once, even when readiness is lost and regained or the
535
+ patch changes. After 60 minutes, send one relay per key, including simultaneous
536
+ blockers and a new key.
537
+ Send `<repo>#<n> waiting <age> on @<login|no reviewer>: <approval|changes|comment>; ready at <sha7>.`
538
+ The check runs on existing watch wakes with no new schedule.
539
+ The ping is a notification only: when the blocker clears, the PR merges with no reply.
540
+
541
+ Npm publication still requires human stage approval; agents never run
542
+ `npm stage approve`.
543
+
400
544
  This policy grants no release, npm publish, or host install
401
545
  authority. See [Preview authority and operations](host-operations.md#private-pr-previews).
402
546
 
@@ -404,7 +548,8 @@ Excluded: CLI proxy, account pooling behind a proxy or shared session, and IP
404
548
  routing; local CI contention handling is deferred. Quota-driven scheduling or
405
549
  model routing (provider/model substitution) is excluded. Per-dispatch selection
406
550
  among the user's own same-provider, same-model accounts is permitted. Automatic
407
- merge of promotion, deploying-base, unknown-base, and peer PRs is excluded. Previews
551
+ merge of work promotion, deploying-base and unknown-base PRs is excluded.
552
+ Automatic merge of peer PRs is excluded. Previews
408
553
  outside the VPS, public previews, and production data are excluded. Nightly triage
409
554
  never sends relay messages.
410
555
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "axstack",
3
- "version": "0.32.0",
3
+ "version": "0.34.0",
4
4
  "description": "Axstack installer and setup CLI: installs owned chat skills and role data, configures supported harness settings, and checks T3 Code capabilities.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -43,7 +43,7 @@
43
43
  "provider": "codex",
44
44
  "modeId": "full-access",
45
45
  "thinkingOptionId": "high",
46
- "notes": "Primary reviewer in the ordered mixed peer pair: Sol high followed by Opus low. Eligible authored reviewer for an Opus-authored candidate. Never author or owner; review exact SHA and base across all six angles and acceptance. Peer first pass stays isolated.",
46
+ "notes": "Primary reviewer in the ordered mixed peer pair: Sol high followed by Opus medium. Eligible authored reviewer for an Opus-authored candidate. Never author or owner; review exact SHA and base across all six angles and acceptance. Peer first pass stays isolated.",
47
47
  "modelClass": "sol"
48
48
  },
49
49
  {
@@ -51,7 +51,7 @@
51
51
  "name": "Axstack reviewer (secondary)",
52
52
  "provider": "claude",
53
53
  "modeId": "bypassPermissions",
54
- "thinkingOptionId": "medium",
54
+ "thinkingOptionId": "high",
55
55
  "notes": "Eligible authored reviewer for a Sol-authored candidate. Never author or owner; review exact SHA and base across all six angles and acceptance. T3 resolves Claude classes from saved capabilities; a Claude rejection holds.",
56
56
  "modelClass": "opus"
57
57
  },
@@ -60,8 +60,8 @@
60
60
  "name": "Axstack reviewer (peer)",
61
61
  "provider": "claude",
62
62
  "modeId": "bypassPermissions",
63
- "thinkingOptionId": "low",
64
- "notes": "Second reviewer in the ordered mixed peer pair: Sol high followed by Opus low. Never author or owner; review exact SHA and base across all six angles and acceptance. Peer first pass stays isolated. T3 resolves Claude classes from saved capabilities; a Claude rejection holds.",
63
+ "thinkingOptionId": "medium",
64
+ "notes": "Second reviewer in the ordered mixed peer pair: Sol high followed by Opus medium. Never author or owner; review exact SHA and base across all six angles and acceptance. Peer first pass stays isolated. T3 resolves Claude classes from saved capabilities; a Claude rejection holds.",
65
65
  "modelClass": "opus"
66
66
  },
67
67
  {
@@ -150,9 +150,9 @@
150
150
  "name": "Axstack explainer reviewer",
151
151
  "provider": "codex",
152
152
  "modeId": "full-access",
153
- "thinkingOptionId": "xhigh",
154
- "notes": "Independent explanation reviewer: reviews consequential or complex claims, archify output, or on request. Checks the exact artifact for text and source fidelity. The rendered pass belongs to axstack-ui-verifier. Any artifact change invalidates its review. Resolve the class from saved T3 capabilities at run start; rejection and unavailable settings hold without substitution.",
155
- "modelClass": "luna"
153
+ "thinkingOptionId": "medium",
154
+ "notes": "Independent Sol medium explanation reviewer: reviews consequential or complex claims, archify output, or on request. Checks the exact artifact for text and source fidelity. The rendered pass belongs to axstack-ui-verifier. Any artifact change invalidates its review. Resolve the class from saved T3 capabilities at run start; rejection and unavailable settings hold without substitution.",
155
+ "modelClass": "sol"
156
156
  },
157
157
  {
158
158
  "id": "axstack-ui-verifier",
@@ -206,9 +206,10 @@
206
206
  "name": "Axstack auditor",
207
207
  "provider": "claude",
208
208
  "modeId": "bypassPermissions",
209
- "thinkingOptionId": "high",
210
- "notes": "Sonnet high read-only end-of-run and checkpoint auditor. Collects scope and outcome evidence with counts and denominators and reports PASS, FAIL, or UNKNOWN without inventing numbers. Never edits, merges, activates, or audits itself. T3 resolves Claude classes from saved capabilities; a Claude rejection holds.",
211
- "modelClass": "sonnet"
209
+ "thinkingOptionId": "xhigh",
210
+ "notes": "Haiku 5.5 xhigh read-only end-of-run and checkpoint auditor. Collects scope and outcome evidence with counts and denominators and reports PASS, FAIL, or UNKNOWN without inventing numbers. Never edits, merges, activates, or audits itself. T3 uses the exact claude-haiku-5-5 pin from saved capabilities; a Claude rejection holds. Missing model or xhigh effort holds without substitution.",
211
+ "modelClass": "haiku",
212
+ "model": "claude-haiku-5-5"
212
213
  },
213
214
  {
214
215
  "id": "axstack-auditor-sol",
@@ -72,7 +72,10 @@ The recorded owning watch thread is the merge actor, including `axstack-owner`
72
72
  for authorized standalone maintenance and small or adopted work.
73
73
  Managers, workers, reviewers, monitors, and nightly triage never merge.
74
74
  Observation-only and peer watches never merge.
75
- Apply watch §5's approval, base, exclusions, and merge-card rules.
75
+ Apply watch §5's approval, base, proven-verification, escalation, and merge-card rules.
76
+ Personal own PRs, including `deploying` bases, follow automatic merge under
77
+ watch §5. Work promotion PRs and `deploying` or unknown bases are merged by the
78
+ user on the forge. Peer PRs are merged by the user on the forge.
76
79
  A stack follows its guarded whole-stack rule.
77
80
  User merges are bottom-up for a stack.
78
81
 
@@ -166,8 +169,20 @@ instructions, always win.
166
169
  Follow [Provider bindings](t3-runtime.md#preflight-and-binding) for driver account re-selection on start, resume and run-watch wakes.
167
170
 
168
171
  Use the run's recorded Notification policy through `axstack-relay`.
169
- Decision holds, including spec and npm approval, are always eligible. Across
170
- implementation and release, merge-ready and merged notifications together are
171
- capped at two per run; deduplicate by purpose and revision. Healthy ticks stay
172
+ Decision holds, including spec and npm approval, are always eligible.
173
+ Relay eligibility is limited to user-decision holds, serious-risk holds, the
174
+ 60-minute blocked ping, unresolved-rating notice and capped peer PR milestones
175
+ under the Notification policy.
176
+ Under the recorded Notification policy, `verification not proven` is a
177
+ user-decision hold. The 60-minute blocked ping is a notification-only category
178
+ under the Notification policy. The unresolved-rating notice is a notification-only
179
+ category under the Notification policy, deduplicated per comment and head.
180
+ The unresolved-rating notice grants no reply authority.
181
+ The unresolved-rating notice never clears the hold.
182
+ Never send routine merge-ready or merged relays
183
+ for own PRs. Notifications for peer PRs are unchanged, including at most two
184
+ merge-ready/merged milestones per run, deduplicated across implementation and release.
185
+ Across implementation and release, peer PR merge-ready and merged notifications
186
+ together are capped at two per run; deduplicate by purpose and revision. Healthy ticks stay
172
187
  quiet. A failed or uncertain delivery preserves the underlying hold. A relay
173
188
  message is only a notification, never authority to approve, merge, or publish.
@@ -7,12 +7,12 @@
7
7
  Only the recorded owning watch thread merges, including `axstack-owner`
8
8
  for authorized standalone maintenance. Other roles never acquire merge authority.
9
9
  - `axstack-reviewer-primary` and `axstack-reviewer-peer` are the ordered
10
- peer pair. In `mixed`, the cross-provider pair is Sol high followed by Opus low.
10
+ peer pair. In `mixed`, the cross-provider pair is Sol high followed by Opus medium.
11
11
  Peer review uses both; authored review uses this table:
12
12
 
13
13
  | Preset | Author class | Reviewer (class/effort) |
14
14
  | --- | --- | --- |
15
- | `mixed` | `codex/sol` | `axstack-reviewer-secondary` (`claude/opus` medium) |
15
+ | `mixed` | `codex/sol` | `axstack-reviewer-secondary` (`claude/opus` high) |
16
16
  | `mixed` | `claude/opus` | `axstack-reviewer-primary` (`codex/sol` high) |
17
17
  | `codex-only` | `codex/sol` | `axstack-reviewer-secondary` (`codex/luna` xhigh) |
18
18
  | `claude-only` | `claude/opus` | `axstack-reviewer-secondary` (`claude/sonnet` high) |
@@ -24,15 +24,18 @@
24
24
  `axstack-auditor` audits; `axstack-checker` reports discrepancies.
25
25
  - `axstack-explainer` authors archify only on an explicit viewer request.
26
26
  - `axstack-explainer-review` reviews consequential or complex claims, archify
27
- output, or on request.
27
+ output, or on request; `codex/sol` medium in mixed.
28
28
  - `axstack-diligence`: read-only [diligence checks](diligence.md) for every PR
29
29
  review round and bounded research, spec, ticket, receipt, and release claims.
30
30
  - `axstack-ui-verifier`: owns the inline page interaction pass under [UI checks](ui-verification.md)
31
31
  for every interactive page, bound to the exact bytes and SHA-256.
32
- - `axstack-auditor`/`axstack-research-requirements`/
32
+ - `axstack-research-requirements`/
33
33
  `axstack-research-code`/`axstack-research-web`/
34
34
  `axstack-explore-execution`:
35
35
  `claude/sonnet` high in mixed/claude-only.
36
+ - `axstack-auditor`: `claude/haiku` xhigh in mixed, pinned to
37
+ `claude-haiku-5-5`. Missing that exact model or xhigh effort holds without
38
+ substitution. `claude/sonnet` high in claude-only; `codex/luna` xhigh in codex-only.
36
39
  - `axstack-explore-codebase`/`axstack-monitor`: `claude/haiku` high in
37
40
  mixed/claude-only, pinned to `claude-haiku-5-5`. Missing that exact model
38
41
  or high effort holds these roles without substitution.
@@ -41,7 +44,7 @@
41
44
  - Sol pairs `axstack-auditor-sol`/`axstack-research-code-sol`/
42
45
  `axstack-explore-execution-sol`: `codex/sol` high in
43
46
  mixed/codex-only; intentionally absent in claude-only. Dispatch each
44
- independently from its Sonnet seat on the same bounded brief without
47
+ independently from its base seat on the same bounded brief without
45
48
  cross-reading. The driver reconciles findings per claim, never averages.
46
49
  Record intentional absence and continue with Sonnet alone.
47
50
  - Optional seats: `axstack-arena-candidate-grok`,
@@ -91,6 +91,7 @@ Account switches must happen before exhaustion at the picker's near-limit cutoff
91
91
  For plan generation, run
92
92
  `scripts/dispatch-plan.js --role <id> --roles <roles.json> --capabilities <saved.json> --picker <picker.json> --kind task|launch`
93
93
  using the recorded role snapshot and saved capabilities.
94
+ Plan JSON carries `inputs`, which callers omit when copying `target`/`modelSelection` and `runtimeMode` into `delegate_task`/`t3_thread_launch`; large plan output requires `--out`.
94
95
  For claude/codex, the `--picker` JSON must come from saved
95
96
  `scripts/pick-instance.js --provider <role provider> --json` output.
96
97
  For grok/antigravity, or after a claude/codex picker exit-1 canonical fallback,
@@ -1,10 +1,11 @@
1
- import { readFileSync } from 'node:fs';
1
+ import { readFileSync, writeFileSync } from 'node:fs';
2
2
 
3
- const flags = ['--role', '--roles', '--capabilities', '--picker', '--kind', '--verify'];
3
+ const flags = ['--role', '--roles', '--capabilities', '--picker', '--kind', '--verify', '--out'];
4
4
  const nonempty = (value) => typeof value === 'string' && value.trim().length > 0;
5
5
  const help = `Usage: bun dispatch-plan.js --role <id> --roles <roles.json>
6
6
  --capabilities <saved.json> --picker <picker.json> --kind task|launch
7
- [--verify <configuration.json>]
7
+ [--verify <configuration.json>] [--out <report.json>]
8
+ Example: bun dispatch-plan.js --role axstack-author --roles roles.json --capabilities capabilities.json --picker picker.json --kind launch
8
9
 
9
10
  --role Role ID from the supplied snapshot
10
11
  --roles Installed-style roles.json (version 1, roles array)
@@ -12,9 +13,13 @@ const help = `Usage: bun dispatch-plan.js --role <id> --roles <roles.json>
12
13
  --picker Saved picker JSON; uses chosenInstanceId
13
14
  --kind task emits target; launch emits modelSelection
14
15
  --verify Compare t3_thread_configuration JSON; print MATCH or each mismatch
16
+ --out Write full JSON here and print input/path/omitted-count summary
15
17
  --help Show all flags and exit codes
16
18
 
17
- Read-only: reads supplied JSON, spawns adjacent resolve-models.js, writes stdout/stderr.
19
+ Read-only inputs: reads supplied JSON, spawns adjacent resolve-models.js, writes stdout/stderr.
20
+ Writes only the caller's --out path when supplied; no retained state.
21
+ Results name input paths. Plan output over 4096 bytes requires --out.
22
+ Large verification retains its exit code, prints mismatches and advises --out on stderr.
18
23
  Never dispatches or changes snapshots. Reuse recorded exact model pins as given.
19
24
  Exit codes: 0 ok, 1 error, 2 hold (including incomplete read-back).
20
25
  `;
@@ -27,20 +32,23 @@ function argumentsFrom(args) {
27
32
  const options = {};
28
33
  for (let i = 0; i < args.length; i += 2) {
29
34
  const flag = args[i];
30
- if (!flags.includes(flag)) throw new Error(`unexpected argument ${flag}`);
35
+ if (!flags.includes(flag)) throw new Error(`unexpected argument ${flag}; use --help`);
31
36
  const value = args[i + 1];
32
- if (!nonempty(value) || value.startsWith('--')) throw new Error(`expected value after ${flag}`);
33
- if (flag in options) throw new Error(`duplicate argument ${flag}`);
37
+ if (!nonempty(value) || value.startsWith('--')) throw new Error(`expected value after ${flag}; pass ${flag} <value>`);
38
+ if (flag in options) throw new Error(`duplicate argument ${flag}; pass it once`);
34
39
  options[flag] = value;
35
40
  }
36
- for (const flag of flags.filter((flag) => flag !== '--verify')) {
37
- if (!options[flag]) throw new Error(`expected ${flag}`);
41
+ for (const flag of flags.filter((flag) => !['--verify', '--out'].includes(flag))) {
42
+ if (!options[flag]) throw new Error(`expected ${flag}; pass ${flag} <value>`);
38
43
  }
39
- if (!['task', 'launch'].includes(options['--kind'])) throw new Error('expected --kind task|launch');
44
+ if (!['task', 'launch'].includes(options['--kind'])) throw new Error('expected --kind task|launch; pass --kind task or --kind launch');
40
45
  return options;
41
46
  }
42
47
 
43
- const readJSON = (path) => JSON.parse(readFileSync(path, 'utf8'));
48
+ function readJSON(path) {
49
+ try { return JSON.parse(readFileSync(path, 'utf8')); }
50
+ catch (error) { throw new Error(`${error.message}; pass a readable, valid JSON file: ${path}`); }
51
+ }
44
52
 
45
53
  function normalizedOptions(options) {
46
54
  const entries = Array.isArray(options) ? options.map((option) => [option?.id, option?.value])
@@ -68,19 +76,36 @@ function verify(configuration, instanceId, provider, model, options) {
68
76
  compare('options', options, selection?.options,
69
77
  normalizedOptions(options) === normalizedOptions(selection?.options));
70
78
  compare('runtimeMode', 'full-access', configuration?.runtimeMode);
71
- console.log(mismatches.length ? mismatches.join('\n') : 'MATCH');
72
- return mismatches.length ? 2 : 0;
79
+ return mismatches;
73
80
  }
74
81
 
75
- function main() {
76
- if (process.argv.slice(2).includes('--help')) {
77
- process.stdout.write(help);
82
+ function plan(argv, io) {
83
+ if (argv.includes('--help')) {
84
+ io.stdout(help);
78
85
  return 0;
79
86
  }
80
- const args = argumentsFrom(process.argv.slice(2));
87
+ const args = argumentsFrom(argv);
88
+ const inputs = Object.fromEntries(Object.entries(args).filter(([flag]) => flag !== '--out').map(([flag, value]) => [flag.slice(2), value]));
89
+ const emit = (result) => {
90
+ const full = { inputs, ...result };
91
+ const inline = result.mismatches
92
+ ? `${JSON.stringify({ inputs })}\n${result.mismatches.join('\n') || 'MATCH'}\n` : `${JSON.stringify(full)}\n`;
93
+ if (!args['--out']) {
94
+ if (new TextEncoder().encode(inline).length > 4096) {
95
+ if (!result.mismatches) throw new Error('large output; pass --out <report.json> to save full JSON');
96
+ io.stderr('Large verification output; pass --out <path>.json for the full list\n');
97
+ }
98
+ io.stdout(inline);
99
+ return;
100
+ }
101
+ try { writeFileSync(args['--out'], `${JSON.stringify(full, null, 2)}\n`); }
102
+ catch (error) { throw new Error(`${error.message}; pass --out <writable report.json path>`); }
103
+ io.stdout(`${JSON.stringify({ inputs, reportPath: args['--out'],
104
+ omittedPayloadFields: Object.keys(result).length, omittedMismatches: result.mismatches?.length ?? 0 })}\n`);
105
+ };
81
106
  const snapshot = readJSON(args['--roles']);
82
107
  if (snapshot?.version !== 1 || !Array.isArray(snapshot.roles)) {
83
- throw new Error('roles snapshot must have version 1 and a roles array');
108
+ throw new Error('roles snapshot must have version 1 and a roles array; pass --roles <valid roles.json>');
84
109
  }
85
110
  const roles = snapshot.roles.filter((role) => role?.id === args['--role']);
86
111
  if (roles.length !== 1) hold(`role ${args['--role']} unavailable or ambiguous`);
@@ -100,7 +125,7 @@ function main() {
100
125
  const resolved = Bun.spawnSync([process.execPath, `${import.meta.dir}/resolve-models.js`,
101
126
  '--provider', role.provider, '--capabilities', args['--capabilities'],
102
127
  '--effort', role.thinkingOptionId, ...modelArgs], { stdout: 'pipe', stderr: 'pipe' });
103
- if (resolved.stderr.length) process.stderr.write(resolved.stderr);
128
+ if (resolved.stderr.length) io.stderr(resolved.stderr.toString());
104
129
  if (resolved.exitCode !== 0) return resolved.exitCode === 1 ? 2 : 1;
105
130
  const { model, effortOption, providerInstanceId } = JSON.parse(resolved.stdout.toString());
106
131
  const chosen = readJSON(args['--capabilities']).providers
@@ -117,18 +142,22 @@ function main() {
117
142
  }
118
143
  const options = effortOption ? [effortOption] : [];
119
144
  if (args['--verify']) {
120
- return verify(readJSON(args['--verify']), picker.chosenInstanceId, providerInstanceId, model, options);
145
+ const mismatches = verify(readJSON(args['--verify']), picker.chosenInstanceId, providerInstanceId, model, options);
146
+ emit({ mismatches });
147
+ return mismatches.length ? 2 : 0;
121
148
  }
122
149
  const selection = { [args['--kind'] === 'launch' ? 'instanceId' : 'providerInstanceId']: picker.chosenInstanceId,
123
150
  model, options };
124
- console.log(JSON.stringify({ [args['--kind'] === 'launch' ? 'modelSelection' : 'target']: selection,
125
- runtimeMode: 'full-access' }));
151
+ emit({ [args['--kind'] === 'launch' ? 'modelSelection' : 'target']: selection, runtimeMode: 'full-access' });
126
152
  return 0;
127
153
  }
128
154
 
129
- try {
130
- process.exitCode = main();
131
- } catch (error) {
132
- console.error(`dispatch-plan ${error.exitCode === 2 ? 'hold' : 'error'}: ${error.message}`);
133
- process.exitCode = error.exitCode ?? 1;
155
+ export function main(argv, io = { stdout: (text) => process.stdout.write(text), stderr: (text) => process.stderr.write(text) }) {
156
+ try { return plan(argv, io); }
157
+ catch (error) {
158
+ io.stderr(`dispatch-plan ${error.exitCode === 2 ? 'hold' : 'error'}: ${error.message}\n`);
159
+ return error.exitCode ?? 1;
160
+ }
134
161
  }
162
+
163
+ if (import.meta.main) process.exitCode = main(process.argv.slice(2));