From cfabbbbc6196f3e44bebab5c73ae6b33e2c05181 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Sat, 7 Nov 2026 06:25:19 -0800 Subject: [PATCH] v1.98.0.0 release: fold the changelog fragments (#4126) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- CHANGELOG.md | 61 +++++++++++++++++++ VERSION | 2 +- agents-digest/gstack-AGENTS.md | 2 +- changelog.d/capy-gst-1642-stable-default.md | 2 - .../capy-gst-1655-ship-budget-successor.md | 1 - .../capy-gst-1659-state-root-isolation.md | 1 - changelog.d/capy-gst-1666-autoplan-gate.md | 4 -- .../capy-gst-1676-fix-check-chain-link-1.md | 1 - .../capy-gst-1677-defer-upgrade-open-run.md | 2 - .../capy-gst-1681-granted-third-fix-round.md | 1 - .../capy-gst-1688-remote-gate-stands-in.md | 1 - changelog.d/capy-gst-1689-stamp-docs-carry.md | 1 - changelog.d/capy-gst-1695-fixture-sweep.md | 1 - .../capy-gst-1698-cut-plan-eng-review.md | 1 - .../capy-gst-1698-retired-followups.md | 1 - ...apy-gst-1700-non-ancestor-targeted-read.md | 1 - changelog.d/capy-gst-1701-ceo-gate-settle.md | 1 - .../capy-gst-1702-post-review-stamps.md | 1 - .../capy-gst-1703-contract-hook-settle.md | 1 - .../capy-gst-1713-github-poll-floor.md | 1 - .../capy-gst-1717-retire-investigate.md | 1 - .../capy-gst-1718-template-ui-scope.md | 1 - .../capy-gst-1747-installer-bun-isolation.md | 4 -- .../capy-gst-1747-make-pdf-local-images.md | 4 -- changelog.d/capy-gst-1747-tunnel-web-urls.md | 4 -- changelog.d/capy-gst-1751-plan-record.md | 4 -- changelog.d/capy-keep-the-gbrain-database.md | 2 - changelog.d/capy-require-the-daemon-secret.md | 4 -- changelog.d/gst-1657-finding-lead-fields.md | 1 - changelog.d/gst-1750-autoplan-ui-scope.md | 1 - changelog.d/gst-1775-ship-tooling-fixes.md | 1 - package.json | 2 +- 32 files changed, 64 insertions(+), 52 deletions(-) delete mode 100644 changelog.d/capy-gst-1642-stable-default.md delete mode 100644 changelog.d/capy-gst-1655-ship-budget-successor.md delete mode 100644 changelog.d/capy-gst-1659-state-root-isolation.md delete mode 100644 changelog.d/capy-gst-1666-autoplan-gate.md delete mode 100644 changelog.d/capy-gst-1676-fix-check-chain-link-1.md delete mode 100644 changelog.d/capy-gst-1677-defer-upgrade-open-run.md delete mode 100644 changelog.d/capy-gst-1681-granted-third-fix-round.md delete mode 100644 changelog.d/capy-gst-1688-remote-gate-stands-in.md delete mode 100644 changelog.d/capy-gst-1689-stamp-docs-carry.md delete mode 100644 changelog.d/capy-gst-1695-fixture-sweep.md delete mode 100644 changelog.d/capy-gst-1698-cut-plan-eng-review.md delete mode 100644 changelog.d/capy-gst-1698-retired-followups.md delete mode 100644 changelog.d/capy-gst-1700-non-ancestor-targeted-read.md delete mode 100644 changelog.d/capy-gst-1701-ceo-gate-settle.md delete mode 100644 changelog.d/capy-gst-1702-post-review-stamps.md delete mode 100644 changelog.d/capy-gst-1703-contract-hook-settle.md delete mode 100644 changelog.d/capy-gst-1713-github-poll-floor.md delete mode 100644 changelog.d/capy-gst-1717-retire-investigate.md delete mode 100644 changelog.d/capy-gst-1718-template-ui-scope.md delete mode 100644 changelog.d/capy-gst-1747-installer-bun-isolation.md delete mode 100644 changelog.d/capy-gst-1747-make-pdf-local-images.md delete mode 100644 changelog.d/capy-gst-1747-tunnel-web-urls.md delete mode 100644 changelog.d/capy-gst-1751-plan-record.md delete mode 100644 changelog.d/capy-keep-the-gbrain-database.md delete mode 100644 changelog.d/capy-require-the-daemon-secret.md delete mode 100644 changelog.d/gst-1657-finding-lead-fields.md delete mode 100644 changelog.d/gst-1750-autoplan-ui-scope.md delete mode 100644 changelog.d/gst-1775-ship-tooling-fixes.md diff --git a/CHANGELOG.md b/CHANGELOG.md index f9604e114..0bd4c5eaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,66 @@ # Changelog +## [1.98.0.0] - 2026-11-07 + +- **New installs follow the stable channel; Capy crews stay on main.** A fresh `git clone … && ./setup` now records `update_channel: stable` the first time it runs on a machine and prints `Update channel: stable (new install)` with the command that switches, so a new user gets the twice-weekly tested release instead of every merge to main (the clone starts at main's tip and joins releases at the next cut that passes it). A gstack contributor's checkout (`bin/dev-setup`'s marker, another branch, local commits, CI) still follows main, and every install set up before this change keeps following main: nothing switches under anyone. Capy crew machines stay on main: `bin/gstack-capy-session` pins `GSTACK_UPDATE_CHANNEL=main` unless the variable names a channel, a fresh Capy machine records main, and the Capy skill bootstrapper (wrapper 5) no longer follows a machine's own channel. The README install section says which channel you get and how to switch. (GST-1642) +- **Capy threads run gstack's latest main at least daily.** When the oldest origin commit a Capy checkout lacks is more than 24 hours old, the session check's trailer says `stale=h` and the skill runs the install before the next workflow. A pin (`GSTACK_CAPY_SELF_UPGRADE=off`, `GSTACK_CAPY_AUTO_UPGRADE=0`, a detached HEAD or another branch) holds past that only with a recorded reason (`GSTACK_PIN_REASON`, `git config gstack.pinReason`, an explicit `--revision`); an unreasoned one is overridden at the workflow boundary and reported (`PIN_UNREASONED`). The upgrade never runs under a live workflow lease or eval run, never switches a checkout another session holds or onto diverged work, and a failed fetch with none successful in 24 hours is reported. (R75, GST-1642) + +- **A /ship run handed to a successor thread keeps one budget.** `gstack-ship-budget resume --meter-source --reading --credits ` (and `resume --rebuild-from-record` with the same flags) opens a new meter epoch at the successor's own counter when the named source differs from the run's, instead of refusing its readings with `BUDGET_READING_REJECTED`. Tokens and credits already spent carry, so `used`, `credits`, admission and the observe hard stop count every epoch; a reading below the last one of the same epoch is still refused. A successor resume is refused while a handoff child or a reader dispatch still spends on the previous thread's counter, so that spend is settled first instead of dropping out of the budget. `start --meter-source` records the run's source. See docs/ship-budget.md#successor-handoff (GST-1655). + +- **The update-channel tests can no longer write your real gstack config.** `test/update-channel.test.ts`'s `gstack-config` case wrote `update_channel` into whatever `GSTACK_STATE_ROOT` the shell exported whenever the run skipped this repository's `bunfig.toml` preload (a run from another directory, or `bun --config=/dev/null test`), which could flip a developer's or crew machine's update channel. The `runBin` helper's `gstackHome` option now points all three state-root variables at the test's directory, every spawn in that file starts from an environment with the state-root chain removed and HOME in a temp dir, and a new guard (`test/state-root-isolation.test.ts`) runs both update-channel test files with `GSTACK_STATE_ROOT`, `GSTACK_HOME`, `GSTACK_STATE_DIR`, a live `CLAUDE_PLUGIN_DATA` and HOME exported to sentinel directories and fails if any of them changes. The install fixture also stops treating a worktree's `node_modules` symlink as source, so `update-channel-setup.test.ts` and the other real-setup tests run from a worktree that shares its main checkout's dependencies. (GST-1659) + +**An opt-in /autoplan gate that holds the session until the pipeline finishes (GST-1666).** In GEVA-2's G admission eval, Opus 5.5 told to run /autoplan with no human cost about 20 times a plain session and showed none of autoplan's phases in 23 sessions: nothing kept a session going until every phase was done, so it could stop after any one. With `./setup --autoplan-gate` (or `GSTACK_AUTOPLAN_GATE=on`, or `gstack-config set autoplan_gate on`), a session that loaded /autoplan can't end its final turn until Phase 0 ran scope, every applicable phase published its report (the `Phase N complete` text, then `true autoplan-published `), and the Final Approval Gate is written. A `--ceo-only` run needs Phase 1 and its `GSTACK_RESULT` end line. The block names the next step. A stop that asks you a question, a stop waiting on a native reviewer still running, and a run driven through the `gstack-autoplan` runner are never held, and after 3 blocks the next stop goes through with an `exhausted` marker. Each decision is a `GSTACK_GATE autoplan-gate` marker, and the session record keeps the run's applicable and published phases, so an eval can read phase exposure from the record instead of the model's wording; `gstack-skill-gates report --expect armed` confirms the gate engaged, and `gstack-skill-gates settings --autoplan` arms it in headless runs. Off by default; autoplan's text and un-gated runs are unchanged. Docs: docs/skill-gates.md#the-autoplan-gate. + +- **A post-review fix-check chain counts its first link, and the planner plans the next link.** In the post-review flow each granted fix-check's stop head is the commit it reads, so link 1's grant names link 1's own commit, which is also link 2's range_base. finish only accepted an earlier link's grant at a stop head before its commit, so link 1 never counted once link 2 existed and the whole chain routed as behavior (founderstar#634: `FIX_CHECK: not counted (… link 1 of 2)`). A link's grant may now name its own commit; grants are still assigned newest link first, one link per grant, so a lone grant at the boundary covers the newer link only and the older link stays ungranted. `gstack-review-plan --post-review --last fix-check --range-base --grant ` now plans the next chained fix-check instead of a stop that asked for the grant it then ignored, and refuses the last link's own grant, so lanes no longer hand-render the dispatch. (GST-1676) + +- **A Capy install run inside an open /ship or review no longer upgrades gstack under it.** During a /ship on gstack-evals #252, the docs audit's session check ran `install.sh --for ` and moved gstack from 1.96.0.0 to 1.97.0.0 mid-run: the workflow lease that was meant to stop it only counts for 120 minutes, and a long /ship outlives it. The Capy session now also looks for a run that is still open on this machine's state root: a /ship budget ledger with no `closed_at` that was written in the last 6 hours, or a review start that `gstack-review-log --finish` has not consumed and that is under 120 minutes old. While one is open the session keeps the installed gstack, prints `SELF_UPGRADE: deferred (...)` naming the run, and still ends `gstack: ok `; once the ledger closes, the next install upgrades as before. Below the supported minimum nothing changes, and the floor still refuses or fast-forwards. (GST-1677) +- **Smaller fixes on the same upgrade path.** The eval-lock probe now works without the `flock` binary (python3 or perl stand in, as on macOS). When an unreasoned pin is overridden, the session takes the setup lock before it switches branches and hands it to `gstack-upgrade`, so no other updater sees the checkout between the switch and the fast-forward; an older `gstack-upgrade` on the local default branch, which cannot take the lock over, gets it released and takes it itself, so it still upgrades. A default branch the override creates now tracks origin. A failed restore after a refused fast-forward says `pin not restored` with the command that puts HEAD back, instead of claiming the pin was kept. The no-fetch stale check reads file times with `stat` instead of GNU-only `date -r`. A pin reason can no longer add its own `fix:` note to the trailer, and a reason with nothing pinned is not reported as a pin. The no-fetch fix note names the install command even when the bootstrapper's path is unknown. (GST-1658) + +- **A Captain-granted fix round after a fix-check is planned, not hand-run.** After round 2's fix-check and one granted fix-check, an owner who granted one more round at the last fix-check's stop head got `NEXT: stop` from `gstack-review-plan`, because in the loop a fix-check never earned another. The lane ran the fix-check by hand on a fix-range token, and finish printed `FIX_CHECK: not counted (fix-range pass; only a later full pass can clear review)` (gstack-evals#254). A new recorded grant naming the last fix-check's commit (`--range-base`) now plans one more scoped fix-check, the next link of the fix-check chain finish already counts; the previous link's grant (one recorded before a later logged fix-check) is refused and a grant at any other commit still stops, so one grant buys one link. (GST-1681) + +- **A cleared remote gate now satisfies a /ship lane whose inventory refuses its own tree.** gbrain's lane inventory refuses, on purpose, any tree that changes its CI-reuse files, so `gstack-ship-release accept` stopped with `CI_CARRY: none label=unit (inventory_failed: …)` even after `gstack-pregate --remote` had cleared the identical tree, and every such PR needed `--no-lanes-approved` plus a manual approval (GBRA-345). Now, when the carry refuses as `inventory_failed` and `.gstack/tmp/pregate.json` verifies for HEAD's exact tree with the remote gate's own report for that tree (tests above 0, not a stamp-only substitution), that receipt satisfies the lane: accept prints `CI_CARRY: remote_gate label= receipt=…` naming the substitution and records the lane as `source=remote_gate` in the evidence ledger. A gate cleared for any other tree, or a receipt naming another tree, still stops, with a `CARRY_DIAGNOSIS:` line saying why. A CI run whose `tree.json` lists no files (a rerun of only the failed jobs) now says to dispatch a fresh full run instead of a bare `no_tests`. (GST-1688) + +- **A restamp plus a plain docs edit after the targeted read no longer forces a third review round.** `gstack-ship-receipt write` and `gstack-review-plan` now decide "the product changed after the read" with one classifier: a path whose change is a release stamp under the base policy's stamp rule (`stamp_files`, `stamp_paths`, `stamp_patterns`, the version file, manifests, lockfiles and the CHANGELOG re-heading or fragment fold) always carries the targeted record, and docs and CHANGELOG edits carry under the existing plain-docs rule, which still refuses a numeric, money, security or other claim. Before, /ship's restamp plus its docs audit made write refuse the record while the plan printed `NEXT: done`, so only `--post-review` (adversarial plus the outside voice) could finish (GBRA-345, gbrain #8727). A code change after the read still drops the record, and a docs claim beside a restamp now gets the one targeted reader instead of a dead end. (GST-1689) + +- **A test run's fixture repositories no longer quarantine a /ship review pass.** /ship's fetch sweep counted any new git repository with a network remote under the temp dir as a reader's fetch, so a lane's background `bun test`, or a reader's own run that hit Bun's 5 s timeout before its cleanup, left fixtures such as `/tmp/ship-bin-*` (origin `https://github.com/fx/fx.git`) that quarantined the stage and then stopped its rerun with `FETCH_RERUN_BLOCKED`; three /ship passes were lost that way on 11-06 and 11-07. Every `bun test` process now puts its temp paths under one root carrying a `.gstack-test-fixture` marker (`TMPDIR` points there, so children spawned with the test's environment follow; a `Bun.spawn` child given no `env` keeps the original temp dir) and removes it after the last file, even after a timeout. Under the free and paid shard runners the shard's own sandbox carries the marker instead, so a shard's `TMPDIR` stays exactly as the runner set it. The sweep skips a marked root below a sweep root and a repository whose remotes resolve to a local path through `url..insteadOf`, and prints a `FETCH_IGNORED:` line for each; an unmarked clone with a real network remote still quarantines the stage and blocks its rerun. (GST-1695) + +- **`/plan-eng-review` is cut; `/autoplan` keeps its eng phase (GST-1698).** Two evals found no gain from the standalone skill. In the seeded-flaw panel, plain Opus 5.5 with no gstack found the planted engineering flaws in 15 of 16 reviews. In the wave 1 outcome test, it built 22 of 22 planned features correctly, and a session told to use the skill cost 7.1 times a plain one (GST-1218, gstack-evals#253). `./setup` no longer installs `/plan-eng-review` on any host, and the next `./setup` or upgrade removes an existing gstack-owned `plan-eng-review` / `gstack-plan-eng-review` entry. A skill you wrote yourself under that name is left alone. The entry is retired through the `disabled_skills` path and can't be re-enabled. Its methodology stays in `plan-eng-review/` as `/autoplan`'s Phase 3, byte for byte. On Codex, Kiro and the other external hosts, that methodology now ships beside `gstack-autoplan/SKILL.md` as `plan-eng-review.md`, because those hosts register every nested `SKILL.md`. To review a plan's architecture and tests, run `/autoplan`; its description now names architecture and engineering-review requests, so they route there. The router, the office-hours handoff and the CEO, design and devex review chaining now point there and no longer call eng review a "required gate". `/autoplan` also skips that chaining step inside its own run. `/ship` still accepts `review` or autoplan's `plan-eng-review` log row for Eng Review, and its over-200-lines hint now names only `/autoplan`. + +- **The dots and generic harness targets no longer register the retired /plan-eng-review.** `gstack-harness register --target dots|generic` listed every committed skill except `gstack`, so after #4108 it still put `plan-eng-review` in its router and, in `all` mode, gave it a folder. It now skips every skill in `RETIRED_SKILLS`; `plan-eng-review/` stays in the source tree as /autoplan's Phase 3 methodology. The paid cases written for the standalone skill each have a recorded fate in `docs/TEST_PORTFOLIO.md`: four that check Phase 3 methodology or its required outputs stay (`plan-eng-review`, `plan-eng-coverage-audit`, `plan-eng-review-artifact`, `journey-plan-eng`), six that checked only the standalone interactive entry are deleted along with their rows in the AUQ matrix and the plan-mode no-op file, and `plan-eng-multi-finding-batching` leaves the weekly lane through `PERIODIC_CI_EXCLUDE` until its counter helpers are removed. (GST-1698) + +- **`gstack-ship-receipt write` now says why a targeted record from a rewritten commit is refused.** When the commit the targeted reader read is not an ancestor of HEAD (usually after a rebase or a redone merge resolution), the RECEIPT_INVALID message now names that commit, says it is not an ancestor of HEAD, and says the record carries only from HEAD or an ancestor of it, even when the product patch is unchanged, so the targeted reader must run on HEAD. Before, it said the product changed after the read while printing two equal product patch ids, and lanes went looking for a product change that wasn't there (qm#2952). What passes and fails is unchanged. (GST-1700) + +**The /plan-ceo-review gate no longer judges a transcript Claude Code hasn't finished writing (GST-1701).** Claude Code can fire Stop about 200 ms before it flushes the turn's last transcript lines. The CEO review gate read the transcript once, so a finished review written outside the repository (a plan under `~/.claude/plans`) could be held because its Write wasn't on disk yet, and a run whose load hadn't landed could stop without being checked. The gate now waits, like the /autoplan gate, until the transcript holds the stop's final message (at most 2 seconds or half its deadline). Both gates share one helper, `settledTranscript` in `lib/skill-gates.ts`. + +- **Release stamps and a docs-audit edit after the last fix-check no longer stop /ship for an owner grant.** `gstack-review-plan --post-review --last fix-check` now reads the edits since the fix-check's commit with the receipt's classifier (`docsChangeSince`): lines the base policy's `stamp_files` and `stamp_patterns` cover (VERSION, `package.json`, `.claude-plugin/plugin.json`, a `since: 'TBD'` row moved to the version) and docs edits are no gate-class edit, so the plan prints `NEXT: done`, or `NEXT: targeted` when a docs claim on a money, gate, auth or secret diff needs the targeted read. Before, any such edit planned `NEXT: stop` ("the fix-check edited a gate-class path"), and GBRA-341 (gbrain #8705) needed a second grant and a fix-check on a 39-line stamp delta. A stamp match is line-exact, so any other line in a stamp file still stops, as do any code edit, an executable or symlink file with a docs name, and a commit whose message the gate scan tags as a CI skip marker. (GST-1702) + +**The skill contract Stop hook no longer judges a transcript Claude Code hasn't finished writing (GST-1703).** Claude Code can fire Stop about 200 ms before it flushes the turn's last transcript lines. The contract hook read the transcript once, so a finished /investigate run could be blocked for a missing repro because its final test rerun wasn't on disk yet, and a run whose skill load hadn't landed could stop unchecked. The hook now re-reads the transcript, up to 2 seconds, until it holds the stop's final message. It uses the same loop as the /autoplan and /plan-ceo-review gates (`readSettledTranscript` in `lib/skill-gates.ts`). + +- **gstack stops draining the shared GitHub GraphQL quota.** `gstack-pr-wait` read the pull request through a GraphQL query and `gh pr checks` every 20 seconds (as often as every 5), about 360 GraphQL calls an hour per watcher, and the Capy app installation's 7,700-point hourly budget ran out right after each reset, stalling merges across crews. It now reads the PR state, base head and head-commit checks from REST, and makes its one GraphQL read (comments, reviews and review-thread resolution) only when the PR changed, every 10 minutes, or once green under `--until ready`. Every gstack wait between GitHub reads now goes through `lib/gh-poll.ts`, which puts a 60-second floor plus up to 15 seconds of jitter under it: `gstack-pr-wait` (was 20 s), `gstack-ci-gate --wait` and its registration re-poll (were 30 s and 10 s), `gstack-ci-carry wait` (was 20 s), `gstack-ship-release accept` (was 15 s), `gstack-greptile-early wait` (was 30 s, and its head read moved to REST), the merge-train and land waits (were 30 s, and 3 s for land's merge readback) and eval-bg's run follow (was 10 to 30 s). A gh call that fails with a rate-limit message is no longer retried at once. The gh runners read `gh api rate_limit`, sleep until that budget resets (at least 60 s) and retry at most twice. The one exception is `gh pr merge`, which runs under the land lock and still fails at once. `test/gh-poll-tripwire.test.ts` fails on any new wait in a GitHub-reading module that skips the floor. (GST-1713) + +- **`/investigate` is cut (GST-1717).** A preregistered eval found no gain: on 18 real bug-fix tasks that plain Opus 5.5 solves about half the time, 20 trials each, Opus told to use `/investigate` passed 50.0% of trials against 47.2% with no skill (+2.8 points, 95% CI -5.8 to +11.4, below the 10-point lift the run could detect), and a passing fix cost about 43% more ([GEVA-380 R3c](https://github.com/garrytan/gstack-evals/blob/c90ab277/wiki/board/2026-11-07-geva-380-r3c-results.md)). `./setup` no longer installs it on any host, and the next `./setup`, relink or upgrade removes an existing gstack-owned `investigate` / `gstack-investigate` entry, even though its source is gone. A skill you wrote yourself under that name is left alone, and the entry is retired through the `disabled_skills` path so config can't re-enable it. Because the source is gone, gstack can't tell its own unedited copy from one you edited, so a real-file `SKILL.md` left in such an entry is moved to the backup root instead of deleted. Relink does this for the Claude skills folder, and setup does it for every copied `gstack-investigate` entry on another host, in a separate backup folder per host, and lists each one in its "Moved N customized SKILL.md file(s)" line. Your own files in that directory stay. The opt-in /investigate gate goes with it: `./setup` removes its four Claude Code hooks from settings, `gstack-config`, `gstack-skill-gates` and `gstack-skill-route` drop its switch, and `./setup` now rejects `--investigate-gate` and `--no-investigate-gate` as unknown options, so drop them from install scripts. If setup can't remove those hooks, it now prints a warning with the command to rerun. The shared contract stop hook and gate machinery stay, because `/review`, the `/plan-ceo-review` gate and the `/autoplan` gate use them. Bug reports now get fixed directly. That means one read-only check of the cause, and when it is still unclear, a focused test that fails on the bug before any edit, then the root-cause fix and a rerun. The router, the CLAUDE.md routing block gstack offers, `/ship`'s test-failure triage, `/ios-fix`, `/learn`, `/verify` and the docs say so and no longer name `/investigate`. One line still does: `/review`'s checklist routes timing flakes to `/investigate` until its no-loss replay and repin land (GST-1744), because that checklist is pinned to replay-tested bytes. + +- **/ship now notices a screen written as a Lit or htm template inside a .ts or .js file.** `gstack-diff-scope` counted only CSS, .tsx, views and similar files as frontend, so a pull request that rendered its UI from tagged `html`` templates in plain TypeScript read `SCOPE_FRONTEND=false`, and /ship skipped its design-lite check and the design reviewer without saying so. qm#3186 shipped three visible spacing problems that way. A changed non-test .ts/.js file that holds a tagged `html`, `css` or `svg` template now counts as frontend when a changed line sits inside one of those templates (a CSS declaration or a `${}` binding counts) or carries markup. Logic-only edits, comments and TypeScript generics don't count. SCOPE_BACKEND is unchanged, so those files still get the backend reviewers. Replayed on the last 100 merged gstack PRs and the last 100 merged qm PRs, no scope flag was lost, and only SCOPE_FRONTEND gained (qm only). On a rendered-UI diff the ship receipt now records `design: { mode: static, reason: no preview declared }`, and the PR body's Design Review line says the same: no rendered /design-review runs inside /ship yet. (GST-1718) + +- **The Capy installer reads project pins with an isolated Bun.** `bin/gstack-capy-install` runs `bin/gstack-runtime-pins.ts` from the caller's project directory to read its Bun and Node pins. Those three calls now pass `--no-env-file --config="${GSTACK_BUN_CONFIG:-/dev/null}"` like every other Bun gstack starts, so a project's `bunfig.toml` and `.env` no longer load into the installer. `test/bun-entry-isolation.test.ts` now also checks Bun runners held in a variable (`"$RUNNER" x.ts`, and any variable whose name says Bun running `-e` or a script) in shipped shell files. (GST-1747) + +- **make-pdf reads local images only from the markdown's own directory, and only image files.** An image reference that resolves outside the input markdown's directory (a `../` path, an absolute path, a `file:` URL or a symlink pointing out) is no longer read: it renders as a visible `[image blocked (outside the document directory): …]` placeholder with a warning, and `--strict` still fails the run. A local file that is not a png, jpeg, gif, webp or svg image renders as an `[image blocked (not an image): …]` placeholder instead of being inlined, and fails the run under `--strict`. The make-pdf skill and docs/howto-diagrams-and-formats.md describe the new defaults. (GST-1747) + +- **Tunnel-paired browse agents stay on web URLs: they can't read local files, can't choose a path on the daemon's disk, and can't make the daemon save screenshot or snapshot images.** Over the `/pair-agent` tunnel, `goto` and `newtab` now accept only http(s) URLs, `screenshot` requires `--base64` (it returns the image) and refuses an output path, `snapshot` refuses `-a`, `-H` and `-o`, and `eval`, which runs a file from the daemon's disk, leaves the tunnel allowlist (now 25 commands). The tunnel's 403 hint names these limits. Local CLI, skill and extension clients behave exactly as before. (GST-1747) + +**Crew PRs now show whether they had their plan step, and a repo can require it (GST-1751).** In qm, a Captain's brief replaced /autoplan with "light plan … no gate to Garry" for a new UI feature (qm#3186), and nothing noticed. A PR now carries its brief's `Plan:` line under `## Approvals` (`gstack-approvals render --plan ""`): `approved: `, `exempt: ` from docs/captain.md's list, or `required`. /ship's finish step (`gstack-ship-release finish --plan ""`) and `gstack-ship-queue ready` resolve it against every branch's approval rows and print `PLAN:`; ready writes the result into the READY note, and `land` (direct, stamp and train) checks the note still matches the PR body. A gate the lane answered in its own words, a newer gate revision that isn't approved, and a ruling or grant row never count as the owner's plan approval; imports now record `words_by` for that. gstack never guesses from the diff or the title whether a change is a feature. No line reads as `required`, and a diff of changelog fragments only needs none. The new `plan_record` ship-policy key is `off` by default; Capy crew sessions warn, and `require` (policy or `GSTACK_PLAN_RECORD=require`) refuses with one of six codes, each with a fix. `gstack-approvals plan-check --plan ""` checks a line by hand. Replayed on the last 60 merged gstack PRs with each PR's brief line, 57 pass and 3 would be refused: two features Garry approved where no approval row was ever written (#4100, #4055), and one whose gate the lane answered in its own words (#4049). + +- **/setup-gbrain's source wireup step no longer puts the gbrain database URL on the command line.** The step now runs `gstack-gbrain-source-wireup --strict` without `--database-url`, with `GBRAIN_DATABASE_URL` and `DATABASE_URL` cleared for the call, so the helper reads `~/.gbrain/config.json` once at startup and pins that URL for every `gbrain` call it makes, as the flag did before; a `DATABASE_URL` in the user's shell can't redirect it. The URL holds the database password, and command-line arguments are visible to other local users in `ps`. A new test keeps every setup-gbrain doc from passing the URL to the helper again. (GST-1747) +- **The redaction gate blocks more credential formats.** `lib/redact-patterns.ts` adds HIGH patterns for OpenRouter keys (`sk-or-v1-`), GitHub App user-to-server and refresh tokens (`ghu_`, `ghr_`), Stripe live restricted keys (`rk_live_`), Supabase secret keys (`sb_secret_`) and personal access tokens (`sbp_`), and `aws.access_key` now also matches temporary `ASIA` key IDs. `gstack-redact`, the pre-push hook and every skill that scans through the redaction engine pick them up. (GST-1747) + +- **The design board daemon and the iOS QA daemon now serve only local clients that address them directly.** The design daemon keeps a per-daemon secret in its private (0600) state file and serves every route except `/health` under that secret, so board URLs from `$D compare --serve` now look like `http://127.0.0.1:N//boards//`. The board page, feedback, progress polling and `BOARD_URL`-based reloads keep working unchanged, and `$D daemon stop` reads the secret from the state file. Both daemons answer 403 to requests whose Host is not a loopback address for their port, to requests with no Host header, and to requests whose Origin is another site. The iOS QA loopback also answers 403 to browser requests that `Sec-Fetch-Site` marks as cross-site or same-site; local agent clients send no such header and work as before. A board link opened from before a design daemon restart now shows a "Board expired" page instead of a bare 401, and the board page's progress polling stops at the first error response instead of reading it as progress. The iOS QA tailnet listener is unchanged. (GST-1747) + +- **A native finding led by `Finding 1 (FIXABLE)` keeps its own Severity and Blocks lines.** The review collector treated only `**1. …**` as a numbered finding heading, so when the native adversarial reader opened a finding with `**Finding 1 (FIXABLE): …**` and put numbered steps or more paragraphs before its `Severity: Medium` and `Blocks: no` lines, those lines split off onto a phantom second record and the finding itself collected CRITICAL from its FIXABLE tag. The judge then refused the downgrade, and gstack-evals #248's /ship blocked until a review-loop exception was granted by hand. A "Finding N" lead is now a numbered heading, so the fields land on their finding and it collects at the Severity the reader wrote (GST-1031 ruling A). A line right under a numbered heading that carries its Severity and Blocks fields, with no other label and no file:line, now joins the heading too, at the stronger label, so the ledger stops counting a `Blocks: no` finding as a blocker and lead judgment may lower it with a reason. A located line there stays a finding of its own, and a heading that names CRITICAL or P0 itself keeps its blocker. Checked against 586 captured reader outputs: only four outputs of this shape changed, and no CRITICAL a reader rated as blocking was lost. (GST-1657) + +- **/autoplan now sees Lit and htm template UI, and a lean run no longer calls a skipped Design phase merged.** /autoplan's UI classifier counted a rendered file only by extension or directory, so a plan naming `plugins/web-ui/src/personas.ts` (Lit `html` templates in a `.ts` file) got no Design phase, and rows, pickers and menus were not UI words. A named non-test `.ts`/`.js` file under the repo root that holds a tagged `html`, `css` or `svg` template now counts as a rendered file, using /ship's template rule from `bin/gstack-diff-scope` (one regex, pinned byte for byte by a test), and row, list, picker, menu and section join the UI terms (near a visual noun they add Design; alone they only keep the UI checklist sections loaded). In a lean `--kind feature` run, a phase the one reviewer does not apply as a lens is now labelled with its real reason, such as `no UI scope detected (lean)`, instead of `lean run: merged into the one eng reviewer`. (GST-1750) + +- **Three /ship tooling bugs from gbrain#8730 are fixed.** When Step 16 stage 2.1's post-review Step 9 plans `NEXT: voices` on a gate-class diff, `gstack-review-dispatch pass --voices` now renders the voices that plan lists instead of printing `PASS: none`, so plan and dispatch agree. A same-tree pregate clear now survives a `--stage tests` run without `--base`, which keeps the base of the record at its head instead of taking `origin/HEAD` (when that names another branch, the run says so), and it survives a base branch that moved between runs, because the carry compares the policy's source and the gate command, not the base commit its label names (a receipt a stamp-only substitution accepted still needs the same base commit). `gstack-pregate clear --receipt` also accepts a stage run's own log, where the gate's `PREGATE_REMOTE:` line sits under `lanes:`, but only when that run's lanes check reads `requires-remote`, so the log of a run whose gate exited non-zero is refused. `gstack-review-scope summary` no longer reads a fix-check ledger in a `p` directory as an unbound pass N. It counts as the fix-check it is. (GST-1775) + ## [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) diff --git a/VERSION b/VERSION index a069f1aef..d3007641e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.97.0.0 +1.98.0.0 diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md index 9aa48df5b..c29e4b68f 100644 --- a/agents-digest/gstack-AGENTS.md +++ b/agents-digest/gstack-AGENTS.md @@ -1,4 +1,4 @@ -# gstack digest v1.97.0.0 — regenerate/re-copy after upgrading gstack +# gstack digest v1.98.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, diff --git a/changelog.d/capy-gst-1642-stable-default.md b/changelog.d/capy-gst-1642-stable-default.md deleted file mode 100644 index 653754677..000000000 --- a/changelog.d/capy-gst-1642-stable-default.md +++ /dev/null @@ -1,2 +0,0 @@ -- **New installs follow the stable channel; Capy crews stay on main.** A fresh `git clone … && ./setup` now records `update_channel: stable` the first time it runs on a machine and prints `Update channel: stable (new install)` with the command that switches, so a new user gets the twice-weekly tested release instead of every merge to main (the clone starts at main's tip and joins releases at the next cut that passes it). A gstack contributor's checkout (`bin/dev-setup`'s marker, another branch, local commits, CI) still follows main, and every install set up before this change keeps following main: nothing switches under anyone. Capy crew machines stay on main: `bin/gstack-capy-session` pins `GSTACK_UPDATE_CHANNEL=main` unless the variable names a channel, a fresh Capy machine records main, and the Capy skill bootstrapper (wrapper 5) no longer follows a machine's own channel. The README install section says which channel you get and how to switch. (GST-1642) -- **Capy threads run gstack's latest main at least daily.** When the oldest origin commit a Capy checkout lacks is more than 24 hours old, the session check's trailer says `stale=h` and the skill runs the install before the next workflow. A pin (`GSTACK_CAPY_SELF_UPGRADE=off`, `GSTACK_CAPY_AUTO_UPGRADE=0`, a detached HEAD or another branch) holds past that only with a recorded reason (`GSTACK_PIN_REASON`, `git config gstack.pinReason`, an explicit `--revision`); an unreasoned one is overridden at the workflow boundary and reported (`PIN_UNREASONED`). The upgrade never runs under a live workflow lease or eval run, never switches a checkout another session holds or onto diverged work, and a failed fetch with none successful in 24 hours is reported. (R75, GST-1642) diff --git a/changelog.d/capy-gst-1655-ship-budget-successor.md b/changelog.d/capy-gst-1655-ship-budget-successor.md deleted file mode 100644 index 3a65e5f52..000000000 --- a/changelog.d/capy-gst-1655-ship-budget-successor.md +++ /dev/null @@ -1 +0,0 @@ -- **A /ship run handed to a successor thread keeps one budget.** `gstack-ship-budget resume --meter-source --reading --credits ` (and `resume --rebuild-from-record` with the same flags) opens a new meter epoch at the successor's own counter when the named source differs from the run's, instead of refusing its readings with `BUDGET_READING_REJECTED`. Tokens and credits already spent carry, so `used`, `credits`, admission and the observe hard stop count every epoch; a reading below the last one of the same epoch is still refused. A successor resume is refused while a handoff child or a reader dispatch still spends on the previous thread's counter, so that spend is settled first instead of dropping out of the budget. `start --meter-source` records the run's source. See docs/ship-budget.md#successor-handoff (GST-1655). diff --git a/changelog.d/capy-gst-1659-state-root-isolation.md b/changelog.d/capy-gst-1659-state-root-isolation.md deleted file mode 100644 index ed1cbd073..000000000 --- a/changelog.d/capy-gst-1659-state-root-isolation.md +++ /dev/null @@ -1 +0,0 @@ -- **The update-channel tests can no longer write your real gstack config.** `test/update-channel.test.ts`'s `gstack-config` case wrote `update_channel` into whatever `GSTACK_STATE_ROOT` the shell exported whenever the run skipped this repository's `bunfig.toml` preload (a run from another directory, or `bun --config=/dev/null test`), which could flip a developer's or crew machine's update channel. The `runBin` helper's `gstackHome` option now points all three state-root variables at the test's directory, every spawn in that file starts from an environment with the state-root chain removed and HOME in a temp dir, and a new guard (`test/state-root-isolation.test.ts`) runs both update-channel test files with `GSTACK_STATE_ROOT`, `GSTACK_HOME`, `GSTACK_STATE_DIR`, a live `CLAUDE_PLUGIN_DATA` and HOME exported to sentinel directories and fails if any of them changes. The install fixture also stops treating a worktree's `node_modules` symlink as source, so `update-channel-setup.test.ts` and the other real-setup tests run from a worktree that shares its main checkout's dependencies. (GST-1659) diff --git a/changelog.d/capy-gst-1666-autoplan-gate.md b/changelog.d/capy-gst-1666-autoplan-gate.md deleted file mode 100644 index da974a59f..000000000 --- a/changelog.d/capy-gst-1666-autoplan-gate.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -bump: minor ---- -**An opt-in /autoplan gate that holds the session until the pipeline finishes (GST-1666).** In GEVA-2's G admission eval, Opus 5.5 told to run /autoplan with no human cost about 20 times a plain session and showed none of autoplan's phases in 23 sessions: nothing kept a session going until every phase was done, so it could stop after any one. With `./setup --autoplan-gate` (or `GSTACK_AUTOPLAN_GATE=on`, or `gstack-config set autoplan_gate on`), a session that loaded /autoplan can't end its final turn until Phase 0 ran scope, every applicable phase published its report (the `Phase N complete` text, then `true autoplan-published `), and the Final Approval Gate is written. A `--ceo-only` run needs Phase 1 and its `GSTACK_RESULT` end line. The block names the next step. A stop that asks you a question, a stop waiting on a native reviewer still running, and a run driven through the `gstack-autoplan` runner are never held, and after 3 blocks the next stop goes through with an `exhausted` marker. Each decision is a `GSTACK_GATE autoplan-gate` marker, and the session record keeps the run's applicable and published phases, so an eval can read phase exposure from the record instead of the model's wording; `gstack-skill-gates report --expect armed` confirms the gate engaged, and `gstack-skill-gates settings --autoplan` arms it in headless runs. Off by default; autoplan's text and un-gated runs are unchanged. Docs: docs/skill-gates.md#the-autoplan-gate. diff --git a/changelog.d/capy-gst-1676-fix-check-chain-link-1.md b/changelog.d/capy-gst-1676-fix-check-chain-link-1.md deleted file mode 100644 index 6165b09e2..000000000 --- a/changelog.d/capy-gst-1676-fix-check-chain-link-1.md +++ /dev/null @@ -1 +0,0 @@ -- **A post-review fix-check chain counts its first link, and the planner plans the next link.** In the post-review flow each granted fix-check's stop head is the commit it reads, so link 1's grant names link 1's own commit, which is also link 2's range_base. finish only accepted an earlier link's grant at a stop head before its commit, so link 1 never counted once link 2 existed and the whole chain routed as behavior (founderstar#634: `FIX_CHECK: not counted (… link 1 of 2)`). A link's grant may now name its own commit; grants are still assigned newest link first, one link per grant, so a lone grant at the boundary covers the newer link only and the older link stays ungranted. `gstack-review-plan --post-review --last fix-check --range-base --grant ` now plans the next chained fix-check instead of a stop that asked for the grant it then ignored, and refuses the last link's own grant, so lanes no longer hand-render the dispatch. (GST-1676) diff --git a/changelog.d/capy-gst-1677-defer-upgrade-open-run.md b/changelog.d/capy-gst-1677-defer-upgrade-open-run.md deleted file mode 100644 index 9362ec1aa..000000000 --- a/changelog.d/capy-gst-1677-defer-upgrade-open-run.md +++ /dev/null @@ -1,2 +0,0 @@ -- **A Capy install run inside an open /ship or review no longer upgrades gstack under it.** During a /ship on gstack-evals #252, the docs audit's session check ran `install.sh --for ` and moved gstack from 1.96.0.0 to 1.97.0.0 mid-run: the workflow lease that was meant to stop it only counts for 120 minutes, and a long /ship outlives it. The Capy session now also looks for a run that is still open on this machine's state root: a /ship budget ledger with no `closed_at` that was written in the last 6 hours, or a review start that `gstack-review-log --finish` has not consumed and that is under 120 minutes old. While one is open the session keeps the installed gstack, prints `SELF_UPGRADE: deferred (...)` naming the run, and still ends `gstack: ok `; once the ledger closes, the next install upgrades as before. Below the supported minimum nothing changes, and the floor still refuses or fast-forwards. (GST-1677) -- **Smaller fixes on the same upgrade path.** The eval-lock probe now works without the `flock` binary (python3 or perl stand in, as on macOS). When an unreasoned pin is overridden, the session takes the setup lock before it switches branches and hands it to `gstack-upgrade`, so no other updater sees the checkout between the switch and the fast-forward; an older `gstack-upgrade` on the local default branch, which cannot take the lock over, gets it released and takes it itself, so it still upgrades. A default branch the override creates now tracks origin. A failed restore after a refused fast-forward says `pin not restored` with the command that puts HEAD back, instead of claiming the pin was kept. The no-fetch stale check reads file times with `stat` instead of GNU-only `date -r`. A pin reason can no longer add its own `fix:` note to the trailer, and a reason with nothing pinned is not reported as a pin. The no-fetch fix note names the install command even when the bootstrapper's path is unknown. (GST-1658) diff --git a/changelog.d/capy-gst-1681-granted-third-fix-round.md b/changelog.d/capy-gst-1681-granted-third-fix-round.md deleted file mode 100644 index f7da7362b..000000000 --- a/changelog.d/capy-gst-1681-granted-third-fix-round.md +++ /dev/null @@ -1 +0,0 @@ -- **A Captain-granted fix round after a fix-check is planned, not hand-run.** After round 2's fix-check and one granted fix-check, an owner who granted one more round at the last fix-check's stop head got `NEXT: stop` from `gstack-review-plan`, because in the loop a fix-check never earned another. The lane ran the fix-check by hand on a fix-range token, and finish printed `FIX_CHECK: not counted (fix-range pass; only a later full pass can clear review)` (gstack-evals#254). A new recorded grant naming the last fix-check's commit (`--range-base`) now plans one more scoped fix-check, the next link of the fix-check chain finish already counts; the previous link's grant (one recorded before a later logged fix-check) is refused and a grant at any other commit still stops, so one grant buys one link. (GST-1681) diff --git a/changelog.d/capy-gst-1688-remote-gate-stands-in.md b/changelog.d/capy-gst-1688-remote-gate-stands-in.md deleted file mode 100644 index c4deabf41..000000000 --- a/changelog.d/capy-gst-1688-remote-gate-stands-in.md +++ /dev/null @@ -1 +0,0 @@ -- **A cleared remote gate now satisfies a /ship lane whose inventory refuses its own tree.** gbrain's lane inventory refuses, on purpose, any tree that changes its CI-reuse files, so `gstack-ship-release accept` stopped with `CI_CARRY: none label=unit (inventory_failed: …)` even after `gstack-pregate --remote` had cleared the identical tree, and every such PR needed `--no-lanes-approved` plus a manual approval (GBRA-345). Now, when the carry refuses as `inventory_failed` and `.gstack/tmp/pregate.json` verifies for HEAD's exact tree with the remote gate's own report for that tree (tests above 0, not a stamp-only substitution), that receipt satisfies the lane: accept prints `CI_CARRY: remote_gate label= receipt=…` naming the substitution and records the lane as `source=remote_gate` in the evidence ledger. A gate cleared for any other tree, or a receipt naming another tree, still stops, with a `CARRY_DIAGNOSIS:` line saying why. A CI run whose `tree.json` lists no files (a rerun of only the failed jobs) now says to dispatch a fresh full run instead of a bare `no_tests`. (GST-1688) diff --git a/changelog.d/capy-gst-1689-stamp-docs-carry.md b/changelog.d/capy-gst-1689-stamp-docs-carry.md deleted file mode 100644 index e9dfdacc3..000000000 --- a/changelog.d/capy-gst-1689-stamp-docs-carry.md +++ /dev/null @@ -1 +0,0 @@ -- **A restamp plus a plain docs edit after the targeted read no longer forces a third review round.** `gstack-ship-receipt write` and `gstack-review-plan` now decide "the product changed after the read" with one classifier: a path whose change is a release stamp under the base policy's stamp rule (`stamp_files`, `stamp_paths`, `stamp_patterns`, the version file, manifests, lockfiles and the CHANGELOG re-heading or fragment fold) always carries the targeted record, and docs and CHANGELOG edits carry under the existing plain-docs rule, which still refuses a numeric, money, security or other claim. Before, /ship's restamp plus its docs audit made write refuse the record while the plan printed `NEXT: done`, so only `--post-review` (adversarial plus the outside voice) could finish (GBRA-345, gbrain #8727). A code change after the read still drops the record, and a docs claim beside a restamp now gets the one targeted reader instead of a dead end. (GST-1689) diff --git a/changelog.d/capy-gst-1695-fixture-sweep.md b/changelog.d/capy-gst-1695-fixture-sweep.md deleted file mode 100644 index f6ebc73a9..000000000 --- a/changelog.d/capy-gst-1695-fixture-sweep.md +++ /dev/null @@ -1 +0,0 @@ -- **A test run's fixture repositories no longer quarantine a /ship review pass.** /ship's fetch sweep counted any new git repository with a network remote under the temp dir as a reader's fetch, so a lane's background `bun test`, or a reader's own run that hit Bun's 5 s timeout before its cleanup, left fixtures such as `/tmp/ship-bin-*` (origin `https://github.com/fx/fx.git`) that quarantined the stage and then stopped its rerun with `FETCH_RERUN_BLOCKED`; three /ship passes were lost that way on 11-06 and 11-07. Every `bun test` process now puts its temp paths under one root carrying a `.gstack-test-fixture` marker (`TMPDIR` points there, so children spawned with the test's environment follow; a `Bun.spawn` child given no `env` keeps the original temp dir) and removes it after the last file, even after a timeout. Under the free and paid shard runners the shard's own sandbox carries the marker instead, so a shard's `TMPDIR` stays exactly as the runner set it. The sweep skips a marked root below a sweep root and a repository whose remotes resolve to a local path through `url..insteadOf`, and prints a `FETCH_IGNORED:` line for each; an unmarked clone with a real network remote still quarantines the stage and blocks its rerun. (GST-1695) diff --git a/changelog.d/capy-gst-1698-cut-plan-eng-review.md b/changelog.d/capy-gst-1698-cut-plan-eng-review.md deleted file mode 100644 index e0db4392b..000000000 --- a/changelog.d/capy-gst-1698-cut-plan-eng-review.md +++ /dev/null @@ -1 +0,0 @@ -- **`/plan-eng-review` is cut; `/autoplan` keeps its eng phase (GST-1698).** Two evals found no gain from the standalone skill. In the seeded-flaw panel, plain Opus 5.5 with no gstack found the planted engineering flaws in 15 of 16 reviews. In the wave 1 outcome test, it built 22 of 22 planned features correctly, and a session told to use the skill cost 7.1 times a plain one (GST-1218, gstack-evals#253). `./setup` no longer installs `/plan-eng-review` on any host, and the next `./setup` or upgrade removes an existing gstack-owned `plan-eng-review` / `gstack-plan-eng-review` entry. A skill you wrote yourself under that name is left alone. The entry is retired through the `disabled_skills` path and can't be re-enabled. Its methodology stays in `plan-eng-review/` as `/autoplan`'s Phase 3, byte for byte. On Codex, Kiro and the other external hosts, that methodology now ships beside `gstack-autoplan/SKILL.md` as `plan-eng-review.md`, because those hosts register every nested `SKILL.md`. To review a plan's architecture and tests, run `/autoplan`; its description now names architecture and engineering-review requests, so they route there. The router, the office-hours handoff and the CEO, design and devex review chaining now point there and no longer call eng review a "required gate". `/autoplan` also skips that chaining step inside its own run. `/ship` still accepts `review` or autoplan's `plan-eng-review` log row for Eng Review, and its over-200-lines hint now names only `/autoplan`. diff --git a/changelog.d/capy-gst-1698-retired-followups.md b/changelog.d/capy-gst-1698-retired-followups.md deleted file mode 100644 index 5c7bcaa78..000000000 --- a/changelog.d/capy-gst-1698-retired-followups.md +++ /dev/null @@ -1 +0,0 @@ -- **The dots and generic harness targets no longer register the retired /plan-eng-review.** `gstack-harness register --target dots|generic` listed every committed skill except `gstack`, so after #4108 it still put `plan-eng-review` in its router and, in `all` mode, gave it a folder. It now skips every skill in `RETIRED_SKILLS`; `plan-eng-review/` stays in the source tree as /autoplan's Phase 3 methodology. The paid cases written for the standalone skill each have a recorded fate in `docs/TEST_PORTFOLIO.md`: four that check Phase 3 methodology or its required outputs stay (`plan-eng-review`, `plan-eng-coverage-audit`, `plan-eng-review-artifact`, `journey-plan-eng`), six that checked only the standalone interactive entry are deleted along with their rows in the AUQ matrix and the plan-mode no-op file, and `plan-eng-multi-finding-batching` leaves the weekly lane through `PERIODIC_CI_EXCLUDE` until its counter helpers are removed. (GST-1698) diff --git a/changelog.d/capy-gst-1700-non-ancestor-targeted-read.md b/changelog.d/capy-gst-1700-non-ancestor-targeted-read.md deleted file mode 100644 index d5eb0b348..000000000 --- a/changelog.d/capy-gst-1700-non-ancestor-targeted-read.md +++ /dev/null @@ -1 +0,0 @@ -- **`gstack-ship-receipt write` now says why a targeted record from a rewritten commit is refused.** When the commit the targeted reader read is not an ancestor of HEAD (usually after a rebase or a redone merge resolution), the RECEIPT_INVALID message now names that commit, says it is not an ancestor of HEAD, and says the record carries only from HEAD or an ancestor of it, even when the product patch is unchanged, so the targeted reader must run on HEAD. Before, it said the product changed after the read while printing two equal product patch ids, and lanes went looking for a product change that wasn't there (qm#2952). What passes and fails is unchanged. (GST-1700) diff --git a/changelog.d/capy-gst-1701-ceo-gate-settle.md b/changelog.d/capy-gst-1701-ceo-gate-settle.md deleted file mode 100644 index c360b495c..000000000 --- a/changelog.d/capy-gst-1701-ceo-gate-settle.md +++ /dev/null @@ -1 +0,0 @@ -**The /plan-ceo-review gate no longer judges a transcript Claude Code hasn't finished writing (GST-1701).** Claude Code can fire Stop about 200 ms before it flushes the turn's last transcript lines. The CEO review gate read the transcript once, so a finished review written outside the repository (a plan under `~/.claude/plans`) could be held because its Write wasn't on disk yet, and a run whose load hadn't landed could stop without being checked. The gate now waits, like the /autoplan gate, until the transcript holds the stop's final message (at most 2 seconds or half its deadline). Both gates share one helper, `settledTranscript` in `lib/skill-gates.ts`. diff --git a/changelog.d/capy-gst-1702-post-review-stamps.md b/changelog.d/capy-gst-1702-post-review-stamps.md deleted file mode 100644 index 4226a45a2..000000000 --- a/changelog.d/capy-gst-1702-post-review-stamps.md +++ /dev/null @@ -1 +0,0 @@ -- **Release stamps and a docs-audit edit after the last fix-check no longer stop /ship for an owner grant.** `gstack-review-plan --post-review --last fix-check` now reads the edits since the fix-check's commit with the receipt's classifier (`docsChangeSince`): lines the base policy's `stamp_files` and `stamp_patterns` cover (VERSION, `package.json`, `.claude-plugin/plugin.json`, a `since: 'TBD'` row moved to the version) and docs edits are no gate-class edit, so the plan prints `NEXT: done`, or `NEXT: targeted` when a docs claim on a money, gate, auth or secret diff needs the targeted read. Before, any such edit planned `NEXT: stop` ("the fix-check edited a gate-class path"), and GBRA-341 (gbrain #8705) needed a second grant and a fix-check on a 39-line stamp delta. A stamp match is line-exact, so any other line in a stamp file still stops, as do any code edit, an executable or symlink file with a docs name, and a commit whose message the gate scan tags as a CI skip marker. (GST-1702) diff --git a/changelog.d/capy-gst-1703-contract-hook-settle.md b/changelog.d/capy-gst-1703-contract-hook-settle.md deleted file mode 100644 index 88f733f7f..000000000 --- a/changelog.d/capy-gst-1703-contract-hook-settle.md +++ /dev/null @@ -1 +0,0 @@ -**The skill contract Stop hook no longer judges a transcript Claude Code hasn't finished writing (GST-1703).** Claude Code can fire Stop about 200 ms before it flushes the turn's last transcript lines. The contract hook read the transcript once, so a finished /investigate run could be blocked for a missing repro because its final test rerun wasn't on disk yet, and a run whose skill load hadn't landed could stop unchecked. The hook now re-reads the transcript, up to 2 seconds, until it holds the stop's final message. It uses the same loop as the /autoplan and /plan-ceo-review gates (`readSettledTranscript` in `lib/skill-gates.ts`). diff --git a/changelog.d/capy-gst-1713-github-poll-floor.md b/changelog.d/capy-gst-1713-github-poll-floor.md deleted file mode 100644 index cc63960a3..000000000 --- a/changelog.d/capy-gst-1713-github-poll-floor.md +++ /dev/null @@ -1 +0,0 @@ -- **gstack stops draining the shared GitHub GraphQL quota.** `gstack-pr-wait` read the pull request through a GraphQL query and `gh pr checks` every 20 seconds (as often as every 5), about 360 GraphQL calls an hour per watcher, and the Capy app installation's 7,700-point hourly budget ran out right after each reset, stalling merges across crews. It now reads the PR state, base head and head-commit checks from REST, and makes its one GraphQL read (comments, reviews and review-thread resolution) only when the PR changed, every 10 minutes, or once green under `--until ready`. Every gstack wait between GitHub reads now goes through `lib/gh-poll.ts`, which puts a 60-second floor plus up to 15 seconds of jitter under it: `gstack-pr-wait` (was 20 s), `gstack-ci-gate --wait` and its registration re-poll (were 30 s and 10 s), `gstack-ci-carry wait` (was 20 s), `gstack-ship-release accept` (was 15 s), `gstack-greptile-early wait` (was 30 s, and its head read moved to REST), the merge-train and land waits (were 30 s, and 3 s for land's merge readback) and eval-bg's run follow (was 10 to 30 s). A gh call that fails with a rate-limit message is no longer retried at once. The gh runners read `gh api rate_limit`, sleep until that budget resets (at least 60 s) and retry at most twice. The one exception is `gh pr merge`, which runs under the land lock and still fails at once. `test/gh-poll-tripwire.test.ts` fails on any new wait in a GitHub-reading module that skips the floor. (GST-1713) diff --git a/changelog.d/capy-gst-1717-retire-investigate.md b/changelog.d/capy-gst-1717-retire-investigate.md deleted file mode 100644 index cac3a51b9..000000000 --- a/changelog.d/capy-gst-1717-retire-investigate.md +++ /dev/null @@ -1 +0,0 @@ -- **`/investigate` is cut (GST-1717).** A preregistered eval found no gain: on 18 real bug-fix tasks that plain Opus 5.5 solves about half the time, 20 trials each, Opus told to use `/investigate` passed 50.0% of trials against 47.2% with no skill (+2.8 points, 95% CI -5.8 to +11.4, below the 10-point lift the run could detect), and a passing fix cost about 43% more ([GEVA-380 R3c](https://github.com/garrytan/gstack-evals/blob/c90ab277/wiki/board/2026-11-07-geva-380-r3c-results.md)). `./setup` no longer installs it on any host, and the next `./setup`, relink or upgrade removes an existing gstack-owned `investigate` / `gstack-investigate` entry, even though its source is gone. A skill you wrote yourself under that name is left alone, and the entry is retired through the `disabled_skills` path so config can't re-enable it. Because the source is gone, gstack can't tell its own unedited copy from one you edited, so a real-file `SKILL.md` left in such an entry is moved to the backup root instead of deleted. Relink does this for the Claude skills folder, and setup does it for every copied `gstack-investigate` entry on another host, in a separate backup folder per host, and lists each one in its "Moved N customized SKILL.md file(s)" line. Your own files in that directory stay. The opt-in /investigate gate goes with it: `./setup` removes its four Claude Code hooks from settings, `gstack-config`, `gstack-skill-gates` and `gstack-skill-route` drop its switch, and `./setup` now rejects `--investigate-gate` and `--no-investigate-gate` as unknown options, so drop them from install scripts. If setup can't remove those hooks, it now prints a warning with the command to rerun. The shared contract stop hook and gate machinery stay, because `/review`, the `/plan-ceo-review` gate and the `/autoplan` gate use them. Bug reports now get fixed directly. That means one read-only check of the cause, and when it is still unclear, a focused test that fails on the bug before any edit, then the root-cause fix and a rerun. The router, the CLAUDE.md routing block gstack offers, `/ship`'s test-failure triage, `/ios-fix`, `/learn`, `/verify` and the docs say so and no longer name `/investigate`. One line still does: `/review`'s checklist routes timing flakes to `/investigate` until its no-loss replay and repin land (GST-1744), because that checklist is pinned to replay-tested bytes. diff --git a/changelog.d/capy-gst-1718-template-ui-scope.md b/changelog.d/capy-gst-1718-template-ui-scope.md deleted file mode 100644 index d0eb07320..000000000 --- a/changelog.d/capy-gst-1718-template-ui-scope.md +++ /dev/null @@ -1 +0,0 @@ -- **/ship now notices a screen written as a Lit or htm template inside a .ts or .js file.** `gstack-diff-scope` counted only CSS, .tsx, views and similar files as frontend, so a pull request that rendered its UI from tagged `html`` templates in plain TypeScript read `SCOPE_FRONTEND=false`, and /ship skipped its design-lite check and the design reviewer without saying so. qm#3186 shipped three visible spacing problems that way. A changed non-test .ts/.js file that holds a tagged `html`, `css` or `svg` template now counts as frontend when a changed line sits inside one of those templates (a CSS declaration or a `${}` binding counts) or carries markup. Logic-only edits, comments and TypeScript generics don't count. SCOPE_BACKEND is unchanged, so those files still get the backend reviewers. Replayed on the last 100 merged gstack PRs and the last 100 merged qm PRs, no scope flag was lost, and only SCOPE_FRONTEND gained (qm only). On a rendered-UI diff the ship receipt now records `design: { mode: static, reason: no preview declared }`, and the PR body's Design Review line says the same: no rendered /design-review runs inside /ship yet. (GST-1718) diff --git a/changelog.d/capy-gst-1747-installer-bun-isolation.md b/changelog.d/capy-gst-1747-installer-bun-isolation.md deleted file mode 100644 index 2720a7323..000000000 --- a/changelog.d/capy-gst-1747-installer-bun-isolation.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -important: security ---- -- **The Capy installer reads project pins with an isolated Bun.** `bin/gstack-capy-install` runs `bin/gstack-runtime-pins.ts` from the caller's project directory to read its Bun and Node pins. Those three calls now pass `--no-env-file --config="${GSTACK_BUN_CONFIG:-/dev/null}"` like every other Bun gstack starts, so a project's `bunfig.toml` and `.env` no longer load into the installer. `test/bun-entry-isolation.test.ts` now also checks Bun runners held in a variable (`"$RUNNER" x.ts`, and any variable whose name says Bun running `-e` or a script) in shipped shell files. (GST-1747) diff --git a/changelog.d/capy-gst-1747-make-pdf-local-images.md b/changelog.d/capy-gst-1747-make-pdf-local-images.md deleted file mode 100644 index 4b50f72b6..000000000 --- a/changelog.d/capy-gst-1747-make-pdf-local-images.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -important: security ---- -- **make-pdf reads local images only from the markdown's own directory, and only image files.** An image reference that resolves outside the input markdown's directory (a `../` path, an absolute path, a `file:` URL or a symlink pointing out) is no longer read: it renders as a visible `[image blocked (outside the document directory): …]` placeholder with a warning, and `--strict` still fails the run. A local file that is not a png, jpeg, gif, webp or svg image renders as an `[image blocked (not an image): …]` placeholder instead of being inlined, and fails the run under `--strict`. The make-pdf skill and docs/howto-diagrams-and-formats.md describe the new defaults. (GST-1747) diff --git a/changelog.d/capy-gst-1747-tunnel-web-urls.md b/changelog.d/capy-gst-1747-tunnel-web-urls.md deleted file mode 100644 index 77428837f..000000000 --- a/changelog.d/capy-gst-1747-tunnel-web-urls.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -important: security ---- -- **Tunnel-paired browse agents stay on web URLs: they can't read local files, can't choose a path on the daemon's disk, and can't make the daemon save screenshot or snapshot images.** Over the `/pair-agent` tunnel, `goto` and `newtab` now accept only http(s) URLs, `screenshot` requires `--base64` (it returns the image) and refuses an output path, `snapshot` refuses `-a`, `-H` and `-o`, and `eval`, which runs a file from the daemon's disk, leaves the tunnel allowlist (now 25 commands). The tunnel's 403 hint names these limits. Local CLI, skill and extension clients behave exactly as before. (GST-1747) diff --git a/changelog.d/capy-gst-1751-plan-record.md b/changelog.d/capy-gst-1751-plan-record.md deleted file mode 100644 index f24125aba..000000000 --- a/changelog.d/capy-gst-1751-plan-record.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -bump: minor ---- -**Crew PRs now show whether they had their plan step, and a repo can require it (GST-1751).** In qm, a Captain's brief replaced /autoplan with "light plan … no gate to Garry" for a new UI feature (qm#3186), and nothing noticed. A PR now carries its brief's `Plan:` line under `## Approvals` (`gstack-approvals render --plan ""`): `approved: `, `exempt: ` from docs/captain.md's list, or `required`. /ship's finish step (`gstack-ship-release finish --plan ""`) and `gstack-ship-queue ready` resolve it against every branch's approval rows and print `PLAN:`; ready writes the result into the READY note, and `land` (direct, stamp and train) checks the note still matches the PR body. A gate the lane answered in its own words, a newer gate revision that isn't approved, and a ruling or grant row never count as the owner's plan approval; imports now record `words_by` for that. gstack never guesses from the diff or the title whether a change is a feature. No line reads as `required`, and a diff of changelog fragments only needs none. The new `plan_record` ship-policy key is `off` by default; Capy crew sessions warn, and `require` (policy or `GSTACK_PLAN_RECORD=require`) refuses with one of six codes, each with a fix. `gstack-approvals plan-check --plan ""` checks a line by hand. Replayed on the last 60 merged gstack PRs with each PR's brief line, 57 pass and 3 would be refused: two features Garry approved where no approval row was ever written (#4100, #4055), and one whose gate the lane answered in its own words (#4049). diff --git a/changelog.d/capy-keep-the-gbrain-database.md b/changelog.d/capy-keep-the-gbrain-database.md deleted file mode 100644 index 159e70c7c..000000000 --- a/changelog.d/capy-keep-the-gbrain-database.md +++ /dev/null @@ -1,2 +0,0 @@ -- **/setup-gbrain's source wireup step no longer puts the gbrain database URL on the command line.** The step now runs `gstack-gbrain-source-wireup --strict` without `--database-url`, with `GBRAIN_DATABASE_URL` and `DATABASE_URL` cleared for the call, so the helper reads `~/.gbrain/config.json` once at startup and pins that URL for every `gbrain` call it makes, as the flag did before; a `DATABASE_URL` in the user's shell can't redirect it. The URL holds the database password, and command-line arguments are visible to other local users in `ps`. A new test keeps every setup-gbrain doc from passing the URL to the helper again. (GST-1747) -- **The redaction gate blocks more credential formats.** `lib/redact-patterns.ts` adds HIGH patterns for OpenRouter keys (`sk-or-v1-`), GitHub App user-to-server and refresh tokens (`ghu_`, `ghr_`), Stripe live restricted keys (`rk_live_`), Supabase secret keys (`sb_secret_`) and personal access tokens (`sbp_`), and `aws.access_key` now also matches temporary `ASIA` key IDs. `gstack-redact`, the pre-push hook and every skill that scans through the redaction engine pick them up. (GST-1747) diff --git a/changelog.d/capy-require-the-daemon-secret.md b/changelog.d/capy-require-the-daemon-secret.md deleted file mode 100644 index f68d745c6..000000000 --- a/changelog.d/capy-require-the-daemon-secret.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -important: security ---- -- **The design board daemon and the iOS QA daemon now serve only local clients that address them directly.** The design daemon keeps a per-daemon secret in its private (0600) state file and serves every route except `/health` under that secret, so board URLs from `$D compare --serve` now look like `http://127.0.0.1:N//boards//`. The board page, feedback, progress polling and `BOARD_URL`-based reloads keep working unchanged, and `$D daemon stop` reads the secret from the state file. Both daemons answer 403 to requests whose Host is not a loopback address for their port, to requests with no Host header, and to requests whose Origin is another site. The iOS QA loopback also answers 403 to browser requests that `Sec-Fetch-Site` marks as cross-site or same-site; local agent clients send no such header and work as before. A board link opened from before a design daemon restart now shows a "Board expired" page instead of a bare 401, and the board page's progress polling stops at the first error response instead of reading it as progress. The iOS QA tailnet listener is unchanged. (GST-1747) diff --git a/changelog.d/gst-1657-finding-lead-fields.md b/changelog.d/gst-1657-finding-lead-fields.md deleted file mode 100644 index 06a3d7b5d..000000000 --- a/changelog.d/gst-1657-finding-lead-fields.md +++ /dev/null @@ -1 +0,0 @@ -- **A native finding led by `Finding 1 (FIXABLE)` keeps its own Severity and Blocks lines.** The review collector treated only `**1. …**` as a numbered finding heading, so when the native adversarial reader opened a finding with `**Finding 1 (FIXABLE): …**` and put numbered steps or more paragraphs before its `Severity: Medium` and `Blocks: no` lines, those lines split off onto a phantom second record and the finding itself collected CRITICAL from its FIXABLE tag. The judge then refused the downgrade, and gstack-evals #248's /ship blocked until a review-loop exception was granted by hand. A "Finding N" lead is now a numbered heading, so the fields land on their finding and it collects at the Severity the reader wrote (GST-1031 ruling A). A line right under a numbered heading that carries its Severity and Blocks fields, with no other label and no file:line, now joins the heading too, at the stronger label, so the ledger stops counting a `Blocks: no` finding as a blocker and lead judgment may lower it with a reason. A located line there stays a finding of its own, and a heading that names CRITICAL or P0 itself keeps its blocker. Checked against 586 captured reader outputs: only four outputs of this shape changed, and no CRITICAL a reader rated as blocking was lost. (GST-1657) diff --git a/changelog.d/gst-1750-autoplan-ui-scope.md b/changelog.d/gst-1750-autoplan-ui-scope.md deleted file mode 100644 index ffb0fd2f5..000000000 --- a/changelog.d/gst-1750-autoplan-ui-scope.md +++ /dev/null @@ -1 +0,0 @@ -- **/autoplan now sees Lit and htm template UI, and a lean run no longer calls a skipped Design phase merged.** /autoplan's UI classifier counted a rendered file only by extension or directory, so a plan naming `plugins/web-ui/src/personas.ts` (Lit `html` templates in a `.ts` file) got no Design phase, and rows, pickers and menus were not UI words. A named non-test `.ts`/`.js` file under the repo root that holds a tagged `html`, `css` or `svg` template now counts as a rendered file, using /ship's template rule from `bin/gstack-diff-scope` (one regex, pinned byte for byte by a test), and row, list, picker, menu and section join the UI terms (near a visual noun they add Design; alone they only keep the UI checklist sections loaded). In a lean `--kind feature` run, a phase the one reviewer does not apply as a lens is now labelled with its real reason, such as `no UI scope detected (lean)`, instead of `lean run: merged into the one eng reviewer`. (GST-1750) diff --git a/changelog.d/gst-1775-ship-tooling-fixes.md b/changelog.d/gst-1775-ship-tooling-fixes.md deleted file mode 100644 index ce560de88..000000000 --- a/changelog.d/gst-1775-ship-tooling-fixes.md +++ /dev/null @@ -1 +0,0 @@ -- **Three /ship tooling bugs from gbrain#8730 are fixed.** When Step 16 stage 2.1's post-review Step 9 plans `NEXT: voices` on a gate-class diff, `gstack-review-dispatch pass --voices` now renders the voices that plan lists instead of printing `PASS: none`, so plan and dispatch agree. A same-tree pregate clear now survives a `--stage tests` run without `--base`, which keeps the base of the record at its head instead of taking `origin/HEAD` (when that names another branch, the run says so), and it survives a base branch that moved between runs, because the carry compares the policy's source and the gate command, not the base commit its label names (a receipt a stamp-only substitution accepted still needs the same base commit). `gstack-pregate clear --receipt` also accepts a stage run's own log, where the gate's `PREGATE_REMOTE:` line sits under `lanes:`, but only when that run's lanes check reads `requires-remote`, so the log of a run whose gate exited non-zero is refused. `gstack-review-scope summary` no longer reads a fix-check ledger in a `p` directory as an unbound pass N. It counts as the fix-check it is. (GST-1775) diff --git a/package.json b/package.json index 88771d54f..33792b7c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.97.0", + "version": "1.98.0", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module",