* fix(ship): base merges, adjacent base edits, test-only and audit-requested fixes no longer force a second review loop (GST-407, GST-384, GST-391) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * ship driver: trim two phrases for headroom with #3662 Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * ship driver: keep the Next steps heading phrase the handoff check reads; trim elsewhere Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: regenerate ship goldens Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pre-landing review fixes comparePath returns same only when every hunk sits where the base change puts it (patch-ids ignore positions) and treats a file-mode change as changed; the scoped-review packet says when a fence is truncated; --docs-result must be the posted audit's result; the --context-reviewed record goes beside the packet, outside the worktree. Negative controls for each. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pass-2 review fixes A test edit routes as tests= only when it adds lines with no gate tag; a removed or changed line or an unreadable test diff goes through the full loop. Diffs skip textconv. A hunkless patch (binary, empty, mode-only) is same when its patch-id matches. Without --reviewed-base test files route as before. finish's packet path and attempt's state directory have fallbacks. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: Step 11 review fixes; a version-only stamp_files edit after review is release metadata (GBRA-251) gateEditedPaths reads removed lines only inside hunks; carry refuses when a path listing fails; docs freshness stops on a failed listing; attempt counts an unreadable state file. releasePaths includes stamp_files paths whose edit is a version swap under stamp_patterns. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: no test edit skips review; guard packet writes; exclusive attempt state A test-only edit with no gate tag gets the scoped fix-check of its test hunks instead of a lanes-only rerun. Packet write failures print a STOP or RECEIPT_CARRY_REFUSED. attempt's allowance uses an exclusive create. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: attempt prints gstack's install command; carry checks the product diff outside context paths The attempt STOP no longer echoes the audit child's fix text. A scoped-review carry now requires the product patch-id without the context paths to match at both heads. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: oversized packets take the full loop; exact --docs-requested match; name-only test globs only in test dirs A scoped-review packet section over 200 lines sends that path to the full loop instead of a truncated packet. The docs-result path match is exact. test_*.py, *Test.java and similar count as test code only under a test directory. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs(unattended): carry's scoped-review refusal and what it keeps Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: carry's scoped-review route in ship-policy, review-policy and troubleshooting (docs-a-1) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(windows): curate ship-rereview-triggers out of the Windows lane like ship-release (run 41193336209) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(review-log): record a notes-only /ship pass clean; name the cause and repair when a pass's tree moved (GST-430) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(receipt): carry and write handle PRs with thousands of files; tree receipt fails closed on a failed diff (GST-448) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(ship): preflight routes RECEIPT_CARRY_SCOPED_REVIEW to the scoped review; carry --dry-run writes no packet (GST-442) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs(ship): Step 1.4 and Step 16 route the scoped-review refusal and the --docs-requested fix; trims keep the cap (GST-442, GST-444) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: /ship review pass 1: notes-only floor, scoped carry order, named temp and diff refusals - A notes-only pass is recorded clean only when every finding is fixed, or noted or deferred below the blocking floor (never CRITICAL or blocking), by its logger-stamped action, and the findings account for the claimed issue and critical counts (GST-430). - The preflight scoped carry route commits and pushes the base-merge fix before writing the packet (GST-442). - patchId refuses by name on an unwritable TMPDIR; the tree receipt names why its diff failed (GST-448). - Step 16 stage 3 spells out --docs-requested with its --docs-result (GST-444). Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: pin the Step 16 --docs-requested route with its --docs-result Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: a claimed clean review row with a logger-stamped open gate finding reads UNVERIFIED (red-team, pass 1) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: Step 11 findings: gate rows from --findings are stamped, one open rule, stale patch-id dirs swept - deriveFromFindings stamps gate rows with the session kind before deriving status, so a findings file cannot dispose of a gate finding the unattended logger forces open (outside-voice#1). - reviewFreshness reads a claimed row's findings with isOpenByAction, the rule the notes-only rewrite uses; noted and deferred clear a finding only when it is marked blocking: false and is not CRITICAL. - patchId sweeps gstack-patch-id-* directories over an hour old that a killed carry left in TMPDIR. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: Step 11 round 2: stamped-open gates win over status and disposition; resolved gate, skipped and below-floor actions stay CURRENT - isOpenFinding treats action 'open' (the logger's forced-open stamp) as open before a row's status or disposition. - reviewFreshness flags a claimed clean row only for an 'open' action or a CRITICAL or blocking deferral or note, so interactive kept and restored gates, skipped advisories and INFORMATIONAL deferrals read CURRENT as before. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: carry dry run offers the scoped route last; patch-id diff under the git dir; changelog matches the review-log floor Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: a claimed clean review row with any unresolved non-gate CRITICAL finding reads UNVERIFIED (pass 2 red-team) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: carry dry-run scoped route, patch-id directory, tree-receipt refusal (docs-d-1 audit) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(ship): Step 3 names merge conflicts in its STOP sentence again (ship-workflow-clarity pin) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(tree-receipt): keep the gated-to-current diff at the file helper timeout (60 s); pregate literals check Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(carry): large-diff fixture sets core.longpaths so Git for Windows can add its long receipt paths (windows-free-shard 1, run 41210080055) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(review): a stale local base branch yields to its origin twin in gate-diff and diff-scope (GST-462) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(ship): Step 11 saves the targeted record and the receipt writes carry it; Step 9 clears it (GST-464) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * changelog: GST-464 and GST-462 Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(review): pass 1: ahead and diverged labels, diff-scope needs shared history; preflight refreshes origin/<base> and stops on a diff far beyond the PR (GST-462, HARN-15) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pass-2 review fixes for base twin, prSized stop and targeted record Resolve the tracking twin by refs/remotes/<remote>/<ref> and only for a bare branch name, keep a local base that is ahead or diverged, quote the prSized stop's base and name the fork case, and have every Step 9 entry create, exclude and clear .gstack/tmp/targeted.json. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: origin's tracking base wins whenever related; prSized lower bound The fix-check found that keeping a local base ahead of origin left unpushed base commits out of the gate scan while review-plan listed them. The tracking branch now wins whenever it shares history with HEAD, an explicit origin/main reads refs/remotes/origin/main, the preflight also stops when the diff lists fewer files than the PR, and the Step 9 exclude line survives a missing .git/info and an exclude file without a final newline. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * changelog: the tracking-ref claim holds while the tracking branch exists Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: repin the two ship section briefs under no-loss replay GST-464 GST-464 brief-identity check: every reviewer brief lib/review-brief.ts cuts from ship/sections/review-army.md and adversarial.md stays byte-identical to the bodies the L and GST-257 replays admitted; only lead instructions outside the cut spans changed. The check is a test CI runs. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * ship: keep Step 9's PASS_HEAD sentence whole; repin review-army under GST-464 The targeted-record line follows the PASS_HEAD/RANGE_BASE sentence instead of splitting it (test/review-fix-range.test.ts pins that sentence). The brief-body check still holds: every reviewer brief is byte-identical. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: restore bin/gstack-ship-receipt after the main merge The merge commitb10e2dbwrote a truncated copy of the file. This is the three-way merge of both sides with the refused() union: main's codes plus RECEIPT_CARRY_SCOPED_REVIEW. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * review-collect: narrow the consumer scan and give it one 300 s deadline (GST-544) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * review-collect: keep the consumer scan deadline under the 180 s plan timeout (review fix) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * review-collect: plain paths under --literal-pathspecs; whole-millisecond timeout override (review fix) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * review-collect: whole-tree grep for non-UTF-8 names, no argument spread, name spawn errors (review fix) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * review-collect: cap the consumer grep output at 256 MB across calls; per-scan deadline wording (review fix, cycle 4) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * review-collect: read a missing spawn status as null (Bun 1.4.2 returns undefined on ENOENT) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: review-collect whole-tree helper reads a null status as -1 (typecheck:test ratchet) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * review-bots: /review and /ship triage every configured review bot, not only Greptile (#3122) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pre-landing review fixes (gh before 2.48 pages without --slurp; driver routes Step 10; tests for guards, review-bot fence, REMOTE_SLUG owner) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: drop the review-bot fence test that could not fail (the tool-fence WARN degrade is unreachable: placeholder spans are skipped first) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: Step 11 review fixes (record by comment id so a filename never reaches a shell; lookup failures are errors; tojson pages; longest bot prefix wins) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pass-2 review fixes (record refuses * paths, colliding ids and a missing fetch dir; parent requires id and dir; botOwning doc) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * review: classify the PR type and always check repo conventions (#356) The checklist now opens with a PR-type line (application, CI/infra, scripts/tooling, docs-only or mixed). Application and mixed diffs run every category; other types always run five categories, including the new Repo Conventions & Platform Fit, and report the rest as not applicable in one line instead of skipping silently. No-loss replay: GSTA-224 H1 preregistered no-loss run (run id gsta224-h1-20261031, Opus 5.5, 81 calls, $9.53) against main's checklist with a no-gstack arm; every preregistered bar passed. The review/checklist.md pin in test/review-packets-removed.test.ts moves to a32963ef with replay 'GSTA-224 H1'. Refs GSTA-224 Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * review-bots: allowlist the record placeholders and match Step 10's id rule in tests The record fence uses <comment-id>, <fix|fp|already-fixed> and <category>, each with an enum or numeric grammar; <greptile-dir> becomes <bot-dir>. review/greptile-triage.md also lists every REVIEW_BOTS skip reason. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: review-bot triage covers every configured bot (#3122) README, CONTRIBUTING, the contributor reference and standalone-binaries name gstack-review-bots and the review-bot fence; TODOS drops the #3122 item; the changelog fragment covers record-by-id and lookup errors. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: the PR-type test also pins the compose, Terraform and README/CHANGELOG phrases Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: follow the review-bot rename in the remote free tier's three reds The cross-skill consistency judge describes review-bot-history.md and its record helper; the tracker-guard exemption for the removed raw Greptile fetch in review/greptile-triage.md is dropped (the fetch now lives in gstack-review-bots, whose bodies still reach context only through gstack-issue-guard); the Step 10 PR-check test expects 'Review bots: not run'. HARNESS_VERSION stays 6: skill-fixture.ts and llm-judge.ts are recorded non-behavioral for every other case. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: keep the cross-skill judge's rubric to one renamed bullet Name review-bot-history.md beside the older greptile-history.md so the calibration corpus still matches, and keep the REMOTE_SLUG bullet. One live 3-sample panel at this tree: consistent 3/3, scores 4/4/4. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * changelog: H1 fragment passes prose lint Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * changelog: name the H1 fragment after its branch; TODOS drops #356 Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: /review's PR type step in docs/skills.md (#356) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * ship-policy: review_exclude_paths key and loadPolicyAt (GST-508) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * review: apply the base policy's review_exclude_paths and the reviewer diff ceiling (GST-508) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * receipt: bind review_exclude in write, carry and the queue (GST-508) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs, tests and changelog for review_exclude_paths (GST-508) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pre-landing review fixes (binary by bytes, links kept, untracked files out of the selection) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: keep binary files at code paths under review_exclude_paths Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: name the reasons that keep a binary file under review_exclude_paths Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: keep every review_exclude_paths candidate when the gate scan is unknown; docs example names its version Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: keep the NUL fixture clear of the sync-spawn tripwire Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * changelog: split the ceiling sentence Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: keep a live binary file whose bytes cannot be read; tests render outside voices from a clean checkout and read the ship fixture text Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(review-exclude): explicit +x for the census script; skip the chmod-000 read case on Windows and root Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * refactor: move loadPolicyAt to lib/ship-policy-at.ts so lib/ship-policy.ts stays under the 800-line module cap Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: refuse a base that contains HEAD; skip a self-pointing origin/HEAD (GST-774) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore: regenerate gstack-doctor-components.sh for BASE_CONTAINS_HEAD Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: land resumes a merged merge-only PR instead of refusing BASE_CONTAINS_HEAD (GST-774 review) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: changelog fragment for GST-774 Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: troubleshooting names diff-scope base_contains_head and the self-pointing no_base case (GST-774 docs audit) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: repin the reviewer briefs to GST-508's no-loss-tested bytes ship/sections/review-army.md, ship/sections/adversarial.md and lib/review-depth.ts now pin the bytes the GST-508 preregistered no-loss replay ran atef2760f(handoffs/GSTA-202/noloss-results.md in the GStack Minder drive): Arm V 33/41 vs R 33/41, delta 4, exact sign flip, p_NI 0.0279 (pass). The merge of main83ae96eleaves these three files byte-identical toef2760f, and an Arm P unit (3593) renders byte-identical briefs atef2760fand at the merge. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: GST-774 review round 1 — merged resume depth, aliased and detached PR checkouts, develop/trunk defaults Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * ship: Step 20 says a metrics row, keeping DRIVER.md at 12 KB after the main merge Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: ship-parts resend case leaves room for the first phase-3 part after the Step 9 targeted-record sentence Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: /ship delivers review/checklist.md in two parts so checklist v1 fits the 24576-byte part cap (#3670) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * changelog: drop the GST-407 and GST-430 fragments, already released on main (stacked history) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * wave review-53cfb5bfe011: lib/ship-policy.ts back to 800 lines (module-size ratchet after merging #3744 and #3795) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * wave review-53cfb5bfe011: /ship pass 1 fixes - lib/ship-receipt-depth.ts: a merged resume (land after a true merge) no longer recomputes the review_exclude digest over the emptied range, so it lands instead of RECEIPT_INVALID (failing-first land test with review_exclude_paths active). - bin/gstack-review-dispatch render: a standalone targeted render under an active review_exclude_paths refuses and points at the pass manifest voices stage, instead of writing a brief with no selection that leaves verify dirty (failing-first test). - test/default-base.test.ts: #3795 expectations follow main R51 depth (light for an 80-line code diff; runtime_lines=80 still proves the real base). - test/review-dispatch.test.ts: the GST-897 fingerprint test renders from the clean checkout, like its neighbor, because CI rebuilds the tracked lib/diagram-render/dist/diagram-render.html and the reviewer diff ceiling refused the ROOT render. * wave review-53cfb5bfe011: /ship pass 2 fixes - lib/review-exclude.ts: binaryContent reads each blob's first 8,000 bytes (git cat-file blob, 8 KB buffer) instead of one --batch read capped at 256 MB, so head mode (receipt, carry, queue) and the plan agree past 256 MB of blobs; an unreadable blob is kept, as in live mode. Failing-first test with two 270 MB blobs. - lib/review-bots.ts: a file-level bot review comment (subject_type file, no line position) is triaged instead of read as outdated. Failing-first test. - test/ship-queue-land.test.ts: the merged-resume exclusion test runs its gate scan under the fixture state root, not the real home (CI free-suite 6 home containment). * fix: merge follow-ups for main3d99c22: origin/* stays qualified in diff-scope's twin step, retro skeleton back under its cap, combined no-loss pin GST-508+R51+GST-911 * fix(review): receipt write gate-scans whenever the base declares review_exclude_paths; collectLive never re-qualifies origin/<base> (#3852 /ship red-team#1, outside-voice#1) * test: the docs-only receipt digest test keeps its gate scan out of the real home (free-suite shard 5 FREE_SUBSHARD_REJECTED) * fix(mergefc292e22f): tentative REVIEW_EXCLUDE_KEY_VERSION, classify targeted.json, render main's new outside-voice test from a clean checkout - lib/ship-policy.ts: REVIEW_EXCLUDE_KEY_VERSION 1.93.0.0 -> 1.94.0.0, the next-minor tentative form stamp_paths moves (docs/ship-policy.md, #3512); 1.93.0.0 is a released heading that never names the key. - test/gstack-clean-producers.test.ts (new from main): KEPT gains targeted.json, the wave's --review-targeted input. - test/review-dispatch.test.ts: main's wrong-tree test rendered the outside voice from ROOT; a CI build that dirties ROOT pushes that diff past the wave's REVIEW_DIFF_MAX_BYTES ceiling, so render refused and verify saw no brief (exit 3). It now runs in cleanCheckout like the wave's other outside-voice tests. * test(merge68410828f): GST-967's brief-body pin carries the wave's GST-508 placeholders The merged sectionTexts differ from main's by exactly the wave's GST-508 exclude-pathspec lines (merged minus main equals wave minusfc292e22f); the 1,440-case plan grid still hashes to main's MAIN_PLANS. Checks: GStack Minder drive handoffs/GSTA-202/merge-6841-checks.md. * ship: CI carries mapped lanes, --reuse tree cache, early push, finish/accept split (GSTA-258 A) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * ship: name the ship and pregate bins in prose so skill-bin-drift does not read a bin Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * size-ledger: re-record ship after the tests.md wording fix Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * pregate: a carried run clears the remote gate through remote_gate_runs, so a paid-only remainder is not a dead end Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * merge cleanup: drop wave fragments main already consumed and a duplicated KEPT entry Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(ship): record-ci verifies the CI run itself; finish's deferred preflight keeps pregate.json /ship pass 1 findings red-team#1 and specialist-batched#1 (GSTA-258 piece A). Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * feat(pregate): CI carry is gstack's default remote gate route; the VM is opt-in (plan choice F1) The uncleared remote_gate obligation names the CI route first when the base declares remote_gate_runs. docs/pregate.md says how gstack clears it, including the pull_request merge-ref tree mismatch. GSTA-258 piece A. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pre-landing review fixes (round 2) record-ci records the base policy that mapped the lane; the CI-first remote gate hint respects --no-ci-carry and reruns the whole pregate; changelog and docs say what the tool does. GSTA-258 piece A. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: CI carry in CONTRIBUTING, recommended-harness and the pregate usage line (ship docs audit) 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>
31 KiB
The recommended harness: gstack + Capy, from zero to the first merged train
This is the operating model a team adopts when it runs gstack under a Capy Captain: one person, one Captain thread that directs a crew, crew threads that each own a branch and stop at an unstamped READY, one controller that merges through trains, a daily flaky pass, a twice-weekly release, and a scorecard that says whether the factory is getting better. It was written from two crews that ran it for a week on their own repositories (about 100 merges a week each) and from the recurring problems four Captains reported while doing so. Every path and check name below is configurable; nothing here depends on gstack's own repository.
Two layers, kept apart on purpose:
- Harness-generic (works in Claude Code, Codex and any host that reads the
SKILL.md files):
/ship,gstack-ship-queue,gstack-restamp,gstack-pregate, the ship receipt, the train protocol, the flaky-rerun rule,release-cut,gstack-factory-scorecard,/retro. - Capy-specific: the user-scope skill folder and its install check, the
Captain and its Drive, crew threads, automations,
tools.pr.create, the approvals relay. Each one is marked Capy below; the section at the end lists what is only Capy.
flowchart LR
P[Person] -->|plain-language asks and one-word answers| C[Captain thread]
C -->|briefs, slot calls| L1[Crew thread: branch A]
C --> L2[Crew thread: branch B]
C --> L3[Crew thread: branch C]
L1 -->|/ship, READY block with SHIP_RECEIPT| C
L2 --> C
L3 --> C
C -->|train build: one gate for up to 5 PRs| G[(Gate workflow)]
G -->|GREEN or RED with flake records| C
C -->|slot: stamp, verify, merge, verify| M[(main)]
M -->|Mon + Thu, 72 h floor| R[Stable release: tag, notes, stable]
G -.->|flake-records artifacts| F[Daily flaky pass]
M -.->|free-suite retries| F
M -.->|/retro Step 7.5| S[Factory scorecard]
Day 0: install
1. The user-scope gstack skill (Capy)
Copy capy/skill/gstack/ from a gstack checkout into the person's user-scope
Drive as skills/gstack/ (SKILL.md, captain.md, scripts/install.sh).
Add to the user-scope AGENTS.md:
At the start of each session on a Capy cloud machine, load the user-scoped
gstackskill and perform its installation check before starting work. If the check fails, follow the skill's session-start steps. Treat a session with nogstack: okline from this machine as having no gstack: run no gstack workflow, claim none works, and report any blocker. Repeat this check on each separate task machine.
What that buys you: every thread runs bash scripts/install.sh --check [--for <skill>] first and reports exactly one trailer, gstack: ok <revision> for=<skill> or gstack: fail <ids>. The install is about 30 seconds cold
and prints the GSTACK_LAUNCH: assignments every later command needs
(docs/capy-install.md). A thread that runs /ship installs
with --for ship; a planning-only verdict (SKIP pins (planning-only)) does
not admit /ship, /review or /qa.
Two things the first week taught every crew:
- Most threads never install. The rule installs right before the first
workflow, so a thread that does not plan one never gets
/reviewor/investigateeither. If most threads in a project run a workflow, bake gstack into the project's Capy Setup snapshot instead (capy-install.md#bake-into-a-capy-snapshot): each session then pays only the check. - One revision per thread. Subagents on fresh machines install separately and can land one patch apart. Put the minimum revision your crew needs in the crew rules and have every first report quote the trailer, so a lane that is behind says so.
2. The repository
The harness reads four files from the repository. A team can adopt them one at a time; each is independent.
| File | Why | Without it |
|---|---|---|
.gstack/pregate.json |
Maps changed paths to test lanes, so /ship runs the tests that cover the diff and the remote gate selects correctly (docs/pregate.md) |
/ship warns on every touched file with no test lane (PREGATE_NO_POLICY) and prints a starter policy; the selected tests still run. A policy that sets only timeout_s or other keys without dependencies, empty_lanes, selection or local behaves the same. Write it once, from one owner, before the crew starts. |
.gstack/ship-policy.json |
queue_mode, the train block (gate and free workflow names, the full-gate check, the train size and fill wait), the stamp files (docs/ship-policy.md) |
Per-PR queue; train … refuses with QUEUE_TRAIN_OFF. |
CHANGELOG.md + VERSION |
A stamp per merge and a twice-weekly release with notes (docs/releases.md) | Skip the version and changelog steps; the train still works on the tree receipt. |
AGENTS.md validation discipline |
Diagnose before fixing, focused checks during repair, one full suite at the end on a frozen tree | Threads retry blindly and report passes from older revisions. |
A .github/workflows gate that the train can dispatch is the one hard
requirement for trains: a workflow that accepts workflow_dispatch with the
train nonce and publishes a check (full_gate_check) and a record artifact on
the train head. gstack's own evals.yml is the reference; a repository with a
plain test workflow sets gate_workflow to it and gets a train without paid
cases.
3. The Captain and its Drive (Capy)
Create a Captain from the Capy app (New Captain) or with
tools.captain.create. Its Drive is mounted read-write on every crew thread;
this layout is what the crews converged on:
captain-<name>/
AGENTS.md the Captain's standing rules: mandate, severity bar, standing approvals, every decision quoted verbatim with its date
CREW.md the crew rules every thread reads at start (install check, validation discipline, READY shape, stamp-on-call, flaky rule, approvals)
DIGEST.md the state of the crew, "Needs you" first; rewritten, never appended
NOTES.md the Captain's running log
skills/ procedures the crew runs: merge-train/, captain-approvals/, and any repo-specific runbook
reports/<code>.md one per thread: Outcome, PR (with head SHAs), Needs, Cost, Lesson, Approvals
handoffs/ datasets and files the next thread needs (the flaky ledger lives here)
plans/ plans brought for approval
The phase-1 templates for these files (AGENTS.md, CREW.md, DIGEST.md, a
report, a READY block, an approvals table, a slot call) ship under
captain/templates/ with the /captain skill, and docs/captain.md
explains how the Captain runs a day. Drop the templates in, replace the
placeholders (repository, crew login list, gate workflow name, minimum
revision, who may approve what), and the crew has its rules before its first
thread starts. The approvals shape itself (plan asks with before → after, why,
cause, big decisions, and the choices as lettered ask lines closed by "do it"; a ## Approvals table in every
report and PR body) is the harness-generic part and is documented in
capy/skill/gstack/captain.md.
The first train
A lane (one crew thread, one branch) does this, in order. Every command is harness-generic; only the thread and the PR tool are Capy.
- Branch from the default branch, build, test with focused checks. Write
the CHANGELOG entry under
## [Unreleased](underchangelog_fragments, a fragment in the policy's fragment directory instead); leave VERSION at main's value. Never pre-pick a version. - Push the final head. Everything below binds to the pushed head; a later push invalidates the receipt, the pregate record and the review binding.
- Open the PR with the host's PR tool (Capy:
tools.pr.create, nevergh pr create, so CI and review callbacks reach the thread), base the default branch. - Run
/shipunattended with theGSTACK_LAUNCHassignments exported: it runs the pregate (on a machine under 8 cores the test tier clears from a verified CI run at the pushed tree when the base declaresremote_gate_runs(CI carry); otherwise it needsgstack-pregate --stage tests --remote, about $0.06 on a Ubicloud VM or free locally for small selections), both reviews, the gate-integrity list, the docs audit, and writes the ship receipt into the PR body (gstack-ship-receipt write --pr <n>). Withqueue_mode: stamp-at-merge,/shipdoes not bump the version./shipnever merges and never merges main into the branch. - READY.
gstack-ship-queue ready --pr <n> --no-screen(after running the skill's own paid cases focused) or--screen(one paid screen on the merge SHA). Send the printed block as is:READY #<n> <head> STAMP_VERSION=unstamped, CI at that head,PREGATE: pass, the screen line andSHIP_RECEIPT: pr=<n> head=<head>. A block withSHIP_RECEIPT: noneis not READY. From now on the head is frozen: a push drops the PR from any train (QUEUE_TRAIN_MOVED) and the lane must READY again. - Post the READY block as a PR comment too. The Captain reads it in the thread; the scorecard (below) measures READY → merge from the first PR comment that carries it. A crew that only reports in-thread has no READY-to-merge number.
The controller (the Captain, or a thread it designates) does this; the
Captain's skills/merge-train/ runbook holds the crew's copy and
ship-policy.md#run-a-merge-train is the
reference:
gstack-ship-queue train build --prs 41@<sha>,42@<sha> --dry-run, then without--dry-run. Up to 5 READY pins, screened first; with fewer than 5 it waits until the oldest READY isfill_hoursold,--urgentfor one PR. Refusals happen before any spend: a moved head, a missing receipt, a fork head, a real conflict (the line names the PR and files; rebuild without it and send it back to its lane to merge main).train verdict: GREEN, possibly withFLAKY N(a case failed once and passed its one in-run rerun; that is green, and the flake record goes to the daily pass), RED (cases that failed twice: bisect on them, drop the attributed PR, or rebuild once after an all-green bisect, never more), or INCOMPLETE (one re-dispatch for an INFRA-only census).- Per slot, in order: call the lane ("#41 slot , base ,
order <…>"). The lane merges main if needed (CHANGELOG by section), runs
gstack-restamp --next --base main --pr 41 --expect-base <sha> --expect-order <…> --allow-repo-commands, commits only the stamp files, pushes, waits for required CI, and reportsREADY #41 <stamped sha> STAMP_VERSION=<X>. The controller runsgstack-ship-queue verify --pr 41 --train <nonce>, merges with the exact head (gh pr merge 41 --squash --match-head-commit <sha>, or--mergewhere the repository keeps merge commits), and verifies again. A stamp commit that holds anything beyond the stamp files failsverify --train, freezes the queue and costs a backstop gate. Underchangelog_fragmentsthere is no slot call and no lane stamp: the controller runsgstack-ship-queue train land --train <nonce> --pr 41per slot, which pushes the stamped prefix the gate covered, waits for checks, merges and confirms main (ship-policy.md#train-land). train prunewhen every slot has merged.
Who merges. One controller, never the lane. Both crews that ran this learned it the same way: a PR merged off a green list state whose gate never passed, and a stamp commit with one extra line that froze the queue.
The daily flaky pass
The rule (set once by the owner and quoted in the crew rules): a case that fails on its own verdict is rerun once at the same tree, inside the same run. A pass is green with a flake record; a second failure is a real red; harness, INFRA and INCOMPLETE reds are never flakes. Nothing is retried twice, and no threshold, budget or trial count moves to manufacture a pass.
The pass reads two sources, the same two the scorecard counts:
- Paid: the gate workflow's
flake-records-a<attempt>artifact (flake-records.jsonl, one record per flaky case: case, kind, PR, head, run, both attempts, symptom, rerun cost). - Free: the free workflow's
flake-ledger-*artifacts (oneflaky-passline per file that failed and passed on the serial retry).
It appends each event to a ledger the crew can read (the crews keep it at
handoffs/flaky-ledger.md in the Captain's Drive: date, case, tier, PR and
head, both runs, a one-line symptom), ranks repeats first, and opens one fix
thread per day with the ledger's Open section as its brief. The fix thread
moves entries to Fixed (with the PR) or Won't fix (with the reason), never
deletes them. bun run test:health is the free weekly check on the free lane
(a file that flakes in more than 5% of main runs over at least 20 runs fails
it).
As a Capy automation (Capy): one automation, one schedule trigger a day in
the owner's timezone, prompt along the lines of "read the newest gate runs'
flake-records-a* artifacts and the free runs' flake-ledger-* artifacts
since the last pass, append new events to handoffs/flaky-ledger.md, rank
repeats first, start one fix thread with the Open section as its brief, and
report the count to the Captain". Pass thread: "this" to keep the history in
one place, and run it once by hand before enabling it.
The stable release
.github/workflows/weekly-release.yml ("Stable release") runs
scripts/release-cut.ts every Monday and Thursday at 14:00 UTC, and never moves
stable more than once in 72 hours except for a verified security or regression
fix (--security or --regression, with --ref): it takes the newest green commit on main's first-parent history
(its required check succeeded, the backstop check succeeded, every check run
completed and none failed, no active freeze), tags it v<VERSION>, publishes a
GitHub Release whose notes are one line per CHANGELOG version since the
previous release, and fast-forwards stable. Team-mode installs follow
stable; gstack-config set update_channel main follows every merge. A
hotfix is --ref <sha on main>, held by the same floor. Details, the floor's
refusal codes and the "nothing green in the window" failure mode:
docs/releases.md. A repository adopting this
copies the workflow, sets its required check and backstop check names
(REQUIRED_CHECK, BACKSTOP_CHECK), and keeps the CHANGELOG headline
convention (docs/CHANGELOG_STYLE.md).
Watch for release starvation: when trains merge faster than the backstop settles, most main commits are superseded before they are gated and the week can end with no candidate. The fix is in tooling (accept a commit a green train record covers), not in holding slots by hand.
The factory scorecard
bin/gstack-factory-scorecard reads one window (default 7 days) from GitHub
through gh and prints a Markdown table plus a CSV; /retro runs it as
Step 7.5 when gh is authenticated. Read-only, no paid work; it downloads the
small verdict and flake artifacts, so a busy repository takes a few minutes
(--max-artifact-runs caps that). Every line prints its counting rule, and a
line whose input could not be read says unavailable (<reason>) rather than 0.
gstack-factory-scorecard --repo owner/name --since-days 7 \
[--crew login,login] [--gate-workflow evals.yml] [--free-workflow free-tests.yml] \
[--train-prefix train/] [--ready-marker 'READY #\d+|SHIP_RECEIPT:'] [--pass-marker '"passes": (\d+)|passes=(\d+)'] \
[--pass-cap 2] [--out-dir .context/factory-scorecard] [--now <iso>]
--repo defaults to the origin remote's owner/name on github.com (or
GH_HOST) and is required without one. Each run writes this window's scorecard.md and scorecard.csv
to --out-dir and appends the same rows to trend.csv there, so a weekly
retro accumulates its own trend without renaming files.
The window is [now - since-days, now): --now (default: the current time)
sets its end, and every line counts only items inside it, so a past week
measured later does not count anything that happened after that week.
| Line | How it is counted | What one of the best software factories would show |
|---|---|---|
| Merged PRs | merged_at in the window, base = default branch; crew by --crew logins, else author association OWNER/MEMBER/COLLABORATOR |
several merges a day, every day; outside contributions land in the same week |
| Lead time, created → merged | PR created_at to merged_at, median and p90 |
median under 24h, p90 under 3 days (DORA "elite" change lead time is under a day) |
| Lead time, READY → merged | first PR comment matching --ready-marker at or before merged_at; PRs with no such comment are left out and counted |
under 2h; every merged PR carries a READY comment |
| Review passes per PR | the largest count --pass-marker captures in the PR body (the ship receipt's "passes": N once /ship writes it) or a PR comment (passes=N, REVIEW_PASSES: N), median and p90, plus how many PRs reached --pass-cap (2, the review policy's cap of two rounds, a fix-check included: pass 1 reviews the whole diff, pass 2 only the range since, and a pass that finds nothing blocking is the exit) |
median 1 pass; few at the cap |
| Gate runs green / red / cancelled | completed runs of the gate workflow (workflow_dispatch by default; --gate-event any); cost from the run's report-verdict-a* artifact (collector-outcomes.json .verdict.costUsd), else the **$N** rule/judge cost or cost=$N line of the PR's verdict comment |
green on 95%+ of runs, cancelled near 0, cost known for every run |
| Known gate cost per merged PR | known cost / merged PRs; unknown-cost runs make it a floor | known, stable or falling week over week |
| Red-train rate | runs whose head branch starts with --train-prefix, cancelled excluded; red = conclusion other than success; red backstops and screens shown beside it |
under 5% (each red train is a bisect plus a rebuild, about two gates) |
| Flaky events | flake-records-a* records of the newest gate runs (count, known rerun spend) and flake-ledger-* entries of free runs on the default branch; a run whose artifacts cannot be read is counted and named, never scored 0, and a main push that reused its pull request's run (no artifacts, reuse job green) is left out and named |
0 repeat cases; under 1 flaky case per 10 gates; free retries under 1% of runs |
| Issues opened / closed | GitHub search, created and closed in the window | closed ≥ opened; nothing severe older than a week |
| Releases cut | releases published in the window, drafts excluded | at least one tagged release a week, on schedule |
The third column is a bar, not a boast: it is what DORA's elite band and the best-run open source projects show, and it is in the table so a reader sees the gap next to the number.
Where gstack's own numbers stand
Measured on gstack's repository with this tool for the week of 2026-10-19
through 2026-10-25, UTC (--now 2026-10-26T00:00:00Z --since-days 7; the first
full week of trains and /ship-before-READY; 83 merged PRs, 54 gate runs,
artifacts read for all 54):
| Line | gstack, that week | Against the bar |
|---|---|---|
| Merged PRs | 83 (crew 80, outside 3) | volume is there; the 3 outside PRs are bot PRs (2 from github-actions, 1 from dependabot), and outside contributions are ported by the crew rather than merged from forks |
| Lead time, created to merged | median 6.5h, p90 20.0h | inside the bar on both |
| Lead time, READY to merged | no number: 0 of 83 PRs had a READY comment | the crew reports READY in-thread; step 6 above (post it on the PR) is what makes this measurable |
| Review passes per PR | no number: 0 of 83 PRs carried a pass count | the receipt's passes field lands with /ship's convergence rule; until then crews record the count in the READY block, and the marker reads it from a PR comment |
| Gate runs | 52 green / 2 red / 0 cancelled; known cost $1,361.94 on 52 of 54 runs | green rate 96%; cost unknown only on the 2 red runs (the red train and the red screen) |
| Known gate cost per merged PR | $16.41 (a floor) | trains averaged $32.48 each for up to 5 PRs (21 trains with a known cost), screens $21.18 each (29 with a known cost) |
| Red-train rate | 5% (1 red of 22 trains; 0 red backstop, 1 red screen) | the tool rounds 1 of 22 (4.5%) to 5%, so this is just under the 5% bar; the 1 red screen is counted separately from trains |
| Flaky events | 17 paid flake records across 11 cases, each passing its one rerun (rerun spend $7.20); 17 free-suite retries in 17 of 83 runs | above the bar on all three: 0.31 flake records per gate against 0.1, 4 cases that flaked more than once against 0, and 20% of free runs retried against 1%. One case (ship-docsync-current) did it four times in three days |
| Issues opened / closed | 28 / 51 | inside the bar |
| Releases cut | 0 | none inside the week: the first weekly release, v1.91.242.0, was published at 12:01 UTC on 2026-10-26, just after the window closed (see the starvation caveat above) |
Read the gap honestly: lead time and throughput are where they should be, the
gate is green and its cost is mostly known, and flakiness is the line that is
not. The daily pass exists for exactly that line; the scorecard's job is to
show next week whether it moved. The inventory lane's per-PR baseline CSV in
the Captain's Drive holds the longer history behind these numbers; from here on
trend.csv in --out-dir carries the weeks.
What is Capy-specific and what is not
| Concern | Harness-generic (any host) | Capy-specific |
|---|---|---|
| Install and health | ./setup, gstack-doctor, gstack-upgrade |
the user-scope skill folder, scripts/install.sh --check and its trailer, the project Setup snapshot |
| Running a workflow | the SKILL.md files, GSTACK_SESSION_KIND=unattended, GSTACK_RESULT: lines |
the GSTACK_LAUNCH assignments, GSTACK_CAPY=1 and the capy-asks block, no enforced hooks (safety skills are advisory) |
| Approvals | gstack-approvals, gstack-owner-brief render --captain, the ## Approvals table |
the Captain relay (needs_you), the Captain drive approvals/ mirror |
| Pull requests | /ship, the ship receipt, gstack-pr-shots |
tools.pr.create (callbacks), tools.pr.uploadAsset (before/after images) |
| Merging | gstack-ship-queue (ready, train build, verdict, verify; train land and land under changelog_fragments), gstack-restamp, gh pr merge --match-head-commit |
the controller is a Captain thread; slot calls are thread messages |
| Flaky pass | the rerun rule, flake-records-a* and flake-ledger-* artifacts, test:health |
the daily automation and the ledger in the Captain's Drive |
| Release | release-cut, weekly-release.yml, the stable channel |
nothing |
| Scorecard | gstack-factory-scorecard, /retro Step 7.5 |
nothing (the CSV can be kept in the Captain's Drive) |
| Memory | reports/<code>.md, DIGEST.md, the flaky ledger |
the Drive scopes they live in |
Options where crews differ
Two crews ran this a week on different repositories and kept different habits on a few points. Ship the one that fits the repository; both are supported.
| Point | Option A | Option B |
|---|---|---|
| Merge method | squash with --match-head-commit (one commit per PR; CHANGELOG merges by section) |
merge commit with --match-head-commit (keeps the lane's commits; history: merge in the policy) |
| Paid evidence per PR | one train gate covers up to 5 PRs; lanes choose --screen or focused own cases |
a full gate per PR at handoff, plus "gate-ahead": PR N+1 gates on (its branch + N's head) so N's merge needs no second gate |
| Reusing a gate | tree rule: a head whose diff from the gated tree touches only stamp files reuses the gate (gstack-tree-receipt) |
the same rule, checked by the controller with git diff <gated> <head> before every merge |
| Severe fixes racing for a slot | the next train, --urgent for one PR |
call the slot for all; the first complete READY wins and the rest restamp to the next patch |
| Cheap CI on PR pushes | free tests on every push; paid work only in trains and screens | a cheap tier on pushes, a full-ci label that dispatches the full suites on the exact head; the cheap tier is never merge evidence |
| Infra flakes | never rerun a harness/INFRA red; one re-dispatch for an INFRA-only census | one named infra flake (for example a known runner wedge) may be rerun once and must be stated in the READY; a second wedge escalates |
| Small fixes | one PR each, batched by the train | batched into wave PRs, since serial slots cost about 30 minutes each |
| After the merge | the main backstop gates any push a train did not cover, and freezes the queue on red | the controller watches main's test, e2e and release runs until the release publishes; a red main freezes the queue until root-caused |
| Severe triage | the Captain watches issues and PRs and starts a thread per unowned severe item | a scheduled severe sweep (every 4 to 8 hours) with a fixed severity bar, dedupe against owned lanes, one hotfix thread per batch |
| Contributor PRs | ported onto a crew branch with the author's commits kept and Co-authored-by; the fork PR is closed with a thank-you that links the port |
landed through a wave PR with "Contributed by @handle", Co-Authored-By and Supersedes lines |
| Privacy | the pregate's sealed-output and secret scans before every push | plus a per-user denylist grep (names, hostnames, identifying counts from private sources) before any public artifact |
Known Capy limits
Collected from the issue files five Captains wrote on 2026-10-26 after a week of running this harness on five repositories. Each is a limit of the platform or of the harness on it as of that date, with the workaround the crews use.
Install and versions
- Every machine, subagents included, needs its own install and trailer; a fresh machine spends 30 seconds to a few minutes on it, and crews with nine shard subagents per run pay it nine times. Workaround: bake gstack into the project Setup snapshot.
- Threads that plan no workflow never install, so
/reviewand/investigatego unused. Workaround: a crew rule that names the workflow each thread will run, or the snapshot. - gstack versions drift between machines in one thread (
behind=1on one, current on another); every minimum bump was a crew-wide broadcast (eight in ten days for one crew). Workaround: the minimum revision in the crew rules, quoted in every first report. - A reviewer installed
--for review-voiceis refused by every workflow and must reinstall--for <skill>when later asked to run one.
Machines and long work
- Machine sleep and restarts kill detached servers, local drivers and long
verify loops;
/tmpdoes not survive a wake. Workaround: watched background operations (they keep the machine awake), run directories under~/.capy/work, long drivers on a remote VM, one worktree per counted run. - Commits on a shared tree sweep up another subagent's uncommitted hunks. Workaround: one committer per tree.
- Capy's git wrapper forces the linked identity, so tests that set
GIT_AUTHOR_NAMEand assert on it fail on every Capy machine and pass in CI (gstack's owntest/gstack-retro-metrics.test.tsis one). Workaround: read those failures as environment, and run the free suite on a remote VM for acceptance. - A broken shell line can print the environment, keys included, into a tool
log. Workaround: never run
env,printenv,setorexportbare; check a secret as set or unset only (gstack's launch-env ratchet enforces this in its own files).
Pull requests and review
tools.pr.createwithoutindexorheadpublishes the primary checkout's branch, not a worktree's, and targets the configured base, which on a project that works off a non-default branch must be passed every time.- Capy's per-push automatic review plus
/ship's two review passes double the review time, and a review loop can run five or more cycles on a P0 as each push surfaces one new finding. Workaround: run the review voices before the first push; on a P0 fix only findings in the P0 path and defer the rest to a follow-up. - A clean review is not carried forward to a tree-identical merge head, which can sit "running" for ten minutes or more.
- PR watches and CI callbacks fire only for repositories in a Capy project; a thread waiting on a repository outside one sleeps until a timer wakes it.
- No per-subagent cost is visible, so
/shipand plan reports cannot state what the host subagents spent ("six host subagents, spend unknown"). - A merge rule keyed on "ship receipt on the head" has no machine-checkable
form on the platform side: the controller greps the PR body.
gstack-ship-queue readyandtrain buildcheck it on the harness side.
Approvals
- An autoplan gate inside a crew thread costs a Captain round trip even when
the owner's standing answer is "take every recommendation"; record the
standing answer once with
gstack-approvalsand cite it. Rows mirror to the Captain drive'sapprovals/directory when the Captain created one, so other crew threads can read them; without it they stay in the private project-user Drive and the Captain carries the text into PR bodies.
Harness gaps the same files name (fixed or owned in gstack, listed so the
reader knows the date): the pregate failing every touched file in a
repository without .gstack/pregate.json; /ship printing no
GSTACK_RESULT: skill=ship line in unattended mode; the receipt going stale
on a late push (now refused by ready); the receipt-write step truncating a
PR body; /ship's docs audit blocking on a risk the owner must accept by
hand; the pregate's secret scan refusing benign per-trial dummy tokens with no
allowlist; no cost ceiling surfaced at autoplan gates.
Where things live
| What | Path |
|---|---|
| The user-scope skill and the Captain guide | capy/skill/gstack/SKILL.md, capy/skill/gstack/captain.md, capy/skill/gstack/scripts/install.sh |
| Captain templates and the Captain's day | captain/templates/, docs/captain.md (ship with the /captain skill) |
| Install on Capy, snapshots, the launch line | docs/capy-install.md |
| Ship policy, queue and trains | docs/ship-policy.md, lib/ship-queue*.ts, bin/gstack-ship-queue, bin/gstack-restamp |
| Pregate and lanes | docs/pregate.md, bin/gstack-pregate |
| Flaky reruns and records | docs/TESTING_INTERNALS.md#flaky-reruns, scripts/lib/paid-rerun.ts, scripts/lib/ci-history.ts |
| Stable release | docs/releases.md, scripts/release-cut.ts, .github/workflows/weekly-release.yml |
| Scorecard | bin/gstack-factory-scorecard, lib/factory-scorecard.ts, retro/SKILL.md.tmpl Step 7.5, test/factory-scorecard.test.ts |