From bc858ffc659bcc61558f60d0e023a009817dd48f Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Thu, 12 Nov 2026 17:36:58 +0800 Subject: [PATCH] v1.99.2.0 release (#4310) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- CHANGELOG.md | 12 ++++++++++++ VERSION | 2 +- agents-digest/gstack-AGENTS.md | 2 +- ...pied-installs-find-browser-and-question-helper.md | 4 ---- changelog.d/declared-test-trees-skip-local-lane.md | 4 ---- ...r-approved-gate-changes-finish-unattended-ship.md | 4 ---- changelog.d/sealed-guard-stream-large-diffs.md | 4 ---- changelog.d/stable-cut-ignores-its-own-check-runs.md | 4 ---- package.json | 2 +- 9 files changed, 15 insertions(+), 23 deletions(-) delete mode 100644 changelog.d/copied-installs-find-browser-and-question-helper.md delete mode 100644 changelog.d/declared-test-trees-skip-local-lane.md delete mode 100644 changelog.d/owner-approved-gate-changes-finish-unattended-ship.md delete mode 100644 changelog.d/sealed-guard-stream-large-diffs.md delete mode 100644 changelog.d/stable-cut-ignores-its-own-check-runs.md diff --git a/CHANGELOG.md b/CHANGELOG.md index bef44d81d..b3cdea4f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [1.99.2.0] - 2026-11-12 + +- **Browser skills and question preferences work again from Codex, Cursor and the other non-Claude hosts' gstack folders.** Setup gives each of those hosts its own gstack folder (`~/.codex/skills/gstack` and the like) with no `package.json` or `node_modules`, and records the gstack checkout it came from in `.source-path`. `gstack-browser-ensure`, which every browser skill runs first, looked for Playwright only in that folder, so it reported `BROWSER_UNAVAILABLE playwright is not installed` even when the checkout had it. It now uses the checkout's dependencies, but only when the folder has no `package.json` of its own and `.source-path` is a regular file naming an absolute path whose real directory holds gstack's own `package.json` and `bin/gstack-browser-ensure`, neither one a link. An empty, stale, relative or linked pointer, or a directory that isn't gstack, is ignored as before, and `--check` still never writes. Setup also now puts `scripts/one-way-doors.ts` and `scripts/psychographic-signals.ts` in those folders. The question-preference helper loads them at run time, so its `--check` and `--signal-keys` stopped with `Cannot find module './scripts/one-way-doors.ts'`. Thanks to @SatriaSuriaAriffin, who found and reported both (#4290). + +- **A repo can keep its tier-only test trees out of /ship's local lane.** A pull request touching tests that only run under a separate tier (alphaclaw's `tests/live/**` and `tests/container/**`, which its test runner skips unless a tier flag is set) made the pregate pick those tests, and the files that import their helpers, and run them locally; the runner found no test files, the lane failed, and /ship wrote no receipt. Declaring `[]` for such a glob in `.gstack/pregate.json` used to do nothing for test files, because a test always selects itself. Now a `[]` on a folder inside a test directory (`tests/live/**`; the directory is `test/`, `tests/`, `spec/` or `__tests__/`) keeps the selected tests under it out of the local lane, and pregate prints one `declared test tree` line per glob with the count it left out, recorded with the rest of the lanes result. Nothing changes without such a declaration. Tests outside the glob still run, a `[]` on any other glob, the whole `test/**` included, never skips a test, and as before a new `[]` counts only once it is on the base branch (or in your own user policy), never from a branch's bootstrap policy. With one, a test `.gstack/always-run.json` names still runs, and whenever the remote gate, the test backend or a platform lane is owed, it still covers every selected test, because only the local run leaves them out. + +- **An unattended /ship can now finish a PR that carries an owner-approved gate change, and the lander has to accept each one before it merges.** When the review finds a loosened gate (a test threshold raised, a check skipped), an unattended run logs it as an open gate finding, because nobody is there to answer the gate question, and until now nothing could close it, so the run stopped for good. That blocked founderstar#906, whose gate change Garry had approved. The owner's answer can now be recorded from outside the run with `gstack-approvals gate --finding --gate-id --head --reason '' --words '' --by ''`. It closes only that finding, only on a review whose range holds that head, and the review reader then shows it as kept so the rest of /ship carries on. It is refused while a /ship run is open on that branch in any worktree, without the owner's words, for a literal `undefined` gate id, or when `--by` names gstack. A row that is missing a field the writer writes, or was edited after it was written, is ignored. The lane runs as the same user, so it could still write a row that looks right. That's why the lander decides: `gstack-ship-queue land --pin` works the dispositions out again from the approval store at the pin, prints each one, and merges only with `--accept-gate-disposition ` for every one. It also refuses when the receipt or the PR body leaves one out or invents one. `gstack-ship-queue verify --pr --pin ` runs the same check without merging, for a lander that merges another way, and `gstack-ship-receipt write` no longer skips the disclosure check when `--body` is left off. Gate findings are logged exactly as before, and a ruling or anything the lane writes on its own review row still closes nothing. A disposition recorded at a commit that a later rewrite removed from the clone just stops applying instead of blocking the branch, and a shallow landing clone is told to deepen itself. + +- The sealed-output guard now reads pull requests of any size. The pre-push hook and `gstack-pregate sealed` read every published commit's patch through `git diff-tree`, and held all of git's output in memory under a 256 MiB limit. A pull request with about 4 million lines of generated fixtures produced 352 MB for the text check and 515 MB for the already-landed check, so the guard always refused it with `SEALED_OUTPUT_REFUSED` and an empty `git diff-tree -p failed:`. Both reads now write git's output to a private temporary file and check it one line at a time, so memory stays flat whatever the patch size. Findings, skipped commits and refusal codes are unchanged, and a test runs the old and new code on the same commits and gets the same answer. A git failure still refuses, and the message now names the exit code, the signal or the timeout, for example `git diff-tree -p failed (exit 128): fatal: ...` or `(killed by SIGKILL)`. The temporary file needs free disk space about the size of the patch. docs/troubleshooting.md#sealed-output-refused. + +- **The stable release no longer blocks itself on its own check run.** The daily cut runs on main's newest commit, so its own "Cut the release" job showed up as a check run on the very commit it might release. While the cut ran, that commit looked unfinished and was skipped; when a cut failed, its red check run kept that commit out of every later cut. On 2026-11-11 this held v1.99.1.0 back after the release gate had passed it. The cut now sets aside check runs that belong to its own workflow (`.github/workflows/weekly-release.yml`, matched through the GitHub Actions run that produced them, not by job name) and judges every other check run exactly as before: a red or running check from any other workflow, a missing or running `free-tests`, a missing gate, a RED release record and a freeze still refuse. The workflow now also needs `actions: read` to look up its own runs; if that lookup fails, nothing is set aside and the cut behaves as it did before. + ## [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. diff --git a/VERSION b/VERSION index 42588f71a..10a69fe68 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.99.1.0 +1.99.2.0 diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md index 1004c797f..fb015840a 100644 --- a/agents-digest/gstack-AGENTS.md +++ b/agents-digest/gstack-AGENTS.md @@ -1,4 +1,4 @@ -# gstack digest v1.99.1.0 — regenerate/re-copy after upgrading gstack +# gstack digest v1.99.2.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/copied-installs-find-browser-and-question-helper.md b/changelog.d/copied-installs-find-browser-and-question-helper.md deleted file mode 100644 index 4bb064216..000000000 --- a/changelog.d/copied-installs-find-browser-and-question-helper.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -important: blocker ---- -- **Browser skills and question preferences work again from Codex, Cursor and the other non-Claude hosts' gstack folders.** Setup gives each of those hosts its own gstack folder (`~/.codex/skills/gstack` and the like) with no `package.json` or `node_modules`, and records the gstack checkout it came from in `.source-path`. `gstack-browser-ensure`, which every browser skill runs first, looked for Playwright only in that folder, so it reported `BROWSER_UNAVAILABLE playwright is not installed` even when the checkout had it. It now uses the checkout's dependencies, but only when the folder has no `package.json` of its own and `.source-path` is a regular file naming an absolute path whose real directory holds gstack's own `package.json` and `bin/gstack-browser-ensure`, neither one a link. An empty, stale, relative or linked pointer, or a directory that isn't gstack, is ignored as before, and `--check` still never writes. Setup also now puts `scripts/one-way-doors.ts` and `scripts/psychographic-signals.ts` in those folders. The question-preference helper loads them at run time, so its `--check` and `--signal-keys` stopped with `Cannot find module './scripts/one-way-doors.ts'`. Thanks to @SatriaSuriaAriffin, who found and reported both (#4290). diff --git a/changelog.d/declared-test-trees-skip-local-lane.md b/changelog.d/declared-test-trees-skip-local-lane.md deleted file mode 100644 index 5f3437685..000000000 --- a/changelog.d/declared-test-trees-skip-local-lane.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -important: blocker ---- -- **A repo can keep its tier-only test trees out of /ship's local lane.** A pull request touching tests that only run under a separate tier (alphaclaw's `tests/live/**` and `tests/container/**`, which its test runner skips unless a tier flag is set) made the pregate pick those tests, and the files that import their helpers, and run them locally; the runner found no test files, the lane failed, and /ship wrote no receipt. Declaring `[]` for such a glob in `.gstack/pregate.json` used to do nothing for test files, because a test always selects itself. Now a `[]` on a folder inside a test directory (`tests/live/**`; the directory is `test/`, `tests/`, `spec/` or `__tests__/`) keeps the selected tests under it out of the local lane, and pregate prints one `declared test tree` line per glob with the count it left out, recorded with the rest of the lanes result. Nothing changes without such a declaration. Tests outside the glob still run, a `[]` on any other glob, the whole `test/**` included, never skips a test, and as before a new `[]` counts only once it is on the base branch (or in your own user policy), never from a branch's bootstrap policy. With one, a test `.gstack/always-run.json` names still runs, and whenever the remote gate, the test backend or a platform lane is owed, it still covers every selected test, because only the local run leaves them out. diff --git a/changelog.d/owner-approved-gate-changes-finish-unattended-ship.md b/changelog.d/owner-approved-gate-changes-finish-unattended-ship.md deleted file mode 100644 index 18be68962..000000000 --- a/changelog.d/owner-approved-gate-changes-finish-unattended-ship.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -important: blocker ---- -- **An unattended /ship can now finish a PR that carries an owner-approved gate change, and the lander has to accept each one before it merges.** When the review finds a loosened gate (a test threshold raised, a check skipped), an unattended run logs it as an open gate finding, because nobody is there to answer the gate question, and until now nothing could close it, so the run stopped for good. That blocked founderstar#906, whose gate change Garry had approved. The owner's answer can now be recorded from outside the run with `gstack-approvals gate --finding --gate-id --head --reason '' --words '' --by ''`. It closes only that finding, only on a review whose range holds that head, and the review reader then shows it as kept so the rest of /ship carries on. It is refused while a /ship run is open on that branch in any worktree, without the owner's words, for a literal `undefined` gate id, or when `--by` names gstack. A row that is missing a field the writer writes, or was edited after it was written, is ignored. The lane runs as the same user, so it could still write a row that looks right. That's why the lander decides: `gstack-ship-queue land --pin` works the dispositions out again from the approval store at the pin, prints each one, and merges only with `--accept-gate-disposition ` for every one. It also refuses when the receipt or the PR body leaves one out or invents one. `gstack-ship-queue verify --pr --pin ` runs the same check without merging, for a lander that merges another way, and `gstack-ship-receipt write` no longer skips the disclosure check when `--body` is left off. Gate findings are logged exactly as before, and a ruling or anything the lane writes on its own review row still closes nothing. A disposition recorded at a commit that a later rewrite removed from the clone just stops applying instead of blocking the branch, and a shallow landing clone is told to deepen itself. diff --git a/changelog.d/sealed-guard-stream-large-diffs.md b/changelog.d/sealed-guard-stream-large-diffs.md deleted file mode 100644 index 6082a7f57..000000000 --- a/changelog.d/sealed-guard-stream-large-diffs.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -important: blocker ---- -- The sealed-output guard now reads pull requests of any size. The pre-push hook and `gstack-pregate sealed` read every published commit's patch through `git diff-tree`, and held all of git's output in memory under a 256 MiB limit. A pull request with about 4 million lines of generated fixtures produced 352 MB for the text check and 515 MB for the already-landed check, so the guard always refused it with `SEALED_OUTPUT_REFUSED` and an empty `git diff-tree -p failed:`. Both reads now write git's output to a private temporary file and check it one line at a time, so memory stays flat whatever the patch size. Findings, skipped commits and refusal codes are unchanged, and a test runs the old and new code on the same commits and gets the same answer. A git failure still refuses, and the message now names the exit code, the signal or the timeout, for example `git diff-tree -p failed (exit 128): fatal: ...` or `(killed by SIGKILL)`. The temporary file needs free disk space about the size of the patch. docs/troubleshooting.md#sealed-output-refused. diff --git a/changelog.d/stable-cut-ignores-its-own-check-runs.md b/changelog.d/stable-cut-ignores-its-own-check-runs.md deleted file mode 100644 index c0a6dbf20..000000000 --- a/changelog.d/stable-cut-ignores-its-own-check-runs.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -important: blocker ---- -- **The stable release no longer blocks itself on its own check run.** The daily cut runs on main's newest commit, so its own "Cut the release" job showed up as a check run on the very commit it might release. While the cut ran, that commit looked unfinished and was skipped; when a cut failed, its red check run kept that commit out of every later cut. On 2026-11-11 this held v1.99.1.0 back after the release gate had passed it. The cut now sets aside check runs that belong to its own workflow (`.github/workflows/weekly-release.yml`, matched through the GitHub Actions run that produced them, not by job name) and judges every other check run exactly as before: a red or running check from any other workflow, a missing or running `free-tests`, a missing gate, a RED release record and a freeze still refuse. The workflow now also needs `actions: read` to look up its own runs; if that lookup fails, nothing is set aside and the cut behaves as it did before. diff --git a/package.json b/package.json index 0a427d1f3..9774d9b73 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.99.1", + "version": "1.99.2", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module",