diff --git a/CHANGELOG.md b/CHANGELOG.md index 89bb3ce23..bef44d81d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,45 @@ # Changelog +## [1.99.1.0] - 2026-11-11 + +- **/ship now accepts a granted follow-up review that starts after a docs-only commit.** When the docs commit /ship writes after its review landed before the owner granted one more scoped review of a fix, that review's range started at the docs commit, and `gstack-ship-release finish` refused it with `FIX_CHECK: not counted (range_base does not hold the reviewed tree)`, so the reviewed fix counted as unreviewed code. finish now accepts the range when every commit between the reviewed commit and the range start changes only docs or release metadata, and prints a `FIX_CHECK_DOCS_BRIDGE:` line naming those commits. The docs still get their usual recheck. A code or test commit in between, a merge, a range that starts before the reviewed commit, and a range that starts on another branch still refuse. + +- **Brain sync now scans binary files and catches an auth value added under an existing key.** Git shows a file with a NUL byte (or UTF-16 text) as "Binary files differ" instead of its contents, so the push-time secret scan never saw it and an AWS key inside such a note was pushed. The scan now diffs every file as text and also reads lines and edited blocks that hold NUL bytes as UTF-16 and with the NULs removed; if git still reports a file as binary instead of showing its bytes, the file is held as `binary-not-scanned`. A Bearer value added on the line under an unchanged `"authorization":` key also slipped through, because the diff's `+` marker broke the match; each edited block is now also scanned as the new file text, in UTF-16 files too, and a second reading of the diff always shows three unchanged lines around each edit so a `diff.context` setting of 0 cannot hide the key. The diff git normally shows, with your `diff.context` and any textconv driver, is still scanned too, so nothing the old scan held is lost. The pre-commit hook that `gstack-artifacts-init` and `gstack-brain-restore` install gets the same fix; rerun `gstack-artifacts-init` (or `gstack-brain-restore` on a machine you restored) to refresh it. docs/gbrain-sync.md now says plainly that a secret already committed in `~/.gstack` stays held after you delete it, and only `--skip-file` clears it. + +- **A secret split across two lines of UTF-16 text is now caught, and deleting a large binary no longer blocks a push.** The secret check and the pre-push hook read a file git calls binary three ways (as bytes and as little- and big-endian UTF-16), but they kept all three readings on one line. So in a UTF-16 file, an AWS secret key on the line under its `aws_secret_access_key:` key passed both guards, because the keyword line ended with another reading. Each reading is now scanned as its own text with the file's line numbers, so that pair fails in UTF-16LE and UTF-16BE as it does in plain text. The pre-push hook also leaves deleted files out of the diff it scans (`--irreversible-delete`). A delete cannot add a secret, and the old bytes of a deleted 70 MiB binary used to fill the hook's 64 MiB buffer and block the push with `git diff failed (status=killed/overflow)`. Adding a binary that large still blocks, and a replaced binary's old bytes still count toward the limit. + +- **The free test suite is green on main again.** Two `gstack-ship-queue ready` tests still wrote a pre-merge check record with a single check, which `ready` has refused since the pre-merge check started requiring every registered check (GST-2194). Their fixtures now record every check, so the tests exercise what they were written for. No product behavior changed (GST-2282). + +- **Under a merge-train policy, `gstack-ship-queue ready` no longer reports READY when its note never reached origin.** `land --pin` reads the READY note from origin's `refs/notes/gstack-trains`, but `ready` exited 0 even when it couldn't push that note, so a lane sent READY and the land was refused with `QUEUE_NOT_READY` (#4245 lost a land round this way). After pushing, `ready` now fetches origin's notes ref back and prints `notes pushed` only when origin holds the note. If three tries fail, or the push exits 0 and origin still lacks the note, it refuses with `QUEUE_READY_NOTE_UNPUSHED` and keeps the local receipt, so rerunning the same `ready` command once the push works records it (GST-2242). + +- **The CI carry now accepts a run where only an unrelated job was rerun.** Rerunning one job of a green run (a flaky `public-export`, say) made GitHub number the run's newest attempt 2 while the checks job, which wasn't rerun, still carried its attempt-1 `tree.json`, so `accept` refused with `artifact_mismatch`; rerunning the checks job too left two same-named artifacts and refused with `artifact_missing`. Run verification now works out which attempt each profile job actually ran in (GitHub relists a job it didn't rerun with its old start and finish times) and checks `tree.json` against that attempt, and with one artifact per attempt it reads the one from the uploading job's newest attempt. Every other field (head, merge commit, tree, event, job conclusion) is still compared exactly, and an older attempt's artifact is never read in place of the newest. The 24-hour freshness check now dates a run by its own test jobs, so rerunning an unrelated job never makes days-old results look fresh. When the pregate's skipped-tests command fails because the checkout has no installed dependencies, the refusal now says so (GST-2284, harness-evals HARN-56). + +- **The QM edition no longer includes `gstack-review`.** QM's default gstack skills are now just `gstack-plan`. A benchmark of the edition inside QM found that `gstack-review` flagged too many problems without making the reviewed work any better, so it was cut. To update a QM deployment, delete its `sandbox/skills/gstack-review/` folder and run `qm up`. gstack's own `/review` skill is unchanged. + +- **Brain sync now catches secrets hidden behind JSON escapes, and its pre-commit hook checks every family the sync does.** A hand-written learnings or timeline row could slip a Stripe key past the push-time scan by writing it with `\u` escapes, or slip an auth header past it inside an escaped JSON string. The scan now decodes each added line's JSON strings and scans them as well as the raw diff, so both are held. The pre-commit hook that `gstack-artifacts-init` and `gstack-brain-restore` install, which catches a manual `git commit` in `~/.gstack`, checked only the original six families; it now runs the same scanner as brain sync (`bin/gstack-brain-secret-scan.py`), so it also refuses Stripe live keys, Slack and npm tokens and database URLs with a password. Rerun `gstack-artifacts-init` to refresh a hook written by an older gstack. + +- **Six test and eval checks that misread correct work, or raced their own fixtures, now read it right (2026-11-10 daily flaky pass, GST-2338).** ship-docsync-store's fixture interface now declares the `gstack-docs-candidate attempt --state ` call that /ship's documentation step has asked for since #4192, so a model that follows the step is no longer stopped by the fixture (census 42748129558). tpa-absent-darwin no longer counts a gstack-browser option as an Aside drive just because it says Aside isn't available. tpa-present's defer check counts a "the step stays blocked on you" status line only when the option puts the step off ("later", "not yet", "not now"), so a status line after another instruction no longer passes as a defer offer. scrape-prototype-path also credits `$B goto >/dev/null && $B html` when the call proves the goto exited 0. Two free tests stop racing themselves: the CSO lease contender publishes its result file by rename, so the test never reads it empty, and the registry-socket deadline test sets a deadline that expires after the watchdog's startup handshake instead of inside it. No threshold, trial count, case kind or retry changed. + +- **The doctor floor-row test no longer fails every release that adds no policy key (GST-2441).** test/policy-floor-stable.test.ts now expects a lagging checkout to read policies as the newest policy key gstack knows, computed from the same key table validatePolicy reads, instead of the repo's own VERSION. Before this, any release stamp past the newest key (v1.99.1.0 in #4291) turned free-tests red. Test-only change; no product code moved. + +- **/review and /ship start again on Windows under Codex, Cursor and the other non-Claude hosts.** On Windows, setup copies gstack's `bin/` and `lib/` into each host's runtime folder instead of linking them, and it left out three `scripts/` files those commands import. The review planner that /review and /ship run first stopped with `Cannot find module '../scripts/resolvers/fix-check'`, and the commands behind /retro and /write-skill had the same gap. Setup now copies `scripts/resolvers/fix-check.ts`, `scripts/discover-skills.ts` and `scripts/lib/ci-history.ts` too, and a test follows the static imports of the commands each host's skills run, so a new static import outside the runtime folder fails CI. Thanks to @SatriaSuriaAriffin, who found and reported the fix-check gap (#4275). + +- **An owner's ruling now clears the new blockers a granted fix-check found, so /ship can finish with them listed as open notes.** When a granted fix-check finds a new CRITICAL, it logs the finding `open` and stops for the owner. The owner can rule it a disclosed note with `gstack-approvals rule`, but `gstack-ship-release finish` rejected the fix-check as unfinished before it looked at any ruling. It also ignored rulings on `open` findings, so the stop could only end with another fix-check (harness-evals#361, GST-2251). Now, once an approved ruling covers every finding that still holds it open, the fix-check counts at the tree it read, and each ruling is printed and listed in the PR body and receipt. A fix-check with a finding still unruled prints `FIX_CHECK_OPEN:`, which names every finding that still holds it open, including below-floor ones, and the full `gstack-approvals rule` command to run. A finding the lane only marks as accepted in its own review-log row still stays open, and so does a ruling at a head outside the fix-check's range. Edits made after the fix-check still route as post-review changes. + +- **`gstack-pregate verify` no longer passes a record from a partial run.** A `--only ` or `--stage preflight|tests` run used to replace `.gstack/tmp/pregate.json` with only the checks it ran, and `verify` (so also `gstack-ship-queue ready` and `gstack-ship-receipt write`) accepted it as a full pass. So following pregate's own "rerun `--stage tests`" hint after a red full run could clear a secrets failure while the token was still in HEAD, and `--only strays` alone verified as green. Now `verify` refuses with `PREGATE_INCOMPLETE` unless the record holds every registered check, and it names what is missing. The PREGATE line prints `checks=N of M`. A staged or `--only` rerun at the same head, tree, base and policy keeps the checks it skipped from the record it replaces (it prints `PREGATE_MERGED: kept …`), so rerunning one stage keeps an earlier failure and an earlier pass. Any other partial run needs a full `gstack-pregate --allow-repo-commands` at that tree (GST-2194). + +- **`gstack-ship-queue ready` no longer refuses a good receipt because the lane's copy of the base was old.** `ready` read `origin/` as the clone last fetched it, so a lane a few landings behind measured the PR from an older merge base, counted files from PRs that had already landed, and refused with a false `RECEIPT_INVALID field=review_exclude` (gbrain #8991). `ready` now fetches the base before it reads the policy or checks any receipt field, and refuses with the new `QUEUE_BASE_UNFETCHED` when the fetch fails; the stamp path of `land` refuses the same way instead of carrying on with the old ref. The selection itself was already measured from the merge base (the PR's own files), and a new test pins that (GST-2232). + +- **`gstack-uninstall` no longer deletes a worktree nested inside another checkout, a locked worktree, a `.gstack/` that is its own git repo, or a worktree you edit while it asks to continue.** Since #4241 a plain uninstall stops when a `.gstack-worktrees/` checkout holds unsaved work, but it only looked at the top-level checkouts, so a worktree created inside a lane (as gstack does when it runs from inside one) was deleted with its uncommitted edits and exit 0. Now every checkout on disk is checked, nested ones included; a submodule inside a clean lane goes with it only when every commit it holds is on a remote-tracking branch, and otherwise stops the uninstall. A locked worktree (`git worktree lock`) stops the uninstall even with `--force-worktrees`, and the fix line says to `git worktree unlock` it. A `.gstack/` that is its own git repository, and that the project tracks nothing in, is kept with a message saying why, where before it was deleted with any commits it held. The worktree check now runs a second time after you confirm, before anything is removed, so work saved while the confirmation prompt waited is kept. Uninstall exits 2 and removes nothing, even when run from inside the gstack install. A `git worktree list` that fails stops the uninstall instead of letting `--force-worktrees` delete blind. The manual steps in docs/uninstall.md no longer tell you to `rm -rf .gstack .gstack-worktrees`; they remove only what git ignores and only worktrees that pass the same checks (GST-2268). + +- **`gstack-uninstall` now checks every folder it deletes for unsaved git work, not only the repo you run it from.** Each host's install (global and project-local), the gstack-made Claude per-skill folders and `~/.gstack` are searched for git checkouts before anything is removed. One with uncommitted changes, commits no remote-tracking branch holds, or a locked worktree stops the uninstall with exit 2, naming the checkout. Before, an unpushed `.gstack` repo inside `~/.claude/skills/gstack`, an edited project-local install or a notes repo under `.agents/skills/gstack-*` was deleted without a check (GST-2303). +- **`--keep-state` no longer deletes the state it keeps.** Run from inside the gstack checkout, `--keep-state` skipped the worktree check and then deleted the whole install, taking the kept `.gstack-worktrees` lanes and tracked `.gstack` files with it. An install that holds kept state is now refused instead (GST-2303). +- **A locked worktree whose path contains a newline is kept under `--force --force-worktrees`.** The worktree list is now read NUL-separated, so the lock check sees the real path. A clean submodule nested inside another submodule of a clean lane no longer stops the uninstall as unregistered (GST-2303). + +- **`gstack-ship-queue ready` and `land` now say when a red check counted as green only because a run from another check suite replaced it.** A lane that dispatches `free-tests.yml` on its branch after a red `pull_request` run gets a green verdict from the branch-only run, which never tested the merge with main. Both commands now print `CI_SUPERSEDED: red run replaced by (suite → )` for each such run. It is a warning only and changes no verdict; a rerun in the same suite prints nothing (GST-2205). +- **Without a train policy, `gstack-ship-queue ready` no longer reports READY when its note never reached origin.** Under `changelog_fragments` it exited 0 even when the READY note push failed, so `land --pin` then refused `QUEUE_NOT_READY` because origin had no note. It now refuses with `QUEUE_READY_NOTE_UNPUSHED`, as the train path does, and rerunning the same `ready` command once the push works records it (GST-2205). +- **`gstack-ship-queue ready` now fetches the base before it picks the train or plain path.** It read the base policy first and fetched only inside plain `ready`, so a clone with no `origin/main` ref, or a stale one, ran plain `ready` under a train policy and skipped the train-only screen, conflict and READY-receipt steps. It now fetches first, and refuses `QUEUE_BASE_UNFETCHED` when it can't (GST-2205). + ## [1.99.0.0] - 2026-11-09 - **The pre-archive check no longer passes a thread whose PR state it can't read.** `gstack-archive-check` treated any PR state other than `OPEN` as closed, so an empty or unknown state from `gh pr view` let a thread with a possibly open PR archive clean. It now accepts only `OPEN`, `CLOSED` and `MERGED` and exits 3 with `ARCHIVE_PR_STATE_UNKNOWN` on anything else, on both `thread` and `landed`. An issue row cut short (no assignee, title or age) is now unreadable instead of a complete row. A failed CI read shows as `ci=unknown (CI read failed: )` on the ORPHAN line, and the open PR still refuses. `landed` with a missing, invalid or empty `.gstack/plain-titles.json` now exits 3 with `ARCHIVE_POLICY_UNREADABLE` instead of treating every code-shaped ref as an issue to close (R91, GST-2241). diff --git a/VERSION b/VERSION index 55dbc3502..42588f71a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.99.0.0 +1.99.1.0 diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md index dd4df1f0e..1004c797f 100644 --- a/agents-digest/gstack-AGENTS.md +++ b/agents-digest/gstack-AGENTS.md @@ -1,4 +1,4 @@ -# gstack digest v1.99.0.0 — regenerate/re-copy after upgrading gstack +# gstack digest v1.99.1.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/accept-follow-up-review-after-docs-only-commit.md b/changelog.d/accept-follow-up-review-after-docs-only-commit.md deleted file mode 100644 index abcbd8af0..000000000 --- a/changelog.d/accept-follow-up-review-after-docs-only-commit.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -important: blocker ---- -- **/ship now accepts a granted follow-up review that starts after a docs-only commit.** When the docs commit /ship writes after its review landed before the owner granted one more scoped review of a fix, that review's range started at the docs commit, and `gstack-ship-release finish` refused it with `FIX_CHECK: not counted (range_base does not hold the reviewed tree)`, so the reviewed fix counted as unreviewed code. finish now accepts the range when every commit between the reviewed commit and the range start changes only docs or release metadata, and prints a `FIX_CHECK_DOCS_BRIDGE:` line naming those commits. The docs still get their usual recheck. A code or test commit in between, a merge, a range that starts before the reviewed commit, and a range that starts on another branch still refuse. diff --git a/changelog.d/brain-sync-scans-binary-files-and-split-authorization-values.md b/changelog.d/brain-sync-scans-binary-files-and-split-authorization-values.md deleted file mode 100644 index 912149f87..000000000 --- a/changelog.d/brain-sync-scans-binary-files-and-split-authorization-values.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -important: security ---- -- **Brain sync now scans binary files and catches an auth value added under an existing key.** Git shows a file with a NUL byte (or UTF-16 text) as "Binary files differ" instead of its contents, so the push-time secret scan never saw it and an AWS key inside such a note was pushed. The scan now diffs every file as text and also reads lines and edited blocks that hold NUL bytes as UTF-16 and with the NULs removed; if git still reports a file as binary instead of showing its bytes, the file is held as `binary-not-scanned`. A Bearer value added on the line under an unchanged `"authorization":` key also slipped through, because the diff's `+` marker broke the match; each edited block is now also scanned as the new file text, in UTF-16 files too, and a second reading of the diff always shows three unchanged lines around each edit so a `diff.context` setting of 0 cannot hide the key. The diff git normally shows, with your `diff.context` and any textconv driver, is still scanned too, so nothing the old scan held is lost. The pre-commit hook that `gstack-artifacts-init` and `gstack-brain-restore` install gets the same fix; rerun `gstack-artifacts-init` (or `gstack-brain-restore` on a machine you restored) to refresh it. docs/gbrain-sync.md now says plainly that a secret already committed in `~/.gstack` stays held after you delete it, and only `--skip-file` clears it. diff --git a/changelog.d/capy-catch-secrets-split-across.md b/changelog.d/capy-catch-secrets-split-across.md deleted file mode 100644 index 2ae07efcf..000000000 --- a/changelog.d/capy-catch-secrets-split-across.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -important: security ---- -- **A secret split across two lines of UTF-16 text is now caught, and deleting a large binary no longer blocks a push.** The secret check and the pre-push hook read a file git calls binary three ways (as bytes and as little- and big-endian UTF-16), but they kept all three readings on one line. So in a UTF-16 file, an AWS secret key on the line under its `aws_secret_access_key:` key passed both guards, because the keyword line ended with another reading. Each reading is now scanned as its own text with the file's line numbers, so that pair fails in UTF-16LE and UTF-16BE as it does in plain text. The pre-push hook also leaves deleted files out of the diff it scans (`--irreversible-delete`). A delete cannot add a secret, and the old bytes of a deleted 70 MiB binary used to fill the hook's 64 MiB buffer and block the push with `git diff failed (status=killed/overflow)`. Adding a binary that large still blocks, and a replaced binary's old bytes still count toward the limit. diff --git a/changelog.d/capy-fix-ship-queue-ready.md b/changelog.d/capy-fix-ship-queue-ready.md deleted file mode 100644 index c48347417..000000000 --- a/changelog.d/capy-fix-ship-queue-ready.md +++ /dev/null @@ -1 +0,0 @@ -- **The free test suite is green on main again.** Two `gstack-ship-queue ready` tests still wrote a pre-merge check record with a single check, which `ready` has refused since the pre-merge check started requiring every registered check (GST-2194). Their fixtures now record every check, so the tests exercise what they were written for. No product behavior changed (GST-2282). diff --git a/changelog.d/capy-make-ship-queue-ready.md b/changelog.d/capy-make-ship-queue-ready.md deleted file mode 100644 index c82f422aa..000000000 --- a/changelog.d/capy-make-ship-queue-ready.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -important: blocker ---- -- **Under a merge-train policy, `gstack-ship-queue ready` no longer reports READY when its note never reached origin.** `land --pin` reads the READY note from origin's `refs/notes/gstack-trains`, but `ready` exited 0 even when it couldn't push that note, so a lane sent READY and the land was refused with `QUEUE_NOT_READY` (#4245 lost a land round this way). After pushing, `ready` now fetches origin's notes ref back and prints `notes pushed` only when origin holds the note. If three tries fail, or the push exits 0 and origin still lacks the note, it refuses with `QUEUE_READY_NOTE_UNPUSHED` and keeps the local receipt, so rerunning the same `ready` command once the push works records it (GST-2242). diff --git a/changelog.d/ci-carry-accepts-unrelated-job-rerun.md b/changelog.d/ci-carry-accepts-unrelated-job-rerun.md deleted file mode 100644 index 9fc3e8606..000000000 --- a/changelog.d/ci-carry-accepts-unrelated-job-rerun.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -important: blocker ---- -- **The CI carry now accepts a run where only an unrelated job was rerun.** Rerunning one job of a green run (a flaky `public-export`, say) made GitHub number the run's newest attempt 2 while the checks job, which wasn't rerun, still carried its attempt-1 `tree.json`, so `accept` refused with `artifact_mismatch`; rerunning the checks job too left two same-named artifacts and refused with `artifact_missing`. Run verification now works out which attempt each profile job actually ran in (GitHub relists a job it didn't rerun with its old start and finish times) and checks `tree.json` against that attempt, and with one artifact per attempt it reads the one from the uploading job's newest attempt. Every other field (head, merge commit, tree, event, job conclusion) is still compared exactly, and an older attempt's artifact is never read in place of the newest. The 24-hour freshness check now dates a run by its own test jobs, so rerunning an unrelated job never makes days-old results look fresh. When the pregate's skipped-tests command fails because the checkout has no installed dependencies, the refusal now says so (GST-2284, harness-evals HARN-56). diff --git a/changelog.d/drop-review-from-qm-edition.md b/changelog.d/drop-review-from-qm-edition.md deleted file mode 100644 index 599b83a77..000000000 --- a/changelog.d/drop-review-from-qm-edition.md +++ /dev/null @@ -1 +0,0 @@ -- **The QM edition no longer includes `gstack-review`.** QM's default gstack skills are now just `gstack-plan`. A benchmark of the edition inside QM found that `gstack-review` flagged too many problems without making the reviewed work any better, so it was cut. To update a QM deployment, delete its `sandbox/skills/gstack-review/` folder and run `qm up`. gstack's own `/review` skill is unchanged. diff --git a/changelog.d/fix-brain-sync-scan-escaped-secrets-and-added-lines.md b/changelog.d/fix-brain-sync-scan-escaped-secrets-and-added-lines.md deleted file mode 100644 index 8e7a556a0..000000000 --- a/changelog.d/fix-brain-sync-scan-escaped-secrets-and-added-lines.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -important: security ---- -- **Brain sync now catches secrets hidden behind JSON escapes, and its pre-commit hook checks every family the sync does.** A hand-written learnings or timeline row could slip a Stripe key past the push-time scan by writing it with `\u` escapes, or slip an auth header past it inside an escaped JSON string. The scan now decodes each added line's JSON strings and scans them as well as the raw diff, so both are held. The pre-commit hook that `gstack-artifacts-init` and `gstack-brain-restore` install, which catches a manual `git commit` in `~/.gstack`, checked only the original six families; it now runs the same scanner as brain sync (`bin/gstack-brain-secret-scan.py`), so it also refuses Stripe live keys, Slack and npm tokens and database URLs with a password. Rerun `gstack-artifacts-init` to refresh a hook written by an older gstack. diff --git a/changelog.d/fix-flaky-checks-2026-11-10.md b/changelog.d/fix-flaky-checks-2026-11-10.md deleted file mode 100644 index 6ba4b6534..000000000 --- a/changelog.d/fix-flaky-checks-2026-11-10.md +++ /dev/null @@ -1 +0,0 @@ -- **Six test and eval checks that misread correct work, or raced their own fixtures, now read it right (2026-11-10 daily flaky pass, GST-2338).** ship-docsync-store's fixture interface now declares the `gstack-docs-candidate attempt --state ` call that /ship's documentation step has asked for since #4192, so a model that follows the step is no longer stopped by the fixture (census 42748129558). tpa-absent-darwin no longer counts a gstack-browser option as an Aside drive just because it says Aside isn't available. tpa-present's defer check counts a "the step stays blocked on you" status line only when the option puts the step off ("later", "not yet", "not now"), so a status line after another instruction no longer passes as a defer offer. scrape-prototype-path also credits `$B goto >/dev/null && $B html` when the call proves the goto exited 0. Two free tests stop racing themselves: the CSO lease contender publishes its result file by rename, so the test never reads it empty, and the registry-socket deadline test sets a deadline that expires after the watchdog's startup handshake instead of inside it. No threshold, trial count, case kind or retry changed. diff --git a/changelog.d/fix-policy-floor-test-on-release-stamps.md b/changelog.d/fix-policy-floor-test-on-release-stamps.md deleted file mode 100644 index 2ac5773d5..000000000 --- a/changelog.d/fix-policy-floor-test-on-release-stamps.md +++ /dev/null @@ -1 +0,0 @@ -- **The doctor floor-row test no longer fails every release that adds no policy key (GST-2441).** test/policy-floor-stable.test.ts now expects a lagging checkout to read policies as the newest policy key gstack knows, computed from the same key table validatePolicy reads, instead of the repo's own VERSION. Before this, any release stamp past the newest key (v1.99.1.0 in #4291) turned free-tests red. Test-only change; no product code moved. diff --git a/changelog.d/keep-review-working-in-copied-windows-installs.md b/changelog.d/keep-review-working-in-copied-windows-installs.md deleted file mode 100644 index adfb5ae4a..000000000 --- a/changelog.d/keep-review-working-in-copied-windows-installs.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -important: blocker ---- -- **/review and /ship start again on Windows under Codex, Cursor and the other non-Claude hosts.** On Windows, setup copies gstack's `bin/` and `lib/` into each host's runtime folder instead of linking them, and it left out three `scripts/` files those commands import. The review planner that /review and /ship run first stopped with `Cannot find module '../scripts/resolvers/fix-check'`, and the commands behind /retro and /write-skill had the same gap. Setup now copies `scripts/resolvers/fix-check.ts`, `scripts/discover-skills.ts` and `scripts/lib/ci-history.ts` too, and a test follows the static imports of the commands each host's skills run, so a new static import outside the runtime folder fails CI. Thanks to @SatriaSuriaAriffin, who found and reported the fix-check gap (#4275). diff --git a/changelog.d/let-owner-rulings-close-a-fix-checks-own-blockers.md b/changelog.d/let-owner-rulings-close-a-fix-checks-own-blockers.md deleted file mode 100644 index 77d4cf1d0..000000000 --- a/changelog.d/let-owner-rulings-close-a-fix-checks-own-blockers.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -important: blocker ---- -- **An owner's ruling now clears the new blockers a granted fix-check found, so /ship can finish with them listed as open notes.** When a granted fix-check finds a new CRITICAL, it logs the finding `open` and stops for the owner. The owner can rule it a disclosed note with `gstack-approvals rule`, but `gstack-ship-release finish` rejected the fix-check as unfinished before it looked at any ruling. It also ignored rulings on `open` findings, so the stop could only end with another fix-check (harness-evals#361, GST-2251). Now, once an approved ruling covers every finding that still holds it open, the fix-check counts at the tree it read, and each ruling is printed and listed in the PR body and receipt. A fix-check with a finding still unruled prints `FIX_CHECK_OPEN:`, which names every finding that still holds it open, including below-floor ones, and the full `gstack-approvals rule` command to run. A finding the lane only marks as accepted in its own review-log row still stays open, and so does a ruling at a head outside the fix-check's range. Edits made after the fix-check still route as post-review changes. diff --git a/changelog.d/pregate-verify-refuses-partial-run.md b/changelog.d/pregate-verify-refuses-partial-run.md deleted file mode 100644 index 9b74f93c6..000000000 --- a/changelog.d/pregate-verify-refuses-partial-run.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -important: security ---- -- **`gstack-pregate verify` no longer passes a record from a partial run.** A `--only ` or `--stage preflight|tests` run used to replace `.gstack/tmp/pregate.json` with only the checks it ran, and `verify` (so also `gstack-ship-queue ready` and `gstack-ship-receipt write`) accepted it as a full pass. So following pregate's own "rerun `--stage tests`" hint after a red full run could clear a secrets failure while the token was still in HEAD, and `--only strays` alone verified as green. Now `verify` refuses with `PREGATE_INCOMPLETE` unless the record holds every registered check, and it names what is missing. The PREGATE line prints `checks=N of M`. A staged or `--only` rerun at the same head, tree, base and policy keeps the checks it skipped from the record it replaces (it prints `PREGATE_MERGED: kept …`), so rerunning one stage keeps an earlier failure and an earlier pass. Any other partial run needs a full `gstack-pregate --allow-repo-commands` at that tree (GST-2194). diff --git a/changelog.d/ship-queue-ready-fetches-base-gst-2232.md b/changelog.d/ship-queue-ready-fetches-base-gst-2232.md deleted file mode 100644 index 6e48314c8..000000000 --- a/changelog.d/ship-queue-ready-fetches-base-gst-2232.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -important: blocker ---- -- **`gstack-ship-queue ready` no longer refuses a good receipt because the lane's copy of the base was old.** `ready` read `origin/` as the clone last fetched it, so a lane a few landings behind measured the PR from an older merge base, counted files from PRs that had already landed, and refused with a false `RECEIPT_INVALID field=review_exclude` (gbrain #8991). `ready` now fetches the base before it reads the policy or checks any receipt field, and refuses with the new `QUEUE_BASE_UNFETCHED` when the fetch fails; the stamp path of `land` refuses the same way instead of carrying on with the old ref. The selection itself was already measured from the merge base (the PR's own files), and a new test pins that (GST-2232). diff --git a/changelog.d/stop-uninstall-deleting-nested-and-locked-worktrees.md b/changelog.d/stop-uninstall-deleting-nested-and-locked-worktrees.md deleted file mode 100644 index c57105869..000000000 --- a/changelog.d/stop-uninstall-deleting-nested-and-locked-worktrees.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -important: data-loss ---- -- **`gstack-uninstall` no longer deletes a worktree nested inside another checkout, a locked worktree, a `.gstack/` that is its own git repo, or a worktree you edit while it asks to continue.** Since #4241 a plain uninstall stops when a `.gstack-worktrees/` checkout holds unsaved work, but it only looked at the top-level checkouts, so a worktree created inside a lane (as gstack does when it runs from inside one) was deleted with its uncommitted edits and exit 0. Now every checkout on disk is checked, nested ones included; a submodule inside a clean lane goes with it only when every commit it holds is on a remote-tracking branch, and otherwise stops the uninstall. A locked worktree (`git worktree lock`) stops the uninstall even with `--force-worktrees`, and the fix line says to `git worktree unlock` it. A `.gstack/` that is its own git repository, and that the project tracks nothing in, is kept with a message saying why, where before it was deleted with any commits it held. The worktree check now runs a second time after you confirm, before anything is removed, so work saved while the confirmation prompt waited is kept. Uninstall exits 2 and removes nothing, even when run from inside the gstack install. A `git worktree list` that fails stops the uninstall instead of letting `--force-worktrees` delete blind. The manual steps in docs/uninstall.md no longer tell you to `rm -rf .gstack .gstack-worktrees`; they remove only what git ignores and only worktrees that pass the same checks (GST-2268). diff --git a/changelog.d/uninstall-checks-every-folder-it-deletes-gst-2303.md b/changelog.d/uninstall-checks-every-folder-it-deletes-gst-2303.md deleted file mode 100644 index 0a0fef0ee..000000000 --- a/changelog.d/uninstall-checks-every-folder-it-deletes-gst-2303.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -important: data-loss ---- -- **`gstack-uninstall` now checks every folder it deletes for unsaved git work, not only the repo you run it from.** Each host's install (global and project-local), the gstack-made Claude per-skill folders and `~/.gstack` are searched for git checkouts before anything is removed. One with uncommitted changes, commits no remote-tracking branch holds, or a locked worktree stops the uninstall with exit 2, naming the checkout. Before, an unpushed `.gstack` repo inside `~/.claude/skills/gstack`, an edited project-local install or a notes repo under `.agents/skills/gstack-*` was deleted without a check (GST-2303). -- **`--keep-state` no longer deletes the state it keeps.** Run from inside the gstack checkout, `--keep-state` skipped the worktree check and then deleted the whole install, taking the kept `.gstack-worktrees` lanes and tracked `.gstack` files with it. An install that holds kept state is now refused instead (GST-2303). -- **A locked worktree whose path contains a newline is kept under `--force --force-worktrees`.** The worktree list is now read NUL-separated, so the lock check sees the real path. A clean submodule nested inside another submodule of a clean lane no longer stops the uninstall as unregistered (GST-2303). diff --git a/changelog.d/warn-superseded-ci-refuse-unpushed-ready-note.md b/changelog.d/warn-superseded-ci-refuse-unpushed-ready-note.md deleted file mode 100644 index 9cee845e2..000000000 --- a/changelog.d/warn-superseded-ci-refuse-unpushed-ready-note.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -important: blocker ---- -- **`gstack-ship-queue ready` and `land` now say when a red check counted as green only because a run from another check suite replaced it.** A lane that dispatches `free-tests.yml` on its branch after a red `pull_request` run gets a green verdict from the branch-only run, which never tested the merge with main. Both commands now print `CI_SUPERSEDED: red run replaced by (suite → )` for each such run. It is a warning only and changes no verdict; a rerun in the same suite prints nothing (GST-2205). -- **Without a train policy, `gstack-ship-queue ready` no longer reports READY when its note never reached origin.** Under `changelog_fragments` it exited 0 even when the READY note push failed, so `land --pin` then refused `QUEUE_NOT_READY` because origin had no note. It now refuses with `QUEUE_READY_NOTE_UNPUSHED`, as the train path does, and rerunning the same `ready` command once the push works records it (GST-2205). -- **`gstack-ship-queue ready` now fetches the base before it picks the train or plain path.** It read the base policy first and fetched only inside plain `ready`, so a clone with no `origin/main` ref, or a stale one, ran plain `ready` under a train policy and skipped the train-only screen, conflict and READY-receipt steps. It now fetches first, and refuses `QUEUE_BASE_UNFETCHED` when it can't (GST-2205). diff --git a/package.json b/package.json index 4a13870c3..0a427d1f3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.99.0", + "version": "1.99.1", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module",