v1.97.0.0 release: fold the changelog fragments (#4090)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
Garry Tanandcapy-ai[bot] authored and GitHub committed 2026-11-06 17:53:31 +00:00
1 parent 02d3d965a6
commit b493d4e31a
58 files changed
+142 -95

No files matched your search

+139
View File
@@ -1,5 +1,144 @@
# Changelog
## [1.97.0.0] - 2026-11-06
- **Every reviewer reads the same trimmed diff, however its brief was rendered.** A brief rendered on its own with `gstack-review-dispatch render` (the Red Team rendered before the specialists finished, or rendered again after a failed dispatch) now gets the base policy's `review_exclude_paths` selection, the readers policy's `forbid_paths`, the kept files, the secret-line excerpts and the disclosure line, the same as the pass's readers. Before, it built its diff from its own flags alone, so harness-evals #269's Red Team refused at the unexcluded 30.3 MB while the other readers read 2 MB. The plan, the pass manifest and the standalone render now share one selection helper (`lib/review-selection.ts`), and the selection is read from the checkout root, so an outside voice rendered from a subdirectory no longer drops the changes outside that directory. (GST-1476)
### Fixed
- **`gstack-brain-sync` no longer reports a rejected push as an auth error when the remote's URL or path happens to contain `auth`, `permission`, `401`, `403` or `forbidden`.** It searched git's whole error text, including the `To <url>` line and the quoted URL, so a remote such as `github.com/author/notes` or a path under `/tmp/x403y` skipped the fetch-and-merge retry, and the sync stayed `push_failed` until someone pushed by hand. The check now ignores the URL. A real auth refusal (for example `The requested URL returned error: 403`) is still reported as one. In CI, the free shard's temporary folder name turned eight brain-sync tests red at once (Free Tests 41847262176).
- **The browse daemon picks its port after Chromium has launched, right before it binds.** It used to check a random port first and bind it only after the browser launch, which takes seconds. Under load another process could take the port in that time, and the daemon failed with "Failed to start server. Is port … in use?" (Free Tests 41951644962).
- **`test/gate-reuse.test.ts`**: the fixture's bare origin turns off auto-maintenance, as the pregate fixture already does. Each push started a detached `git maintenance run --auto` that could remove an object folder while a later push was writing into it (Free Tests 41980850843).
- **Paid evals**: the ship doc-sync interface now says how to create the private folder it allows (one `mkdir` as its own call), after two `ship-docsync-store` runs each joined a no-op mkdir stand-in to a read line (41850017482, 41987975678). The coverage-audit prompt asks for the two file reads after the skill files, so the skill's dedicated-read step is seen before the files are read (`plan-eng-coverage-audit`, 41932813292). The QA-only cleanup names the terminal record, port file, daemon and daemon-log state when it can't confirm the terminal's identity (`qa-only-no-fix`, 41962523474).
- **/ship's finish stops flagging main's edits after a stacked parent squash-merges.** A child PR reviewed against its parent branch saves the parent's head as its reviewed base. Once the parent squash-merged and the child merged main, `gstack-ship-release finish` refused that head as not on main, and the old merge base it fell back to counted the parent's and main's later edits as changes since review, so #4008 paid two review exceptions and a split for files it never changed. finish now takes a landed stack parent as `--reviewed-base`: a commit the branch contains whose change a main commit holds, found at the squash even when main edited those lines afterwards (`REVIEWED_BASE: stack parent <sha> landed on origin/main at <sha>`). The route, the granted fix-check's credit, the scoped-review packet and the docs carry then compare the child's own patch, and a file the child no longer changes counts as base only when main already holds the reviewed change to it. A child edit after review, a revert to main's content, a parent main never received (including a `package.json` script the child's version bump later touched, or a logic edit to a `stamp_paths` file) and any other commit off main still stop. A post-review edit on this path never takes the one-reader bounded route; it stops for a full re-review. The receipt carry's landed-parent search finds the squash the same way. (GST-1399)
- **Receipt carry survives a base policy change that excludes none of the PR's files.** `gstack-ship-receipt carry` refused with `review_exclude changed` whenever main's `review_exclude_paths` digest moved, even when no file the PR changed fell under any pattern, so harness-evals #263 paid a full five-reader re-review of an unchanged product. Carry now also carries when neither the receipt's recorded selection nor HEAD's holds a file the PR changed (a pattern gained, added, removed or reverted), and the carried receipt records HEAD's digest so the queue binds it. A pattern change that selects a PR file, excluded or kept, and an unreadable or malformed base policy still refuse. (GST-1415)
- **On Capy, agents comment and request reviews only through Capy's PR tools, never `gh`.** docs/triage.md, CONTRIBUTING.md, docs/captain.md, docs/recommended-harness.md and /modernize now say that comments and tags go only through `tools.pr.comment` and review requests only through `tools.pr.edit`, both acting as `capy-ai[bot]`. `gh pr comment`, `gh issue comment`, `gh api` and `gstack-post` are never used for them, because `gh` on Capy runs as the maintainer's own account. An unattended /modernize run pushes, closes and posts nothing. (GST-1417)
- **A docs audit after the review no longer forces another review.** /ship's Step 14.5 docs audit runs after the review, so when it added, edited or removed a file under `review_exclude_paths`, `gstack-ship-receipt write` refused the digest the reviewers read and the run had to review again. Write now takes the audit's post snapshot (`--docs-post <audit-id>-post.json`) and its Prepare snapshot (`--docs-pre`), and binds the reviewed digest when the audit's own files are the only change to the selection. It prints `REVIEW_EXCLUDE_DOCS_AUDIT:` when it does. Docs the review read while they were still uncommitted stay as they are, and files that git converts on checkout (autocrlf, filters) compare as committed. Any other change still refuses. The ship driver's Step 19 write now passes `--review-exclude`, and a refusal says when the flag was left out. (GST-1429)
- **`ready` binds a receipt with an older review_exclude digest when the policy change touches none of the PR's files.** After a `review_exclude_paths` edit landed on main, `gstack-ship-queue ready` refused every receipt that had recorded the old digest, even when no file the PR changed fell under any pattern (harness-evals #274 and #276 after policy PR #278). The queue now uses carry's rule. When neither the receipt's recorded selection nor HEAD's holds a file the PR changed, the receipt binds. A selection that holds a PR file, excluded or kept, and an unreadable base policy still refuse. (GST-1434)
- **The review_exclude name scan no longer times out on large results repos.** The scan that keeps a file a program names by full path ran one `git grep` over the whole tree, so on harness-evals #269 it searched 30 MB of receipts for 4,354 candidate paths, ran 278 s past its 120 s limit and kept every file. It now greps only the code and test files that can name a candidate, so its cost follows the code, not the data the PR adds. A git failure or timeout still keeps every candidate. (GST-1435)
- **/ship's docs audit runs on your model preference.** Step 14.5's docs audit was told to run on Sonnet 5.5 at low effort no matter what model you prefer, so crews set to Opus in `preferences.yaml` still audited on Sonnet. `gstack-review-dispatch pass --audit-only` now gives a docs reader with no model your `agent_model` at low effort and prints the pick as `AUDIT_MODEL:`. With no preference it stays on Sonnet 5.5 at low effort, `host` uses the lane's own model, and a model named in the extras still wins. (GST-1439)
- **A policy edit that changes nothing about a PR's files no longer forces another review.** After main's `review_exclude_paths` gained patterns that match none of a PR's files, `gstack-ship-receipt write` refused the digest the reviewers read, and ready, carry, train build and land bound it only when both selections were empty (harness-evals #222: its reviewers read 7cf39a7e1150, which excludes 1,260 of its files, and write refused after #278 added six globs that match none of them). The shared check now binds when the recorded selection and HEAD's give the PR's files the same applied patterns, excluded files and kept files. Write gets only the digest, so it rebuilds the recorded selection from the policies the base held and prints `REVIEW_EXCLUDE_POLICY_MOVED:`. A selection that differs, a digest no base policy gives, an omitted `--review-exclude` and an unreadable base policy still refuse. (GST-1446)
### Fixed
- **Results PRs that capture model requests no longer stop at the review ceiling just because their captured prompts mention credentials.** A file under `review_exclude_paths` stays in review when a changed line reads a credential, and until now every such file went to each reader whole. On harness-evals #269, 75 captured requests whose system prompts name `${…_API_KEY}` variables and keychain commands added 12.9 MB, and `/ship` refused with `REVIEW_DIFF_TOO_LARGE` against the 2 MB limit. When the secret line is the only reason a file is kept, `gstack-review-dispatch pass` now gives readers an excerpt instead. The excerpt holds every changed line the secret-line rule matches or the credential scanner finds a secret-shaped value on, with up to 3 lines of context, the path and the match counts. Only the excerpt counts toward the limit. A file kept for any other reason (a program names it, it is outside the applied patterns, the gate or name scan could not run) is still shown whole, and so is any file whose excerpt cannot be built. See [secret-line excerpts](docs/ship-policy.md#secret-line-excerpts).
- **/ship's coverage and plan audits run on your model preference too.** Steps 7 and 8 were still told to run on Sonnet 5.5 at low effort after the Step 14.5 docs audit moved to your `agent_model`. A reviewer pass now gives any audit reader with no model your `agent_model` at low effort and prints the pick as `AUDIT_MODEL:`, the same way `--audit-only` does. With no preference they stay on Sonnet 5.5 at low effort, a model named in the extras still wins, and the Step 11 outside voice keeps its other-family model. (GST-1449)
### Fixed
- **`gstack-redact` no longer reports a property read in code as an internal hostname (#3987).** The `internal.hostname` rule matches any name ending in `.local`, `.prod`, `.staging`, `.internal`, `.corp` or `.lan`, so `server.local('/x')`, `if (config.prod)` and `if settings.internal:` were reported as leaked hosts. The pregate secrets stage listed each one as a warning, and /ship then asked for a confirm. In gstack's own source files, 57 of the 239 findings this rule made were property reads like these. Now, in a TypeScript (`.ts`, `.mts`, `.cts`), Dart or Python file, a name is treated as code when it sits outside every string and comment on its line and has code on both sides of it: an operator, bracket or expression keyword before it, and a bracket, operator, method call or the line end after it. A quoted host, a host in a comment, a host and port (`db.internal:5432`), a host and path (`proxy.corp/api`), prose, a host inside a TypeScript template literal or block comment, every host in a `.tsx`, `.jsx`, config, dotenv or Markdown file, and a scan without a file path still report. The check reads at most 1,000 characters back on a line and reads each line once. A 4 MB file of ordinary source scans in the same time as before; a buffer packed with these names on every line takes up to about 40% longer. Thanks to @nazarli-shabnam (#4026) for the approach.
**An opt-in /plan-ceo-review gate that holds "rank the top risks before you finish" (GST-1461).** With `./setup --ceo-review-gate` (or `GSTACK_CEO_REVIEW_GATE=on`, or `gstack-config set ceo_review_gate on`), a session or review subagent that loaded /plan-ceo-review can't end its final turn until the review's output file, changed after the skill loaded, has a `Findings` or `Top risks` heading followed by a numbered list of real risks starting at 1. The gate looks at `GSTACK_CEO_REVIEW_OUTPUT`, the files the run wrote, changed or untracked markdown in the repository (so a review written through a Bash heredoc counts) and the project's `ceo-plans` directory. A stop that asks you a question is never blocked, an unrelated subagent is never touched, and after 3 blocks the next stop goes through with an `exhausted` marker. Decisions are `GSTACK_GATE ceo-review-gate` markers (`fired`, `blocked via=stop`, `released file=…`, `exhausted`, `error`) in the session record, `hooks/ceo-review-gate.log` and the transcript; errors fail open. `gstack-skill-gates settings --ceo-review` (alone or with `--investigate`) prints a `--settings` JSON for headless runs, and the headless smoke now plays a CEO review too. Off by default; the /plan-ceo-review skill text is unchanged. The /investigate gate's rollback is also tighter: a fix committed on a branch behind the baseline is undone to that branch's own tip; a restored file gets its own bytes back (two files that differ only in line endings no longer share a backup), keeps its permissions and gets its index entry back, so a rejected edit that was `git add`ed isn't left staged and a change you had only staged survives an edit to that file being put back; `linguist-generated` exemptions come from the baseline commit, so a new `.gitattributes` can't exempt source; and `gstack-skill-gates settings` quotes hook paths, so a checkout whose path has a space still runs its hooks. Docs: docs/skill-gates.md.
**An opt-in /investigate gate that holds "no fix before a root cause", through Bash too (GST-1461).** On Opus 5.5 the skill text alone gets that step followed 0 of 46 times, and GST-938's Edit/Write-only gate held in just 13 of 28 sessions because Opus edits through the shell. With `./setup --investigate-gate` (or `GSTACK_INVESTIGATE_GATE=on`, or `gstack-config set investigate_gate on`), a session that loaded /investigate can't change tracked source until it writes a `Root cause: <what is wrong>` line: Edit, Write, MultiEdit and NotebookEdit are denied, and any Bash command that changes a tracked source file (`sed -i`, redirects, `tee`, `python`, `perl`, `node`, `git apply`, a commit) is caught by comparing the tree with a baseline, put back and reported. Tests, docs, lockfiles and new untracked files are never blocked. Every decision is a `GSTACK_GATE` marker (`fired`, `blocked`, `released`, `observed`, `skipped`, `error`) counted in `$GSTACK_STATE_ROOT/skill-gates/<session>/record.json`, traced in `hooks/investigate-gate.log` and recorded in the transcript; a hook error fails open with an `error code=…` marker. `gstack-skill-gates settings --investigate` prints a `--settings` JSON for headless `claude -p` runs and `gstack-skill-gates report --expect armed` checks a session engaged. Off by default; the /investigate skill text is unchanged. Docs: docs/skill-gates.md.
- **A finding the owner rules a disclosed note no longer keeps a granted fix-check from counting, and every ruling is listed.** On qm#3122 at 532cc5c the owner ruled two CRITICALs notes, not fixes. The runner logged them as `skipped`, `finish` counted a skipped CRITICAL as open, so the granted fix-check row (its own findings all below the floor) never read CURRENT and `finish` stopped with "behavior changed since review". The owner now records each ruling with `gstack-approvals rule --finding <fingerprint> --head <stop head> --reason <why> --words <exact words> --by <who ruled>`: an approved approval row with the ruler, the head, the reason and the time. `rule` refuses a head that is not a commit, a multi-line reason, a `--by` naming gstack and any ruling inside the checkout's open /ship run, and `add` refuses the ruling scope. `finish` reads rulings from the approval record only (a `ruled` field or `action: "ruled"` on the lane's own review-log row is ignored), treats a ruled finding as closed when the ruling's head lies inside the fix-check's range, whichever came first, and prints each as `RULED_NOTE:`; a ruling at an earlier head is listed as not applied. A skipped CRITICAL with no ruling stays open. `gstack-approvals rulings` prints the `Ruled note: …` lines for the Pre-Landing Review and the PR body; the receipt records them as `ruled_notes`, `gstack-ship-receipt write --body` refuses a body missing one, and the READY block prints each. See docs/ship-policy.md#ruled-notes. (GST-1537)
- **/ship's finish routes a declared version or rules-number retake after a base merge as mechanical, with no review round.** founderstar#563 merged its base after two full passes and retook its rules number 80 → 81: one line in `scripts/core/game.gd`, `save_version` in 8 save fixtures and one golden. finish routed all 10 paths as behavior and said to insert Steps 5–11.5, while `gstack-review-plan` at the round cap said `done`, so the only exit was a fresh /ship. A base policy can now declare the constant under `mechanical_retake` (its file and one-line template, the fixture fields that carry the value, and the `.gstack/generated.json` entries that rebuild its goldens). finish proves each changed path from the diff. It needs one literal on the constant's line, a value above the reviewed one and the base's, the same new value in every changed field, goldens that regenerate byte-identically, and nothing else changed. It prints `MECHANICAL:` per path and `mechanical=<n>` on `ROUTE:`. Any other edit in those files, a value that doesn't match or a golden that doesn't regenerate keeps its old route with a `ROUTE_MECHANICAL_REFUSED:` reason. The receipt lists the paths as `Mechanical retake: …` lines, and receipt write refuses a PR body missing one. finish's behavior STOP now names `gstack-review-plan --post-review`, which plans that Step 9 as a fix to a gate-class path: a pass on the range below the round cap, and a stop for the owner's grant of one scoped fix-check at the cap, never `done`. Prior art: #3966 (GSTA-167's `carry_mechanical`). (GST-1560)
- **A receipt carry with both a changed path and a moved-context path gets one scoped re-check instead of deadlocking.** founderstar#563 merged a base that took `SAVE_VERSION := 83` under the branch's reviewed `:= 84` and also moved a block next to a reviewed hunk. Carry called the constant file changed, J1's re-check refused because context had moved, and the scoped review opened only when nothing had changed, so every rules-number PR needed a fresh /ship. Now a hunk whose added lines are byte-identical and whose removed lines differ only by the base's edit of the lines the branch replaces counts as a context path, so carry and finish put it in the scoped-review packet for one reader and never carry it silently. When real changed paths and context paths come together, carry writes one packet: the range-diff, the changed paths' diff, then the context hunks. The existing 400-line and 64 KB caps count the whole packet, `range_diff_sha256` binds all of it, and the reader's record names `range_base`, which the carry reason logs. Past the caps, carry refuses as before. A changed path alone, or context paths alone, keep their routes with unchanged output, and a branch-side change to an added line still needs review. (GST-1561)
### Changed
- **Captain docs: a lane sleeps while /ship runs.** `docs/captain.md` (the lane's /ship sequence), `captain/templates/brief.md` and the Capy gstack skill's "Sleep while /ship runs" bullet now say that after dispatching /ship, the lane ends its turn and wakes only when /ship completes, holding no pull request watch meanwhile; that one wake relays READY, and after READY the lane handles pull request events as before. Each READY duty has a named owner so `train build` never refuses a pin with `QUEUE_NOT_READY` for a step nobody ran: /ship owns the ship receipt and the pregate record behind the `PREGATE:` line; the lane owns `gstack-ship-queue ready --screen|--no-screen` and its notes, the decision-trail audit and the `Attention` section. The tradeoff is that a red check that lands while /ship runs waits for /ship to finish instead of being fixed in parallel.
- **A /ship helper on Capy can count its own calls.** Every helper reported `CALLS: unavailable` because a subagent can't read its own transcript. On Capy, the gstack-ship-* commands and the review pass loader now add one row each to `<state root>/ship-calls.jsonl` when they exit, and `gstack-ship-budget calls --record <rec>` turns those rows into a `CALLS: source=log` line: phase programs, review-pass polls, section reads, an estimate of the keep-alive waits between them, how far past 200 s each poll rerun starts, and gaps over the 300 s cache. Counting adds no call. A run window with no rows prints `CALLS: unavailable` instead of zeros, and a background lane or `finish` no longer hides the calls made while it runs. (GST-1585)
- **A main merge after the targeted read no longer voids the record.** `gstack-ship-receipt write --review-targeted` refused with "the product changed after the read" whenever main brought in money- or secret-tagged files, though the PR's product patch was unchanged, because the classes changed since the read were computed across the merge commit (harness-evals #293, #292, #225). Write now computes those classes, and the plain-docs carry, over the PR's own change, meaning the paths whose part of the PR's diff against its merge base differs between the read and HEAD. A branch-side edit to money, secret, auth or gate code after the read, or a merge resolution that changes the PR's own hunks, still refuses. (GST-1593)
- **`/ship` accept gates under a branch's own pregate policy when it only adds test lanes, says why a carried lane's test file is missing from CI's JUnit tree, and takes an owner-granted test-lane exception.** On gstack-evals #240 a PR that added a runtime file and its own lane line passed preflight under `--policy-from head`, then `gstack-ship-release accept` reran the pregate under the base policy and refused with `PREGATE_NO_LANE`. `accept`, and `finish` when it reruns the whole pregate, now pass `--policy-from head` when the branch's `.gstack/pregate.json` differs from the copy it forked from in nothing but added test lanes and `.gstack/generated.json` is unchanged, printing `POLICY: head …`; a removed or shrunk lane, or any other changed key, keeps the base policy with `POLICY: base … (the branch's copy <what differs> …)`. The same PR then stopped on `inventory_uncovered`: bun registers an imported test file's tests under the importer, so its JUnit suite never appears. The refusal now adds `CARRY_DIAGNOSIS: bun registers <imported> under <importer>; its JUnit suite won't appear`, and the owner can record `gstack-approvals lane-exception --head <pushed head> --files <the uncovered files> --reason … --words … --by …` (refused inside the lane's open /ship run, by gstack, or through `add`). `gstack-evidence record-ci` binds it only at that head for exactly those files, after a local preflight of them with the lane's `per_file` runner passes; `accept` prints `TEST_LANE_EXCEPTION:`, the receipt's `ci_carried` entry records the grant and the preflight, and `gstack-ship-receipt write --body` refuses a body without the `Test-lane exception: …` line. See docs/pregate.md#accept-head-policy and docs/ship-policy.md#test-lane-exceptions. (GST-1595)
- **/ship's finish routes a Godot 4 UID sidecar added after review with no reader, and a base policy can declare its own test files under `test_code`.** founderstar#611 (FOUN-32) committed `tests/cutscene_check.gd.uid`, the one-line `uid://…` file Godot writes beside a script, after the owner's granted fix-check, for a test script the PR added before review. No route took it, so a random id cost another fix-check or a fresh /ship. finish now proves each `.uid` that would route as behavior. The branch must add it after review, its sibling script is one the reviewed range added or modified or the review-loop exception accepted, it is a regular non-executable file whose whole content is one canonical `uid://<id>` line exactly as Godot 4 writes it (base 34 over a–y and 0–8, at most 2^63−1, optional trailing newline), neither file matches the base's `review_exclude_paths` or `readers.forbid_paths`, and no other file in the tree declares the same UID (another `.uid`, a `[gd_scene]` or `[gd_resource]` header, or an `.import` file's `uid=` line; a scene that only references the script by it is fine). A proven sidecar prints `UID_SIDECAR:` and counts as `sidecar=<n>` on `ROUTE:`. Any other `.uid` keeps its old route with a `ROUTE_SIDECAR_REFUSED:` reason, and `.import` files are never candidates. The receipt lists each sidecar as a `Godot UID sidecar: …` line, and receipt write refuses a PR body missing one. GDScript checks (`tests/*_check.gd`) match none of the built-in test globs, so the new base-only `test_code` policy key adds globs that finish routes as test code (one scoped reader) instead of behavior. A declared glob never matches a file under `.gstack/` or `.github/`, and a head-only declaration changes nothing. (GST-1604)
- **`/ship` accept's `record-ci` bound comes from the base ship policy, and accept says when its final pregate ran the tests here.** accept stopped every `gstack-evidence record-ci` at a fixed 600 s, so on harness-evals #271, whose record-ci needed about 745 s, the lane ran record-ci and the rest of accept by hand. The new `ci_record_timeout_s` key in `.gstack/ship-policy.json` sets the bound (default 600, at least 60, clamped to 3600). accept reads it from the base branch only and prints `CI_RECORD_TIMEOUT: <n>s (…)`. The same run's final pregate found no run profile for this OS and ran 3,666 tests locally with no reason given. When the tests stage gets no CI carry, accept now prints `CI_CARRY: none (<reason>); the final pregate's tests stage ran here instead: tests <verdict> in <n>s`. A profile without `os` carries `ship_lanes_from` lanes but not the tests stage. See docs/ship-policy.md#ci-record-timeout and docs/pregate.md#accept-local-tests. (GST-1606)
- **`/ship` finish's `FIX_CHECK:` line names the grant that covered the fix-check.** When a branch held two approved `review_loop_grant` rows whose stop heads both fell inside the fix-check's `range_base..commit`, finish named the first one recorded. On gstack-evals#244 the line credited fix-check fc4 to the earlier fix-check's grant, so the trail misattributed the owner's approval. finish now names the grant whose stop head is newest in the range, which is the stop the fix-check answered. Whether the fix-check counts is unchanged. (GST-1610)
### Fixed
- **`test/ship-queue-land-direct.test.ts` and `test/ubicloud-runner-recovery.test.ts` pass in one bun process.** The land file's merged-checks case called `runMergedChecks` (lib/land-merged-checks.ts) inside the test runner. On Linux that call sets `PR_SET_CHILD_SUBREAPER` and clears `PR_SET_DUMPABLE` on its caller and never undoes them, so the runner adopted every later orphan and never reaped it. The fake VM's detached phases and the sink pusher's `sleep` stayed zombies that `tail --pid` and `kill -0` count as alive, so recovery's run cases hung at the 90 s timeout (Free Tests 42207455885, shard 10). The case now runs `runMergedChecks` in a child Bun. The fake VM fails at once with a named error when the runner is a subreaper, recovery fails on any zombie child it leaves, and a new regression runs the pair in one process. (GST-1613)
- **A /ship run that audits its docs twice keeps the review it already has.** When Step 14.5 ran again after a base merge and restamp, `gstack-ship-receipt write` reset only the last audit's files, so a file the first audit edited and committed stayed in the `review_exclude_paths` selection and write refused the reviewed digest (gbrain #8678); the only way out was HEAD's digest, which skips review of the delta. Each docs snapshot now names its /ship run (`--run`), each post snapshot records the files its audit edited (`--pre <Prepare snapshot>`), and a re-audit's post snapshot carries the earlier audit (`--carry`). Write resets every file this run's audits edited to its content before them and binds the reviewed digest. A file no audit edited, an edit after an audit, and a carried audit from another run still refuse. (GST-1616)
- **finish counts every granted fix-check in a chain, and accept keeps clears made at the same tree.** A second granted fix-check (a merge resolution, say) logs its range from the first fix-check's commit. `gstack-ship-release finish` read only the newest `review-fix-check` row and refused any range that didn't start at the reviewed tree, so the later fix-check unbound the earlier one and lanes logged a synthetic combined row (#4063). finish now chains each row to the earlier row whose commit is its `range_base`, back to the reviewed tree, and counts the chain when every link is clean and has its own owner grant (one grant counts for one link); a gap or an ungranted link still refuses. Separately, accept's final `gstack-pregate` rewrote pregate.json and dropped the `macos-named-regressions` and `windows-smoke` clears the lane had made at the same tree, so accept never exited 0. A rerun at the same head, tree, policy and base branch now keeps each earlier receipt for the same obligation and prints `PREGATE_CLEAR_KEPT:`; another head or tree, or a rerun with a failed or unfinished check, keeps nothing. (GST-1627)
- **The R56 inventory records L1's verdicts on gstack's untold routing and /investigate's told record (GST-1629).** GEVA-2's L1 run (gstack-evals#247; Opus 5.5 on the claude_code preset, 80 SWE-rebench tasks, a no-gstack arm, pins 82db89ab to 699a41cb) is now in `docs/inventory/evidence.json` and the regenerated `docs/inventory.md`: the untold routing package (route hook, contracts and the description rewrite) helps, B1 +21.3 points [+12.5, +30.0], KEEP, and needs routing or contracts on; the description rewrite alone had no effect (0 of 80); told record completeness is untested, B3 +10.0 [0, +20], FIX-METHOD because probe I failed its blind audit; pass rate O5 +5.0 at a 65% control, MDD 18.7, is descriptive only. /investigate keeps its no-effect status and CUT-CANDIDATE, which measured something else.
- **/investigate's check-after-last-edit item sees fixes written through Bash.** A Bash command that writes a file inside the session directory (a heredoc into python or cat, `sed -i`, `tee`, a `>` or `>>` redirect, `patch`, `git apply`) now counts as an edit, so a run that tests, then fixes the code through the shell and never tests again reads `missing` instead of `met`. Before, only Edit, Write, MultiEdit and NotebookEdit counted, and 5 of 7 probe-I fixes in GEVA-2's L1 run were heredoc writes. The parser (`lib/shell-writes.ts`) is conservative: reads, output to stdout, `2>&1`, and writes under `/tmp`, `~`, another directory or a variable path do not count. (GST-1633)
- **The browse audit origin and network log no longer keep URL query values or credentials, and short digit pieces leave audit error text.** The `origin` field of `.gstack/browse-audit.jsonl` stored the full page URL, so after a GET form was submitted the next line held what the user typed, such as `?user=a&password=hunter2pw`, and `browse-network.log` stored request URLs with their token query values. In those two places a URL now keeps its scheme, host, path and query parameter names; every query value is written as `[REDACTED]`, a query piece with no value (such as a bare token) is written as `[REDACTED]` whole, and any username, password or `#fragment` is dropped. The audit error scrub now also removes 2- and 3-character pieces of a redacted value that contain a digit, such as a CVV or an expiry month, matched as whole tokens; shorter word pieces like `a` or `is` stay so the error text is readable. Still recorded as typed: navigation command arguments such as a `goto` URL with its query, URL paths, and the text in `browse-console.log`. Lines written before this release still hold the old values; delete `.gstack/browse-audit.jsonl` and `.gstack/browse-network.log` to clear them. (GST-1635)
- **`mechanical_retake` and `test_code` now name 1.97.0.0 as the gstack release that introduced them.** Their key constants said 1.96.1.0, a version the next release skips: its fragments carry `bump: minor`, so `gstack-release-stamp open` stamps 1.96.0.0 -> 1.97.0.0. A policy that declares either key, or `ci_record_timeout_s`, sets `min_gstack_version` to 1.97.0.0.
- **A ship-policy floor above the newest stable gstack release is refused by name, and the doctor stops sending sessions into a fix that cannot work.** `gstack-ship-policy validate`, and the new pregate `policy-floor` check on a branch that changes `.gstack/ship-policy.json`, refuse a `min_gstack_version` above the newest stable gstack release (`POLICY_FLOOR_ABOVE_STABLE`, naming the floor and the release), because every stable-channel lane would refuse that policy and no upgrade could clear it. The release is the highest `v<version>` tag at origin's `stable` commit (a tag a half-way cut left above stable does not count), else the VERSION at the local stable ref; a vendored copy with no `.git` of its own never reads the enclosing project's tags; when none resolves, both warn (`STABLE_RELEASE_UNKNOWN`) and say the floor was not compared. The doctor's `floor` row now compares the version the checkout reads policies as (`runningGstackVersion`, as `validatePolicy` does), so an unstamped main checkout that already reads the floor's keys passes instead of warning forever, and when no stable release meets the floor it says `no release meets this floor yet` and offers no fast-forward; the trailer note becomes `floor=<f> (<file>; no release meets this floor yet)` with no `fix:`. The gstack skill now runs a floor fix once and reports a floor no release meets instead of rerunning the install before every workflow. (GST-1637)
**Important fixes reach stable within a day, and stable is never more than a week stale.** The Stable release workflow now runs every day at 14:00 UTC as a daily check (R74).
### Added
- A changelog fragment can mark its change important with `important: security|regression|data-loss|blocker` in its front matter. The daily check cuts stable once it is 24 hours old when a gated change stable lacks carries the field.
- The daily check also forces a cut when stable is 7 days old and main has gated changes. Monday and Thursday stay the regular cuts with the 72-hour floor. On a day with nothing to do it changes nothing and logs why.
- A due cut that finds no release PR reports `NO_RELEASE_PR: important change waiting, no release PR` and goes red. A week-old stable whose main has nothing newer gated reports `STABLE_STALE`.
- docs/releases.md documents the daily check, when the Captain stamps a release PR, the exit codes, and the exact commands to roll stable back.
### Security
- **Security fix: gstack's bundled browser no longer trusts the compromised eMudhra intermediates (#3338).** gstack now pins Playwright 1.65.0, which bundles Chromium 158.0.8127.18. Chrome removed emSign ECC Root CA - G3, the root over the eight eMudhra intermediate CAs whose keys were compromised, from the Chrome Root Store, but Playwright 1.64.1's Chromium 156 still carried it. gstack's default headless browser gets no CRLSet updates, so it accepted certificates those intermediates issued, and anyone holding the weak keys could impersonate a site to it. With 1.65.0, `$B goto https://dv-g3b-emudhra.we-have-a-problem.com/` fails with `ERR_CERT_AUTHORITY_INVALID` in both the headless shell and the full Chromium; on 1.64.1 it loaded the page. Run `./setup` after upgrading to download the new Chromium. `/setup-browser-cookies` drops its warning about this gap, and BROWSER.md says the gap is closed. The Windows windowsHide patch is re-cut for 1.65.0, and its backport of Playwright's TcpPortRandomizationWin fix is gone because 1.65.0 ships that fix itself. New free tests fail if the bundled Chromium is older than 158.0.8115.0 or still carries the root.
- **The free-CI shard-overrun warning now works on split shards.** `--ci-verify` compared a shard's wall time with its serial seed prediction, so at the CI default of 4 sub-shards per shard it only warned after about a 6x slowdown. The prediction is now divided by the shard's job count, and the warning says how many parallel jobs it ran across. Thanks to @indraoverflow (#4051, #3851).
- **Land closes its own environment to merged checks on Linux.** A scrubbed environment didn't stop a `land.merged_checks` command from reading land's own through `/proc/$PPID/environ` or a `/proc` scan, GitHub token included. Land now makes itself non-dumpable before the first check, and becomes the subreaper of everything the checks start and kills whatever a check leaves running — even a process that left its group with `setsid` or a double fork — before the `git` and `gh` processes it starts next could be read. When it can't set either, the checks don't run and land refuses. docs/ship-policy.md names what is still open: on macOS `ps -E` shows land's starting environment, and a check can still read anything else the user can, so pass `--allow-repo-commands` only for policies and PRs you trust. (GST-1421)
- **Narrow paid screens no longer run every gate case when a PR edits `setup`.** `setup` was listed as an input every paid case consumes, so any PR touching it (#4045, #4047) screened all 113 gate cases for about $37. No paid CI run executes `./setup`, and the touchfile map already names the cases that test it, so a narrow screen now treats it like any other file, and those two diffs select their 5 cases. `pull_request` runs, broad screens and `--profile full` keep the full gate for it. `modules/loadouts.json` is now placed through its reader, `scripts/modules.ts`, instead of restoring the full gate as an unknown file. That reader reaches every gate case, so a narrow screen leaves a loadouts-only edit to the train gate's full census. (GST-1575)
- **The Ubicloud runner no longer reads an API error as success.** A create Ubicloud refuses (a 409 name collision, any 4xx) now disarms teardown, so `up` and `run` never destroy a VM someone else holds under that name. A create goes out only after the name answers 404 to a check, so a name another VM holds, or a check with no answer, is refused before any request. A 5xx or unanswered create is looked up by name before anything is decided: a VM that landed anyway is adopted, and one nobody can confirm either way keeps its record and recovery bundle and exits `UBI_CREATE_UNKNOWN`. `down` counts a VM as gone only on a readable answer, so a failed or garbled VM list exits non-zero and keeps the bundle. `pull` exits non-zero when ssh fails, and `run` fails when one of its pulls does. `gc` exits non-zero when a stale VM could not be destroyed, a run record whose VM the API cannot check refuses with `UBI_ATTACH_UNKNOWN` instead of starting a second VM, and the API key reaches curl as a header on a pipe, out of `ps`. (GST-1441)
- **The review plan finishes in about 35 seconds on a 195,000-file results tree, and the pass says so when the plan runs out of time.** The `review_exclude_paths` name scan now writes the code and test files into a scratch index and runs one `git grep` per tree, and the binary check reads small blobs through one `git cat-file --batch` per 64 MB instead of one git call per file. The number of git calls no longer grows with the size of the tree or the PR. On harness-evals #269 the plan went from 2 min 38 s and 432 git calls to 32 s and 76 calls; on #279 it went from 206 s to 35 s, with the same output. When `gstack-review-plan` still runs past the pass's 180-second limit (`GSTACK_REVIEW_PLAN_TIMEOUT_MS` changes it), `gstack-review-dispatch pass` now prints a `REVIEW_PLAN_STOPPED` line naming the step, the reason and the limit instead of exiting with no output. (GST-1442)
- **`/runaway-stopper` no longer flags lanes that follow the warm-wait rule.** `gstack-runaway scan` counted each 240-second warm-wait check-in as an empty wake, so a compliant lane waiting on CI could cross the 25% empty-wake warning. A turn whose only call re-issues the wait on the same operation, or a 240-second timer on that operation or none, within 30 minutes of the wait that started the run is now left out of the rate, including when `/runaway-stopper` fetches the thread itself. A re-wait past 30 minutes, a wait on other operations, or a turn that also reads status still counts, and a status read does not restart the 30 minutes. (GST-1597)
- **A docs claim added after the review no longer leaves /ship stuck.** When the review loop had finished and a later commit added only docs with a money, security or numeric claim (such as a changelog line with dollar figures), `gstack-review-plan` printed `NEXT: done`, so nothing ran the targeted reader the receipt then asked for. Given `--range-base <the commit the targeted reader read>` and `--targeted-ran <its patch id>`, the plan now prints `NEXT: targeted`, a `TARGETED_DELTA:` line and one targeted reader on HEAD. It is not a review round. A code change, plain docs, a range base the reader did not read, or uncommitted edits keep `done` and print the reason. A main merge that only edits lines next to a reviewed hunk does not count as a branch change. `--force targeted` now refuses and names this route. (GST-1598)
- **A granted fix round no longer needs a second grant to refresh the targeted read.** When the owner grants a fix round past the review-loop cap, that fix changes the product patch the targeted record names, so the receipt asks for a new targeted read. The plan used to print `done` once the granted fix-check came back clean, which left the lane needing another manual grant. `gstack-review-plan` now takes the same `--grant` (checked as before) and plans `NEXT: targeted`: one targeted reader on the head for its targeted classes. Without a grant, a code change still plans `done`. The Capy pass program now runs a targeted manifest's voices stage too, so that reader actually runs there. A granted fix that edits a gate-class path still stops for the owner. (GST-1607)
- **The /investigate and /plan-ceo-review gates no longer pass a slow scan unchecked.** Their hooks were registered with a 10 second timeout while `GSTACK_SKILL_GATE_DEADLINE_MS` could set a longer gate deadline, and Claude Code kills a hook at its timeout and lets the stop through with no marker. `gstack-skill-gates settings` and `./setup` now register a timeout derived from the longest deadline plus a margin (25 seconds), and a deadline above 20 seconds is clamped to 20. A killed hook still cannot fail closed; docs/skill-gates.md#hook-timeout says why. Re-run `./setup` to update an existing registration. (GST-1615)
- **A reader's closing sentence that rates nothing CRITICAL no longer counts as a blocker.** The review collector read "None of these rises to CRITICAL." (gstack-evals#247) and "Nothing reaches the CRITICAL floor." (#4055) as CRITICAL findings, because its denial grammar knew only "is", so `blocking_found` carried a phantom that lead judgment was not allowed to lower. A rating verb (rises to, reaches, meets, qualifies as, would rise to) now denies the way "is" does: such a closing sentence collects at INFORMATIONAL, and on a line that also names a file:line it is a summary candidate that lead judgment may lower with a reason. Every other label is unchanged, checked against 296 captured reader outputs. (GST-1628)
- **The browse audit log no longer stores typed secrets.** `.gstack/browse-audit.jsonl` wrote each command's arguments verbatim, so a password passed to `fill`, text sent with `type`, a `js` expression, a `dialog-accept` answer, a `cookie`, `header` or `storage set` value, and a `skill run --arg` value all reached disk in plain text, and an error that echoed the value stored a second copy. Each of those values, including inside `tab-each` and `chain`, is now written as `[REDACTED]` before the line is truncated, with the selector, cookie or header name, storage key, arg key and `--out` path kept, and the error text is scrubbed of the same values. Navigation and click lines are unchanged. `browse-dialog.log` no longer stores the prompt answer either. Lines written before this release still hold the old values; delete `.gstack/browse-audit.jsonl` and `.gstack/browse-dialog.log` to clear them. Reported in #4057 by @derHaken. (GST-1631)
- **The GST-1631 audit redaction covers typed command values, not every secret.** The page URL in `origin` and very short values echoed in error text are not yet redacted (GST-1635).
- **The Ubicloud runner no longer destroys a VM another caller took under the same name.** With an explicit `-n` name, a second caller could take the name in the second between our name check and our create. If our create's 409 answer was lost, `up` found that caller's VM by name, adopted it, and its teardown destroyed it. Now a VM found after an unanswered or 5xx create is adopted with teardown off. Teardown turns on only once this call's own ssh key opens the VM; keys from the ssh agent or ssh config don't count. If the key never does (within `UBI_SSH_WAIT` seconds, default 300), `up` keeps the record and recovery bundle, exits `UBI_CREATE_UNKNOWN`, and leaves the VM to its TTL. `run` reports `left=unknown` for it instead of `left=0`. (GST-1463)
- **gstack for QM: three skills QM loads on demand.** `qm/sandbox/skills/` holds `gstack-investigate`, `gstack-review` and `gstack-plan` in QM's skill format, generated from gstack's own investigate, review and plan-eng-review templates by `scripts/gen-qm-edition.ts` (spec in `hosts/qm.ts`). Install them as a QM deployment layer with no install step. Each file fits QM's whole-file read (under 15,000 characters), each first step is a command QM's sandbox has, and `test/qm-edition.test.ts` fails on a stale edition, an oversized file or a command QM can't run. `qm/edition.json` names each skill's first command for evals. (GST-1492)
### Changed
- **Crew lanes keep their context cheap, and docs/captain.md says how.** A new "Lane context" section covers four habits. A lane that expects a wait under about 30 minutes checks in every 240 seconds, for about 30 minutes at most, so its prompt cache stays warm; a cold restart after five idle minutes costs about eight times as much as a warm step. Longer waits still sleep, the controller never warm-waits, and a lane still sleeps while /ship runs. Independent reads go in one parallel program, each call returns 4k characters or less, and long output goes to files. A lane past twice the crew's median lane spend writes `handoffs/<code>/next.md`, and the controller decides whether it continues, rotates or parks, below the crew's hard spend stop. The brief template gains a `Read:` line and a `Context:` line that say the same, and docs/unattended.md notes the warm-wait exception.
- **The gstack Capy skill has a lane start card.** `capy/skill/gstack/lane-card.md` (about 1,100 tokens) is what a crew lane reads once its install check passes, instead of the rest of the skill's SKILL.md. It points to the SKILL.md /ship sections, which lanes still read before shipping. SKILL.md stays inside its 5,315-token context budget. Owners copy the skill folder as usual to pick it up.
- **AGENTS.md lists skills by name only.** The catalog tables, about 9.8 KB that every agent reading AGENTS.md loaded each session, are now a names-only index of about 2.7 KB that points to docs/skills.md for what each skill does. Validation discipline, build commands, platform support and key conventions are unchanged. The /cso runtime and scanner catalog details that lived only in AGENTS.md moved to docs/skills.md.
- **gstack's own policy turns on `land.merged_checks`.** When main has moved past a PR's merge base, `land --pin` now runs typecheck, the generated-docs freshness check, and the size and budget guards plus the core of `test:quick` (through the strict free runner, so a test that exits early fails the check) on the PR merged onto main's tip, and lands only when all three pass. This catches the stale-base break from #3886, which pushed `ship/DRIVER.md` past its 12 KB cap on main. `min_gstack_version` moves to 1.96.0.0, the release that added the key. (GST-1332)
- **gstack's own policy turns on the release gate.** `.gstack/ship-policy.json` now declares `release_gate`, so one full paid gate runs on main every 8 hours instead of a gate on every push, and a red gate holds the stable release rather than freezing the queue. The per-push main backstop stands down. `land --pin` now only warns about a backstop freeze written before the switch, and `release-cut` lets that freeze go once a green release gate runs on a later commit. The bisect after a red gate counts the red case's rerun against `bisect_spend_cap_usd` (now $80), follows the policy's current cap, and resumes when the cap is raised again. (GST-1133)
- gstack's own CI carry now runs the test files CI skipped entirely. Free Tests records `skipped_files` (the planned files whose every test skipped on the runner) in its `tested-tree` record, so a /ship carry runs those files here through the `bun run test` lane's `per_file` runner instead of counting them as run (GST-1190). A file that skips here too is not owed (GST-1314), so the carry no longer falls back to the full lane.
### Tests
- `test/ci-tree-reuse.test.ts`: only a file whose every test skipped is listed, a partly skipped file stays covered, and a run without complete per-file counts records no list.
- **The tree receipt accepts a release output its release tool regenerates.** A `release_tool` that renders content (harness-evals folds `todos.d/` into TODOS.md) writes more than version rewrites, so `gstack-tree-receipt` called the stamp `changed` and a stamped train refused it. With `--allow-repo-commands`, and in `train build --allow-repo-commands`, a `release_outputs` file is now a stamp when the tool, rerun on the current head at its version, writes it byte for byte, the same rule the release PR and the Version Gate apply. Without the flag the tool never runs and the file stays `changed`. (GST-1176)
- **Flaky pass 2026-11-06: four flaky tests now report why they failed, and one paid case is scored instead of killed.** The QA-only cleanup diagnostic reads `browse-daemon.log`, the file a CLI-spawned browse daemon writes, so a missing terminal record now shows the daemon's terminal-agent lines instead of "none". The router failure-routing cases give their session a deadline 10 seconds inside the test's own timeout, so a session that runs long is scored on the routing it already did rather than lost to Bun's kill (paid census 42103721891: routed to /investigate after two reads, then a 300-second stress command ran out the clock). The bootstrap retention test prints the paid shard's outcome and log lines when its status is wrong, and the Xvfb welcome test prints the daemon log when the page is not the welcome page. No threshold, trial count, case kind or model budget changed. (GST-1632)
## [1.96.0.0] - 2026-11-05
- **A reviewer's "I found nothing CRITICAL" no longer counts as a CRITICAL finding.** /ship's review collect step used to turn a reader's own closing sentence into a blocking finding when the sentence only denied a severity ("I found nothing CRITICAL.", "I found no defect that is CRITICAL.", "Neither finding is CRITICAL."), so the review loop counted blockers that named no defect and a Captain had to grant an exception. A sentence that is wholly a denial, a count of findings or "nothing blocks", with no file, line, exception clause or field on its line, now collects nothing at the label it denies; when the same line also holds other prose, that line stays on record as an INFORMATIONAL note instead of a blocker. Every real blocker still collects: a file:line, an exception clause ("except the token leak in log.ts:3"), "nothing else" or "no other", a defect named in the same sentence, another labelled sentence, a list item or a Severity or Blocks field keeps the line at its label. (GST-435)
+1 -1
View File
@@ -1 +1 @@
1.96.0.0
1.97.0.0
+1 -1
View File
@@ -1,4 +1,4 @@
# gstack digest v1.96.0.0 — regenerate/re-copy after upgrading gstack
# gstack digest v1.97.0.0 — regenerate/re-copy after upgrading gstack
Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed
for agent hosts without a full skill install. The full skills add workflows,
@@ -1 +0,0 @@
- **Every reviewer reads the same trimmed diff, however its brief was rendered.** A brief rendered on its own with `gstack-review-dispatch render` (the Red Team rendered before the specialists finished, or rendered again after a failed dispatch) now gets the base policy's `review_exclude_paths` selection, the readers policy's `forbid_paths`, the kept files, the secret-line excerpts and the disclosure line, the same as the pass's readers. Before, it built its diff from its own flags alone, so harness-evals #269's Red Team refused at the unexcluded 30.3 MB while the other readers read 2 MB. The plan, the pass manifest and the standalone render now share one selection helper (`lib/review-selection.ts`), and the selection is read from the checkout root, so an outside voice rendered from a subdirectory no longer drops the changes outside that directory. (GST-1476)
-6
View File
@@ -1,6 +0,0 @@
### Fixed
- **`gstack-brain-sync` no longer reports a rejected push as an auth error when the remote's URL or path happens to contain `auth`, `permission`, `401`, `403` or `forbidden`.** It searched git's whole error text, including the `To <url>` line and the quoted URL, so a remote such as `github.com/author/notes` or a path under `/tmp/x403y` skipped the fetch-and-merge retry, and the sync stayed `push_failed` until someone pushed by hand. The check now ignores the URL. A real auth refusal (for example `The requested URL returned error: 403`) is still reported as one. In CI, the free shard's temporary folder name turned eight brain-sync tests red at once (Free Tests 41847262176).
- **The browse daemon picks its port after Chromium has launched, right before it binds.** It used to check a random port first and bind it only after the browser launch, which takes seconds. Under load another process could take the port in that time, and the daemon failed with "Failed to start server. Is port … in use?" (Free Tests 41951644962).
- **`test/gate-reuse.test.ts`**: the fixture's bare origin turns off auto-maintenance, as the pregate fixture already does. Each push started a detached `git maintenance run --auto` that could remove an object folder while a later push was writing into it (Free Tests 41980850843).
- **Paid evals**: the ship doc-sync interface now says how to create the private folder it allows (one `mkdir` as its own call), after two `ship-docsync-store` runs each joined a no-op mkdir stand-in to a read line (41850017482, 41987975678). The coverage-audit prompt asks for the two file reads after the skill files, so the skill's dedicated-read step is seen before the files are read (`plan-eng-coverage-audit`, 41932813292). The QA-only cleanup names the terminal record, port file, daemon and daemon-log state when it can't confirm the terminal's identity (`qa-only-no-fix`, 41962523474).
@@ -1 +0,0 @@
- **/ship's finish stops flagging main's edits after a stacked parent squash-merges.** A child PR reviewed against its parent branch saves the parent's head as its reviewed base. Once the parent squash-merged and the child merged main, `gstack-ship-release finish` refused that head as not on main, and the old merge base it fell back to counted the parent's and main's later edits as changes since review, so #4008 paid two review exceptions and a split for files it never changed. finish now takes a landed stack parent as `--reviewed-base`: a commit the branch contains whose change a main commit holds, found at the squash even when main edited those lines afterwards (`REVIEWED_BASE: stack parent <sha> landed on origin/main at <sha>`). The route, the granted fix-check's credit, the scoped-review packet and the docs carry then compare the child's own patch, and a file the child no longer changes counts as base only when main already holds the reviewed change to it. A child edit after review, a revert to main's content, a parent main never received (including a `package.json` script the child's version bump later touched, or a logic edit to a `stamp_paths` file) and any other commit off main still stop. A post-review edit on this path never takes the one-reader bounded route; it stops for a full re-review. The receipt carry's landed-parent search finds the squash the same way. (GST-1399)
@@ -1 +0,0 @@
- **Receipt carry survives a base policy change that excludes none of the PR's files.** `gstack-ship-receipt carry` refused with `review_exclude changed` whenever main's `review_exclude_paths` digest moved, even when no file the PR changed fell under any pattern, so harness-evals #263 paid a full five-reader re-review of an unchanged product. Carry now also carries when neither the receipt's recorded selection nor HEAD's holds a file the PR changed (a pattern gained, added, removed or reverted), and the carried receipt records HEAD's digest so the queue binds it. A pattern change that selects a PR file, excluded or kept, and an unreadable or malformed base policy still refuse. (GST-1415)
@@ -1 +0,0 @@
- **On Capy, agents comment and request reviews only through Capy's PR tools, never `gh`.** docs/triage.md, CONTRIBUTING.md, docs/captain.md, docs/recommended-harness.md and /modernize now say that comments and tags go only through `tools.pr.comment` and review requests only through `tools.pr.edit`, both acting as `capy-ai[bot]`. `gh pr comment`, `gh issue comment`, `gh api` and `gstack-post` are never used for them, because `gh` on Capy runs as the maintainer's own account. An unattended /modernize run pushes, closes and posts nothing. (GST-1417)
@@ -1 +0,0 @@
- **A docs audit after the review no longer forces another review.** /ship's Step 14.5 docs audit runs after the review, so when it added, edited or removed a file under `review_exclude_paths`, `gstack-ship-receipt write` refused the digest the reviewers read and the run had to review again. Write now takes the audit's post snapshot (`--docs-post <audit-id>-post.json`) and its Prepare snapshot (`--docs-pre`), and binds the reviewed digest when the audit's own files are the only change to the selection. It prints `REVIEW_EXCLUDE_DOCS_AUDIT:` when it does. Docs the review read while they were still uncommitted stay as they are, and files that git converts on checkout (autocrlf, filters) compare as committed. Any other change still refuses. The ship driver's Step 19 write now passes `--review-exclude`, and a refusal says when the flag was left out. (GST-1429)
@@ -1,2 +0,0 @@
- **`ready` binds a receipt with an older review_exclude digest when the policy change touches none of the PR's files.** After a `review_exclude_paths` edit landed on main, `gstack-ship-queue ready` refused every receipt that had recorded the old digest, even when no file the PR changed fell under any pattern (harness-evals #274 and #276 after policy PR #278). The queue now uses carry's rule. When neither the receipt's recorded selection nor HEAD's holds a file the PR changed, the receipt binds. A selection that holds a PR file, excluded or kept, and an unreadable base policy still refuse. (GST-1434)
- **The review_exclude name scan no longer times out on large results repos.** The scan that keeps a file a program names by full path ran one `git grep` over the whole tree, so on harness-evals #269 it searched 30 MB of receipts for 4,354 candidate paths, ran 278 s past its 120 s limit and kept every file. It now greps only the code and test files that can name a candidate, so its cost follows the code, not the data the PR adds. A git failure or timeout still keeps every candidate. (GST-1435)
@@ -1 +0,0 @@
- **/ship's docs audit runs on your model preference.** Step 14.5's docs audit was told to run on Sonnet 5.5 at low effort no matter what model you prefer, so crews set to Opus in `preferences.yaml` still audited on Sonnet. `gstack-review-dispatch pass --audit-only` now gives a docs reader with no model your `agent_model` at low effort and prints the pick as `AUDIT_MODEL:`. With no preference it stays on Sonnet 5.5 at low effort, `host` uses the lane's own model, and a model named in the extras still wins. (GST-1439)
@@ -1 +0,0 @@
- **A policy edit that changes nothing about a PR's files no longer forces another review.** After main's `review_exclude_paths` gained patterns that match none of a PR's files, `gstack-ship-receipt write` refused the digest the reviewers read, and ready, carry, train build and land bound it only when both selections were empty (harness-evals #222: its reviewers read 7cf39a7e1150, which excludes 1,260 of its files, and write refused after #278 added six globs that match none of them). The shared check now binds when the recorded selection and HEAD's give the PR's files the same applied patterns, excluded files and kept files. Write gets only the digest, so it rebuilds the recorded selection from the policies the base held and prints `REVIEW_EXCLUDE_POLICY_MOVED:`. A selection that differs, a digest no base policy gives, an omitted `--review-exclude` and an unreadable base policy still refuse. (GST-1446)
@@ -1,3 +0,0 @@
### Fixed
- **Results PRs that capture model requests no longer stop at the review ceiling just because their captured prompts mention credentials.** A file under `review_exclude_paths` stays in review when a changed line reads a credential, and until now every such file went to each reader whole. On harness-evals #269, 75 captured requests whose system prompts name `${…_API_KEY}` variables and keychain commands added 12.9 MB, and `/ship` refused with `REVIEW_DIFF_TOO_LARGE` against the 2 MB limit. When the secret line is the only reason a file is kept, `gstack-review-dispatch pass` now gives readers an excerpt instead. The excerpt holds every changed line the secret-line rule matches or the credential scanner finds a secret-shaped value on, with up to 3 lines of context, the path and the match counts. Only the excerpt counts toward the limit. A file kept for any other reason (a program names it, it is outside the applied patterns, the gate or name scan could not run) is still shown whole, and so is any file whose excerpt cannot be built. See [secret-line excerpts](docs/ship-policy.md#secret-line-excerpts).
@@ -1 +0,0 @@
- **/ship's coverage and plan audits run on your model preference too.** Steps 7 and 8 were still told to run on Sonnet 5.5 at low effort after the Step 14.5 docs audit moved to your `agent_model`. A reviewer pass now gives any audit reader with no model your `agent_model` at low effort and prints the pick as `AUDIT_MODEL:`, the same way `--audit-only` does. With no preference they stay on Sonnet 5.5 at low effort, a model named in the extras still wins, and the Step 11 outside voice keeps its other-family model. (GST-1449)
@@ -1,3 +0,0 @@
### Fixed
- **`gstack-redact` no longer reports a property read in code as an internal hostname (#3987).** The `internal.hostname` rule matches any name ending in `.local`, `.prod`, `.staging`, `.internal`, `.corp` or `.lan`, so `server.local('/x')`, `if (config.prod)` and `if settings.internal:` were reported as leaked hosts. The pregate secrets stage listed each one as a warning, and /ship then asked for a confirm. In gstack's own source files, 57 of the 239 findings this rule made were property reads like these. Now, in a TypeScript (`.ts`, `.mts`, `.cts`), Dart or Python file, a name is treated as code when it sits outside every string and comment on its line and has code on both sides of it: an operator, bracket or expression keyword before it, and a bracket, operator, method call or the line end after it. A quoted host, a host in a comment, a host and port (`db.internal:5432`), a host and path (`proxy.corp/api`), prose, a host inside a TypeScript template literal or block comment, every host in a `.tsx`, `.jsx`, config, dotenv or Markdown file, and a scan without a file path still report. The check reads at most 1,000 characters back on a line and reads each line once. A 4 MB file of ordinary source scans in the same time as before; a buffer packed with these names on every line takes up to about 40% longer. Thanks to @nazarli-shabnam (#4026) for the approach.
@@ -1,4 +0,0 @@
---
bump: minor
---
**An opt-in /plan-ceo-review gate that holds "rank the top risks before you finish" (GST-1461).** With `./setup --ceo-review-gate` (or `GSTACK_CEO_REVIEW_GATE=on`, or `gstack-config set ceo_review_gate on`), a session or review subagent that loaded /plan-ceo-review can't end its final turn until the review's output file, changed after the skill loaded, has a `Findings` or `Top risks` heading followed by a numbered list of real risks starting at 1. The gate looks at `GSTACK_CEO_REVIEW_OUTPUT`, the files the run wrote, changed or untracked markdown in the repository (so a review written through a Bash heredoc counts) and the project's `ceo-plans` directory. A stop that asks you a question is never blocked, an unrelated subagent is never touched, and after 3 blocks the next stop goes through with an `exhausted` marker. Decisions are `GSTACK_GATE ceo-review-gate` markers (`fired`, `blocked via=stop`, `released file=…`, `exhausted`, `error`) in the session record, `hooks/ceo-review-gate.log` and the transcript; errors fail open. `gstack-skill-gates settings --ceo-review` (alone or with `--investigate`) prints a `--settings` JSON for headless runs, and the headless smoke now plays a CEO review too. Off by default; the /plan-ceo-review skill text is unchanged. The /investigate gate's rollback is also tighter: a fix committed on a branch behind the baseline is undone to that branch's own tip; a restored file gets its own bytes back (two files that differ only in line endings no longer share a backup), keeps its permissions and gets its index entry back, so a rejected edit that was `git add`ed isn't left staged and a change you had only staged survives an edit to that file being put back; `linguist-generated` exemptions come from the baseline commit, so a new `.gitattributes` can't exempt source; and `gstack-skill-gates settings` quotes hook paths, so a checkout whose path has a space still runs its hooks. Docs: docs/skill-gates.md.
@@ -1,4 +0,0 @@
---
bump: minor
---
**An opt-in /investigate gate that holds "no fix before a root cause", through Bash too (GST-1461).** On Opus 5.5 the skill text alone gets that step followed 0 of 46 times, and GST-938's Edit/Write-only gate held in just 13 of 28 sessions because Opus edits through the shell. With `./setup --investigate-gate` (or `GSTACK_INVESTIGATE_GATE=on`, or `gstack-config set investigate_gate on`), a session that loaded /investigate can't change tracked source until it writes a `Root cause: <what is wrong>` line: Edit, Write, MultiEdit and NotebookEdit are denied, and any Bash command that changes a tracked source file (`sed -i`, redirects, `tee`, `python`, `perl`, `node`, `git apply`, a commit) is caught by comparing the tree with a baseline, put back and reported. Tests, docs, lockfiles and new untracked files are never blocked. Every decision is a `GSTACK_GATE` marker (`fired`, `blocked`, `released`, `observed`, `skipped`, `error`) counted in `$GSTACK_STATE_ROOT/skill-gates/<session>/record.json`, traced in `hooks/investigate-gate.log` and recorded in the transcript; a hook error fails open with an `error code=…` marker. `gstack-skill-gates settings --investigate` prints a `--settings` JSON for headless `claude -p` runs and `gstack-skill-gates report --expect armed` checks a session engaged. Off by default; the /investigate skill text is unchanged. Docs: docs/skill-gates.md.
-1
View File
@@ -1 +0,0 @@
- **A finding the owner rules a disclosed note no longer keeps a granted fix-check from counting, and every ruling is listed.** On qm#3122 at 532cc5c the owner ruled two CRITICALs notes, not fixes. The runner logged them as `skipped`, `finish` counted a skipped CRITICAL as open, so the granted fix-check row (its own findings all below the floor) never read CURRENT and `finish` stopped with "behavior changed since review". The owner now records each ruling with `gstack-approvals rule --finding <fingerprint> --head <stop head> --reason <why> --words <exact words> --by <who ruled>`: an approved approval row with the ruler, the head, the reason and the time. `rule` refuses a head that is not a commit, a multi-line reason, a `--by` naming gstack and any ruling inside the checkout's open /ship run, and `add` refuses the ruling scope. `finish` reads rulings from the approval record only (a `ruled` field or `action: "ruled"` on the lane's own review-log row is ignored), treats a ruled finding as closed when the ruling's head lies inside the fix-check's range, whichever came first, and prints each as `RULED_NOTE:`; a ruling at an earlier head is listed as not applied. A skipped CRITICAL with no ruling stays open. `gstack-approvals rulings` prints the `Ruled note: …` lines for the Pre-Landing Review and the PR body; the receipt records them as `ruled_notes`, `gstack-ship-receipt write --body` refuses a body missing one, and the READY block prints each. See docs/ship-policy.md#ruled-notes. (GST-1537)
@@ -1 +0,0 @@
- **/ship's finish routes a declared version or rules-number retake after a base merge as mechanical, with no review round.** founderstar#563 merged its base after two full passes and retook its rules number 80 → 81: one line in `scripts/core/game.gd`, `save_version` in 8 save fixtures and one golden. finish routed all 10 paths as behavior and said to insert Steps 5–11.5, while `gstack-review-plan` at the round cap said `done`, so the only exit was a fresh /ship. A base policy can now declare the constant under `mechanical_retake` (its file and one-line template, the fixture fields that carry the value, and the `.gstack/generated.json` entries that rebuild its goldens). finish proves each changed path from the diff. It needs one literal on the constant's line, a value above the reviewed one and the base's, the same new value in every changed field, goldens that regenerate byte-identically, and nothing else changed. It prints `MECHANICAL:` per path and `mechanical=<n>` on `ROUTE:`. Any other edit in those files, a value that doesn't match or a golden that doesn't regenerate keeps its old route with a `ROUTE_MECHANICAL_REFUSED:` reason. The receipt lists the paths as `Mechanical retake: …` lines, and receipt write refuses a PR body missing one. finish's behavior STOP now names `gstack-review-plan --post-review`, which plans that Step 9 as a fix to a gate-class path: a pass on the range below the round cap, and a stop for the owner's grant of one scoped fix-check at the cap, never `done`. Prior art: #3966 (GSTA-167's `carry_mechanical`). (GST-1560)
-1
View File
@@ -1 +0,0 @@
- **A receipt carry with both a changed path and a moved-context path gets one scoped re-check instead of deadlocking.** founderstar#563 merged a base that took `SAVE_VERSION := 83` under the branch's reviewed `:= 84` and also moved a block next to a reviewed hunk. Carry called the constant file changed, J1's re-check refused because context had moved, and the scoped review opened only when nothing had changed, so every rules-number PR needed a fresh /ship. Now a hunk whose added lines are byte-identical and whose removed lines differ only by the base's edit of the lines the branch replaces counts as a context path, so carry and finish put it in the scoped-review packet for one reader and never carry it silently. When real changed paths and context paths come together, carry writes one packet: the range-diff, the changed paths' diff, then the context hunks. The existing 400-line and 64 KB caps count the whole packet, `range_diff_sha256` binds all of it, and the reader's record names `range_base`, which the carry reason logs. Past the caps, carry refuses as before. A changed path alone, or context paths alone, keep their routes with unchanged output, and a branch-side change to an added line still needs review. (GST-1561)
@@ -1,3 +0,0 @@
### Changed
- **Captain docs: a lane sleeps while /ship runs.** `docs/captain.md` (the lane's /ship sequence), `captain/templates/brief.md` and the Capy gstack skill's "Sleep while /ship runs" bullet now say that after dispatching /ship, the lane ends its turn and wakes only when /ship completes, holding no pull request watch meanwhile; that one wake relays READY, and after READY the lane handles pull request events as before. Each READY duty has a named owner so `train build` never refuses a pin with `QUEUE_NOT_READY` for a step nobody ran: /ship owns the ship receipt and the pregate record behind the `PREGATE:` line; the lane owns `gstack-ship-queue ready --screen|--no-screen` and its notes, the decision-trail audit and the `Attention` section. The tradeoff is that a red check that lands while /ship runs waits for /ship to finish instead of being fixed in parallel.
@@ -1 +0,0 @@
- **A /ship helper on Capy can count its own calls.** Every helper reported `CALLS: unavailable` because a subagent can't read its own transcript. On Capy, the gstack-ship-* commands and the review pass loader now add one row each to `<state root>/ship-calls.jsonl` when they exit, and `gstack-ship-budget calls --record <rec>` turns those rows into a `CALLS: source=log` line: phase programs, review-pass polls, section reads, an estimate of the keep-alive waits between them, how far past 200 s each poll rerun starts, and gaps over the 300 s cache. Counting adds no call. A run window with no rows prints `CALLS: unavailable` instead of zeros, and a background lane or `finish` no longer hides the calls made while it runs. (GST-1585)
@@ -1 +0,0 @@
- **A main merge after the targeted read no longer voids the record.** `gstack-ship-receipt write --review-targeted` refused with "the product changed after the read" whenever main brought in money- or secret-tagged files, though the PR's product patch was unchanged, because the classes changed since the read were computed across the merge commit (harness-evals #293, #292, #225). Write now computes those classes, and the plain-docs carry, over the PR's own change, meaning the paths whose part of the PR's diff against its merge base differs between the read and HEAD. A branch-side edit to money, secret, auth or gate code after the read, or a merge resolution that changes the PR's own hunks, still refuses. (GST-1593)
@@ -1 +0,0 @@
- **`/ship` accept gates under a branch's own pregate policy when it only adds test lanes, says why a carried lane's test file is missing from CI's JUnit tree, and takes an owner-granted test-lane exception.** On gstack-evals #240 a PR that added a runtime file and its own lane line passed preflight under `--policy-from head`, then `gstack-ship-release accept` reran the pregate under the base policy and refused with `PREGATE_NO_LANE`. `accept`, and `finish` when it reruns the whole pregate, now pass `--policy-from head` when the branch's `.gstack/pregate.json` differs from the copy it forked from in nothing but added test lanes and `.gstack/generated.json` is unchanged, printing `POLICY: head …`; a removed or shrunk lane, or any other changed key, keeps the base policy with `POLICY: base … (the branch's copy <what differs> …)`. The same PR then stopped on `inventory_uncovered`: bun registers an imported test file's tests under the importer, so its JUnit suite never appears. The refusal now adds `CARRY_DIAGNOSIS: bun registers <imported> under <importer>; its JUnit suite won't appear`, and the owner can record `gstack-approvals lane-exception --head <pushed head> --files <the uncovered files> --reason … --words … --by …` (refused inside the lane's open /ship run, by gstack, or through `add`). `gstack-evidence record-ci` binds it only at that head for exactly those files, after a local preflight of them with the lane's `per_file` runner passes; `accept` prints `TEST_LANE_EXCEPTION:`, the receipt's `ci_carried` entry records the grant and the preflight, and `gstack-ship-receipt write --body` refuses a body without the `Test-lane exception: …` line. See docs/pregate.md#accept-head-policy and docs/ship-policy.md#test-lane-exceptions. (GST-1595)
-1
View File
@@ -1 +0,0 @@
- **/ship's finish routes a Godot 4 UID sidecar added after review with no reader, and a base policy can declare its own test files under `test_code`.** founderstar#611 (FOUN-32) committed `tests/cutscene_check.gd.uid`, the one-line `uid://…` file Godot writes beside a script, after the owner's granted fix-check, for a test script the PR added before review. No route took it, so a random id cost another fix-check or a fresh /ship. finish now proves each `.uid` that would route as behavior. The branch must add it after review, its sibling script is one the reviewed range added or modified or the review-loop exception accepted, it is a regular non-executable file whose whole content is one canonical `uid://<id>` line exactly as Godot 4 writes it (base 34 over a–y and 0–8, at most 2^63−1, optional trailing newline), neither file matches the base's `review_exclude_paths` or `readers.forbid_paths`, and no other file in the tree declares the same UID (another `.uid`, a `[gd_scene]` or `[gd_resource]` header, or an `.import` file's `uid=` line; a scene that only references the script by it is fine). A proven sidecar prints `UID_SIDECAR:` and counts as `sidecar=<n>` on `ROUTE:`. Any other `.uid` keeps its old route with a `ROUTE_SIDECAR_REFUSED:` reason, and `.import` files are never candidates. The receipt lists each sidecar as a `Godot UID sidecar: …` line, and receipt write refuses a PR body missing one. GDScript checks (`tests/*_check.gd`) match none of the built-in test globs, so the new base-only `test_code` policy key adds globs that finish routes as test code (one scoped reader) instead of behavior. A declared glob never matches a file under `.gstack/` or `.github/`, and a head-only declaration changes nothing. (GST-1604)
@@ -1 +0,0 @@
- **`/ship` accept's `record-ci` bound comes from the base ship policy, and accept says when its final pregate ran the tests here.** accept stopped every `gstack-evidence record-ci` at a fixed 600 s, so on harness-evals #271, whose record-ci needed about 745 s, the lane ran record-ci and the rest of accept by hand. The new `ci_record_timeout_s` key in `.gstack/ship-policy.json` sets the bound (default 600, at least 60, clamped to 3600). accept reads it from the base branch only and prints `CI_RECORD_TIMEOUT: <n>s (…)`. The same run's final pregate found no run profile for this OS and ran 3,666 tests locally with no reason given. When the tests stage gets no CI carry, accept now prints `CI_CARRY: none (<reason>); the final pregate's tests stage ran here instead: tests <verdict> in <n>s`. A profile without `os` carries `ship_lanes_from` lanes but not the tests stage. See docs/ship-policy.md#ci-record-timeout and docs/pregate.md#accept-local-tests. (GST-1606)
@@ -1 +0,0 @@
- **`/ship` finish's `FIX_CHECK:` line names the grant that covered the fix-check.** When a branch held two approved `review_loop_grant` rows whose stop heads both fell inside the fix-check's `range_base..commit`, finish named the first one recorded. On gstack-evals#244 the line credited fix-check fc4 to the earlier fix-check's grant, so the trail misattributed the owner's approval. finish now names the grant whose stop head is newest in the range, which is the stop the fix-check answered. Whether the fix-check counts is unchanged. (GST-1610)
@@ -1,3 +0,0 @@
### Fixed
- **`test/ship-queue-land-direct.test.ts` and `test/ubicloud-runner-recovery.test.ts` pass in one bun process.** The land file's merged-checks case called `runMergedChecks` (lib/land-merged-checks.ts) inside the test runner. On Linux that call sets `PR_SET_CHILD_SUBREAPER` and clears `PR_SET_DUMPABLE` on its caller and never undoes them, so the runner adopted every later orphan and never reaped it. The fake VM's detached phases and the sink pusher's `sleep` stayed zombies that `tail --pid` and `kill -0` count as alive, so recovery's run cases hung at the 90 s timeout (Free Tests 42207455885, shard 10). The case now runs `runMergedChecks` in a child Bun. The fake VM fails at once with a named error when the runner is a subreaper, recovery fails on any zombie child it leaves, and a new regression runs the pair in one process. (GST-1613)
@@ -1 +0,0 @@
- **A /ship run that audits its docs twice keeps the review it already has.** When Step 14.5 ran again after a base merge and restamp, `gstack-ship-receipt write` reset only the last audit's files, so a file the first audit edited and committed stayed in the `review_exclude_paths` selection and write refused the reviewed digest (gbrain #8678); the only way out was HEAD's digest, which skips review of the delta. Each docs snapshot now names its /ship run (`--run`), each post snapshot records the files its audit edited (`--pre <Prepare snapshot>`), and a re-audit's post snapshot carries the earlier audit (`--carry`). Write resets every file this run's audits edited to its content before them and binds the reviewed digest. A file no audit edited, an edit after an audit, and a carried audit from another run still refuse. (GST-1616)
@@ -1 +0,0 @@
- **finish counts every granted fix-check in a chain, and accept keeps clears made at the same tree.** A second granted fix-check (a merge resolution, say) logs its range from the first fix-check's commit. `gstack-ship-release finish` read only the newest `review-fix-check` row and refused any range that didn't start at the reviewed tree, so the later fix-check unbound the earlier one and lanes logged a synthetic combined row (#4063). finish now chains each row to the earlier row whose commit is its `range_base`, back to the reviewed tree, and counts the chain when every link is clean and has its own owner grant (one grant counts for one link); a gap or an ungranted link still refuses. Separately, accept's final `gstack-pregate` rewrote pregate.json and dropped the `macos-named-regressions` and `windows-smoke` clears the lane had made at the same tree, so accept never exited 0. A rerun at the same head, tree, policy and base branch now keeps each earlier receipt for the same obligation and prints `PREGATE_CLEAR_KEPT:`; another head or tree, or a rerun with a failed or unfinished check, keeps nothing. (GST-1627)
@@ -1 +0,0 @@
- **The R56 inventory records L1's verdicts on gstack's untold routing and /investigate's told record (GST-1629).** GEVA-2's L1 run (gstack-evals#247; Opus 5.5 on the claude_code preset, 80 SWE-rebench tasks, a no-gstack arm, pins 82db89ab to 699a41cb) is now in `docs/inventory/evidence.json` and the regenerated `docs/inventory.md`: the untold routing package (route hook, contracts and the description rewrite) helps, B1 +21.3 points [+12.5, +30.0], KEEP, and needs routing or contracts on; the description rewrite alone had no effect (0 of 80); told record completeness is untested, B3 +10.0 [0, +20], FIX-METHOD because probe I failed its blind audit; pass rate O5 +5.0 at a 65% control, MDD 18.7, is descriptive only. /investigate keeps its no-effect status and CUT-CANDIDATE, which measured something else.
-1
View File
@@ -1 +0,0 @@
- **/investigate's check-after-last-edit item sees fixes written through Bash.** A Bash command that writes a file inside the session directory (a heredoc into python or cat, `sed -i`, `tee`, a `>` or `>>` redirect, `patch`, `git apply`) now counts as an edit, so a run that tests, then fixes the code through the shell and never tests again reads `missing` instead of `met`. Before, only Edit, Write, MultiEdit and NotebookEdit counted, and 5 of 7 probe-I fixes in GEVA-2's L1 run were heredoc writes. The parser (`lib/shell-writes.ts`) is conservative: reads, output to stdout, `2>&1`, and writes under `/tmp`, `~`, another directory or a variable path do not count. (GST-1633)
@@ -1 +0,0 @@
- **The browse audit origin and network log no longer keep URL query values or credentials, and short digit pieces leave audit error text.** The `origin` field of `.gstack/browse-audit.jsonl` stored the full page URL, so after a GET form was submitted the next line held what the user typed, such as `?user=a&password=hunter2pw`, and `browse-network.log` stored request URLs with their token query values. In those two places a URL now keeps its scheme, host, path and query parameter names; every query value is written as `[REDACTED]`, a query piece with no value (such as a bare token) is written as `[REDACTED]` whole, and any username, password or `#fragment` is dropped. The audit error scrub now also removes 2- and 3-character pieces of a redacted value that contain a digit, such as a CVV or an expiry month, matched as whole tokens; shorter word pieces like `a` or `is` stay so the error text is readable. Still recorded as typed: navigation command arguments such as a `goto` URL with its query, URL paths, and the text in `browse-console.log`. Lines written before this release still hold the old values; delete `.gstack/browse-audit.jsonl` and `.gstack/browse-network.log` to clear them. (GST-1635)
@@ -1 +0,0 @@
- **`mechanical_retake` and `test_code` now name 1.97.0.0 as the gstack release that introduced them.** Their key constants said 1.96.1.0, a version the next release skips: its fragments carry `bump: minor`, so `gstack-release-stamp open` stamps 1.96.0.0 -> 1.97.0.0. A policy that declares either key, or `ci_record_timeout_s`, sets `min_gstack_version` to 1.97.0.0.
@@ -1 +0,0 @@
- **A ship-policy floor above the newest stable gstack release is refused by name, and the doctor stops sending sessions into a fix that cannot work.** `gstack-ship-policy validate`, and the new pregate `policy-floor` check on a branch that changes `.gstack/ship-policy.json`, refuse a `min_gstack_version` above the newest stable gstack release (`POLICY_FLOOR_ABOVE_STABLE`, naming the floor and the release), because every stable-channel lane would refuse that policy and no upgrade could clear it. The release is the highest `v<version>` tag at origin's `stable` commit (a tag a half-way cut left above stable does not count), else the VERSION at the local stable ref; a vendored copy with no `.git` of its own never reads the enclosing project's tags; when none resolves, both warn (`STABLE_RELEASE_UNKNOWN`) and say the floor was not compared. The doctor's `floor` row now compares the version the checkout reads policies as (`runningGstackVersion`, as `validatePolicy` does), so an unstamped main checkout that already reads the floor's keys passes instead of warning forever, and when no stable release meets the floor it says `no release meets this floor yet` and offers no fast-forward; the trailer note becomes `floor=<f> (<file>; no release meets this floor yet)` with no `fix:`. The gstack skill now runs a floor fix once and reports a floor no release meets instead of rerunning the install before every workflow. (GST-1637)
@@ -1,7 +0,0 @@
**Important fixes reach stable within a day, and stable is never more than a week stale.** The Stable release workflow now runs every day at 14:00 UTC as a daily check (R74).
### Added
- A changelog fragment can mark its change important with `important: security|regression|data-loss|blocker` in its front matter. The daily check cuts stable once it is 24 hours old when a gated change stable lacks carries the field.
- The daily check also forces a cut when stable is 7 days old and main has gated changes. Monday and Thursday stay the regular cuts with the 72-hour floor. On a day with nothing to do it changes nothing and logs why.
- A due cut that finds no release PR reports `NO_RELEASE_PR: important change waiting, no release PR` and goes red. A week-old stable whose main has nothing newer gated reports `STABLE_STALE`.
- docs/releases.md documents the daily check, when the Captain stamps a release PR, the exit codes, and the exact commands to roll stable back.
@@ -1,6 +0,0 @@
---
important: security
---
### Security
- **Security fix: gstack's bundled browser no longer trusts the compromised eMudhra intermediates (#3338).** gstack now pins Playwright 1.65.0, which bundles Chromium 158.0.8127.18. Chrome removed emSign ECC Root CA - G3, the root over the eight eMudhra intermediate CAs whose keys were compromised, from the Chrome Root Store, but Playwright 1.64.1's Chromium 156 still carried it. gstack's default headless browser gets no CRLSet updates, so it accepted certificates those intermediates issued, and anyone holding the weak keys could impersonate a site to it. With 1.65.0, `$B goto https://dv-g3b-emudhra.we-have-a-problem.com/` fails with `ERR_CERT_AUTHORITY_INVALID` in both the headless shell and the full Chromium; on 1.64.1 it loaded the page. Run `./setup` after upgrading to download the new Chromium. `/setup-browser-cookies` drops its warning about this gap, and BROWSER.md says the gap is closed. The Windows windowsHide patch is re-cut for 1.65.0, and its backport of Playwright's TcpPortRandomizationWin fix is gone because 1.65.0 ships that fix itself. New free tests fail if the bundled Chromium is older than 158.0.8115.0 or still carries the root.
@@ -1 +0,0 @@
- **The free-CI shard-overrun warning now works on split shards.** `--ci-verify` compared a shard's wall time with its serial seed prediction, so at the CI default of 4 sub-shards per shard it only warned after about a 6x slowdown. The prediction is now divided by the shard's job count, and the warning says how many parallel jobs it ran across. Thanks to @indraoverflow (#4051, #3851).
@@ -1 +0,0 @@
- **Land closes its own environment to merged checks on Linux.** A scrubbed environment didn't stop a `land.merged_checks` command from reading land's own through `/proc/$PPID/environ` or a `/proc` scan, GitHub token included. Land now makes itself non-dumpable before the first check, and becomes the subreaper of everything the checks start and kills whatever a check leaves running — even a process that left its group with `setsid` or a double fork — before the `git` and `gh` processes it starts next could be read. When it can't set either, the checks don't run and land refuses. docs/ship-policy.md names what is still open: on macOS `ps -E` shows land's starting environment, and a check can still read anything else the user can, so pass `--allow-repo-commands` only for policies and PRs you trust. (GST-1421)
@@ -1 +0,0 @@
- **Narrow paid screens no longer run every gate case when a PR edits `setup`.** `setup` was listed as an input every paid case consumes, so any PR touching it (#4045, #4047) screened all 113 gate cases for about $37. No paid CI run executes `./setup`, and the touchfile map already names the cases that test it, so a narrow screen now treats it like any other file, and those two diffs select their 5 cases. `pull_request` runs, broad screens and `--profile full` keep the full gate for it. `modules/loadouts.json` is now placed through its reader, `scripts/modules.ts`, instead of restoring the full gate as an unknown file. That reader reaches every gate case, so a narrow screen leaves a loadouts-only edit to the train gate's full census. (GST-1575)
@@ -1 +0,0 @@
- **The Ubicloud runner no longer reads an API error as success.** A create Ubicloud refuses (a 409 name collision, any 4xx) now disarms teardown, so `up` and `run` never destroy a VM someone else holds under that name. A create goes out only after the name answers 404 to a check, so a name another VM holds, or a check with no answer, is refused before any request. A 5xx or unanswered create is looked up by name before anything is decided: a VM that landed anyway is adopted, and one nobody can confirm either way keeps its record and recovery bundle and exits `UBI_CREATE_UNKNOWN`. `down` counts a VM as gone only on a readable answer, so a failed or garbled VM list exits non-zero and keeps the bundle. `pull` exits non-zero when ssh fails, and `run` fails when one of its pulls does. `gc` exits non-zero when a stale VM could not be destroyed, a run record whose VM the API cannot check refuses with `UBI_ATTACH_UNKNOWN` instead of starting a second VM, and the API key reaches curl as a header on a pipe, out of `ps`. (GST-1441)
@@ -1 +0,0 @@
- **The review plan finishes in about 35 seconds on a 195,000-file results tree, and the pass says so when the plan runs out of time.** The `review_exclude_paths` name scan now writes the code and test files into a scratch index and runs one `git grep` per tree, and the binary check reads small blobs through one `git cat-file --batch` per 64 MB instead of one git call per file. The number of git calls no longer grows with the size of the tree or the PR. On harness-evals #269 the plan went from 2 min 38 s and 432 git calls to 32 s and 76 calls; on #279 it went from 206 s to 35 s, with the same output. When `gstack-review-plan` still runs past the pass's 180-second limit (`GSTACK_REVIEW_PLAN_TIMEOUT_MS` changes it), `gstack-review-dispatch pass` now prints a `REVIEW_PLAN_STOPPED` line naming the step, the reason and the limit instead of exiting with no output. (GST-1442)
@@ -1 +0,0 @@
- **`/runaway-stopper` no longer flags lanes that follow the warm-wait rule.** `gstack-runaway scan` counted each 240-second warm-wait check-in as an empty wake, so a compliant lane waiting on CI could cross the 25% empty-wake warning. A turn whose only call re-issues the wait on the same operation, or a 240-second timer on that operation or none, within 30 minutes of the wait that started the run is now left out of the rate, including when `/runaway-stopper` fetches the thread itself. A re-wait past 30 minutes, a wait on other operations, or a turn that also reads status still counts, and a status read does not restart the 30 minutes. (GST-1597)
-1
View File
@@ -1 +0,0 @@
- **A docs claim added after the review no longer leaves /ship stuck.** When the review loop had finished and a later commit added only docs with a money, security or numeric claim (such as a changelog line with dollar figures), `gstack-review-plan` printed `NEXT: done`, so nothing ran the targeted reader the receipt then asked for. Given `--range-base <the commit the targeted reader read>` and `--targeted-ran <its patch id>`, the plan now prints `NEXT: targeted`, a `TARGETED_DELTA:` line and one targeted reader on HEAD. It is not a review round. A code change, plain docs, a range base the reader did not read, or uncommitted edits keep `done` and print the reason. A main merge that only edits lines next to a reviewed hunk does not count as a branch change. `--force targeted` now refuses and names this route. (GST-1598)
-1
View File
@@ -1 +0,0 @@
- **A granted fix round no longer needs a second grant to refresh the targeted read.** When the owner grants a fix round past the review-loop cap, that fix changes the product patch the targeted record names, so the receipt asks for a new targeted read. The plan used to print `done` once the granted fix-check came back clean, which left the lane needing another manual grant. `gstack-review-plan` now takes the same `--grant` (checked as before) and plans `NEXT: targeted`: one targeted reader on the head for its targeted classes. Without a grant, a code change still plans `done`. The Capy pass program now runs a targeted manifest's voices stage too, so that reader actually runs there. A granted fix that edits a gate-class path still stops for the owner. (GST-1607)
@@ -1 +0,0 @@
- **The /investigate and /plan-ceo-review gates no longer pass a slow scan unchecked.** Their hooks were registered with a 10 second timeout while `GSTACK_SKILL_GATE_DEADLINE_MS` could set a longer gate deadline, and Claude Code kills a hook at its timeout and lets the stop through with no marker. `gstack-skill-gates settings` and `./setup` now register a timeout derived from the longest deadline plus a margin (25 seconds), and a deadline above 20 seconds is clamped to 20. A killed hook still cannot fail closed; docs/skill-gates.md#hook-timeout says why. Re-run `./setup` to update an existing registration. (GST-1615)
-1
View File
@@ -1 +0,0 @@
- **A reader's closing sentence that rates nothing CRITICAL no longer counts as a blocker.** The review collector read "None of these rises to CRITICAL." (gstack-evals#247) and "Nothing reaches the CRITICAL floor." (#4055) as CRITICAL findings, because its denial grammar knew only "is", so `blocking_found` carried a phantom that lead judgment was not allowed to lower. A rating verb (rises to, reaches, meets, qualifies as, would rise to) now denies the way "is" does: such a closing sentence collects at INFORMATIONAL, and on a line that also names a file:line it is a summary candidate that lead judgment may lower with a reason. Every other label is unchanged, checked against 296 captured reader outputs. (GST-1628)
@@ -1 +0,0 @@
- **The browse audit log no longer stores typed secrets.** `.gstack/browse-audit.jsonl` wrote each command's arguments verbatim, so a password passed to `fill`, text sent with `type`, a `js` expression, a `dialog-accept` answer, a `cookie`, `header` or `storage set` value, and a `skill run --arg` value all reached disk in plain text, and an error that echoed the value stored a second copy. Each of those values, including inside `tab-each` and `chain`, is now written as `[REDACTED]` before the line is truncated, with the selector, cookie or header name, storage key, arg key and `--out` path kept, and the error text is scrubbed of the same values. Navigation and click lines are unchanged. `browse-dialog.log` no longer stores the prompt answer either. Lines written before this release still hold the old values; delete `.gstack/browse-audit.jsonl` and `.gstack/browse-dialog.log` to clear them. Reported in #4057 by @derHaken. (GST-1631)
@@ -1 +0,0 @@
- **The GST-1631 audit redaction covers typed command values, not every secret.** The page URL in `origin` and very short values echoed in error text are not yet redacted (GST-1635).
-1
View File
@@ -1 +0,0 @@
- **The Ubicloud runner no longer destroys a VM another caller took under the same name.** With an explicit `-n` name, a second caller could take the name in the second between our name check and our create. If our create's 409 answer was lost, `up` found that caller's VM by name, adopted it, and its teardown destroyed it. Now a VM found after an unanswered or 5xx create is adopted with teardown off. Teardown turns on only once this call's own ssh key opens the VM; keys from the ssh agent or ssh config don't count. If the key never does (within `UBI_SSH_WAIT` seconds, default 300), `up` keeps the record and recovery bundle, exits `UBI_CREATE_UNKNOWN`, and leaves the VM to its TTL. `run` reports `left=unknown` for it instead of `left=0`. (GST-1463)
-1
View File
@@ -1 +0,0 @@
- **gstack for QM: three skills QM loads on demand.** `qm/sandbox/skills/` holds `gstack-investigate`, `gstack-review` and `gstack-plan` in QM's skill format, generated from gstack's own investigate, review and plan-eng-review templates by `scripts/gen-qm-edition.ts` (spec in `hosts/qm.ts`). Install them as a QM deployment layer with no install step. Each file fits QM's whole-file read (under 15,000 characters), each first step is a command QM's sandbox has, and `test/qm-edition.test.ts` fails on a stale edition, an oversized file or a command QM can't run. `qm/edition.json` names each skill's first command for evals. (GST-1492)
-5
View File
@@ -1,5 +0,0 @@
### Changed
- **Crew lanes keep their context cheap, and docs/captain.md says how.** A new "Lane context" section covers four habits. A lane that expects a wait under about 30 minutes checks in every 240 seconds, for about 30 minutes at most, so its prompt cache stays warm; a cold restart after five idle minutes costs about eight times as much as a warm step. Longer waits still sleep, the controller never warm-waits, and a lane still sleeps while /ship runs. Independent reads go in one parallel program, each call returns 4k characters or less, and long output goes to files. A lane past twice the crew's median lane spend writes `handoffs/<code>/next.md`, and the controller decides whether it continues, rotates or parks, below the crew's hard spend stop. The brief template gains a `Read:` line and a `Context:` line that say the same, and docs/unattended.md notes the warm-wait exception.
- **The gstack Capy skill has a lane start card.** `capy/skill/gstack/lane-card.md` (about 1,100 tokens) is what a crew lane reads once its install check passes, instead of the rest of the skill's SKILL.md. It points to the SKILL.md /ship sections, which lanes still read before shipping. SKILL.md stays inside its 5,315-token context budget. Owners copy the skill folder as usual to pick it up.
- **AGENTS.md lists skills by name only.** The catalog tables, about 9.8 KB that every agent reading AGENTS.md loaded each session, are now a names-only index of about 2.7 KB that points to docs/skills.md for what each skill does. Validation discipline, build commands, platform support and key conventions are unchanged. The /cso runtime and scanner catalog details that lived only in AGENTS.md moved to docs/skills.md.
@@ -1 +0,0 @@
- **gstack's own policy turns on `land.merged_checks`.** When main has moved past a PR's merge base, `land --pin` now runs typecheck, the generated-docs freshness check, and the size and budget guards plus the core of `test:quick` (through the strict free runner, so a test that exits early fails the check) on the PR merged onto main's tip, and lands only when all three pass. This catches the stale-base break from #3886, which pushed `ship/DRIVER.md` past its 12 KB cap on main. `min_gstack_version` moves to 1.96.0.0, the release that added the key. (GST-1332)
@@ -1 +0,0 @@
- **gstack's own policy turns on the release gate.** `.gstack/ship-policy.json` now declares `release_gate`, so one full paid gate runs on main every 8 hours instead of a gate on every push, and a red gate holds the stable release rather than freezing the queue. The per-push main backstop stands down. `land --pin` now only warns about a backstop freeze written before the switch, and `release-cut` lets that freeze go once a green release gate runs on a later commit. The bisect after a red gate counts the red case's rerun against `bisect_spend_cap_usd` (now $80), follows the policy's current cap, and resumes when the cap is raised again. (GST-1133)
@@ -1,4 +0,0 @@
- gstack's own CI carry now runs the test files CI skipped entirely. Free Tests records `skipped_files` (the planned files whose every test skipped on the runner) in its `tested-tree` record, so a /ship carry runs those files here through the `bun run test` lane's `per_file` runner instead of counting them as run (GST-1190). A file that skips here too is not owed (GST-1314), so the carry no longer falls back to the full lane.
### Tests
- `test/ci-tree-reuse.test.ts`: only a file whose every test skipped is listed, a partly skipped file stays covered, and a run without complete per-file counts records no list.
@@ -1 +0,0 @@
- **The tree receipt accepts a release output its release tool regenerates.** A `release_tool` that renders content (harness-evals folds `todos.d/` into TODOS.md) writes more than version rewrites, so `gstack-tree-receipt` called the stamp `changed` and a stamped train refused it. With `--allow-repo-commands`, and in `train build --allow-repo-commands`, a `release_outputs` file is now a stamp when the tool, rerun on the current head at its version, writes it byte for byte, the same rule the release PR and the Version Gate apply. Without the flag the tool never runs and the file stays `changed`. (GST-1176)
-1
View File
@@ -1 +0,0 @@
- **Flaky pass 2026-11-06: four flaky tests now report why they failed, and one paid case is scored instead of killed.** The QA-only cleanup diagnostic reads `browse-daemon.log`, the file a CLI-spawned browse daemon writes, so a missing terminal record now shows the daemon's terminal-agent lines instead of "none". The router failure-routing cases give their session a deadline 10 seconds inside the test's own timeout, so a session that runs long is scored on the routing it already did rather than lost to Bun's kill (paid census 42103721891: routed to /investigate after two reads, then a 300-second stress command ran out the clock). The bootstrap retention test prints the paid shard's outcome and log lines when its status is wrong, and the Xvfb welcome test prints the daemon log when the page is not the welcome page. No threshold, trial count, case kind or model budget changed. (GST-1632)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "gstack",
"version": "1.96.0",
"version": "1.97.0",
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
"license": "MIT",
"type": "module",