* fix(autoplan): empty findings fences, repo-relative rows, PLAN_REALITY flags, pending/--all/provider (Lane B: B3 B6 B7 B8 B9 B10 B11)
B3 parseFences: the body group is optional (lazy), so an LF/CRLF empty fence is zero
findings in both the canonical and record grammars; opener without closer still
RESULT_FINDINGS_MISSING.
B6 plan-reality/3: item/section references are not quantities; backtick phrases are
exempt only from the plan-scope rule-phrase check (B-f); a measured number inside
backticks stays flagged in review scope. vaguePlanTerms() feeds an advisory
`flagged: PLAN_REALITY ...` line under the p1 approval item (never blocking).
B7 canonical rows: absolute repo paths become repo-relative; snapshot/active-plan
citations become the plan file name with the source line mapped (or dropped).
B8 `status` prints `PENDING: <id> phase=<p> voice=<v> result=<file>` per open attempt.
B9 `gstack-gate render --all`; paged output says to relay every page.
B10 `gstack-plan-reality check --plan <snapshot>`: a number in a "quoted" span copied
verbatim from the reviewed plan needs no receipt (reviewer scope only).
B11 submit defaults the provider to host-subagent when --runner is omitted.
Lane B files: lib/autoplan-reconcile.ts lib/plan-reality.ts lib/autoplan-gate.ts
lib/gate-list.ts lib/autoplan-plan-text.ts lib/autoplan-run.ts bin/gstack-gate
bin/gstack-plan-reality scripts/resolvers/plan-reality.ts autoplan/sections/ceo-phase.md
autoplan/sections/eng-phase.md test/plan-reality.test.ts test/autoplan-gate-artifacts.test.ts
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(ubicloud): ubi-runner up/run -S accepts only Ubicloud's storage sizes, before any API call (A11)
Files: scripts/ubicloud/ubi-runner.sh, test/ubicloud-runner.test.ts
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(issue-links): promotion candidates, Related PRs, validated flake issues, body/receipt consistency (Lane D: D1-D3)
D1: the GBRA-40 fix wave's gbrain #6279 body had no `## Issues` section and no
receipt (it was not composed by /ship, so gstack-issue-links never ran), and
#6252 appeared only as a subject parenthetical `(#6252)`, which the closure
rule correctly treats as Refs. The rule is kept. Same-repo references are now
printed as promotion candidates (`(promote: fix #N)`, `candidates` in --json),
and `gstack-ship-receipt write --issues ... --body` refuses a body that lacks a
line its issues object records (ISSUE_LINKS_MISSING), so a /ship body can no
longer drop its closing lines.
D2: open PRs by other authors whose closing set overlaps this PR's Fixes are
`Related: #N (@author)` (paged GraphQL closedByPullRequestsReferences; viewer
and this branch excluded; cross-repo PRs keep owner/repo#N). `Supersedes` and
`Contributed by` only with --supersedes/--credit; --no-auto-links skips the
lookup; the stderr preview names each added line's source. A failed lookup is
ISSUE_LOOKUP_FAILED (installer form with docs anchor) and keeps the other lines.
D3: a --ci-run match becomes a Flake line only for a bot-opened issue of this
repository that names that run (flake:json first_seen.run_id or this repo's
actions/runs URL) and a test; duplicates of one test keep the lowest number;
every refused match prints its reason.
Files: bin/gstack-issue-links, bin/gstack-ship-receipt, lib/issue-links.ts,
lib/ship-receipt.ts, lib/headless-artifacts.ts (ship-receipt issues.related),
lib/result-codes.ts (ISSUE_LOOKUP_FAILED, ISSUE_LINKS_MISSING,
resultLineWithDocs), bin/gstack-doctor-components.sh (regenerated),
docs/pregate.md, docs/troubleshooting.md, test/ship-issue-intent.test.ts,
test/helpers/pregate-fixture.ts (gh shim: repo view, api graphql, full issue rows).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(ship): link issues by intent at publish and handoff; Fixes: trailer at commit (Lane D: D1, D2, D-a)
- Step 15: a commit fixing a user-named issue carries a `Fixes: #<n>` trailer
(offset by tightening Step 15 so the skeleton stays at 80,998 <= 81,000).
- pr-body: an explicit "Issue links" step runs gstack-issue-links before the
body is composed (previously only a placeholder inside the body template);
interactive runs ask once about `(promote: fix #N)` references and add
`--supersedes`/`--credit` only on the owner's confirmation; unattended runs
keep Refs and list unpromoted candidates, Related lines and lookup failures
in the completion message; `ISSUE_LINKS_MISSING` from the receipt write.
- handoff (no-PR lanes): runs gstack-issue-links and passes --issues to the
receipt; the completion message gains an `issues:` line.
- Regenerated ship/SKILL.md and sections; refreshed the four ship goldens.
- Parity: union 309,431 / 187,706 = 1.6485 (> 1.6450 by 655 bytes); maxSizeRatio
raised to the measured 1.6485 with its justification, as previous waves did.
Files: ship/SKILL.md.tmpl, ship/SKILL.md, ship/sections/pr-body.md.tmpl,
ship/sections/pr-body.md, ship/sections/handoff.md.tmpl, ship/sections/handoff.md,
test/fixtures/golden/{claude,codex,factory,agy}-ship-SKILL.md,
test/helpers/carve-guards.ts (ship maxSizeRatio).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(install): one Bun resolution contract, versioned runtime store, verdict per (checkout, project), drift and wrapper rows (A1-A3, A5, A6, A8, A-c, A-d, A-f, A-h, A-i)
- bin/gstack-bun-version.sh: GSTACK_BUN_DEFAULT=1.4.3 beside the 1.4.2 floor,
gstack_bun_install_version (GSTACK_BUN_INSTALL_VERSION override),
gstack_bun_store, portable gstack_realpath, and gstack_bun_resolve (pin and
default in the store, $BUN_INSTALL/bin, ~/.bun/bin, PATH; first candidate
satisfying every pin; shim-marked files never returned). gstack_bun_launch_dir
is replaced by it.
- lib/pregate/runtime.ts: resolveBun, the TypeScript twin; launchRuntime runs
the Bun the install verdict recorded under the admission condition and
explains the skip otherwise.
- bin/gstack-capy-install: install and --check resolve through the contract;
verified archives publish atomically to <state root>/runtimes/bun/<v>/bun
under ~/.gstack-runtime.lock and ~/.bun/bin is never written; the "no Bun
satisfies" line names the candidates judged; --check reports
BUN_RESOLUTION_DRIFT with one converging fix.
- bin/gstack-install-verdict.sh, lib/install-verdict.ts: schema 2 verdicts
keyed by (checkout, project) with pin, bun_path, bun_version, bun_rule;
schema 1 path still read; admission binds test-running workflows to the
project and refuses a running Bun the verdict did not record.
- bin/gstack-doctor-check.sh, lib/doctor-components.ts: wrapper core row
(CAPY_WRAPPER_STALE by active provenance, WARN for stale Drive copies),
runtime drift row, shim note, install-version note.
- lib/result-codes.ts: BUN_RESOLUTION_DRIFT, BUN_INSTALL_VERSION_INVALID;
CAPY_WRAPPER_STALE fix names the workaround and the owner action.
Files: bin/gstack-bun-version.sh, bin/gstack-capy-install,
bin/gstack-doctor-check.sh, bin/gstack-doctor-components.sh (generated),
bin/gstack-install-verdict.sh, lib/doctor-components.ts,
lib/install-verdict.ts, lib/pregate/runtime.ts, lib/result-codes.ts,
test/bun-resolve.test.ts, test/gstack-capy-install.test.ts,
test/gstack-doctor.test.ts, test/install-verdict.test.ts,
test/pregate-guards.test.ts
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(issue-links): --author for the Related lookup when gh runs as an app (Lane D: D2)
On a Capy machine the gh viewer is `capy-ai[bot]`, not the owner (checked live
against garrytan/gbrain #6252), so "other authors" needs the PR author named:
`--author <login>` overrides the viewer; the pr-body Issue links step says to
pass it when gh runs as an app.
Files: bin/gstack-issue-links, docs/pregate.md, ship/sections/pr-body.md.tmpl,
ship/sections/pr-body.md, test/ship-issue-intent.test.ts,
test/helpers/carve-guards.ts (ship maxSizeRatio re-measured: 309,483 / 187,706 = 1.6488).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs(troubleshooting): BUN_RESOLUTION_DRIFT and BUN_INSTALL_VERSION_INVALID anchors; CAPY_WRAPPER_STALE covers the wrapper row (A5, A8, A-f, G1)
Files: docs/troubleshooting.md, bin/gstack-capy-install, bin/gstack-doctor-check.sh, test/gstack-capy-install.test.ts
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(install): marked per-directory bun and bunx shims for fresh Capy shells (A4, A-e)
gstack-capy-install (install mode, Capy machines only, GSTACK_BUN_SHIM=0 opts
out) writes ~/.local/bin/bun and bunx under ~/.gstack-runtime.lock. The shim
bakes the store, verdict directory, checkout and install default, walks up to
.git without spawning git, runs the Bun the (checkout, project) verdict
recorded, else a store Bun matching the .bun-version/package.json subset
(stderr names the --check to run when none matches), else the install
default, else the next unmarked bun on PATH. A foreign file or dangling link
is never touched. GSTACK_BUN_SHIM_EXPLAIN=1 prints project, record, choice and
rule; the doctor's runtime row shows the shim and its target.
Files: bin/gstack-capy-install, test/gstack-capy-install.test.ts, test/gstack-doctor.test.ts
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(pregate): test stage never hangs silently; small machines record the remote gate (Lane C, GBRA-95)
C1 diagnosis (gbrain 92f0ea87, a core edit selecting 3,683 local files, 358
serial; Bun 1.4.2 and 1.4.3 identical): --timeout was applied per runner
spawn (unit group plus one per serial file), so the check was unbounded, and
spawnSync buffered all output until it returned: 360 s with 0 bytes on stdout
and stderr. A backgrounded child holding stdout made spawnSync wait the full
timeout and then report exit 0 (a silent wait, then green). Not the Bun 1.4.2
spawnSync hang.
C2/C-b: test-tier commands (lanes runner, guards, the --remote gate) run via
lib/pregate/supervise.ts: own process group (setsid), stdout/stderr appended
to a log file (no pipes), async supervision; CheckDef.run may return a
promise and runPregate/withFingerprint await it (a rejection is incomplete).
Leftover group members are killed after exit; SIGINT/SIGTERM to pregate
SIGKILLs running groups, writes no pregate.json, exits 130/143. Windows:
taskkill /T /F (reparented or detached descendants can survive; documented).
C3/C-a: --timeout bounds the whole check; groups past the deadline are not
started. --stall-timeout <s> (default stall_timeout_s, else 300; 0 disables)
kills a group whose log stops growing and records PREGATE_STALLED naming the
last file started. Live stderr: PREGATE_TEST per file (bun test adapter; any
runner for one-file runs), PREGATE_HEARTBEAT after 60 s without progress,
PREGATE: tests <verdict>. --json stdout unchanged.
C4/C-c: under 8 cores, a base-branch remote_gate or a non-local test_backend
records the local tier as a requires-remote obligation with its command
(pregate.json/remote_gate or test_backend/<backend>). --remote runs the
declared remote_gate now (needs --allow-repo-commands, never --policy-from
head) under --spend-cap/GSTACK_SPEND_CAP_USD; it clears only on
PREGATE_REMOTE: tree=<tested tree> tests>0 failed=0 with exit 0, and reports
spent/reserved/unknown.
G1: new code PREGATE_STALLED has its result-codes row, troubleshooting anchor
and the installer-form error line (CODE; url#anchor).
Files (G2): lib/pregate/supervise.ts (new), lib/pregate/types.ts,
lib/pregate/context.ts, lib/pregate/index.ts, lib/pregate/checks/lanes.ts,
lib/pregate/checks/guards.ts, lib/pregate/config.ts, lib/headless-artifacts.ts,
lib/result-codes.ts, bin/gstack-pregate, bin/gstack-doctor-components.sh
(generated), docs/pregate.md, docs/troubleshooting.md,
test/pregate-supervise.test.ts (new), test/pregate-remote.test.ts (new).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs(ship): Step 5 pre-gate text for stalls and the small-machine remote gate (Lane C-c)
The pre-gate paragraph now says `incomplete` blocks (a stall is incomplete)
and that `requires-remote` covers the remote gate under 8 cores, cleared by
`gstack-pregate clear` or `--remote --spend-cap <usd>` only under a cap the
user gave. Kept within the ship parity size cap (union 308,771 of 308,776
bytes; base 308,767), so the `--explain` hint moved to docs/pregate.md. The
handoff's escalation line already names any `requires-remote` lane; unchanged.
Files (G2): ship/sections/tests.md.tmpl, ship/sections/tests.md (generated).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(install): one ok-trailer grammar with requested, pins-skipped and latest notes; bootstrapper revision 2 exports origin's VERSION (A9, A-a)
Notes ride only on the ok trailer in fixed order; the failure head is
unchanged, pinned by a test that runs the Capy skill's dispatch examples
against real doctor output. The latest note comes only from
GSTACK_CAPY_REMOTE_VERSION, which the bootstrapper sets from a fetch in this
run and unsets otherwise (a stale FETCH_HEAD never reaches the installer).
Files: bin/gstack-capy-install, bin/gstack-capy-session,
capy/skill/gstack/scripts/install.sh, capy/skill/gstack/SKILL.md,
test/gstack-capy-install.test.ts, test/capy-skill-wrapper.test.ts,
test/gstack-doctor.test.ts
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(upgrade): every success path certifies the caller's project; a pins failure keeps the move, ends on gstack: fail pins, exit 1, no pending marker (A7, A-b)
gstack-upgrade captures the caller's directory and git toplevel before any
cd. Already-current, fast-forwarded and resumed-pending paths run
bin/gstack-capy-install --project <it> on Capy (installing a verified pinned
Bun) or gstack-doctor --check --require runtime,pins elsewhere. The
reinstall's own trailer is held back when a project certification follows,
so no ok precedes a pins failure; a RUNTIME_PIN_CONFLICT stop becomes
gstack: fail pins — <conflict>. --check and --ff-only-no-reinstall certify
nothing.
Files: bin/gstack-upgrade, test/gstack-upgrade-bin.test.ts
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(install): fresh-install log lines name the clone and every host installed (A10)
gstack-capy-install prints `cloned <repo> at <sha> (<version>)` for a checkout
it cloned, or one the bootstrapper (revision 2, GSTACK_CAPY_CLONED) cloned in
this run, instead of `reusing`. setup's final line is
`gstack ready (<hosts>)` from the same rows as its summary table, including
the --refresh-registered upgrade summary.
Files: bin/gstack-capy-install, bin/gstack-capy-session,
capy/skill/gstack/scripts/install.sh, setup, test/gstack-capy-install.test.ts,
test/host-conformance.test.ts
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(pregate): --remote runs the gate only when local tests are selected (Lane C4)
With only platform lanes selected, --remote no longer launches the remote
gate with an empty file list; the existing no-local-lane path applies.
Files (G2): lib/pregate/checks/lanes.ts.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(autoplan): one-word rec, verified pairing, sealed attempt results, deferred reopen (Lane B: B1 B2 B4 B5)
B1/B-b/B-c `rec` (aliases: all, approve all, accept all, take all recommendations)
takes every recommendation, plan changes included; `as-is` keeps every
direction. The render offers `rec` first and prints what it resolves to
with gate_rev and rec_hash. `gstack-autoplan recommend` changes one
recommendation before relay (provenance recorded, p1 and decisions follow)
and bumps rec_rev, so a `rec` without the new --rec-hash is refused
(GATE_REC_STALE). An agent answer needs the owner's words verbatim
(--owner-reply <text>|-, --owner-reply-file; OWNER_REPLY_REQUIRED); a
parseable owner reply that resolves differently from the relay is refused
(GATE_OWNER_REPLY_MISMATCH). Supersedes field-wave R1 per the owner's
gate_rev 1 decision.
B2/B-a reconcile associates by title first; a file:line-only match is
`unverified` (associated, never confirmed). `b` is recommended only for a
confirmed pair with the same `changes` and one shared proposal (override)
or the same normalized remedy; the basis says which. Negative control and
positives copied from run-20261014225954 (test/fixtures/autoplan-pairing).
B4/B-e each attempt writes <phase>-<voice>.<attempt>.md; submit refuses another
attempt's file (ATTEMPT_MISMATCH) and binds a sealed 0444
<phase>-<voice>.<attempt>.bound.md copy, so a late write changes nothing
bound; reopen keeps sealed copies in place.
B5/B-d answer records accepted changes as amendments (status amend_pending) and
reopens nothing; revise reopens what changed. `revise --settle <id|d4>`
discharges a packaging/ordering change on an unchanged plan
(AMENDMENT_NOT_SETTLEABLE otherwise). APPLY/NEXT/status lines say which.
DRIVER.md and the reply table move to the rec grammar.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(autoplan): durable checkpoint and resume --from; Capy checklist (Lane B: B12)
With GSTACK_AUTOPLAN_CHECKPOINT_DIR set, every command that leaves a run at
rest (gate_pending, amend_pending, complete, incomplete) publishes
<dir>/<run>/: checkpoint.json listing every file's sha256, the run under run/,
the owner's plan file under plan-input/ when it lives outside the run, and
restore.md as the human index. Publication swaps in a fully written sibling.
Checkpointing at every rest state, not only gate_pending, keeps accepted
decisions from rolling back on restore.
`gstack-autoplan resume --out <new dir> --from <checkpoint>` checks
containment (no absolute or .. paths, regular files only) and every hash
before publishing the run with one rename. It restores a missing plan file
into the run, journals `restored` with both hosts, and takes the lock.
CHECKPOINT_INVALID and CHECKPOINT_TARGET_EXISTS each get a result-codes row
and a troubleshooting anchor. Journal and revise staging paths are now
run-relative, so a moved run recovers.
DRIVER.md and the Capy skill's autoplan section get a short Capy checklist:
always drive gstack-autoplan, never present a hand review as an autoplan run,
relay every gate page with rec as the default, pass --owner-reply, and point
the checkpoint at the private project-user Drive folder.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* A-g: run the quick free tier on GSTACK_BUN_DEFAULT in CI; drift test pins it
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs(autoplan): unattended contract and reply-unparsed fix move to rec, sealed results, settle and checkpoints (Lane B)
docs/unattended.md: rec/as-is/rec except grammar, --owner-reply and recommend/--rec-hash, unverified pairing and the shared-proposal rule for b, amend_pending and revise --settle, attempt-owned result paths with the sealed .bound.md copy, render --all, and the checkpoint/resume --from paragraph. GATE_REPLY_UNPARSED's fix names rec/as-is (DRIVER.md regenerated).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs/capy-install: resolver, versioned store, bun shim, trailer notes; wrapper minimum 2
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(pregate): --timeout bounds each runner process again, not the whole lanes check
Lane C made --timeout a deadline for the whole lanes check, so a repo whose
unit group and serial files each fit in 600 s but exceed it together ended
incomplete and blocked /ship. --timeout is per runner process again (unit
group, each serial file); the stall timeout still catches hung or silent
processes. A timed-out process is incomplete naming its last file, and the
next serial file still runs. Regression: three 1.5 s serial files under
--timeout 3 pass; one 5 s file is incomplete.
Files (G2): lib/pregate/checks/lanes.ts, bin/gstack-pregate (help),
docs/pregate.md, test/pregate-supervise.test.ts.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(pins): CI exact pins for this platform are alternatives; the installer prefers a non-CI exact pin, else the install default among CI alternatives, else the highest verified (integration item 1)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(pregate): clear validates the remote gate receipt; test_backend run URLs are attested
`gstack-pregate clear` accepted any --run-url for a small machine's
obligation. A pregate.json/remote_gate obligation now clears only with
--receipt <file> holding the gate's PREGATE_REMOTE: line, validated as
--remote validates it (tree equals the tested tree, tests > 0, failed = 0;
shared parseRemoteReport/remoteReportProblem); a run URL is usage (exit 2)
and a bad receipt is PREGATE_RECEIPT_INVALID (exit 1, obligation unchanged).
A test_backend/<backend> obligation has no machine-checkable receipt: its
--run-url is recorded as attested:<url> and the output says attested, not
validated. Platform lanes are unchanged. Hints name the right clear form.
New code PREGATE_RECEIPT_INVALID: result-codes row, troubleshooting anchor,
installer-form error line, regenerated doctor components.
Files (G2): lib/pregate/index.ts, lib/pregate/checks/lanes.ts,
bin/gstack-pregate, lib/result-codes.ts, bin/gstack-doctor-components.sh
(generated), docs/pregate.md, docs/troubleshooting.md,
test/pregate-remote.test.ts.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(issue-links): derive this PR's author when gh runs as an app, so handoff and pr-body exclude the owner's own PRs (Lane D fix 1)
The handoff ran gstack-issue-links without --author, and the pr-body step only
asked the agent to pass one, so under Capy's app identity (viewer
`capy-ai[bot]`) the owner's own open PRs were listed as `Related:`. The bin now
derives the author itself: --author, else the gh viewer, else (for a `…[bot]`
viewer) the HEAD commit's GitHub user, else the owner of a user-owned
repository; with none it keeps every PR and reports ISSUE_LOOKUP_FAILED.
`--json` gains `author: {login, source}`; the stderr preview names it. Both ship
steps work unchanged, so the pr-body `--author` clause is dropped (union
309,487 -> 309,435 bytes, under the unchanged 1.6488 cap; skeleton 80,998).
Files: bin/gstack-issue-links, lib/result-codes.ts (ISSUE_LOOKUP_FAILED row),
docs/troubleshooting.md (#issue-lookup-failed), docs/pregate.md,
ship/sections/pr-body.md.tmpl, ship/sections/pr-body.md,
test/helpers/pregate-fixture.ts (gh stub: PR-author query),
test/ship-issue-intent.test.ts.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(wrapper): GSTACK_CAPY_MIN_WRAPPER back to 1; an active wrapper below the minimum is a WARN; gstack-upgrade never leaves a pending marker for the wrapper row and maps every installer stop to a trailer (integration items 2, 9)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(shim): validate and contain store pins, read the 'none' record outside projects and in the checkout, fall back in the resolver's order, stay quiet without a pin; quote baked paths; publish bunx and replace store entries without a window (integration items 3, 4, 6, 9)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(install): --check fails BUN_RESOLUTION_DRIFT only when the recorded Bun is gone, changed or no longer satisfies the pins; a newer candidate is a note and the recorded Bun stays the launch Bun (integration item 5)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(install): one final 'gstack ready (claude, codex)' after both host setups (integration item 7)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(pregate): lanes and guards validate the install verdict like admission and return incomplete (INSTALL_UNVERIFIED, BUN_RESOLUTION_DRIFT) before launching; the checkout binds the 'none' verdict (integration item 8)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs: CI pin alternatives, shim order and containment, drift note, wrapper WARN, final ready line
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(autoplan): settle after or with a revise, title pairs need the same remedy, legacy gates refuse rec (integration items 1, 7, 8)
1. A gate with a scope change (uc1) and a packaging change (uc2) answered
`rec` deadlocked: after `revise --plan v2 --incorporates uc1` reopened the
phases, `revise --settle uc2` compared against the first phase snapshot
(now undefined) and was refused forever. Settle evidence is now the plan
hash recorded when the owner accepted the change (Amendment.plan_sha256)
or a later plan_revised hash, plus "the plan file has no unrevised edits".
`--settle` may ride the same revise as `--plan/--incorporates` and binds
to the revision's hash. Settling the last amendment after a reopen leaves
the run `running` (the reopened phases rerun) instead of approving it.
NEXT and PLAN_AMEND_PENDING list the settle step before the edit step.
7. A title association alone no longer confirms: the same remedy confirms,
opposite remedies (one keeps, drops or negates what the other changes)
disagree, different or missing remedies stay `unverified`. The pairing
fixture gains same-title negative controls (opposite and different
remedies) and a same-remedy positive, for ordinary findings and user
challenges.
8. A gate.json without rec_rev (written by 1.91.101.0, whose render said
`all` leaves b items pending) refuses rec/all/approve all/`all except`
with GATE_REC_STALE until a re-render binds --rec-hash; explicit tokens
still answer. New gates carry rec_rev: 0.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(autoplan): checkpoints never touch the live run; generations, newest-valid restore, validated staging (integration items 2-6)
2. checkpointRun canonicalizes the collection and the run directory (symlinks
resolved through the deepest existing ancestor) and refuses, before
touching anything, when they are the same or one contains the other
(CHECKPOINT_OVERLAP); restore applies the same check to source, target and
staging. Before, <root>/<run> equal to the live run replaced it.
3. Publication writes an immutable generation (generations/g<revision>-…),
renames it into place complete, then switches current.json with one
rename; the previous generation is kept, so an interruption at the publish
boundary (fault hook checkpoint-before-pointer) leaves the last checkpoint
current. A generation never replaces a newer one (higher run revision):
"CHECKPOINT: skipped … newer". Publication and restore hold the
collection's .lock; restore also holds <out>.lock.
4. Restore checks every listed file's real path stays inside the generation
(an ancestor symlink no longer copies outside files), parses run.json and
runs validateRun on the staged run before publishing; a refusal leaves
--out absent. readState reroots plan_input and plan_source for moved runs,
and a restore with the owner's plan file gone points both at the restored
copy, so a second restore cycle and a later revise work.
5. A checkpoint failure after a committed command prints
"CHECKPOINT: failed for <run>: … ; the command's result stands" on stderr
and keeps the command's exit code.
6. `gstack-autoplan checkpoints --from <dir>` lists generations newest first
(run, gate_rev, status, revision, time, hash ok|bad, current).
`resume --from <collection or root>` restores the newest generation that
verifies, printing each corrupt newer one it skipped; a root holding
several runs must name one.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore: budget the Capy skill's autoplan checklist and trim its install notes
Files: capy/skill/gstack/SKILL.md, test/fixtures/context-budget.json (capy/skill/gstack ceiling only: 2364 tok x 1.10)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore: release 1.91.102.0 (tentative stamp) — GBRA-40 field list
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test: supply _setup_ready_line to setup-tail harnesses, register the pairing fixture, make the admission test runner-Bun independent; announce shim writes
Files: test/setup-hook-syntax-gate.test.ts, test/redact-prepush-hook.test.ts, test/setup-playwright-best-effort.test.ts, test/helpers/free-fixtures-data.ts, test/pregate-guards.test.ts, bin/gstack-capy-install
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* ci: run quick-bun-default on the ubicloud-standard-8 class the free suite uses
The ubuntu-24.04 runner timed out two Swift typecheck tests at 30 s (render @Observable marker, ios-qa-regen); both pass on Bun 1.4.3 on Ubicloud.
Files: .github/workflows/free-tests.yml
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(pregate): read bun test's GitHub Actions group headers; exclude pregate-remote from the Windows subset; give quick-bun-default the free suite's flake policy
Files: lib/pregate/supervise.ts, test/pregate-supervise.test.ts, scripts/lib/windows-curation.ts, .github/workflows/free-tests.yml
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test: run the Capy wrapper and shim doctor-row tests on POSIX only (Git Bash prints MSYS paths)
Files: test/gstack-doctor.test.ts
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore: restamp 1.91.107.0 (1.91.106.0 is claimed by #3209)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(pregate): spawn the supervised shell through lib/script-invocation (#3197 tripwire)
The supervisor now resolves bash via bashCommandInvocation, so Windows uses
Git Bash instead of a PATH bash, and an unresolvable bash reports spawn-failed.
guards.ts and lanes.ts no longer spawn bash (they run through supervise.ts),
so their KNOWN_SITES entries are removed.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(qa-functional): disable auto gc and maintenance in the fixture repo
On eval-slices (9), qa-functional-webhook-fix failed mid-run with 100+
'directory watch lost: .git/objects/..' entries and forbidden writes to
.git/info/refs and .git/objects/pack. That is git's detached auto
maintenance repacking the fixture under the write observer. This applies the
same gc.auto=0 / maintenance.auto=false config qa-callers-fixture uses.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(autoplan-guard-pty): say Phase 2 starts without waiting for user input
On eval-slices (1), haiku published Phase 1 and then ended its turn with
'Waiting for next invocation to continue with step 8'. Step 5 tells it to end
its turn while the reviewer runs, so step 8's 'In a later message' read as a
later user turn. Phase 2 never started, the guard logged 2 of the 4 expected
allows, and the run hit its deadline. Step 8 now says to continue in the next
assistant message, still separate from the step 7 publication message.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
---------
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
25 KiB
Unattended runs: the artifact contract
A gstack skill runs unattended when a parent agent starts it with
GSTACK_SESSION_KIND=unattended and reads its artifacts instead of its prose.
This page is the contract between that parent and the run: what the session
kind changes, which files a run writes, how the parent decides whether the run
finished, and how it reads the result without reading any markdown.
The session kinds are: interactive (a human answers prompts), headless
(GSTACK_HEADLESS, CI, the eval harness; a question BLOCKs), spawned
(an orchestrator subagent; the recommended option is auto-chosen) and
unattended (this page). unattended is honored only as the explicit
GSTACK_SESSION_KIND=unattended override, which outranks GSTACK_HEADLESS
so a parent can drive a run inside CI. Nothing in the test or eval harness sets
it, so headless keeps its BLOCK semantics.
What the session kind changes
gstack-skill-start prints SESSION_KIND: unattended and
UNATTENDED_SESSION: true, then applies these rules for the whole skill:
- No prompts. The update check, upgrade offer, telemetry and sync consent,
onboarding tips and routing blocks are suppressed exactly as they are for
spawned. An unanswered consent isoff. - No publishing or sync, enforced at the writers. Usage analytics are not
written (
TELEMETRY_WRITE: skipped (unattended session)), the artifacts repo is neither pulled nor pushed (ARTIFACTS_SYNC: skipped (unattended session; mode=<m> queue=<n> retained)), the sync spool is not fed (gstack-brain-enqueueis a no-op, andgstack-review-logsaysreview-log: sync skipped (unattended session)on stderr), andgstack-skill-enddrains nothing. Inherited enabled settings and pending queue work are left for the next human session. The upgrade offer becomes one line at skill end, read from the local cache:UPGRADE_AVAILABLE: <version>; run /gstack-upgrade. - Questions decide themselves, gates do not. A question with a
(recommended)option takes it and appends the choice todecisions.jsonlwithkind: "auto". A question with no recommendation, any consent, any destructive or irreversible option, and the final approval gate are appended withkind: "approval"andstatus: "pending". The skill ends at its next gate with the pending list rendered; it never approves and never publishes. - State root durability is reported.
STATE_ROOT: <path> durable=yes|no(nowhen the host setsGSTACK_EPHEMERAL=1). On an ephemeral root the run sayslearnings: skipped (state root is ephemeral; set GSTACK_STATE_ROOT)andtiming: analytics skipped (...)instead of writing to a disk that vanishes. PointGSTACK_STATE_ROOTat a durable directory when you want cross-run history (see state-root.md). - The autoplan publication guard is named, not faked. On hosts that do not
execute Claude Code hooks, and in every unattended session,
gstack-skill-startprintsautoplan guard: not enforced by this host; publication order is unverified (GUARD_NOT_INSTALLED)once.gstack-artifact validaterequires that line in an unattended run'sreview-record.md. Claude Code's deny-on-absent behavior is unchanged.
An unknown GSTACK_SESSION_KIND value coerces to interactive and says so:
SESSION_KIND: interactive (unknown kind '<x>').
The terminal line
Every workflow skill ends an unattended run with one line the parent greps:
GSTACK_RESULT: skill=<name> status=<status> run=<dir>
status is one of complete, gate_pending, incomplete, interrupted or
refused; run is the directory that holds the artifacts below. /autoplan
always ends gate_pending when it reaches the final gate, because the gate is
written and never approved. Pure queries (gstack-artifact schema,
gstack-gate render) print no result line.
Every workflow command shares one exit table: 0 ok, 1 fail, 2 usage,
3 refused or needs a flag. Error lines end with a result code in parentheses,
for example findings.jsonl:12: ... (ARTIFACT_SCHEMA); each code has an anchor
in troubleshooting.md with
a fix: clause. Grep the code, not the prose.
The run directory
| File | Schema | Contents |
|---|---|---|
run.json |
run v1 |
The manifest: run id, status, required_phases, each phase's native and outside outcome (status, model, provider), input snapshot hashes, counts, every artifact's path (relative to the run directory) and sha256, session_kind, gate_rev, deviations, consumed_by. |
decisions.jsonl |
decisions v1 |
One row per decision: id (<run>-<label> at gate revision 1, <run>-r<rev>-<label> after a reopen, since labels restart at each gate), title, options, recommended, kind (auto, approval, user_challenge), status (pending, approved, overridden, rejected), gate_rev, and once answered chosen, answered_at, reply. |
findings.jsonl |
findings v1 |
One row per reviewer finding: id, phase, voice (native/outside), model, severity, title, disposition, plan_items, native_counterpart (string or null), source. Rows share the review-log row shape, so gstack-review-log --findings derives review status from this file. |
tasks.jsonl |
tasks v2 |
Implementation tasks with id, title, status, depends_on, findings (ids in findings.jsonl), blocked_by, acceptance, optional pr/item/tier. |
timing.json |
timing v1 |
Per-phase started_at, ended_at, wall_s, outside_s, native_s; started_at and total_wall_s for the run; the Phase 0 estimate. |
plan.md |
prose | The ## Implementation plan section, with ## URGENT, outside this plan written above it. |
review-record.md |
prose | The review record and decision audit trail; carries the GUARD_NOT_INSTALLED line in an unattended run. |
gate.json |
see gstack-gate --help |
The final gate list /autoplan renders and parses. |
Ids are run-bound: every row's id starts with <run>-, and run.json is the
only place the run id is declared. gstack-artifact schema <name> prints the
JSON Schema for any of findings, decisions, tasks, timing, run,
pregate and ship-receipt; schema --list prints the names.
How a parent consumes a run
- Decide whether the run finished.
gstack-artifact validate <dir>/run.jsonis the deterministic completion check. It exits0and printsARTIFACT_VALID: run=<id> status=<status>only when the manifest parses and matches its schema, every required phase and each reviewer voice has a terminal outcome, every listed artifact exists inside the run directory with the recorded hash, every JSONL file parses row by row against its schema with run-bound unique ids, task dependencies and finding references resolve without cycles,countsagree with the files, and (unattended) the guard line is present. Otherwise it exits1with one error line per problem andARTIFACT_INVALID: <n> error(s);--jsonreturns the same as{ valid, run, status, run_dir, errors[] }. A run that was interrupted shows up here asARTIFACT_RUN_INTERRUPTED, never as a partial success. - Read the terminal line.
grep '^GSTACK_RESULT:' <log> | tail -1. Withstatus=gate_pending, the gate is in the artifacts; nothing was approved. - List what is pending.
jq -c 'select(.status=="pending")' <dir>/decisions.jsonl gstack-gate render <dir>/gate.json --all # the numbered list and reply grammar, every page - Answer the gate by composing the reply grammar (
rec,as-is,<id><option>tokens such asd3b uc1a, orrec except <tokens>; bare yes/no is rejected) and feeding it back with the gate revision you read:A stale revision isgstack-gate parse <dir>/gate.json --reply "rec except d3b" --gate-rev 1 --json gstack-gate decisions <dir>/gate.json --reply "rec except d3b" --gate-rev 1 >> <dir>/decisions.jsonlGATE_REV_STALEand a reply with unparsed tokens isGATE_REPLY_UNPARSED; both exit1and write nothing. - Read findings and tasks without the prose.
jq -c 'select(.severity=="Critical" and .disposition!="accepted")' <dir>/findings.jsonl jq -r '.id + "\t" + .title' <dir>/tasks.jsonl gstack-artifact urgent <dir>/findings.jsonl # the URGENT block, or "None." - Acknowledge consumption so a second parent (or a retry) can tell the run
was already read:
gstack-artifact ack <dir>/run.json --consumer <id>is idempotent and recordsconsumed_byin the manifest.
Driving /autoplan from a parent: gstack-autoplan
On a host where the parent dispatches the reviewers itself (Capy, any
orchestrator without a Claude Code harness), bin/gstack-autoplan is the
explicit, durable state machine for the whole run. The parent never reads the
review record; it runs a loop over two commands and reads the printed lines.
A parent on any host reads autoplan/DRIVER.md (its path is printed as
DRIVER: by gstack-autoplan --help and the first next) instead of
autoplan/SKILL.md: the phase order, the artifact contract and the gate protocol
in about a hundred lines, generated from the code.
AP=bin/gstack-autoplan
$AP next --out <dir> --plan <plan.md> [--ui|--no-ui] [--developer-tool] [--no-dx] [--light] [--spend-cap <usd>]
# PHASE: ceo snapshot=<sha> order=ceo,design,dx,eng
# SCOPE: ui=yes (...) dx=yes (detected: ...) source=ui:detected,dx:detected
# DRIVER: <root>/autoplan/DRIVER.md
# Skipped sections: none (scope: ui=yes,dx=yes,source=ui:detected,dx:detected; checklist=loaded)
# ATTEMPT: <run>-a1 phase=ceo voice=native prompt=<dir>/ceo-native-prompt.md result=<dir>/ceo-native.a1.md model_family=...
# ATTEMPT: <run>-a2 phase=ceo voice=outside prompt=<dir>/ceo-outside-prompt.md result=<dir>/ceo-outside.a2.md model_family=differs from the native reviewer
# SPEND: spent=0.00 reserved=0.00 unknown=0(0.00) worst_case=0.00 cap=none
# GSTACK_RESULT: skill=autoplan status=awaiting_result run=<dir>
The first next prints SCOPE: … source=ui:<flag|marker|detected>,dx:<…>
and, when CLI flags differ from the plan's marker, SCOPE_PIN: add <!-- gstack-autoplan-scope: … --> as the first line of <file>; gstack never writes
the plan.
- Dispatch each
ATTEMPT:. The native voice is a subagent on the parent's own model given the prompt file; the outside voice is a subagent on another model family, orbin/gstack-outside-voice run --runner codex-cli|api --prompt <file> --out <file> [--model <id>], which printsOUTSIDE_STATUS: <status> provider=<runner> model=<id> family=<f>. Every reviewer writes its result to the printedresult=path under the RESULT FORMAT below.nextis idempotent: it reprints open attempts and never redispatches on its own (EXECUTION_UNKNOWNlists attempts whose process died without a result;--redispatch <attempt>mints a fresh one andrelease --attempt <id>returns its reservation). - Submit each result exactly once.
Submit seals the bytes it bound as
$AP submit --out <dir> --phase ceo --voice native --result <dir>/ceo-native.a1.md --model <id> --attempt <run>-a1 $AP submit --out <dir> --phase ceo --voice outside --result <dir>/ceo-outside.a2.md --model <id> --attempt <run>-a2 [--runner host-subagent (default)] [--usage-usd <n>] # BOUND: <run>-a1 phase=ceo voice=native model=<id> findings=7 sha256=<result hash prefix> # PHASE_CLOSED: ceo confirmed=4 disagree=1 new=2 native_only=1 coverage=both<phase>-<voice>.<attempt>.bound.md(read-only); a later write to the reviewer's file changes nothing bound, and another attempt's result file is refused (ATTEMPT_MISMATCH). Binding checks the receipt line against the phase snapshot (RESULT_RECEIPT_MISSING), parses the canonical findings fence (RESULT_FINDINGS_MISSING), refuses an outside model from the native family (MODEL_FAMILY_CONFLICT), and refuses a second bind of the same attempt or a bind while the phase is not awaiting it (ATTEMPT_MISMATCH,PHASE_NOT_AWAITING, exit 3). Both voices in closes the phase: the reconciliation (confirmed/unverified/disagree/newper outside finding, with disagreement and resolution kept separate) is written to<phase>-consensus.{json,md}, and every later phase's prompts (DX, design and Eng alike) carry each closed predecessor's consensus as data; a phase refuses to open without one (CONSENSUS_MISSING). - Loop
nextuntilstatus=gate_pending. Eng always runs last. The gate (gate.json,gate_rev) lists user challenges as approval items, disagreements as auto items andp1plan approval; nothing is approved. - Answer the gate with the reply grammar and the revision you read:
$AP answer --out <dir> --gate-rev 1 --reply "rec" --by human # every recommendation, plan changes included (APPLY: ...; amend_pending) $AP answer --out <dir> --gate-rev 1 --reply "as-is" --by human # APPROVED: keeps every direction, no new paid phase $AP answer --out <dir> --gate-rev 1 --reply "rec except d1b" --by agent:<id> --owner-reply "<owner's words>"--by humanmeans the owner typed the tokens;--by agent:<thread or id>means an agent relayed them, and then--owner-reply(or-for stdin,--owner-reply-file <path>) stores the owner's words verbatim (OWNER_REPLY_REQUIRED); owner words that parse to different choices are refused (GATE_OWNER_REPLY_MISMATCH). An unattended run refuses an answer without--by(ANSWER_BY_REQUIRED, exit 3) and records the value asanswered_byon every decision row and in the review record's gate line, so a gate an agent closed is distinguishable from one the owner closed.gstack-autoplan recommendchanges one recommendation before relay; it bumps the gate'srec_rev, and a laterrecneeds the--rec-hashfrom the render the owner saw (GATE_REC_STALE). A user challenge both voices raised on a confirmed finding (a title match, not file:line proximity alone, which isunverified) with the samechangesvalue is recommended asbonly with one shared proposal: a--reconcilerecord naming it, or the same remedy from both voices. Different or missing remedies keepa, and option b shows both verbatim with the basis.rec(aliasall) takes every recommendation, so it accepts the items recommendedb. Every accepted plan change printsAPPLY: <decision id> <title> -> <chosen text>and becomes a pending amendment:nextrefuses withPLAN_AMEND_PENDINGandexportrefuses acompleterun untilrevise --incorporatesnames it, orrevise --settle <id>settles a packaging or ordering change that needs no plan edit. A stale revision (GATE_REV_STALE, exit 3), bareyes/no, or a conflicting reply (GATE_REPLY_UNPARSED) writes nothing. A reply that leaves approval items pending records only the answered auto items and keeps the gate; a later reply never overwrites an earlier answer. An accepted change reopens nothing at answer (statusamend_pending);revisereopens each phase whose reviewed plan changed plus Eng, archives their prompts and consensus as*.r<rev>.*(sealed*.bound.mdresults stay in place), and the next gate isgate_rev + 1;p1dreruns every phase on the current plan;p1eendsincomplete. - Export and validate.
$AP export --out <dir>writesplan.md(with the## URGENT, outside this planblock first),review-record.md,tasks.jsonl,decisions.jsonl,findings.jsonl,timing.jsonandrun.json; thengstack-artifact validate <dir>/run.jsonand the consumption steps above apply unchanged.$AP status --out <dir>andresumework after the run directory moves (paths are re-rooted; journal paths are run-relative). WithGSTACK_AUTOPLAN_CHECKPOINT_DIRset, a run at rest publishes an immutable, hash-listed generation under<dir>/<run>/generations/and switchescurrent.jsonto it (the previous generation is kept; a stale copy never replaces a newer one; a directory overlapping the run is refused withCHECKPOINT_OVERLAP; a failure is aCHECKPOINT: failedwarning that leaves the command's exit code alone).$AP checkpoints --from <dir>lists generations with their hash status, and$AP resume --out <new dir> --from <dir>/<run>restores the newest valid one after validating the staged run (CHECKPOINT_INVALID).
Concurrency and recovery: every state write is a locked read-modify-write
(run.lock; a second writer sees RUN_LOCKED), attempts are journaled to
attempts.jsonl before dispatch, and a parent killed before or after a
submit resumes with next without a duplicate charge
(test/autoplan-run-recovery.test.ts, test/autoplan-run-concurrency.test.ts,
test/autoplan-answer-invalidation.test.ts). --spend-cap admits each attempt
through spend.json (SPEND_CAP_EXCEEDED refuses, exit 3); host-subagent
spend is unknown and counted in worst_case. --deadline <min> makes the
run subcommand cancel the children it started and marks the run
interrupted.
RESULT FORMAT (what every reviewer file must contain)
Each prompt file ends with this contract; submit enforces it.
- The first line is exactly
INPUT: <phase> <snapshot sha256>(the receipt that binds the result to the snapshot both voices reviewed). - The review is prose, then one fenced block of canonical findings, one JSON
object per line:
{"id":"F1","severity":"High","title":"...","file":"...","line":12,"fix":"..."}inside a```gstack-findingsfence.severityis Critical | High | Medium | Low | Informational;"user_challenge": truemarks a recommended change to the plan's stated direction and needs"changes": "scope" | "cost" | "direction"(what accepting it changes);"urgent": truewith"suggested_owner"marks a security or data-loss finding the plan does not own. An empty fence means no findings; a missing fence is refused. - The whole result is written to the printed
result=path.
Outside-voice runners
| Runner | Access | Record label | Needs |
|---|---|---|---|
codex-cli |
repository, read-only, executes here | repository review |
codex on PATH and logged in (bin/gstack-codex-login reads OPENAI_API_KEY from stdin) |
api |
supplied input only | supplied-input review |
--model <id> and OPENAI_API_KEY or ANTHROPIC_API_KEY; never silently equal to a repository review |
host-subagent |
the host's | host-dispatched review |
--result <file> the subagent wrote, --model <id>; spend unknown |
bin/gstack-outside-voice runners prints each runner's availability on this
machine; family <model> prints the model family submit compares. A completed
api or host-subagent review is a full outside voice in the record, labeled
by its runner. Result codes: EXECUTION_UNKNOWN, RUN_LOCKED,
RUN_NOT_INITIALIZED, PHASE_NOT_AWAITING, ATTEMPT_MISMATCH,
RESULT_RECEIPT_MISSING, RESULT_FINDINGS_MISSING, MODEL_FAMILY_CONFLICT,
SPEND_CAP_EXCEEDED, CONSENSUS_MISSING, OUTSIDE_RUNNER_UNAVAILABLE
(anchors in troubleshooting.md).
Hand-run records, revised plans and admission
- Hand-run reviews.
submit --record <file>binds a review that did not follow the RESULT FORMAT: thegstack-findingsfence, one findings table (Severity and Title columns), one severity-tagged list (- [High] title) or aNo findings.line. Without itsINPUT:line it binds only with--attest "<reason>"and--attempt <id>(receiptattested; refused after a laterrevise), andgstack-artifact validateprintsattested=<n>.--attemptmay be omitted when exactly one attempt is open.submit --voice outside --unavailable "<reason>"(or--failed) closes the phasenative_onlyonce native is bound;nextandresumenever redispatch it. - Revising the plan in the same run. Edit the source plan, then
$AP revise --out <dir> --plan <file> [--incorporates <decision id,...>]. Every phase whose reviewed Implementation-plan hash differs reopens (plus Eng); newly in-scope phases are added; archives keep.r<rev>names with.v<k>only on collision; a revise atgate_pendingmakes the next gategate_rev + 1.--incorporatesdischarges only the named amendments and refuses an unchanged plan;revise --out <dir> --withdraw <decision id>drops one as a journaled owner decision.nextprintsPLAN_CHANGEDandanswerprintsPLAN_SOURCE_CHANGEDwhen the source plan moved since the snapshot. A plan without both## Implementation planand## Review recordrefuses withPLAN_SECTIONS_INVALID;next --scaffoldreviews a copy with the missing heading added.resumecompletes an interrupted revise. - Admission and upgrades.
next,submit,answer,revise,exportandrunrefuse on a Capy host (GSTACK_CAPY=1) without an ok install verdict for this checkout, HEAD and workflow at$GSTACK_INSTALL_VERDICT(INSTALL_UNVERIFIED), and in any unattended session when the running Bun is below gstack's supported minimum (RUNTIME_BELOW_MINIMUM); the decision is the same onegstack-skill-startmakes (see Install admission);GSTACK_ALLOW_UNSUPPORTED_RUNTIME=1overrides and records a run deviation.statusandresumeare never gated. A run directory written by an older gstack printsGSTACK_VERSION_CHANGEDonce and moves forward (legacy decision ids are renamed and journaled); one written by a newer gstack is refused.
Owner brief
gstack-owner-brief render --out <dir> is what the owner sees at the final
gate, interactive or unattended: one page built from gate.json,
decisions.jsonl and findings.jsonl (BRIEF_INPUT_MISSING when one is
absent). It opens with the URGENT, outside this plan block, then a
plain-language summary (phases closed, findings accepted, how many decisions
take their default, the cycle time; --summary <file> or <dir>/summary.md
replaces the generated sentence with the run's own), then the numbered
decisions through the gate list: seven per page (--page N), stable ids,
each auto item annotated with the default taken if unanswered, each
approval item pending until you answer or decided <id><letter> on <date>.
The last line names review-record.md, where the full review lives, and the
gstack-autoplan answer call that answers the page. --write also saves
brief.md in the run directory. /autoplan Phase 4 presents this output
verbatim and writes the former long report to review-record.md.
After the final gate, gstack-owner-brief close --out <dir> --plan <file>
rewrites every draft direction stands until the owner decides line in the plan
to the decided option and date (DRAFT_DIRECTION: line <n> <id> rewritten);
lines whose decision is still pending stay and are reported as
DRAFT_DIRECTION_UNRESOLVED (exit 1). gstack-autoplan answer runs the same
rewrite when a reply completes the gate (journal event draft_directions_closed).
Lane done: pregate, the sealed guard and the handoff lines
A lane under a coordinator runs gstack-pregate run --allow-repo-commands on
its branch before it reports done (the unit tier on the changed files plus
the repo's guards, docs/pregate.md#guards). In a repo
whose .gstack/pregate.json has a sealed key it runs gstack-pregate sealed
before any push or PR creation, since a PR tool may not run local hooks. Its
handoff carries, right after the GSTACK_RESULT line, the gstack: ok|fail <rev> line and then the PREGATE: line. The coordinator contract
(gstack-autoplan contract) states the same rule word for word.
Timing
/autoplan prints ESTIMATE: ... at Phase 0 (gstack-autoplan-timing estimate --run <id> --out <dir>, medians of the last ten recorded runs per
phase, or no history), records each phase close (gstack-autoplan-timing close), and prints the CYCLE: line at the final gate (gstack-autoplan-timing summary). A close without an explicit start measures from the previous phase's
end, else from the run start. The cross-run history lives at
<state root>/analytics/autoplan-timing.jsonl and is skipped, with a printed
line, on an ephemeral state root.
Reference run
test/fixtures/multi-agent-wave/reference-run/ is a complete unattended
/autoplan run (the one that produced this wave's plan) and validates clean.
test/fixtures/multi-agent-wave/INCIDENTS.md records what went wrong while it
was produced by hand and which validator rule now catches each incident.
Install admission
An unattended run, and a GSTACK_SESSION_KIND=spawned run with GSTACK_CAPY=1,
starts only on a supported Bun and, on a Capy machine (GSTACK_CAPY=1, set by
the installer's GSTACK_LAUNCH line), with a passing install verdict for this
checkout, its HEAD and the workflow. Otherwise gstack-skill-start prints
INSTALL: fail runtime|verdict — <detail>; fix: <command> (<CODE>) and
GSTACK_RESULT: skill=<s> status=refused run=-, and exits 3; stop and run the
fix. GSTACK_ALLOW_UNSUPPORTED_RUNTIME=1 admits anyway (INSTALL_VERDICT: overridden), recorded as a deviation. TypeScript entrypoints use
checkInstallAdmission from lib/install-verdict.ts; the Bash side is
bin/gstack-install-verdict.sh. See capy-install.md.