v1.99.0.0 release (#4263)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
Garry Tanandcapy-ai[bot] authored and GitHub committed 2026-11-10 00:46:54 +00:00
1 parent 7dace1fdea
commit 9ab50347ac
75 files changed
+164 -148

No files matched your search

+161
View File
@@ -1,5 +1,166 @@
# Changelog
## [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: <reason>)` 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).
- **The /autoplan docs now say what each kind of run actually gives its reviewers.** README.md, docs/skills.md, docs/unattended.md, autoplan/DRIVER.md, crew/docs/captain.md, the skill's description and the `gstack-autoplan --help` text described a full-depth, two-voice review for every run. That is true only of an interactive session, where the model reads each review skill's own method. The lean run (the Capy default) is one engineering reviewer whose prompt holds the eng brief and short CEO, eng, design and DX checklists (28 to 37 lines each), never those skills' methods, with no outside voice. A `--full` run has a reviewer pair per phase, each with a short brief, plus the checklist for the native one. docs/skills.md has a new "What each run loads" section. No runner behavior, prompt, mode or default changed (GEVA-412, GST-1985).
- **/autoplan's final approval gate renders again, and replies to its plan-approval item are accepted.** The skill told the agent to name that gate item `plan` and to read replies such as `plana` and `planc`, but `gstack-gate` only accepts an id made of a lowercase label and a number, so `gstack-gate render` refused the whole gate and those replies never parsed (#4181). The skill now names it `p1`, with replies `p1a`, `p1c`, `p1d` and `p1e`, the id gstack's own gate builder and docs/unattended.md already use. A test now builds the gate exactly as the skill describes it and runs it through `gstack-gate render` and `parse`. Reported by @exGeni in #4181; fixes proposed by @ElNeMeR70 (#4189) and @szsunyuan (#4191).
- **Dependency and CI action patch bumps.** `@types/bun` 1.4.3 → 1.4.4, `@huggingface/transformers` 4.3.1 → 4.3.2 and `marked` 18.1.1 → 18.1.3 in `package.json` and `bun.lock`, plus `docker/login-action` 4.6.0 → 4.7.0 (seven steps in six workflows) and `github/codeql-action/upload-sarif` 4.39.0 → 4.39.1 (scorecard), each still pinned to the full commit SHA of its upstream tag. None of the new package versions adds an install script. Carries Dependabot's #3488, #3489 and #3890 onto current main.
- **/autoplan no longer stops after a phase with `publication_missing` when Claude Code saves the phase report as a summary that opens with another sentence.** Claude Code can save the prose between tool calls as a paraphrased summary. When the report started with a check sentence, the summary read "I checked the packet... Phase 1 is complete with 9 native issues and no outside review, so the work moves on to Phase 2." The guard only counted a phase declaration at the start of a line, so the run died after Phase 1. It now also counts one that starts a sentence in plain prose. A declaration inside quotes or inline code, one after an example or template introduction on the same line, a negated or future one, a list item, a heading and another phase's number still don't count. A "with" result may now mention "no outside review" after its counts; one that opens with "no" still doesn't count. Thanks to @mstormo for finding the cause in #4155.
- **gstack's browser now tells you how to fix a Linux host that blocks Chromium's sandbox, and /design-review --build checks the browser really starts.** On stock Ubuntu 23.10+ (AppArmor) and in non-root Docker, Chromium can't create the user namespaces its sandbox needs and aborts with "No usable sandbox!". browse used to exit with a wall of Playwright log after `Server failed to start:`. Now it prints one error that names both ways out. You can allow user namespaces (the Ubuntu AppArmor setting), or set `GSTACK_CHROMIUM_NO_SANDBOX=1`, which is only for pages you trust. Chromium's own line stays below it. gstack still never turns the sandbox off by itself, except that Docker's `/.dockerenv` now counts as a container, the same as `CONTAINER`. `gstack-design-build preflight` used to say `PREFLIGHT: ok` without starting the browser, so the run died later; it now opens and closes one tab first and stops with `code=BROWSER_SANDBOX` and the same error when the launch fails. (GST-1906)
- **`gstack-ci-carry` and `gstack-pregate --from-run` can verify a GitHub Actions run again after it has been rerun.** Reading a rerun attempt's job list 100 jobs at a time could take GitHub about 11 seconds for a large workflow (86 jobs in gbrain's Test runs), and a proxy that cuts requests off at about 10 seconds answered 502. So any run whose newest attempt was a rerun failed with `api_error`, and /ship fell back to running the whole suite locally, even after a green rerun of a known flake. The job list is now read 50 per page, every page, and a server error (5xx) reads the whole list once more at 25 per page. A page that still fails leaves the run unverified (`api_error`), as before: a partial job list never counts. (GST-2215)
- **/ship no longer stops when the docs checker's reply has a small mistake in it.** Before shipping, /ship asks a helper to check the docs and reply with a short summary line. When that helper typed a field name with a stray space and left the docs summary as a stand-in like "x", /ship treated the whole check as blocked and an unattended run stopped. Now `gstack-docs-candidate result` reads that last line strictly. It forgives spaces around field names, still refuses anything else that's wrong (a missing or extra field, the wrong audit id, an empty or stand-in summary), names each problem, and hands the one retry a note saying exactly which fields to fix. A reply that passes is saved with its summary for the PR. The docs test fixture accepts the new command, and odd audit ids or field names can no longer write outside the record folder or fake a line in the output. (GST-1843)
- **Seven test and eval checks that misread correct work, or raced their neighbours, now read it right (2026-11-09 daily flaky pass, GST-2131).** tpa-present's consent check now accepts the defer option in any words when the option says the step will be marked or left as blocked or waiting on you (gate 42431616220: "D) Do it later. I'll mark this step as waiting on you."), and still rejects every refusal of it, such as "I will not mark this step as blocked on you" or "I won't mark this step as waiting on you". Three weekly-census reds were harness misreads. scrape-prototype-path now credits browse's own "Navigated to <fixture URL> (2xx)" line when the model navigated through `$PWD`; codex-review's skill excerpt now points every helper path, `bin/` and `lib/` alike, at the checkout instead of an install the fixture never seeds; and ship-coverage-value resolves a `file:line` gap inside refundPayment instead of requiring the word "refund". The handoff integration test takes its X display from the race-safe `spawnFreeXvfb()`, the AUQ capture fixture turns off git's detached auto-maintenance that left a directory behind after cleanup, and the Windows curated lane skips test/ship-finish-fixed-links.test.ts, which spawns shebang bins like its excluded siblings. No threshold, trial count, case kind or retry changed.
- **Flaky pass 2026-11-07: the two marathon census reds on 297f50c7 are repaired, and two free tests stop failing at random.** Eval children now run gh with `GH_TELEMETRY=0`, because gh 2.103 records a telemetry device-id under `$HOME/.local/state/gh`, and `autoplan-ceo-only` counted that file as a stray write in its fixture HOME (marathon 42371706491). The office-hours completion check accepts the next skills the handoff menu offers now that `/autoplan` is the default recommendation (#4108), and it rejects the retired `/plan-eng-review`. The ruled-notes chain test names both shas after `review_loop_grant`, so a short sha made only of digits no longer reads as a phone number to the approval scanner (Free Tests 42345572767). The two slowest release-cut cases get the free suite's 30-second per-test timeout, so a bare local `bun test` stops timing out at 5 seconds. No threshold, trial count, case kind or model budget changed.
**The plan check works on public repos (GST-1774).** A repo that commits no `.gstack/` can turn the check on per machine with `gstack-config set plan_record warn|require|off`. The mode order is a tracked `plan_record` of warn or require, then `GSTACK_PLAN_RECORD`, then this key, then warn in a Capy session. A typo is rejected, and `PLAN:` names the file. With no tracked policy the key gates `gstack-ship-queue ready` and /ship finish, not land. On a public or unknown-visibility repo, `gstack-approvals render --plan` now writes `Plan: approved (record r-<id>)` in place of the autoplan run id. That id is an opaque hash of the approval row, so the run id, the row id and the ask stay out of the PR body, its edit history and the READY note pushed to origin. `ready` resolves the id to the same row and applies the same rules as before. Render refuses with no output when an approved line does not resolve, instead of printing a misleading `Plan: required`.
**The opt-in /autoplan gate now makes each phase publish in order, right after its own report (GST-1814).** In GEVA-2's E2 eval (3 told Opus 5.5 sessions, 1 exposed) every failure was in the report step: one session used all 3 Stop blocks on `missing=ceo` before doing any work and then published every phase with no report, one published Eng's report first and back-filled CEO and DX, and one published everything with no reports and back-filled one report per block. With `autoplan_gate` on, `true autoplan-published <phase>` is now refused before it runs unless that phase is the next one due and its report was sent since the previous publish; the refusal starts `Next due: the Phase N (<name>) report.` A publish with no report or out of order never counts at Stop either, so a later report can't rescue it. Stop blocks now count against the step they name (3 per step, reset as the run advances), blocks before the run's first phase report don't count, and 12 blocks end any run; once a run is released or exhausted, its publishes are no longer refused. Setup registers one more hook for the gate (PreToolUse on Bash; its shim starts bun only for the publish command). Still off by default, and nothing changes with the switch off. Docs: docs/skill-gates.md#the-autoplan-gate.
- **/ship's review gate no longer blocks on a Low finding just because the native reviewer wrote it as a numbered item with a nested list.** When the native reviewer wrote `1. **FIXABLE. …**`, then a sub-list, then a closing paragraph with `Severity: Low. Blocks: no.`, the collector split that paragraph into a second record. The numbered item was left with no fields, so its FIXABLE label counted as CRITICAL and stopped /ship until an owner ruled on it (6 findings collected as 9 in one alphaclaw run). The collector now keeps that first closing paragraph on its finding. The join stays narrow so it can't hide a real blocker. It applies only to the native reviewer's items, ends after the first closing paragraph, never takes in a bold-led paragraph, a verdict line, a list item, a heading or a file:line, and never lowers an item that names CRITICAL or P0.
- **/autoplan's `rec` and `all` replies now apply every recommended plan change, as `gstack-gate` already did.** The skill's reply table sent `all` (and left `rec` unmapped) straight to "approve as written", while `gstack-gate parse` resolves both to every item's recommendation, plan changes included, so decisions.jsonl and the approved plan could disagree. `rec` is now the one-word default that takes those changes through the affected-phase rerun before the gate comes back; `as-is` stays the plan as first written. The plan-approval item now recommends `b` (take the recommended changes) whenever a recommended option changes the plan. A bare `p1a` with a user challenge unanswered no longer approves anything: the gate waits for the owner's own answer to that `uc` item and re-parses their whole reply. Every `uc`/`d` option in a hand-written gate now sets `changes_plan`, and an explicit override of an option with no flag still counts as a plan change, as `gstack-autoplan answer` already treats it. A test reads the skill's reply table and checks it against what `gstack-gate parse` returns for `all`, `rec`, `p1a` and an override.
- **Missing-browser errors stop telling Linux and Windows hosts to open the Aside app.** `NO_BROWSER_HELP`, used by `/make-pdf` and the renderer's no-browser error, now offers the Aside remedy only on macOS, where Aside ships, and keeps the `./setup` and `GSTACK_BROWSE_BIN` remedies on every platform. The docs/skills.md `/design-html` refinement loop also stops naming GPT-4o and describes the configured vision model, matching the template. (#3735, #3375; contributed by @szsunyuan in #4122)
- **The office-hours section-loading eval's preservation judge uses the shared judge token budget.** The case passed `max_tokens: 2048`, a local raise from the old 1024 default, and that now sat below llm-judge's 8,192 default for frontier judges. claude-fable-5-1 spent the 2,048 tokens before writing any verdict, so the case failed with a truncated judge response instead of a score (marathon 42371706491 follow-up, #4125). The threshold, trial count and rubric are unchanged.
- **/ship now writes pull request titles, commit messages, branch names and changelog entries that say in everyday words what changed for someone using the project.** An issue code may follow at the end, as in "Stop gstack from hitting GitHub's rate limit while it waits on PRs (GST-1713)", but the front of a title stays plain: no `type(scope):` prefix, no tracker codes and none of the repository's own coined terms. The new `gstack-plain-title` command checks titles, branch names, PR openings and changelog entries against a word list a repository keeps in `.gstack/plain-titles.json`; a repository without that file gets warnings, never a refusal. /ship and `gstack-post` refuse to post a title that fails, and a "Plain title" check on every new pull request does the same for PRs opened any other way. Pull requests opened before the rule started are exempt.
- **PR bodies now open with one or two plain sentences and end with one collapsed "Agent notes" section.** The ship receipt, approval and plan lines, issue codes and review bookkeeping that gstack's merge tools read now live in that section instead of a JSON block at the top of the body. The merge tools read it there and still accept the older layout, so open pull requests keep working. When gstack merges a pull request, main's commit title is the pull request's checked title.
- **The README now lists what gstack is for**, and /ship's PR opening names the goal a change serves.
- **gstack now waits for the "Plain title" check to pass before it lands a pull request.** The check reads the pull request's description from the event file GitHub hands the workflow, so a very long description no longer breaks it. A pull request that has never had the check run gets one when its title or description is edited, or when new commits are pushed.
- **A ship receipt posted on its own now goes into the pull request's Agent notes section and keeps the opening paragraph where it was.** The new receipt replaces the one in that section, so it is the one gstack reads.
- **An older receipt at the very top of a pull request description is still read after an Agent notes section without a receipt is added below it.** Receipt examples quoted in the readable part of a description stay where they are.
- **Correction to the earlier entry about plain titles:** a repository without `.gstack/plain-titles.json` gets no title checks at all, not warnings.
- **`gstack-ship-queue land --pin` and the first `ready` on a head no longer spend 15 s re-reading the queue notes.** Rebuilding local queue state from `refs/notes/gstack-trains` ran one `git cat-file blob` process per note, so every land and first READY took 14 to 15 s on the live 1,442 notes and grew about 8 s a week, on course to pass the 120 s command timeout in about three months, where a killed land leaves the land lock held. All the notes are now read by one `git cat-file --batch` process, which takes about 120 ms per rebuild on the same notes and restores byte-identical records. Malformed, empty and wrong-schema notes are still skipped. If that one read fails, the rebuild now says the notes were not read, so `land --repair` and the freeze lock refuse instead of trusting an empty queue (GST-2198).
- **The merge queue no longer lands a stacked child's parent commits under the child's receipt (GST-2153).** A child PR shipped against its parent branch (#3570) has a receipt that covers only the child's own change. If that child was retargeted to main while the parent was still open, `gstack-ship-queue ready`, `land --pin` and train build used to accept the receipt, so the parent's commits could land without their own review, READY or merged checks. They now refuse it with `QUEUE_RECEIPT_BASE_MISMATCH` until the parent lands, the base is merged into the child, and `gstack-ship-receipt carry --base <base>` moves the receipt to the base. Receipts written against the base, including stacked children reviewed against main (GST-1199), are unaffected.
- **Trunk Dynamic CI now also watches the Windows checks, the quality gate and the eval jobs on pull requests, still in learning mode, and it still can't skip anything.** Trunk answers only for jobs in the workflow that asks, so `windows-smoke.yml`, `windows-scoped-tests.yml`, `windows-setup-e2e.yml`, `quality-gate.yml` and `evals.yml` each get the same learning-only `trunk-dynamic-ci` job that `free-tests.yml` already has. Trunk records whether it would have run or skipped `windows-smoke`, `windows-scoped-shard`, `windows-setup`, `held-install-journey`, `quality`, `build-image` and `eval-slices`. No job reads the answer, so every check still runs with its own name and result. Each observer runs one pinned action, checks out no code, holds no GitHub token scopes, skips fork pull requests, skips its step when the token is missing, and never fails the run. `test/trunk-dynamic-ci-observers.test.ts` holds every observer to that shape.
- **A Captain can no longer archive a thread that still holds an open PR, and a landed PR's issue closes in the same step.** The new `gstack-archive-check thread` runs right before a Captain archives a crew thread. The Captain hands it the thread and issue lists it read from Capy, and it re-reads every PR the thread holds from GitHub. It refuses while any of those PRs is open (a successor does not change that) or while an issue in a working state is still assigned to the thread. It also refuses when a read is stale, truncated, unparsable or failed. Every run prints a handoff stub (each open PR's branch, head commit and CI state at that head, plus the open issues) for the next thread's brief. `gstack-archive-check landed --pr <n>` runs after `gstack-ship-queue land` and names the tracker code in the merged PR's title, so the Captain closes that issue in the same program, but only when the issue links this PR, no other open PR and no open sub-issues. crew/docs/captain.md (No orphaned work, Direct land), the gstack factory page, /captain and the /runaway-stopper rotation steps now say a thread with an open PR stays until that PR merges or closes.
- **/ship's finish now counts a chain of follow-up reviews after a granted follow-up review found a blocker.** A granted follow-up review that finds issues has to be logged `converged:false`, and finish refused every chain holding such a link, so a later clean follow-up review that read the fix could not count and only a fresh /ship review recovered the receipt, past the two-round cap. An earlier link that found issues now links when it finished its read on its own commit, its findings account for its counts, and every open or blocking finding names a file it read (a leading `./` on a finding's path names the same file). It counts no path itself, a path its findings name counts only through a clean follow-up review after it, and the `FIX_CHECK:` line marks it `(found issues)`. The chain must still be contiguous, every link still needs its own grant, and the newest link must still be a clean read; a gap, a range that starts mid-link, a newest link with findings, an unfinished read, and findings with no file all still refuse. A finding an earlier link logged on a file that no later clean follow-up review's range touched, even a file the branch never changed, now refuses the chain too, where before it dropped out and the chain counted.
- **Crew threads keep their PR to the end, and a repo with more than 10 finished PRs waiting gets no new fix thread.** gstack's rules pages (factory.md, the lane start card, the Captain manual and /runaway-stopper) no longer tell a crew thread to hand off mid-PR on a token count: it keeps its PR until it merges or closes, and checks in with its Captain once when its own tokens pass 1 billion (about $330). Standing threads that wake on every event (merge lanes, watchers, an automation's standing thread) still rotate at 500M own tokens or 3 days, and subagent limits are unchanged. A Captain now starts no new fix thread in a repository while more than 10 green, reviewed PRs sit waiting only to merge; security, data-loss, red-main and broken-install hotfixes are exempt. Wording only: `gstack-runaway scan` still flags every non-Captain thread past 500M.
- **`/captain` is now installed only on Capy machines.** gstack's crew tools for Capy (the Captain skill, and later the merge queue, receipts and spend caps) are being gathered under `crew/`, which `./setup` and `gstack-relink` load only where `CAPY_MACHINE_CONFIG` is set or `~/.capy/system` exists, the test the Capy installer already used. Everywhere else `/captain` gets no entry on any host, and an entry an earlier install made is removed on the next setup or upgrade; a skill of your own named `captain` is left alone. Every other skill, the install size and the install time are unchanged. The README has a short "gstack on Capy" section, and crew/README.md says what Capy users get.
- **`/design-review --build <preview url>` is a new opt-in loop for UI you are still building.** It skips the audit's clean-tree stop and never commits, stashes or asks. It lists the states your change can show, lints each one with `gstack-design-lint --scope app` at 1440 and 375 wide, takes a screenshot of each, checks seven spacing and hit-area items, and fixes what fails for up to three rounds. Then it runs one more pass that changes nothing and prints a single `DESIGN_BUILD:` line. The lane card, the CLAUDE.md routing block, docs/skills.md and /design-html's last step recommend it for UI work, but it is not required before a pull request is ready.
- **New `gstack-design-build` decides that line in code, not in skill prose.** The result is clean, waived, open, incomplete, unrendered or stale. It reads the lint's JSON verdict, never its exit status. A skipped render, a state measured at only some sizes, a state with no screenshot, an API call the preview could not answer, evidence older than the final pass, or a tree edited after the final pass can never print clean. Preflight checks the browser first. It refuses a target that is not on this machine unless someone granted that host, and it turns a refused design-review start into `code=INSTALL`. It lets the loop click through changes only when the preview itself answers at every local page; any other page, or a redirect, means the loop leaves changed actions alone. The receipt is a private JSON file tied to a hash of the exact files it measured, and `gstack-design-build check` reports it stale after any later edit.
- **New preview template (`design-review/templates/preview-fixtures.mjs`) serves an app on 127.0.0.1 with canned API answers, one scenario per state (`?as=<scenario>`).** It uses the app's own Vite and config, or `--static` for plain files. It never installs Vite. A small script it adds to every page carries the scenario to the app's own API calls. Any API request without a matching fixture gets a 501 that names the route and the file to edit, and never reaches a real backend. The log is private and hides tokens, the port is never silently moved, and `stop` shuts it down cleanly. See docs/howto-preview-a-frontend.md.
- **Design lint URLs are now redacted even when they do not parse** (a port out of range, for example) **and when a token value holds punctuation such as `)`.** Before, such tokens could show up in text, JSON, stderr and trace files.
- **/ship's docs check no longer burns its one retry on a badly shaped reply.** Before shipping, /ship asks a helper to audit the docs and end with one summary line in a fixed format. When that line had the right findings but the wrong shape (a missing field, or a misnamed field holding "x"), /ship counted it as a failed audit and spent its only re-audit, and an unattended run could stop with sound docs. Now `gstack-docs-candidate result` tells a shape-only mistake apart from a real one. For a shape-only mistake it prints `FORMAT_REPAIR:`, and /ship asks a fresh helper with no tools once, using no attempt, to restate just that line: `gstack-docs-candidate repair-prompt` builds the request from the format and the helper's own output. The reply is accepted only if every value the first line already got right is unchanged, so a repair can fill in a field but never change the outcome. A missing or wrong audit id, a missing or unknown status, either one given twice, a malformed list of edited files or blockers, a status that contradicts the file lists, or a last line that isn't JSON at all still counts as a failed attempt, and each audit gets one repair.
- **A pull request that only changes human-facing docs now skips the test suites.** It runs the docs lane instead: links on the lines it added resolve, generated pages stay fresh, the credential scan passes, and the few tests that read docs by directory run. Required checks still report success, so branch protection doesn't hang, and pushes to main keep the full suites. Most Markdown in gstack is agent instructions, so the class is a short allowlist (root README.md, docs/**/*.md, changelog.d/*.md) with a denylist on top. SKILL.md, templates, sections, policy docs, captain and crew files, pinned results, preregistrations, generated pages, and any doc a script or test names all still run full CI. One classifier (`gstack-docs-only`) decides the class for CI, pregate, CI carry, /ship's lanes and land. A crashed classifier, an unknown ref, or a push to main always means the full suites run. Garry, 2026-11-08: "For docs changes pls stop doing full CI and merge without tests. If there are really only docs." (GST-1951)
- **Crew docs now say how a factory keeps work from being orphaned, and how outside PRs land.** These are lessons from gbrain's merge queue, which reached 220 open PRs in November 2026. crew/docs/captain.md has a new "No orphaned work" section. A Captain archives or rotates a thread only once a live successor owns its open PRs and issues. Successor triggers sit on an issue, never only in notes. Sweeps re-own open PRs whose owning thread is archived. A crew PR closes only as landed, superseded or dropped, and anything else is salvaged onto current main. Before archiving a thread, the Captain re-assigns its issues instead of closing them or moving them to Backlog. The wave guidance adds one open queue PR per lane, follow-ups riding that PR or the next wave, waves cut after a day or once full, and when to move from direct landing to waves. Outside PRs are now bug reports. Each gets a verdict within 7 days, and real bugs are rewritten from main with credit, so no contributor commit enters the tree. CONTRIBUTING.md, captain/templates/CREW.md rule 18 and docs/review-policy.md say the same, including how to pass a contributor's commits to gstack-coauthor-trailers for credit. The report and brief templates carry an "Open work" line. Docs only; no tool behavior changed (GST-2077).
- **Findings and results write-ups now open with two or three plain takeaways, what works first.** ETHOS.md's Explain the Number says every findings file, investigation write-up and results README opens with "We learned X. You should do X." bullets: what works first, then what didn't, stated plainly with its number, and a null turned into the lesson its data supports. Internal mechanics go last, every experiment names the public lesson it is after, cost appears only when it is part of the lesson, and a repo, dataset or tool is opened only with a stated reason. No number is rounded in our favour and no caveat is dropped. /eval-plan's preregistration asks for a `lesson:` line under Bars, its results step points to the rule, and docs/TESTING_INTERNALS.md says the same beside its results guidance.
- `browse scroll <selector>` brings the element into view again when `--wait` is absent. The flag parser treated the first argument as the `--wait` value and dropped it, so `scroll footer` and `scroll footer --times 2` scrolled to the page bottom and replied "Scrolled to bottom".
- gstack's renderer no longer mistakes Debian and Ubuntu's `/usr/bin/browse`, a link to xdg-open, for gstack's own browser. A `browse` on PATH whose real file has another name is now skipped, so you get the clear "no browser" message instead of a desktop opener launching.
- The browse daemon no longer files every project's domain skills in one shared "unknown" bucket on installs without `~/.claude` (Codex, repo-local and other hosts). It now tries the checkout's own `gstack-slug`, then the Claude install, then the git remote rule, so skills saved in one repository stop showing up in another. Skills saved before this change stay in `~/.gstack/projects/unknown/learnings.jsonl` and no longer appear in a project's list; copy the lines you want into that project's `learnings.jsonl`.
- A question marker with a malformed signal hint, such as `<gstack-qid:ship-x;signal=scope_appetite>`, keeps its question id in the never-ask hook and the question log. Only the bad hint is dropped, so a never-ask preference still applies and the question log no longer records it as unmarked.
- The never-ask preference hook reads the question id lowercased, as the log hook and stored preferences already do, so a marker written with capitals no longer asks a question you silenced.
- **/autoplan no longer gets stuck between phases after Claude Code 2.1.317 compacts a long session.** On 2.1.317 a compaction's boundary names a parent record that is never written to the session file, so the guard's journal reader dropped every record after the compaction and refused each later phase entry on every retry. The boundary now continues from the last record the compaction kept in place (`compactMetadata.preservedSegment.tailUuid`). Boundaries from 2.1.300 and earlier, a tail that is not flushed yet, and lists that name no record of this session read exactly as before. Reported and fixed by @vilano93 in #4200.
- **The Capy install docs now agree on what a `behind=` trailer means, and the unattended rules say a moved base is merged in, never rebased.** The Capy skill said both "run the fix" and "`behind=` alone is informational" for the same trailer, and most lanes took the second reading and kept working on an old gstack. Now `behind=1` or more means run the trailer's fix before the next workflow, everywhere the rule is written. A pin with a recorded reason still holds, and so does an install that was refused. docs/unattended.md no longer names a slot call as a reason to rebase; it points at docs/ship-policy.md, which says to merge the base into a reviewed branch. (GST-2136)
- **A daemon launched with `BROWSE_TUNNEL=1` no longer opens a second ngrok tunnel when `/tunnel/start` arrives during its own start.** The earlier fix made overlapping `/tunnel/start` calls share one start, but the daemon's own startup tunnel skipped that lock. A `/tunnel/start` that arrived while the startup tunnel was still opening saw the tunnel off and opened its own ngrok session, and the daemon kept only one of the two. The other stayed public on your ngrok account until the daemon exited, and closing the tunnel didn't reach it. Now every tunnel start, from the route or from startup, goes through one shared lock and gets the same URL, so there is only ever one session.
- **Two quick `/tunnel/start` calls no longer leave a public ngrok tunnel you can't close.** When two callers asked the browse daemon to start its tunnel at the same moment, both saw it off and each opened its own ngrok session. The daemon kept only the second, so the first stayed public on your ngrok account until the daemon exited, and closing the tunnel didn't reach it. Now a start that arrives while another is running waits for it and gets the same URL, so there is only ever one session. Also, when a start failed after ngrok had opened (for example, the state file write failed), the daemon cleaned up the session but still believed a tunnel was live, so it never shut down on idle; it now resets its tunnel state on that failure too.
- **Two machines that each log a decision between pushes no longer stop one of them from syncing.** The second machine's push was rejected, its fetch-and-merge retry conflicted on `projects/<slug>/decisions.active.json`, and `gstack-brain-sync` aborted the merge and wrote `push_failed` on every later drain until someone repaired the clone by hand (#4174). The snapshot is derived from `decisions.jsonl`, so when a merge's only conflicts are decision snapshots whose logs merged cleanly, brain-sync now rebuilds each snapshot from the merged log with the new `gstack-decision-log --rebuild-snapshot <project-slug>`, commits the merge and pushes; it never merges the JSON array itself. This needs no `.gitattributes` change, so a clone already stuck this way publishes on its next drain that has records to sync. A conflict in any other file still aborts the merge as before, and so does a log that lost lines on either side, as `--compact` does to a redacted decision, since rebuilding from the union of both logs would bring that decision back. New artifacts repositories also get `projects/*/decisions.active.json merge=binary` in `.gitattributes`, so a merge run by hand leaves no conflict markers in the snapshot.
- **The pregate secrets check no longer stops /ship on Django's `session = self.client.session`.** env.kv read any assignment to a name ending in `session`, `token` or `key` as a credential when the value looked random enough, so a Python test that grabbed the test client's session failed a check nobody can downgrade, and the only way past it was a hand-edited allowlist (#4209). In Python and Dart source, an unquoted dotted name such as `self.client.session` or `request.auth`, alone on the right of `=`, now reads as an attribute. A quoted value, a random-looking dotted token, the same line inside a `"""` string, anything after it on the line, and every .env, YAML, config and TypeScript file still report. Reported by @dgrant in #4209.
- **`./setup` no longer refuses healthy hooks when Bun crashes while checking them.** The hook parse check bundles each TypeScript hook with one Bun process; when that process was killed by a signal (a Bun segfault, seen in Free Tests 42327570347), every hook it hadn't reported was listed as "does not parse" and setup exited 1 with a "this is a gstack bug" message. Now the results Bun printed before the crash still count and the shell checks still run, so a hook that really fails is still refused, and only a hook Bun never reached is registered unchecked. The check reports itself unavailable (exit 3, the same as a missing Bun) only when a hook went unreported and nothing else failed; setup then warns that hooks were registered unchecked, and the auto-updater holds the update as it does for any unavailable check. A Bun that exits normally without a report still fails every hook it left out.
- Two flaky tests are fixed. The gbrain prune test no longer sees another test's `gbrain autopilot` process, and the plan-review floor actor now answers its own cropped "overwrite" permission for the working plan instead of waiting it out.
- **`gstack-uninstall` no longer deletes your repo's committed `.gstack/` files or unsaved work in `.gstack-worktrees/`.** Run from inside a repo, uninstall used to delete the whole `.gstack/` folder and every `.gstack-worktrees/` checkout, even with `--keep-state`, so a committed `.gstack/ship-policy.json` showed up as deleted in `git status` and a worktree's uncommitted files were gone. Now `--keep-state` keeps both folders. A plain uninstall still clears gstack's own ignored browse state, but keeps every git-tracked file under `.gstack/` (and says so), and it stops with exit 2 before removing anything when a checkout there has uncommitted changes (even with `status.showUntrackedFiles=no`), holds commits no branch or tag contains, or isn't a worktree this repo registered (as after moving the repo, or a separate clone put there, which uninstall never opens with git), naming the worktree and the fix: commit to a branch or stash, re-run with `--keep-state`, or pass the new `--force-worktrees` to delete them anyway. A symlinked `.gstack-worktrees` is only unlinked, as before, and never followed, so the checkouts it points at are kept. A worktree nested inside another checkout under `.gstack-worktrees/` is not checked yet and is still deleted with its parent, as before. The checkouts it removes also leave `git worktree list`; the registrations of your other worktrees are never touched, even when their folder is missing for now (GSTA-461, GST-2197).
- **Free Tests no longer fails every PR's plan when the test-duration seed goes stale.** Test files missing from `scripts/free-test-durations.json` are still packed at the seed's 99th percentile, but when that estimate would push a CI shard's job past the 90-minute cap, `--ci-plan --job-setup-seconds` now cuts the shard's prediction back to the cap instead of throwing `FREE_JOB_TIMEOUT_OVER_CAP`. Recorded durations are never cut, so a shard whose seeded files alone need more than 90 minutes still fails the plan, and plans under the cap are unchanged. The seed is refreshed from a new recording (196 files had no recorded time), and a weekly seed PR that is closed without merging now opens a blocker issue, which closes when a seed PR from the recorder merges.
- **New design note: what a lighter gate for crew-only pull requests would save, and how crew steps will switch off outside Capy.** docs/designs/GSTACK_ON_CAPY.md measures, for free from recorded CI runs, what the 54 crew-only pull requests merged 2026-10-24 to 2026-11-07 spent on paid evals and review (about $735–855 a lighter gate would have saved, close to $0 under today's direct landing), with every caveat. It also specifies the one switch Phase 2 will use so `/ship`, `/review` and `/autoplan` run crew steps only on a Capy machine. Nothing in gstack's behavior changes.
- **`gstack-ship-release accept` no longer reruns a CI carry's local remainder that Step 5 already ran at the same tree.** /ship's Step 5 and accept both call `gstack-evidence record-ci`, which ran the lane's `per_file` remainder (the inventory files CI skipped entirely) each time, so on harness-evals #315 a remainder file of about 7 minutes ran twice and accept's record-ci hit the 600 s `ci_record_timeout_s`. After verifying the run live, record-ci now reuses the label's newest ledger row when it is the CI row it would write again: same commit, clean tree, command, run id and attempt, profiles, jobs, base policy, covered count and remainder files, FRESH, no test-lane exception, and each remainder file's newest local run at this tree written before that row and a pass under this runtime. It prints `CI_RECORD_REUSED: label=<lane> runs=<id>/<attempt> tree=<tree12> policy=<policy>`, and accept's lane line ends `(reused the CI record at tree <tree12>)`. Any mismatch, a newer row for the label, or a run that is no longer green records afresh or refuses as before. No timeout or coverage change. See docs/pregate.md#record-ci-reuse. (GST-1795)
- **A clean Codex outside review that signs off with a sentence about its checks now reads `VERDICT: clean`.** Since #3304 only a review's last sentence (or its Recommendation because-clause) can conclude it, so a reply such as "No actionable regressions were identified in the diff. Tests, lint, and build all pass." read `VERDICT: unverified` / `REASON: untagged_review`, and /ship and /review reported missing outside coverage for a review that ran and found nothing. Closing sentences that only report passing checks are now skipped when the conclusion is read, and "The changes introduce no definite, actionable regressions." counts as a no-findings statement held to the whole-change scope rule. It stays fail-closed: a check sentence that quotes code, fails, is cut off, continues a wrapped line or follows a colon is never skipped, skipping one never makes a scoped claim the reply's only sentence, a clause joined after the whole-change scope by a comma, dash, slash, bracket or a word such as "and", "so" or "while" ("No issues in the diff, while the change disables auth.") is never a conclusion, and a review with findings is never read as clean. See docs/troubleshooting.md#outside-review-verdict. (#3469, GST-1803; design and tests from #3641 by @vilano93)
- **An outside review's no-findings conclusion must now be one complete sentence, so a review that reports a problem after its no-findings statement can no longer read as clean.** Until now a few reply shapes read `VERDICT: clean` on a merge gate while saying something was wrong: a Recommendation-only reply ("Recommendation: Ship because no issues in the diff, so the change disables auth."), an exception after the scope ("No issues in the diff with the exception of the auth change."), a question ("No issues in the diff?"), and a colon or symbol after the scope ("No issues in the diff: the fallback path now swallows ENOENT.", also with ` & `, ` + `, ` -> `, ` | ` and similar). The parser now reads the conclusion as a closed sentence. It may hold an optional lead, the no-findings statement, an optional "were found", an optional scope that names the whole change ("in the diff", "in the changes on this branch", "in the diff against main"), then at most one `.`, `!` or `;`. Anything else is `VERDICT: unverified` / `REASON: untagged_review`, so the rule no longer depends on lists of exception words and joiners. This fails closed in a few clean-looking shapes too: a scope that names part of the change ("No issues in the parser.", "in changed.ts") and a colon explanation ("No findings: the cap is enforced.") now read `unverified` even as the reply's only sentence. No captured fixture reply moves toward clean. Whole-change scopes that #4130 left fail-closed after other prose now read `clean`: "in the change as a whole", "in the committed and uncommitted changes" and "in the staged / unstaged changes". See docs/troubleshooting.md#outside-review-verdict. Also, test/outside-voice-preflight.test.ts no longer writes the machine's real ~/.gstack/config.yaml through an inherited state root. (GST-1808, GST-1809)
- **An autoplan `rec_hash` now names its run, so two unrelated gates no longer print the same one.** The hash a gate render prints, and `gstack-autoplan answer --rec-hash` checks, covered only the gate revision and the recommended letters, so any two runs with the same decision shape shared it (GST-1749's and GST-1811's gates both printed `f94689f795a5`), and a `rec` relayed for one run passed the check on the other. It now also covers the run id, the reviewed plan's sha256 and each item's title and option keys, and a hash from another run's gate is refused as `GATE_REC_STALE`. Gates written before this keep working. A `gate.json` without the new `rec_hash_v` marker still accepts the hash it printed then, and the answer records that hash. That old hash is not bound to its run, so re-render a gate still pending from before this change and answer with the new hash its render prints. Recorded approvals are untouched, since `gstack-approvals import --autoplan` and the plan record bind by run id and gate revision, never by `rec_hash`. See docs/troubleshooting.md#gate-rec-stale. (GST-1819)
- **The design lint can now check one section of a running app screen for crowded rows, help text jammed under a list, notes as loud as names, and small tap targets.** It is render-only and opt-in: `--url <url>` (one state each) or `--state-file <states.json>`, a required `--within <selector>` matched through open shadow roots, `--viewport WxH` (1440x900 and 375x812 by default), `--wait-for`, `--waivers <file>` and `--verbose`. Five new rules at ask report only under `--scope app`: `row-crowding`, `copy-flush-under-list`, `note-in-row-weight`, `small-hit-area` and `section-overflow`, each finding with its state, viewport, a shadow-piercing locator, the measured px and the threshold. On gstack's replica of the qm#3186 Sharing panel it reports the four defect rules; on the fixed copy, the negative controls and a 12-page false-positive corpus it reports none. A `LINT_APP: verdict=<clean|open|incomplete> code=…` line follows `LINT_SUMMARY`, and the exit is 0 clean, 1 open, 4 incomplete (no browser, unreachable, timeout, not ready, state mismatch, no match, closed shadow root, probe error, missing stylesheet), 3 usage. URLs are redacted in every output and probe failures write a private trace file. See docs/design-lint.md "App profile (--scope app)".
- **Behavior change for every caller: `gstack-design-lint --url` with no browser now exits 4.** It prints `gstack-design-lint: RENDER_UNAVAILABLE: nothing was measured at <url> (<reason>) | fix: gstack-browser-ensure, then rerun` on stderr instead of passing with exit 0. `--render` over files without a browser is unchanged. `--within`, `--viewport`, `--wait-for`, `--waivers` and `--state-file` without `--scope app` exit 3, and the usage text gains the app line. Page and component output and exit codes are otherwise byte-identical (test/design-lint-scope-guard.test.ts).
- **/ship no longer files deferred review findings as issues.** A blocking finding in code the PR did not change (`review_scope: frozen`) now stays on the PR: `gstack-review-scope defer` records it for the PR body's `## Deferred findings` section and never calls `gh`, so it opens no GitHub issue and needs no marker search. The `--pr`, `--repo`, `--branch` and `--dry-run` options are gone, and a deferred record no longer carries `issue`, `url` or `reason`. When the reviewer stated its own severity or Blocks field, the deferred line in the PR body shows it next to the label, as the issue body used to. /ship's Step 9.4, docs/review-policy.md, docs/ship-policy.md, the READY template, crew/docs/captain.md and the Capy factory page now say the same thing: deferred findings stay on the PR, and follow-up work someone will actually do gets one issue with a named owner. The pre-existing test failure option in /ship that assigns an issue to the person who broke the test is unchanged, since that issue has an owner.
- **`gstack-approvals render` keeps local paths out of the approvals table in a PR body, and `--redacted` prints the public form on any repository.** On a private repository the full table printed each row's Covers text as recorded, so a spend-cap row carried its ledger path (`/home/<user>/.capy/drive/…`) into the PR body, where gstack-post flagged it as `internal.user_path` and the PR's edit history kept it after the repository went public (gstack-evals#289). The table now shows each local path in the Asked, Covers and Still pending columns as `…/<file name>`, and a bare home directory as `~`: absolute and home paths (`~/`, `~user/`, `$HOME/`, `/home/…`, `/Users/…`), Windows-drive, UNC and `file://` paths, and Drive-relative ones (`.capy/drive/…`). Exact words stays as the owner wrote it, and `gstack-approvals list` still shows the full record. `render --redacted` prints the same bytes a public repository gets, for a private repository that may go public, and `--redacted` with `--full` refuses with a usage error instead of letting one win. The redacted line now ends "The full approval record stays private." without naming a reason, so it stays true when `--redacted` is used on a private repository.
- **A data file whose name git quotes no longer slips past the secret-line keep rule in `review_exclude_paths`.** git still C-quotes patch headers for names holding a double quote, a backslash, a tab or another control byte, even with `core.quotePath=false`, so the review collector stored their added lines under the quoted header text and the keep rule read no lines for the real path: a file such as `r/q"x.json` holding a credential value was excluded from every reader's diff. The collector now decodes quoted headers (octal escapes, `\"`, `\\`, `\t` and the rest) and pins the `a/` and `b/` prefixes, and the exclusion fails closed: a matched text file with changed lines that the collector holds no lines for is kept as unread instead of excluded.
- **The factory page says one open PR per lane and never archive a thread that still owns work.** Point 2 of capy/skill/gstack/factory.md, the page every Capy session reads, now says each lane keeps one open PR and follow-ups ride it. Point 12 says a Captain never archives a thread with an open PR or issue until a live successor holds it, and a PR closes only as landed, superseded or dropped. Both are lessons from gbrain's merge queue, which reached 220 open PRs in November 2026. The page's byte budget moves from 3,475 to 3,650 for these two rules. Docs only (GST-2077).
- **gstack's Capy-only tooling now lives under `crew/`.** The merge-train and wave code, the release gate, the release cut and its daily check, version-gate trains, lane ownership and the paid-eval CI steps only Capy crews run moved from `lib/`, `scripts/` and `bin/` into `crew/lib/`, `crew/scripts/` and `crew/bin/` (`gstack-release-gate`, `gstack-coauthor-trailers`), with their tests in `crew/test/`. The Captain manuals moved to `crew/docs/captain.md` and `crew/docs/recommended-harness.md`, with short pointers left at the old paths. Workflows, imports and docs point at the new paths; behavior is unchanged. Setup copies `crew/lib/` into each host's install folder beside `bin/` and `lib/`, so Windows installs, which copy files instead of linking them, still start `gstack-ship-receipt`, `gstack-pregate` and the other commands that use it. On Capy the session launch file puts `crew/bin/` on PATH, so the commands keep their names; nothing else does, so other installs never see them. `gstack-flaky` and the factory scorecard stay where they are because outside users run them. A Mac or other paired device running Capy now always counts as a Capy machine for `/captain`: any `~/.capy` entry counts, not only `~/.capy/system`, so an unclear case installs the skill instead of dropping it.
- **Correction to the earlier entry "An outside review's no-findings conclusion must now be one complete sentence".** That entry said a review that reports a problem after its no-findings statement "can no longer read as clean". That held only for a problem in the same sentence, after the statement. A problem written before the statement ("The fallback swallows ENOENT; no issues in the diff."), or in a later sentence after a clean Recommendation, still read clean, and so did scopes that name only part of a branch ("in the latest commit", "in the staged changes"). The verdict-line change above retires all of these rules. Now gstack no longer reads a no-findings sentence at all, and a review counts as clean only when the reviewer itself ends with `VERDICT: CLEAN`. A problem the reviewer describes without a severity tag above that line is still the reviewer's call.
- **An outside review now counts as clean only when the reviewer's last line is exactly `VERDICT: CLEAN` or `VERDICT: FINDINGS`.** Every outside-review prompt (in /ship, /review, /codex, /claude-code, /autoplan, the plan reviews, /office-hours, /design-review and /document-release) now asks for that line, and gstack no longer reads prose such as "No issues found in the diff." as a pass. Every earlier rule for reading a no-findings sentence let some reply that named a problem pass the merge check, for example "The fallback swallows ENOENT; no issues in the diff.", a clean Recommendation followed by a listed problem, "No issues found in the latest commit." on a whole-branch review, and "Recommendation: fix the auth bypass because no issues in the diff." A reply without the line now reads `VERDICT: unverified` / `REASON: missing_verdict`: missing coverage, never a pass. A P0 or P1 finding (or a Critical or High label) and a last `Verdict: not ready` still block whatever the line says, and advisory P2 or P3 findings still pass. Codex's structured outside review in /ship and /review, and the default `/codex review`, now run through `codex exec` with gstack's prompt instead of `codex review --base`, which takes no prompt and so could never give the line. The diff they review is the same (`git diff $(git merge-base <base> HEAD)`: the branch commits plus uncommitted tracked edits). Skills rendered before this change still read unverified until `./setup` re-renders them. A reply that ends with `VERDICT: CLEAN` but lists numbered items with no severity tag now reads `VERDICT: unverified` / `REASON: untagged_review` in /ship and /review, and a `/codex` consult or challenge answer with numbered items and either a `Verdict:` line or a last `VERDICT: CLEAN` now prints `FINDINGS: unspecified`. `/codex review` now stops before calling Codex when it cannot write the diff into the prompt. See docs/troubleshooting.md#outside-review-missing-verdict.
- **Crew threads now stay quiet between READYs, and busy Captains can hand PR and CI watching to one merge thread.** gstack's factory page tells crew threads to message their Captain only for a READY, a blocker, a decision the Captain or owner must make, or done, and to end every other turn with end_turn, because each message wakes the Captain cold. The Captain guide (`crew/docs/captain.md`, Roles) and the Capy skill's `captain.md` describe the merge thread: one crew thread holds the PR watches so check, review and merge events stop waking the Captain, while every other crew thread keeps owning its own PR's checks and review until READY.
- **A base merge that only moves the lines around a reviewed change no longer forces a second full /ship.** On founderstar#802, a base merge changed the context lines around the PR's 22 reviewed and unchanged hunks in `scripts/core/game.gd` (+66/-11 at both the old receipt head and the new one). The scoped-review packet counted those context lines and the hunk headers toward its 200-line section limit, so the section came to about 205 lines and `gstack-ship-receipt carry` refused with `RECEIPT_CARRY_PRODUCT_CHANGED`, which meant a full /ship on every mechanical retake. 1.94.39.0 (GST-1226) had stopped counting the base's +/- lines, but not the context lines inside the branch's hunks or in the base's change. Now only the branch's own +/- lines count, in carry and in `gstack-ship-release finish`; context lines, hunk headers and the base's change are still shown to the reader. A section whose branch +/- lines pass 200 still takes the full loop, and a base change over 2,000 lines in one file still counts in full. (GST-2229)
- **A main merge inside the review fix range no longer makes main's files count as the PR's fix.** On harness-evals#354, the PR's own change since the last targeted read was a changelog fragment, one docs line and one manifest line, but `gstack-review-plan` measured the fix range as `--range-base` to HEAD, so the 68-line bootstrap script and the `.gstack/pregate.json` edit a main merge brought in made the range gate class. With the owner's grant it printed `next=voices` and re-reviewed code main had already shipped, instead of the one targeted-only read. The fix range, and the check for gate-class edits after the last fix-check, now read only the PR's own change since the range base, the same change the receipt already reads (GST-1593). A gate-class edit in the PR's own change still escalates. (GST-2229)
- **An agent's own words no longer settle a user challenge at the /autoplan gate.** `gstack-autoplan answer --words-by agent:<id>` let a Captain choose a `uc` item (a user challenge both reviewers raised, or a round-cap approval) on its own authority, though only the owner may settle one. Such a reply, whether `rec`, `as-is` or a `uc` token, is now refused with `OWNER_REPLY_REQUIRED`, naming the item and asking for the owner's words, and the gate stays open. The agent can still answer the `d` items in its own words and leave the `uc` items to the owner. The rule that agent words are never the owner's now lives in one place, shared by the answer path, carry-forward and plan-sha citations.
- **A repository can name its own human-docs folders, so prose there skips the test suites too.** Docs that live outside `docs/` (gstack-evals keeps posts in `wiki/board/`) used to run every suite, and /ship ran them again locally. Add `"docs_only": { "allow": ["wiki/board/**.md"], "tests": ["tests/wiki.test.ts"] }` to `.gstack/ship-policy.json` on the base branch. gstack reads it only from the base, so a pull request can't add itself, and a pull request that edits the policy always runs full CI. Patterns must be narrow Markdown globs: `**`, `*`, `*.md` and anything aimed at agent files are refused with an example of a good one. gstack's own deny list (agent instructions, templates, prompts, preferences, tool configuration, pinned results, generated pages) always wins. A script that reads an allowed folder keeps the class off unless one of the listed tests covers it, and those tests run in the docs check. A listed test missing from the branch keeps the class off too, and land keeps its checks when the base gained a reader of the folder. Unknown keys inside `docs_only` and `land` are now warnings instead of errors. `land.docs_only_skip` names its own release, so `gstack-ship-policy validate` says which `min_gstack_version` turns it on. (GST-1974)
- **Free Tests now reports every free-suite result to Trunk Flaky Tests, for detection only.** Each shard keeps bun's JUnit report from every child, the flaky retry included (`--junit-dir`), and a new `trunk-flaky-tests` job uploads them to Trunk's "gstack free suite" collection on same-repository pull requests and main pushes, so Trunk can flag tests that fail and then pass on the same code. Nothing is quarantined or skipped, no job depends on the upload, and an upload failure never turns a check red. A test child that times out or crashes writes no report, so Trunk sees none of that child's results; it also skips the flaky retry, so the flake ledger has no entry for it and the red shard result is the only record. The token lives only in that job, which checks out and runs nothing from the pull request. The flake ledger and the daily flaky pass are unchanged.
- **Big results PRs now fit under /ship's review ceiling when their transcripts only name a credential.** Under `review_exclude_paths`, a data file used to stay in review for any line that named a credential, so captured model prompts that mention `${OPENAI_API_KEY}`, `$AGENT_API_TOKEN` or a keychain kept every such file (harness-evals#333: 2,610 files). That many kept files pushed the pattern past the 24,000-byte keep-command limit, so it wasn't applied at all, and readers got over 5 MB against the 2 MB ceiling. A line now keeps its file only when it names a credential and carries a value, meaning the credential scan finds a secret on it or a base64 or token run of 20+ characters follows the name. A key name given a value, such as `OPENAI_API_KEY=sk-…` or `"GH_TOKEN": "…"`, now keeps its file too. Files kept only for a secret line are excerpted, so they no longer count toward the keep-command limit, gstack reads them in git calls under that limit, and `DIFF_KEEP:` leaves them out. On harness-evals#333 the reader's diff drops from 5,458,045 bytes to 1,061,721.
- **/ship no longer stalls planning review on a branch that merged in thousands of data files, and `gstack-review-scope classify` reads a branch diff of any size.** `gstack-review-plan` checked each changed data or docs file for a leading `#!` with its own git call, so a fix range that took in another run's 11,749 trial files spent about 200 s per read of that range on a Capy machine (harness-evals #320 waited 25 to 58 minutes); it now finds those files in a few batched git calls. A plan run by itself now stops at the same 180 s limit `gstack-review-dispatch pass` gives it (`GSTACK_REVIEW_PLAN_TIMEOUT_MS` sets it), printing `REVIEW_PLAN_STOPPED` and no plan, and an unreadable fix range lists its first five problems and a count instead of every file. `classify` failed with an empty reason once the branch diff passed 64 MB; it now reads git's diff from a file, and a failure names git's error or the timeout.
- **Fewer false privacy holds when posting PR descriptions and running the pre-merge checks.** Three kinds of machine-written numbers no longer read as personal data. GitHub Actions run numbers in the JSON summary /ship writes into a PR description (`ci_carried[].run`, `flaky_exception[].runs`, `issues.flakes[].run`) no longer count as phone numbers, so a post with reused CI no longer asks for `--confirm` once per run; only a block exactly as /ship's writer renders it qualifies, and the same number in prose, next to the block, under any other key, in one of those fields holding anything but an 11-to-20-digit run number, or inside a hand-written or broken block still needs confirmation. A four-part version written to or read from a `VERSION` file, or set right beside a three-part version (`path.join(dir, "VERSION"), "1.98.0.0\n"`, `v1.97.2 to 1.98.0.0`), and a `VERSION` file holding only the version, no longer count as public IP addresses; a public IP with no version beside it still reports.
- **`gstack-approvals import` records plans whose autoplan run id looks like a phone or card number.** A run id such as `HARN-64-20261109177601` ends in a 14-digit time stamp, and the scanner could read it, alone or with the digits before it, as a phone or card number, so import refused the approval. Import now skips those two findings only in the time stamp (and the thread number right before it) where it places the run id itself, and only for an id of the minted `<name>-<14 digits>` shape. A phone number in the rest of the run id's name, in the same approval's words or plan title, or the same id typed into `gstack-approvals add`, still refuses.
- Home-directory paths and other low-severity findings stay notes in both `gstack-post` and the pre-merge checks, now pinned by tests, and keys still refuse everywhere, inside /ship's summary block included.
- **`gstack-runaway scan` no longer tells a Captain to rotate a crew thread that holds an open PR.** The scan now matches the rule the docs already carried: only a thread the Captain marks standing (a merge lane, a watcher, an automation's standing thread) gets `FLAG: ROTATE`, at 500M own tokens or 3 active days, and never while it holds an open PR; that case prints `DEFER: holds an open PR; rotate after every PR merges or closes (R91)` instead. Every other thread is a crew thread, which gets one `FLAG: CHECK_IN` past 1 billion own tokens and no rotate or own-token warn line. A thread file whose `pull_requests` list is missing or partial counts as holding a PR, so old or truncated input never leads to a rotation. /runaway-stopper's fetch program gains a `standing` list, its line table gains the `CHECK_IN` row, and `SWEEP:` gains a `check_in=` count.
- **The secret check and the pre-push hook now read files git calls binary.** A GitHub token in a file with a NUL byte, in UTF-16 text, or in a file a `.gitattributes` line marks `-diff` (which the same branch can add) used to pass both guards, because git printed "Binary files differ" instead of the added lines. Both now diff with `--text` and read each line of such a file three ways. They read it as little- and big-endian UTF-16 text without its NUL bytes, even when most of it is Cyrillic or CJK, and as bytes where each run of control or non-UTF-8 bytes reads as a space, so a token is found in whichever reading keeps it whole. That catches a token on one line of UTF-16 text. A keyword and its value split across two lines of UTF-16 text, such as an AWS secret key on the line under its YAML key, are not caught yet. UTF-32 text and compressed containers such as zip, docx and PDF files are not read. Only credential findings count in such a file, so a run of digits in a compressed image is not reported as a phone number; this repo's own PNG, JPEG, GIF and ICNS files add no finding. A very large binary is not read in pieces, so it fails closed instead of passing. A file whose scanned text is over the scanner's 16 MiB limit fails the `secrets` check, and a push whose diff is over 64 MiB is blocked; keep such files in Git LFS, whose small pointer files scan as text. The pregate `secrets` check also stops treating a merge-base diff git did not finish (an error, a kill or a full buffer) as complete: it is now `PREGATE_ENVIRONMENT`, never a pass. Its pass line names how many files it read, for example `12 added lines clean in 3 files (1 binary)`.
- **The merge queue no longer calls a head green while a check on it failed.** `gstack-ship-queue ready`, `land`, train land, wave and the post-merge watch all read CI through one helper, and it missed two kinds of failed run. GitHub lists only the newest attempt of each workflow run by default, so when a rerun attempt created no jobs, the failed run before it vanished from the list; gstack-evals#303 went READY with `passed=1 failed=0` while its `checks` job was red. The helper now also reads every attempt. A check suite whose latest attempt ran jobs is judged by that attempt alone, so a rerun that dropped a failed matrix job clears it, and a suite whose latest attempt had no jobs is judged by its earlier attempts and its workflow run's result, so a passing first attempt no longer hides a rerun that failed without starting. A workflow run that ended without creating any job (a startup failure, or a run that failed before scheduling one) never appears as a check run at all; it now counts as a failed check named `<workflow> (run <id>, no jobs)` unless a later run of the same workflow at that head replaced it; a replacing run counts only once it has jobs or has finished, and a run cancelled before any job (a concurrency group dropping a pending run for a newer SHA, as the main backstop does) never ran, so it does not count. A green head, a pending or skipped check and a rerun that passed read the same as before (GST-2167).
- **`/ship` no longer asks for a money review of files your review policy leaves out.** When a repo's `review_exclude_paths` skipped a file such as a committed SQLite ledger, `gstack-review-plan` correctly said no targeted review was needed, but `gstack-ship-receipt write` at the same commit still refused with `REVIEW_TARGETED: required=money`, so the run could not finish (GST-1900). The receipt write, the receipt carry, the merge queue's check and the plan now all leave excluded files out of that check the same way. A money-looking file the patterns don't cover still needs the money review, and an exclusion that isn't active (no policy key, a pattern only the branch adds, or a gate scan the receipt doesn't bind) still excludes nothing.
- **`/ship` no longer keeps a whole results file in review because a model reply says "fit (".** The gate scan's focused-or-skipped-test check (RH-1) read every non-prose file, so English like "don't fit (the gap never drops below one space)" in a committed `turns.jsonl` tagged the file and kept it in the reviewers' diff even under `review_exclude_paths`. On harness-evals#333 that put the reader's diff back at 5,120,181 bytes against the 2 MB ceiling and `/ship` stopped with `REVIEW_DIFF_TOO_LARGE` (GST-2006). RH-1 now skips only data and logs, which no test runner executes: JSON, JSONL, NDJSON, CSV, TSV, `.log` and `.out` files no longer carry RH-1 unless they were renamed from another kind of file. Every other file keeps RH-1, in a test folder or not, and every other gate tag still reads data files, and code under an excluded path with `fit(`, `fdescribe(`, `it.only(` or `describe.skip(` is still tagged and kept.
- **QA captures on macOS no longer record a finished command as incomplete.** After a command exited, the cleanup kill of its process group could get `EPERM` from macOS while the group was being torn down, and `gstack-qa-evidence capture` then recorded `status: incomplete`, `exitCode: 2`. On macOS, an `EPERM` now makes the capture check the group directly and count it as gone only when every remaining member is a zombie or is exiting (`ps` state starting with `Z` or carrying the `E` flag); a live member, or a check that cannot run, still fails closed, and `EPERM` on other platforms fails closed as before. The macOS teardown state is inferred from XNU and `ps`, not measured. Reported with measurements by @vilano93 (#4166).
- **pregate's `strays` check no longer flags real files whose names only start with `tmp` or `scratch`.** It treated `tmp` and `scratch` as name prefixes, so a real module such as `lib/server/disk-space/tmp-watch.js` failed with `PREGATE_STRAYS`, and `tmpfs-mount.js` or `scratchpad.ts` would have too. A branch can't allowlist its own strays, so nothing on the branch could clear it. `tmp` and `scratch` now count only as the whole name stem, optionally numbered: `tmp`, `tmp.js`, `tmp2.js`, `tmp_1.txt`, `TMP.js`, `scratch.md` and `scratch2.ts` are still flagged, and so is every `zz-*` name and editor leftover. The tradeoff: a stem followed by a word, such as `tmp-repro.ts` or `scratch_notes.md`, is no longer caught either, so name a scratch file `zz-*` to keep it flagged.
- **A reader's clean reply, or a sentence denying a severity, no longer stops /ship.** The review collector took a severity word anywhere in a reader's reply as a finding at that label. #4208's pass-2 red team returned `NO FINDINGS` but quoted `CRITICAL (the reviewer wrote …)` in a coverage note, and an alphaclaw fix-check wrote "No fix introduced a CRITICAL defect". Both collected as CRITICAL, judge may not drop a collected finding, and `gstack-review-plan` printed `NEXT: stop` (GST-2122). Now a reply that says `NO FINDINGS` keeps only its structured findings, which still collect at their label. Those are JSON objects (malformed ones included), lead labels, label headings, numbered headings, `Severity` fields, `Blocks: yes`, the native FIXABLE or INVESTIGATE tag, and anything naming a file:line. Its other prose collects nothing. A denial that a change introduced a severity is a no-defect clause, including after a bold lead such as `**1. Coverage:**`. Other prose keeps its label as before, so a real prose blocker such as "Neither fix clears the P0." still blocks.
- **The preflight secrets check no longer fails a branch for code that reads an environment variable, or for a self-describing fixture label in a test file.** `env.kv` already let `process.env.X` through, but `token = opts?.env?.AGENT_API_TOKEN;`, `ctx.env.X;`, `import.meta.env.X;`, `process.env["X"];` and `System.getenv("X");` read as credential values and failed `gstack-pregate` (yc-software/qm, GST-1894). An `env` member of any object read by an ALL-CAPS name, and an indexed or call read whose argument is a quoted ALL-CAPS name, are now code, but only when the line ends the expression with `;`, `,` or `)` and holds no fallback or default (`??`, `||`, `or`, a ternary `:`, or a second argument). Any other line, including one whose fallback follows a comment, a blank line or a line break, gets no new exemption. In `*.test.*`, `*.spec.*`, `test_*.py` and `*_test.*` files only, a value made only of lowercase words with a credential word among them (`"personal-secret"`, `"staging-api-key"`) is a fixture label. A literal fallback, the same label outside a test file, and every known key prefix still fail. A quoted name that isn't ALL-CAPS in an indexed or call read now fails where it used to pass: a lowercase name such as `os.environ["db_password"]`, and a literal used as the name such as `os.environ["<secret>"]`, which passed because the check skipped the quote the match had taken.
- **Learnings, timeline events and brain sync no longer store or push Stripe live keys, Slack tokens, npm tokens or database URLs with a real password.** `gstack-learnings-log` and `gstack-timeline-log` now refuse any row whose keys or string values hold a HIGH-tier secret by `lib/redact-engine`, the same gate `gstack-decision-log` already applied: the writer exits 1, names the rule (for example `db.url_with_password`) but never the value, and writes nothing. Before, `gstack-learnings-log` stored `DATABASE_URL=postgres://app:<password>@…` and exited 0, and `gstack-timeline-log` had no secret check at all. Brain sync's push-time scanner, which only knew six families, now also holds a file with a Stripe live or restricted key, a Slack token, an npm token or a database URL with a password, using redact-engine's placeholder rules so `postgres://app:${DB_PASSWORD}@…`, `PASSWORD` placeholders and the local `postgres:postgres@localhost` default still sync. Ordinary rows that mention tokens or passwords in prose still write and sync.
## [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)
+1 -1
View File
@@ -1 +1 @@
1.98.0.0
1.99.0.0
+1 -1
View File
@@ -1,4 +1,4 @@
# gstack digest v1.98.0.0 — regenerate/re-copy after upgrading gstack
# gstack digest v1.99.0.0 — regenerate/re-copy after upgrading gstack
Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed
for agent hosts without a full skill install. The full skills add workflows,
@@ -1 +0,0 @@
- **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: <reason>)` 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).
@@ -1 +0,0 @@
- **The /autoplan docs now say what each kind of run actually gives its reviewers.** README.md, docs/skills.md, docs/unattended.md, autoplan/DRIVER.md, crew/docs/captain.md, the skill's description and the `gstack-autoplan --help` text described a full-depth, two-voice review for every run. That is true only of an interactive session, where the model reads each review skill's own method. The lean run (the Capy default) is one engineering reviewer whose prompt holds the eng brief and short CEO, eng, design and DX checklists (28 to 37 lines each), never those skills' methods, with no outside voice. A `--full` run has a reviewer pair per phase, each with a short brief, plus the checklist for the native one. docs/skills.md has a new "What each run loads" section. No runner behavior, prompt, mode or default changed (GEVA-412, GST-1985).
@@ -1 +0,0 @@
- **/autoplan's final approval gate renders again, and replies to its plan-approval item are accepted.** The skill told the agent to name that gate item `plan` and to read replies such as `plana` and `planc`, but `gstack-gate` only accepts an id made of a lowercase label and a number, so `gstack-gate render` refused the whole gate and those replies never parsed (#4181). The skill now names it `p1`, with replies `p1a`, `p1c`, `p1d` and `p1e`, the id gstack's own gate builder and docs/unattended.md already use. A test now builds the gate exactly as the skill describes it and runs it through `gstack-gate render` and `parse`. Reported by @exGeni in #4181; fixes proposed by @ElNeMeR70 (#4189) and @szsunyuan (#4191).
@@ -1 +0,0 @@
- **Dependency and CI action patch bumps.** `@types/bun` 1.4.3 → 1.4.4, `@huggingface/transformers` 4.3.1 → 4.3.2 and `marked` 18.1.1 → 18.1.3 in `package.json` and `bun.lock`, plus `docker/login-action` 4.6.0 → 4.7.0 (seven steps in six workflows) and `github/codeql-action/upload-sarif` 4.39.0 → 4.39.1 (scorecard), each still pinned to the full commit SHA of its upstream tag. None of the new package versions adds an install script. Carries Dependabot's #3488, #3489 and #3890 onto current main.
@@ -1,4 +0,0 @@
---
important: blocker
---
- **/autoplan no longer stops after a phase with `publication_missing` when Claude Code saves the phase report as a summary that opens with another sentence.** Claude Code can save the prose between tool calls as a paraphrased summary. When the report started with a check sentence, the summary read "I checked the packet... Phase 1 is complete with 9 native issues and no outside review, so the work moves on to Phase 2." The guard only counted a phase declaration at the start of a line, so the run died after Phase 1. It now also counts one that starts a sentence in plain prose. A declaration inside quotes or inline code, one after an example or template introduction on the same line, a negated or future one, a list item, a heading and another phase's number still don't count. A "with" result may now mention "no outside review" after its counts; one that opens with "no" still doesn't count. Thanks to @mstormo for finding the cause in #4155.
@@ -1,4 +0,0 @@
---
important: blocker
---
- **gstack's browser now tells you how to fix a Linux host that blocks Chromium's sandbox, and /design-review --build checks the browser really starts.** On stock Ubuntu 23.10+ (AppArmor) and in non-root Docker, Chromium can't create the user namespaces its sandbox needs and aborts with "No usable sandbox!". browse used to exit with a wall of Playwright log after `Server failed to start:`. Now it prints one error that names both ways out. You can allow user namespaces (the Ubuntu AppArmor setting), or set `GSTACK_CHROMIUM_NO_SANDBOX=1`, which is only for pages you trust. Chromium's own line stays below it. gstack still never turns the sandbox off by itself, except that Docker's `/.dockerenv` now counts as a container, the same as `CONTAINER`. `gstack-design-build preflight` used to say `PREFLIGHT: ok` without starting the browser, so the run died later; it now opens and closes one tab first and stops with `code=BROWSER_SANDBOX` and the same error when the launch fails. (GST-1906)
@@ -1,4 +0,0 @@
---
important: blocker
---
- **`gstack-ci-carry` and `gstack-pregate --from-run` can verify a GitHub Actions run again after it has been rerun.** Reading a rerun attempt's job list 100 jobs at a time could take GitHub about 11 seconds for a large workflow (86 jobs in gbrain's Test runs), and a proxy that cuts requests off at about 10 seconds answered 502. So any run whose newest attempt was a rerun failed with `api_error`, and /ship fell back to running the whole suite locally, even after a green rerun of a known flake. The job list is now read 50 per page, every page, and a server error (5xx) reads the whole list once more at 25 per page. A page that still fails leaves the run unverified (`api_error`), as before: a partial job list never counts. (GST-2215)
@@ -1 +0,0 @@
- **/ship no longer stops when the docs checker's reply has a small mistake in it.** Before shipping, /ship asks a helper to check the docs and reply with a short summary line. When that helper typed a field name with a stray space and left the docs summary as a stand-in like "x", /ship treated the whole check as blocked and an unattended run stopped. Now `gstack-docs-candidate result` reads that last line strictly. It forgives spaces around field names, still refuses anything else that's wrong (a missing or extra field, the wrong audit id, an empty or stand-in summary), names each problem, and hands the one retry a note saying exactly which fields to fix. A reply that passes is saved with its summary for the PR. The docs test fixture accepts the new command, and odd audit ids or field names can no longer write outside the record folder or fake a line in the output. (GST-1843)
@@ -1 +0,0 @@
- **Seven test and eval checks that misread correct work, or raced their neighbours, now read it right (2026-11-09 daily flaky pass, GST-2131).** tpa-present's consent check now accepts the defer option in any words when the option says the step will be marked or left as blocked or waiting on you (gate 42431616220: "D) Do it later. I'll mark this step as waiting on you."), and still rejects every refusal of it, such as "I will not mark this step as blocked on you" or "I won't mark this step as waiting on you". Three weekly-census reds were harness misreads. scrape-prototype-path now credits browse's own "Navigated to <fixture URL> (2xx)" line when the model navigated through `$PWD`; codex-review's skill excerpt now points every helper path, `bin/` and `lib/` alike, at the checkout instead of an install the fixture never seeds; and ship-coverage-value resolves a `file:line` gap inside refundPayment instead of requiring the word "refund". The handoff integration test takes its X display from the race-safe `spawnFreeXvfb()`, the AUQ capture fixture turns off git's detached auto-maintenance that left a directory behind after cleanup, and the Windows curated lane skips test/ship-finish-fixed-links.test.ts, which spawns shebang bins like its excluded siblings. No threshold, trial count, case kind or retry changed.
-1
View File
@@ -1 +0,0 @@
- **Flaky pass 2026-11-07: the two marathon census reds on 297f50c7 are repaired, and two free tests stop failing at random.** Eval children now run gh with `GH_TELEMETRY=0`, because gh 2.103 records a telemetry device-id under `$HOME/.local/state/gh`, and `autoplan-ceo-only` counted that file as a stray write in its fixture HOME (marathon 42371706491). The office-hours completion check accepts the next skills the handoff menu offers now that `/autoplan` is the default recommendation (#4108), and it rejects the retired `/plan-eng-review`. The ruled-notes chain test names both shas after `review_loop_grant`, so a short sha made only of digits no longer reads as a phone number to the approval scanner (Free Tests 42345572767). The two slowest release-cut cases get the free suite's 30-second per-test timeout, so a bare local `bun test` stops timing out at 5 seconds. No threshold, trial count, case kind or model budget changed.
@@ -1,4 +0,0 @@
---
bump: patch
---
**The plan check works on public repos (GST-1774).** A repo that commits no `.gstack/` can turn the check on per machine with `gstack-config set plan_record warn|require|off`. The mode order is a tracked `plan_record` of warn or require, then `GSTACK_PLAN_RECORD`, then this key, then warn in a Capy session. A typo is rejected, and `PLAN:` names the file. With no tracked policy the key gates `gstack-ship-queue ready` and /ship finish, not land. On a public or unknown-visibility repo, `gstack-approvals render --plan` now writes `Plan: approved (record r-<id>)` in place of the autoplan run id. That id is an opaque hash of the approval row, so the run id, the row id and the ask stay out of the PR body, its edit history and the READY note pushed to origin. `ready` resolves the id to the same row and applies the same rules as before. Render refuses with no output when an approved line does not resolve, instead of printing a misleading `Plan: required`.
@@ -1 +0,0 @@
**The opt-in /autoplan gate now makes each phase publish in order, right after its own report (GST-1814).** In GEVA-2's E2 eval (3 told Opus 5.5 sessions, 1 exposed) every failure was in the report step: one session used all 3 Stop blocks on `missing=ceo` before doing any work and then published every phase with no report, one published Eng's report first and back-filled CEO and DX, and one published everything with no reports and back-filled one report per block. With `autoplan_gate` on, `true autoplan-published <phase>` is now refused before it runs unless that phase is the next one due and its report was sent since the previous publish; the refusal starts `Next due: the Phase N (<name>) report.` A publish with no report or out of order never counts at Stop either, so a later report can't rescue it. Stop blocks now count against the step they name (3 per step, reset as the run advances), blocks before the run's first phase report don't count, and 12 blocks end any run; once a run is released or exhausted, its publishes are no longer refused. Setup registers one more hook for the gate (PreToolUse on Bash; its shim starts bun only for the publish command). Still off by default, and nothing changes with the switch off. Docs: docs/skill-gates.md#the-autoplan-gate.
@@ -1 +0,0 @@
- **/ship's review gate no longer blocks on a Low finding just because the native reviewer wrote it as a numbered item with a nested list.** When the native reviewer wrote `1. **FIXABLE. …**`, then a sub-list, then a closing paragraph with `Severity: Low. Blocks: no.`, the collector split that paragraph into a second record. The numbered item was left with no fields, so its FIXABLE label counted as CRITICAL and stopped /ship until an owner ruled on it (6 findings collected as 9 in one alphaclaw run). The collector now keeps that first closing paragraph on its finding. The join stays narrow so it can't hide a real blocker. It applies only to the native reviewer's items, ends after the first closing paragraph, never takes in a bold-led paragraph, a verdict line, a list item, a heading or a file:line, and never lowers an item that names CRITICAL or P0.
@@ -1 +0,0 @@
- **/autoplan's `rec` and `all` replies now apply every recommended plan change, as `gstack-gate` already did.** The skill's reply table sent `all` (and left `rec` unmapped) straight to "approve as written", while `gstack-gate parse` resolves both to every item's recommendation, plan changes included, so decisions.jsonl and the approved plan could disagree. `rec` is now the one-word default that takes those changes through the affected-phase rerun before the gate comes back; `as-is` stays the plan as first written. The plan-approval item now recommends `b` (take the recommended changes) whenever a recommended option changes the plan. A bare `p1a` with a user challenge unanswered no longer approves anything: the gate waits for the owner's own answer to that `uc` item and re-parses their whole reply. Every `uc`/`d` option in a hand-written gate now sets `changes_plan`, and an explicit override of an option with no flag still counts as a plan change, as `gstack-autoplan answer` already treats it. A test reads the skill's reply table and checks it against what `gstack-gate parse` returns for `all`, `rec`, `p1a` and an override.
@@ -1 +0,0 @@
- **Missing-browser errors stop telling Linux and Windows hosts to open the Aside app.** `NO_BROWSER_HELP`, used by `/make-pdf` and the renderer's no-browser error, now offers the Aside remedy only on macOS, where Aside ships, and keeps the `./setup` and `GSTACK_BROWSE_BIN` remedies on every platform. The docs/skills.md `/design-html` refinement loop also stops naming GPT-4o and describes the configured vision model, matching the template. (#3735, #3375; contributed by @szsunyuan in #4122)
@@ -1 +0,0 @@
- **The office-hours section-loading eval's preservation judge uses the shared judge token budget.** The case passed `max_tokens: 2048`, a local raise from the old 1024 default, and that now sat below llm-judge's 8,192 default for frontier judges. claude-fable-5-1 spent the 2,048 tokens before writing any verdict, so the case failed with a truncated judge response instead of a score (marathon 42371706491 follow-up, #4125). The threshold, trial count and rubric are unchanged.
@@ -1,6 +0,0 @@
---
bump: minor
---
- **/ship now writes pull request titles, commit messages, branch names and changelog entries that say in everyday words what changed for someone using the project.** An issue code may follow at the end, as in "Stop gstack from hitting GitHub's rate limit while it waits on PRs (GST-1713)", but the front of a title stays plain: no `type(scope):` prefix, no tracker codes and none of the repository's own coined terms. The new `gstack-plain-title` command checks titles, branch names, PR openings and changelog entries against a word list a repository keeps in `.gstack/plain-titles.json`; a repository without that file gets warnings, never a refusal. /ship and `gstack-post` refuse to post a title that fails, and a "Plain title" check on every new pull request does the same for PRs opened any other way. Pull requests opened before the rule started are exempt.
- **PR bodies now open with one or two plain sentences and end with one collapsed "Agent notes" section.** The ship receipt, approval and plan lines, issue codes and review bookkeeping that gstack's merge tools read now live in that section instead of a JSON block at the top of the body. The merge tools read it there and still accept the older layout, so open pull requests keep working. When gstack merges a pull request, main's commit title is the pull request's checked title.
- **The README now lists what gstack is for**, and /ship's PR opening names the goal a change serves.
@@ -1,4 +0,0 @@
- **gstack now waits for the "Plain title" check to pass before it lands a pull request.** The check reads the pull request's description from the event file GitHub hands the workflow, so a very long description no longer breaks it. A pull request that has never had the check run gets one when its title or description is edited, or when new commits are pushed.
- **A ship receipt posted on its own now goes into the pull request's Agent notes section and keeps the opening paragraph where it was.** The new receipt replaces the one in that section, so it is the one gstack reads.
- **An older receipt at the very top of a pull request description is still read after an Agent notes section without a receipt is added below it.** Receipt examples quoted in the readable part of a description stay where they are.
- **Correction to the earlier entry about plain titles:** a repository without `.gstack/plain-titles.json` gets no title checks at all, not warnings.
@@ -1,4 +0,0 @@
---
important: blocker
---
- **`gstack-ship-queue land --pin` and the first `ready` on a head no longer spend 15 s re-reading the queue notes.** Rebuilding local queue state from `refs/notes/gstack-trains` ran one `git cat-file blob` process per note, so every land and first READY took 14 to 15 s on the live 1,442 notes and grew about 8 s a week, on course to pass the 120 s command timeout in about three months, where a killed land leaves the land lock held. All the notes are now read by one `git cat-file --batch` process, which takes about 120 ms per rebuild on the same notes and restores byte-identical records. Malformed, empty and wrong-schema notes are still skipped. If that one read fails, the rebuild now says the notes were not read, so `land --repair` and the freeze lock refuse instead of trusting an empty queue (GST-2198).
@@ -1 +0,0 @@
- **The merge queue no longer lands a stacked child's parent commits under the child's receipt (GST-2153).** A child PR shipped against its parent branch (#3570) has a receipt that covers only the child's own change. If that child was retargeted to main while the parent was still open, `gstack-ship-queue ready`, `land --pin` and train build used to accept the receipt, so the parent's commits could land without their own review, READY or merged checks. They now refuse it with `QUEUE_RECEIPT_BASE_MISMATCH` until the parent lands, the base is merged into the child, and `gstack-ship-receipt carry --base <base>` moves the receipt to the base. Receipts written against the base, including stacked children reviewed against main (GST-1199), are unaffected.
@@ -1 +0,0 @@
- **Trunk Dynamic CI now also watches the Windows checks, the quality gate and the eval jobs on pull requests, still in learning mode, and it still can't skip anything.** Trunk answers only for jobs in the workflow that asks, so `windows-smoke.yml`, `windows-scoped-tests.yml`, `windows-setup-e2e.yml`, `quality-gate.yml` and `evals.yml` each get the same learning-only `trunk-dynamic-ci` job that `free-tests.yml` already has. Trunk records whether it would have run or skipped `windows-smoke`, `windows-scoped-shard`, `windows-setup`, `held-install-journey`, `quality`, `build-image` and `eval-slices`. No job reads the answer, so every check still runs with its own name and result. Each observer runs one pinned action, checks out no code, holds no GitHub token scopes, skips fork pull requests, skips its step when the token is missing, and never fails the run. `test/trunk-dynamic-ci-observers.test.ts` holds every observer to that shape.
@@ -1 +0,0 @@
- **A Captain can no longer archive a thread that still holds an open PR, and a landed PR's issue closes in the same step.** The new `gstack-archive-check thread` runs right before a Captain archives a crew thread. The Captain hands it the thread and issue lists it read from Capy, and it re-reads every PR the thread holds from GitHub. It refuses while any of those PRs is open (a successor does not change that) or while an issue in a working state is still assigned to the thread. It also refuses when a read is stale, truncated, unparsable or failed. Every run prints a handoff stub (each open PR's branch, head commit and CI state at that head, plus the open issues) for the next thread's brief. `gstack-archive-check landed --pr <n>` runs after `gstack-ship-queue land` and names the tracker code in the merged PR's title, so the Captain closes that issue in the same program, but only when the issue links this PR, no other open PR and no open sub-issues. crew/docs/captain.md (No orphaned work, Direct land), the gstack factory page, /captain and the /runaway-stopper rotation steps now say a thread with an open PR stays until that PR merges or closes.
@@ -1,4 +0,0 @@
---
important: blocker
---
- **/ship's finish now counts a chain of follow-up reviews after a granted follow-up review found a blocker.** A granted follow-up review that finds issues has to be logged `converged:false`, and finish refused every chain holding such a link, so a later clean follow-up review that read the fix could not count and only a fresh /ship review recovered the receipt, past the two-round cap. An earlier link that found issues now links when it finished its read on its own commit, its findings account for its counts, and every open or blocking finding names a file it read (a leading `./` on a finding's path names the same file). It counts no path itself, a path its findings name counts only through a clean follow-up review after it, and the `FIX_CHECK:` line marks it `(found issues)`. The chain must still be contiguous, every link still needs its own grant, and the newest link must still be a clean read; a gap, a range that starts mid-link, a newest link with findings, an unfinished read, and findings with no file all still refuse. A finding an earlier link logged on a file that no later clean follow-up review's range touched, even a file the branch never changed, now refuses the chain too, where before it dropped out and the chain counted.
@@ -1 +0,0 @@
- **Crew threads keep their PR to the end, and a repo with more than 10 finished PRs waiting gets no new fix thread.** gstack's rules pages (factory.md, the lane start card, the Captain manual and /runaway-stopper) no longer tell a crew thread to hand off mid-PR on a token count: it keeps its PR until it merges or closes, and checks in with its Captain once when its own tokens pass 1 billion (about $330). Standing threads that wake on every event (merge lanes, watchers, an automation's standing thread) still rotate at 500M own tokens or 3 days, and subagent limits are unchanged. A Captain now starts no new fix thread in a repository while more than 10 green, reviewed PRs sit waiting only to merge; security, data-loss, red-main and broken-install hotfixes are exempt. Wording only: `gstack-runaway scan` still flags every non-Captain thread past 500M.
@@ -1 +0,0 @@
- **`/captain` is now installed only on Capy machines.** gstack's crew tools for Capy (the Captain skill, and later the merge queue, receipts and spend caps) are being gathered under `crew/`, which `./setup` and `gstack-relink` load only where `CAPY_MACHINE_CONFIG` is set or `~/.capy/system` exists, the test the Capy installer already used. Everywhere else `/captain` gets no entry on any host, and an entry an earlier install made is removed on the next setup or upgrade; a skill of your own named `captain` is left alone. Every other skill, the install size and the install time are unchanged. The README has a short "gstack on Capy" section, and crew/README.md says what Capy users get.
-7
View File
@@ -1,7 +0,0 @@
---
bump: minor
---
- **`/design-review --build <preview url>` is a new opt-in loop for UI you are still building.** It skips the audit's clean-tree stop and never commits, stashes or asks. It lists the states your change can show, lints each one with `gstack-design-lint --scope app` at 1440 and 375 wide, takes a screenshot of each, checks seven spacing and hit-area items, and fixes what fails for up to three rounds. Then it runs one more pass that changes nothing and prints a single `DESIGN_BUILD:` line. The lane card, the CLAUDE.md routing block, docs/skills.md and /design-html's last step recommend it for UI work, but it is not required before a pull request is ready.
- **New `gstack-design-build` decides that line in code, not in skill prose.** The result is clean, waived, open, incomplete, unrendered or stale. It reads the lint's JSON verdict, never its exit status. A skipped render, a state measured at only some sizes, a state with no screenshot, an API call the preview could not answer, evidence older than the final pass, or a tree edited after the final pass can never print clean. Preflight checks the browser first. It refuses a target that is not on this machine unless someone granted that host, and it turns a refused design-review start into `code=INSTALL`. It lets the loop click through changes only when the preview itself answers at every local page; any other page, or a redirect, means the loop leaves changed actions alone. The receipt is a private JSON file tied to a hash of the exact files it measured, and `gstack-design-build check` reports it stale after any later edit.
- **New preview template (`design-review/templates/preview-fixtures.mjs`) serves an app on 127.0.0.1 with canned API answers, one scenario per state (`?as=<scenario>`).** It uses the app's own Vite and config, or `--static` for plain files. It never installs Vite. A small script it adds to every page carries the scenario to the app's own API calls. Any API request without a matching fixture gets a 501 that names the route and the file to edit, and never reaches a real backend. The log is private and hides tokens, the port is never silently moved, and `stop` shuts it down cleanly. See docs/howto-preview-a-frontend.md.
- **Design lint URLs are now redacted even when they do not parse** (a port out of range, for example) **and when a token value holds punctuation such as `)`.** Before, such tokens could show up in text, JSON, stderr and trace files.
@@ -1 +0,0 @@
- **/ship's docs check no longer burns its one retry on a badly shaped reply.** Before shipping, /ship asks a helper to audit the docs and end with one summary line in a fixed format. When that line had the right findings but the wrong shape (a missing field, or a misnamed field holding "x"), /ship counted it as a failed audit and spent its only re-audit, and an unattended run could stop with sound docs. Now `gstack-docs-candidate result` tells a shape-only mistake apart from a real one. For a shape-only mistake it prints `FORMAT_REPAIR:`, and /ship asks a fresh helper with no tools once, using no attempt, to restate just that line: `gstack-docs-candidate repair-prompt` builds the request from the format and the helper's own output. The reply is accepted only if every value the first line already got right is unchanged, so a repair can fill in a field but never change the outcome. A missing or wrong audit id, a missing or unknown status, either one given twice, a malformed list of edited files or blockers, a status that contradicts the file lists, or a last line that isn't JSON at all still counts as a failed attempt, and each audit gets one repair.
@@ -1 +0,0 @@
- **A pull request that only changes human-facing docs now skips the test suites.** It runs the docs lane instead: links on the lines it added resolve, generated pages stay fresh, the credential scan passes, and the few tests that read docs by directory run. Required checks still report success, so branch protection doesn't hang, and pushes to main keep the full suites. Most Markdown in gstack is agent instructions, so the class is a short allowlist (root README.md, docs/**/*.md, changelog.d/*.md) with a denylist on top. SKILL.md, templates, sections, policy docs, captain and crew files, pinned results, preregistrations, generated pages, and any doc a script or test names all still run full CI. One classifier (`gstack-docs-only`) decides the class for CI, pregate, CI carry, /ship's lanes and land. A crashed classifier, an unknown ref, or a push to main always means the full suites run. Garry, 2026-11-08: "For docs changes pls stop doing full CI and merge without tests. If there are really only docs." (GST-1951)
@@ -1 +0,0 @@
- **Crew docs now say how a factory keeps work from being orphaned, and how outside PRs land.** These are lessons from gbrain's merge queue, which reached 220 open PRs in November 2026. crew/docs/captain.md has a new "No orphaned work" section. A Captain archives or rotates a thread only once a live successor owns its open PRs and issues. Successor triggers sit on an issue, never only in notes. Sweeps re-own open PRs whose owning thread is archived. A crew PR closes only as landed, superseded or dropped, and anything else is salvaged onto current main. Before archiving a thread, the Captain re-assigns its issues instead of closing them or moving them to Backlog. The wave guidance adds one open queue PR per lane, follow-ups riding that PR or the next wave, waves cut after a day or once full, and when to move from direct landing to waves. Outside PRs are now bug reports. Each gets a verdict within 7 days, and real bugs are rewritten from main with credit, so no contributor commit enters the tree. CONTRIBUTING.md, captain/templates/CREW.md rule 18 and docs/review-policy.md say the same, including how to pass a contributor's commits to gstack-coauthor-trailers for credit. The report and brief templates carry an "Open work" line. Docs only; no tool behavior changed (GST-2077).
@@ -1 +0,0 @@
- **Findings and results write-ups now open with two or three plain takeaways, what works first.** ETHOS.md's Explain the Number says every findings file, investigation write-up and results README opens with "We learned X. You should do X." bullets: what works first, then what didn't, stated plainly with its number, and a null turned into the lesson its data supports. Internal mechanics go last, every experiment names the public lesson it is after, cost appears only when it is part of the lesson, and a repo, dataset or tool is opened only with a stated reason. No number is rounded in our favour and no caveat is dropped. /eval-plan's preregistration asks for a `lesson:` line under Bars, its results step points to the rule, and docs/TESTING_INTERNALS.md says the same beside its results guidance.
@@ -1,5 +0,0 @@
- `browse scroll <selector>` brings the element into view again when `--wait` is absent. The flag parser treated the first argument as the `--wait` value and dropped it, so `scroll footer` and `scroll footer --times 2` scrolled to the page bottom and replied "Scrolled to bottom".
- gstack's renderer no longer mistakes Debian and Ubuntu's `/usr/bin/browse`, a link to xdg-open, for gstack's own browser. A `browse` on PATH whose real file has another name is now skipped, so you get the clear "no browser" message instead of a desktop opener launching.
- The browse daemon no longer files every project's domain skills in one shared "unknown" bucket on installs without `~/.claude` (Codex, repo-local and other hosts). It now tries the checkout's own `gstack-slug`, then the Claude install, then the git remote rule, so skills saved in one repository stop showing up in another. Skills saved before this change stay in `~/.gstack/projects/unknown/learnings.jsonl` and no longer appear in a project's list; copy the lines you want into that project's `learnings.jsonl`.
- A question marker with a malformed signal hint, such as `<gstack-qid:ship-x;signal=scope_appetite>`, keeps its question id in the never-ask hook and the question log. Only the bad hint is dropped, so a never-ask preference still applies and the question log no longer records it as unmarked.
- The never-ask preference hook reads the question id lowercased, as the log hook and stored preferences already do, so a marker written with capitals no longer asks a question you silenced.
@@ -1 +0,0 @@
- **/autoplan no longer gets stuck between phases after Claude Code 2.1.317 compacts a long session.** On 2.1.317 a compaction's boundary names a parent record that is never written to the session file, so the guard's journal reader dropped every record after the compaction and refused each later phase entry on every retry. The boundary now continues from the last record the compaction kept in place (`compactMetadata.preservedSegment.tailUuid`). Boundaries from 2.1.300 and earlier, a tail that is not flushed yet, and lists that name no record of this session read exactly as before. Reported and fixed by @vilano93 in #4200.
@@ -1 +0,0 @@
- **The Capy install docs now agree on what a `behind=` trailer means, and the unattended rules say a moved base is merged in, never rebased.** The Capy skill said both "run the fix" and "`behind=` alone is informational" for the same trailer, and most lanes took the second reading and kept working on an old gstack. Now `behind=1` or more means run the trailer's fix before the next workflow, everywhere the rule is written. A pin with a recorded reason still holds, and so does an install that was refused. docs/unattended.md no longer names a slot call as a reason to rebase; it points at docs/ship-policy.md, which says to merge the base into a reviewed branch. (GST-2136)
@@ -1,4 +0,0 @@
---
important: security
---
- **A daemon launched with `BROWSE_TUNNEL=1` no longer opens a second ngrok tunnel when `/tunnel/start` arrives during its own start.** The earlier fix made overlapping `/tunnel/start` calls share one start, but the daemon's own startup tunnel skipped that lock. A `/tunnel/start` that arrived while the startup tunnel was still opening saw the tunnel off and opened its own ngrok session, and the daemon kept only one of the two. The other stayed public on your ngrok account until the daemon exited, and closing the tunnel didn't reach it. Now every tunnel start, from the route or from startup, goes through one shared lock and gets the same URL, so there is only ever one session.
@@ -1,4 +0,0 @@
---
important: security
---
- **Two quick `/tunnel/start` calls no longer leave a public ngrok tunnel you can't close.** When two callers asked the browse daemon to start its tunnel at the same moment, both saw it off and each opened its own ngrok session. The daemon kept only the second, so the first stayed public on your ngrok account until the daemon exited, and closing the tunnel didn't reach it. Now a start that arrives while another is running waits for it and gets the same URL, so there is only ever one session. Also, when a start failed after ngrok had opened (for example, the state file write failed), the daemon cleaned up the session but still believed a tunnel was live, so it never shut down on idle; it now resets its tunnel state on that failure too.
@@ -1 +0,0 @@
- **Two machines that each log a decision between pushes no longer stop one of them from syncing.** The second machine's push was rejected, its fetch-and-merge retry conflicted on `projects/<slug>/decisions.active.json`, and `gstack-brain-sync` aborted the merge and wrote `push_failed` on every later drain until someone repaired the clone by hand (#4174). The snapshot is derived from `decisions.jsonl`, so when a merge's only conflicts are decision snapshots whose logs merged cleanly, brain-sync now rebuilds each snapshot from the merged log with the new `gstack-decision-log --rebuild-snapshot <project-slug>`, commits the merge and pushes; it never merges the JSON array itself. This needs no `.gitattributes` change, so a clone already stuck this way publishes on its next drain that has records to sync. A conflict in any other file still aborts the merge as before, and so does a log that lost lines on either side, as `--compact` does to a redacted decision, since rebuilding from the union of both logs would bring that decision back. New artifacts repositories also get `projects/*/decisions.active.json merge=binary` in `.gitattributes`, so a merge run by hand leaves no conflict markers in the snapshot.
@@ -1,4 +0,0 @@
---
important: blocker
---
- **The pregate secrets check no longer stops /ship on Django's `session = self.client.session`.** env.kv read any assignment to a name ending in `session`, `token` or `key` as a credential when the value looked random enough, so a Python test that grabbed the test client's session failed a check nobody can downgrade, and the only way past it was a hand-edited allowlist (#4209). In Python and Dart source, an unquoted dotted name such as `self.client.session` or `request.auth`, alone on the right of `=`, now reads as an attribute. A quoted value, a random-looking dotted token, the same line inside a `"""` string, anything after it on the line, and every .env, YAML, config and TypeScript file still report. Reported by @dgrant in #4209.
@@ -1,2 +0,0 @@
- **`./setup` no longer refuses healthy hooks when Bun crashes while checking them.** The hook parse check bundles each TypeScript hook with one Bun process; when that process was killed by a signal (a Bun segfault, seen in Free Tests 42327570347), every hook it hadn't reported was listed as "does not parse" and setup exited 1 with a "this is a gstack bug" message. Now the results Bun printed before the crash still count and the shell checks still run, so a hook that really fails is still refused, and only a hook Bun never reached is registered unchecked. The check reports itself unavailable (exit 3, the same as a missing Bun) only when a hook went unreported and nothing else failed; setup then warns that hooks were registered unchecked, and the auto-updater holds the update as it does for any unavailable check. A Bun that exits normally without a report still fails every hook it left out.
- Two flaky tests are fixed. The gbrain prune test no longer sees another test's `gbrain autopilot` process, and the plan-review floor actor now answers its own cropped "overwrite" permission for the working plan instead of waiting it out.
@@ -1,4 +0,0 @@
---
important: data-loss
---
- **`gstack-uninstall` no longer deletes your repo's committed `.gstack/` files or unsaved work in `.gstack-worktrees/`.** Run from inside a repo, uninstall used to delete the whole `.gstack/` folder and every `.gstack-worktrees/` checkout, even with `--keep-state`, so a committed `.gstack/ship-policy.json` showed up as deleted in `git status` and a worktree's uncommitted files were gone. Now `--keep-state` keeps both folders. A plain uninstall still clears gstack's own ignored browse state, but keeps every git-tracked file under `.gstack/` (and says so), and it stops with exit 2 before removing anything when a checkout there has uncommitted changes (even with `status.showUntrackedFiles=no`), holds commits no branch or tag contains, or isn't a worktree this repo registered (as after moving the repo, or a separate clone put there, which uninstall never opens with git), naming the worktree and the fix: commit to a branch or stash, re-run with `--keep-state`, or pass the new `--force-worktrees` to delete them anyway. A symlinked `.gstack-worktrees` is only unlinked, as before, and never followed, so the checkouts it points at are kept. A worktree nested inside another checkout under `.gstack-worktrees/` is not checked yet and is still deleted with its parent, as before. The checkouts it removes also leave `git worktree list`; the registrations of your other worktrees are never touched, even when their folder is missing for now (GSTA-461, GST-2197).
-4
View File
@@ -1,4 +0,0 @@
---
important: blocker
---
- **Free Tests no longer fails every PR's plan when the test-duration seed goes stale.** Test files missing from `scripts/free-test-durations.json` are still packed at the seed's 99th percentile, but when that estimate would push a CI shard's job past the 90-minute cap, `--ci-plan --job-setup-seconds` now cuts the shard's prediction back to the cap instead of throwing `FREE_JOB_TIMEOUT_OVER_CAP`. Recorded durations are never cut, so a shard whose seeded files alone need more than 90 minutes still fails the plan, and plans under the cap are unchanged. The seed is refreshed from a new recording (196 files had no recorded time), and a weekly seed PR that is closed without merging now opens a blocker issue, which closes when a seed PR from the recorder merges.
@@ -1 +0,0 @@
- **New design note: what a lighter gate for crew-only pull requests would save, and how crew steps will switch off outside Capy.** docs/designs/GSTACK_ON_CAPY.md measures, for free from recorded CI runs, what the 54 crew-only pull requests merged 2026-10-24 to 2026-11-07 spent on paid evals and review (about $735–855 a lighter gate would have saved, close to $0 under today's direct landing), with every caveat. It also specifies the one switch Phase 2 will use so `/ship`, `/review` and `/autoplan` run crew steps only on a Capy machine. Nothing in gstack's behavior changes.
-1
View File
@@ -1 +0,0 @@
- **`gstack-ship-release accept` no longer reruns a CI carry's local remainder that Step 5 already ran at the same tree.** /ship's Step 5 and accept both call `gstack-evidence record-ci`, which ran the lane's `per_file` remainder (the inventory files CI skipped entirely) each time, so on harness-evals #315 a remainder file of about 7 minutes ran twice and accept's record-ci hit the 600 s `ci_record_timeout_s`. After verifying the run live, record-ci now reuses the label's newest ledger row when it is the CI row it would write again: same commit, clean tree, command, run id and attempt, profiles, jobs, base policy, covered count and remainder files, FRESH, no test-lane exception, and each remainder file's newest local run at this tree written before that row and a pass under this runtime. It prints `CI_RECORD_REUSED: label=<lane> runs=<id>/<attempt> tree=<tree12> policy=<policy>`, and accept's lane line ends `(reused the CI record at tree <tree12>)`. Any mismatch, a newer row for the label, or a run that is no longer green records afresh or refuses as before. No timeout or coverage change. See docs/pregate.md#record-ci-reuse. (GST-1795)
@@ -1 +0,0 @@
- **A clean Codex outside review that signs off with a sentence about its checks now reads `VERDICT: clean`.** Since #3304 only a review's last sentence (or its Recommendation because-clause) can conclude it, so a reply such as "No actionable regressions were identified in the diff. Tests, lint, and build all pass." read `VERDICT: unverified` / `REASON: untagged_review`, and /ship and /review reported missing outside coverage for a review that ran and found nothing. Closing sentences that only report passing checks are now skipped when the conclusion is read, and "The changes introduce no definite, actionable regressions." counts as a no-findings statement held to the whole-change scope rule. It stays fail-closed: a check sentence that quotes code, fails, is cut off, continues a wrapped line or follows a colon is never skipped, skipping one never makes a scoped claim the reply's only sentence, a clause joined after the whole-change scope by a comma, dash, slash, bracket or a word such as "and", "so" or "while" ("No issues in the diff, while the change disables auth.") is never a conclusion, and a review with findings is never read as clean. See docs/troubleshooting.md#outside-review-verdict. (#3469, GST-1803; design and tests from #3641 by @vilano93)
@@ -1 +0,0 @@
- **An outside review's no-findings conclusion must now be one complete sentence, so a review that reports a problem after its no-findings statement can no longer read as clean.** Until now a few reply shapes read `VERDICT: clean` on a merge gate while saying something was wrong: a Recommendation-only reply ("Recommendation: Ship because no issues in the diff, so the change disables auth."), an exception after the scope ("No issues in the diff with the exception of the auth change."), a question ("No issues in the diff?"), and a colon or symbol after the scope ("No issues in the diff: the fallback path now swallows ENOENT.", also with ` & `, ` + `, ` -> `, ` | ` and similar). The parser now reads the conclusion as a closed sentence. It may hold an optional lead, the no-findings statement, an optional "were found", an optional scope that names the whole change ("in the diff", "in the changes on this branch", "in the diff against main"), then at most one `.`, `!` or `;`. Anything else is `VERDICT: unverified` / `REASON: untagged_review`, so the rule no longer depends on lists of exception words and joiners. This fails closed in a few clean-looking shapes too: a scope that names part of the change ("No issues in the parser.", "in changed.ts") and a colon explanation ("No findings: the cap is enforced.") now read `unverified` even as the reply's only sentence. No captured fixture reply moves toward clean. Whole-change scopes that #4130 left fail-closed after other prose now read `clean`: "in the change as a whole", "in the committed and uncommitted changes" and "in the staged / unstaged changes". See docs/troubleshooting.md#outside-review-verdict. Also, test/outside-voice-preflight.test.ts no longer writes the machine's real ~/.gstack/config.yaml through an inherited state root. (GST-1808, GST-1809)
@@ -1 +0,0 @@
- **An autoplan `rec_hash` now names its run, so two unrelated gates no longer print the same one.** The hash a gate render prints, and `gstack-autoplan answer --rec-hash` checks, covered only the gate revision and the recommended letters, so any two runs with the same decision shape shared it (GST-1749's and GST-1811's gates both printed `f94689f795a5`), and a `rec` relayed for one run passed the check on the other. It now also covers the run id, the reviewed plan's sha256 and each item's title and option keys, and a hash from another run's gate is refused as `GATE_REC_STALE`. Gates written before this keep working. A `gate.json` without the new `rec_hash_v` marker still accepts the hash it printed then, and the answer records that hash. That old hash is not bound to its run, so re-render a gate still pending from before this change and answer with the new hash its render prints. Recorded approvals are untouched, since `gstack-approvals import --autoplan` and the plan record bind by run id and gate revision, never by `rec_hash`. See docs/troubleshooting.md#gate-rec-stale. (GST-1819)
-5
View File
@@ -1,5 +0,0 @@
---
bump: minor
---
- **The design lint can now check one section of a running app screen for crowded rows, help text jammed under a list, notes as loud as names, and small tap targets.** It is render-only and opt-in: `--url <url>` (one state each) or `--state-file <states.json>`, a required `--within <selector>` matched through open shadow roots, `--viewport WxH` (1440x900 and 375x812 by default), `--wait-for`, `--waivers <file>` and `--verbose`. Five new rules at ask report only under `--scope app`: `row-crowding`, `copy-flush-under-list`, `note-in-row-weight`, `small-hit-area` and `section-overflow`, each finding with its state, viewport, a shadow-piercing locator, the measured px and the threshold. On gstack's replica of the qm#3186 Sharing panel it reports the four defect rules; on the fixed copy, the negative controls and a 12-page false-positive corpus it reports none. A `LINT_APP: verdict=<clean|open|incomplete> code=…` line follows `LINT_SUMMARY`, and the exit is 0 clean, 1 open, 4 incomplete (no browser, unreachable, timeout, not ready, state mismatch, no match, closed shadow root, probe error, missing stylesheet), 3 usage. URLs are redacted in every output and probe failures write a private trace file. See docs/design-lint.md "App profile (--scope app)".
- **Behavior change for every caller: `gstack-design-lint --url` with no browser now exits 4.** It prints `gstack-design-lint: RENDER_UNAVAILABLE: nothing was measured at <url> (<reason>) | fix: gstack-browser-ensure, then rerun` on stderr instead of passing with exit 0. `--render` over files without a browser is unchanged. `--within`, `--viewport`, `--wait-for`, `--waivers` and `--state-file` without `--scope app` exit 3, and the usage text gains the app line. Page and component output and exit codes are otherwise byte-identical (test/design-lint-scope-guard.test.ts).
@@ -1 +0,0 @@
- **/ship no longer files deferred review findings as issues.** A blocking finding in code the PR did not change (`review_scope: frozen`) now stays on the PR: `gstack-review-scope defer` records it for the PR body's `## Deferred findings` section and never calls `gh`, so it opens no GitHub issue and needs no marker search. The `--pr`, `--repo`, `--branch` and `--dry-run` options are gone, and a deferred record no longer carries `issue`, `url` or `reason`. When the reviewer stated its own severity or Blocks field, the deferred line in the PR body shows it next to the label, as the issue body used to. /ship's Step 9.4, docs/review-policy.md, docs/ship-policy.md, the READY template, crew/docs/captain.md and the Capy factory page now say the same thing: deferred findings stay on the PR, and follow-up work someone will actually do gets one issue with a named owner. The pre-existing test failure option in /ship that assigns an issue to the person who broke the test is unchanged, since that issue has an owner.
@@ -1,4 +0,0 @@
---
important: security
---
- **`gstack-approvals render` keeps local paths out of the approvals table in a PR body, and `--redacted` prints the public form on any repository.** On a private repository the full table printed each row's Covers text as recorded, so a spend-cap row carried its ledger path (`/home/<user>/.capy/drive/…`) into the PR body, where gstack-post flagged it as `internal.user_path` and the PR's edit history kept it after the repository went public (gstack-evals#289). The table now shows each local path in the Asked, Covers and Still pending columns as `…/<file name>`, and a bare home directory as `~`: absolute and home paths (`~/`, `~user/`, `$HOME/`, `/home/…`, `/Users/…`), Windows-drive, UNC and `file://` paths, and Drive-relative ones (`.capy/drive/…`). Exact words stays as the owner wrote it, and `gstack-approvals list` still shows the full record. `render --redacted` prints the same bytes a public repository gets, for a private repository that may go public, and `--redacted` with `--full` refuses with a usage error instead of letting one win. The redacted line now ends "The full approval record stays private." without naming a reason, so it stays true when `--redacted` is used on a private repository.
@@ -1 +0,0 @@
- **A data file whose name git quotes no longer slips past the secret-line keep rule in `review_exclude_paths`.** git still C-quotes patch headers for names holding a double quote, a backslash, a tab or another control byte, even with `core.quotePath=false`, so the review collector stored their added lines under the quoted header text and the keep rule read no lines for the real path: a file such as `r/q"x.json` holding a credential value was excluded from every reader's diff. The collector now decodes quoted headers (octal escapes, `\"`, `\\`, `\t` and the rest) and pins the `a/` and `b/` prefixes, and the exclusion fails closed: a matched text file with changed lines that the collector holds no lines for is kept as unread instead of excluded.
@@ -1 +0,0 @@
- **The factory page says one open PR per lane and never archive a thread that still owns work.** Point 2 of capy/skill/gstack/factory.md, the page every Capy session reads, now says each lane keeps one open PR and follow-ups ride it. Point 12 says a Captain never archives a thread with an open PR or issue until a live successor holds it, and a PR closes only as landed, superseded or dropped. Both are lessons from gbrain's merge queue, which reached 220 open PRs in November 2026. The page's byte budget moves from 3,475 to 3,650 for these two rules. Docs only (GST-2077).
@@ -1 +0,0 @@
- **gstack's Capy-only tooling now lives under `crew/`.** The merge-train and wave code, the release gate, the release cut and its daily check, version-gate trains, lane ownership and the paid-eval CI steps only Capy crews run moved from `lib/`, `scripts/` and `bin/` into `crew/lib/`, `crew/scripts/` and `crew/bin/` (`gstack-release-gate`, `gstack-coauthor-trailers`), with their tests in `crew/test/`. The Captain manuals moved to `crew/docs/captain.md` and `crew/docs/recommended-harness.md`, with short pointers left at the old paths. Workflows, imports and docs point at the new paths; behavior is unchanged. Setup copies `crew/lib/` into each host's install folder beside `bin/` and `lib/`, so Windows installs, which copy files instead of linking them, still start `gstack-ship-receipt`, `gstack-pregate` and the other commands that use it. On Capy the session launch file puts `crew/bin/` on PATH, so the commands keep their names; nothing else does, so other installs never see them. `gstack-flaky` and the factory scorecard stay where they are because outside users run them. A Mac or other paired device running Capy now always counts as a Capy machine for `/captain`: any `~/.capy` entry counts, not only `~/.capy/system`, so an unclear case installs the skill instead of dropping it.
@@ -1 +0,0 @@
- **Correction to the earlier entry "An outside review's no-findings conclusion must now be one complete sentence".** That entry said a review that reports a problem after its no-findings statement "can no longer read as clean". That held only for a problem in the same sentence, after the statement. A problem written before the statement ("The fallback swallows ENOENT; no issues in the diff."), or in a later sentence after a clean Recommendation, still read clean, and so did scopes that name only part of a branch ("in the latest commit", "in the staged changes"). The verdict-line change above retires all of these rules. Now gstack no longer reads a no-findings sentence at all, and a review counts as clean only when the reviewer itself ends with `VERDICT: CLEAN`. A problem the reviewer describes without a severity tag above that line is still the reviewer's call.
@@ -1 +0,0 @@
- **An outside review now counts as clean only when the reviewer's last line is exactly `VERDICT: CLEAN` or `VERDICT: FINDINGS`.** Every outside-review prompt (in /ship, /review, /codex, /claude-code, /autoplan, the plan reviews, /office-hours, /design-review and /document-release) now asks for that line, and gstack no longer reads prose such as "No issues found in the diff." as a pass. Every earlier rule for reading a no-findings sentence let some reply that named a problem pass the merge check, for example "The fallback swallows ENOENT; no issues in the diff.", a clean Recommendation followed by a listed problem, "No issues found in the latest commit." on a whole-branch review, and "Recommendation: fix the auth bypass because no issues in the diff." A reply without the line now reads `VERDICT: unverified` / `REASON: missing_verdict`: missing coverage, never a pass. A P0 or P1 finding (or a Critical or High label) and a last `Verdict: not ready` still block whatever the line says, and advisory P2 or P3 findings still pass. Codex's structured outside review in /ship and /review, and the default `/codex review`, now run through `codex exec` with gstack's prompt instead of `codex review --base`, which takes no prompt and so could never give the line. The diff they review is the same (`git diff $(git merge-base <base> HEAD)`: the branch commits plus uncommitted tracked edits). Skills rendered before this change still read unverified until `./setup` re-renders them. A reply that ends with `VERDICT: CLEAN` but lists numbered items with no severity tag now reads `VERDICT: unverified` / `REASON: untagged_review` in /ship and /review, and a `/codex` consult or challenge answer with numbered items and either a `Verdict:` line or a last `VERDICT: CLEAN` now prints `FINDINGS: unspecified`. `/codex review` now stops before calling Codex when it cannot write the diff into the prompt. See docs/troubleshooting.md#outside-review-missing-verdict.
@@ -1 +0,0 @@
- **Crew threads now stay quiet between READYs, and busy Captains can hand PR and CI watching to one merge thread.** gstack's factory page tells crew threads to message their Captain only for a READY, a blocker, a decision the Captain or owner must make, or done, and to end every other turn with end_turn, because each message wakes the Captain cold. The Captain guide (`crew/docs/captain.md`, Roles) and the Capy skill's `captain.md` describe the merge thread: one crew thread holds the PR watches so check, review and merge events stop waking the Captain, while every other crew thread keeps owning its own PR's checks and review until READY.
@@ -1,5 +0,0 @@
---
important: blocker
---
- **A base merge that only moves the lines around a reviewed change no longer forces a second full /ship.** On founderstar#802, a base merge changed the context lines around the PR's 22 reviewed and unchanged hunks in `scripts/core/game.gd` (+66/-11 at both the old receipt head and the new one). The scoped-review packet counted those context lines and the hunk headers toward its 200-line section limit, so the section came to about 205 lines and `gstack-ship-receipt carry` refused with `RECEIPT_CARRY_PRODUCT_CHANGED`, which meant a full /ship on every mechanical retake. 1.94.39.0 (GST-1226) had stopped counting the base's +/- lines, but not the context lines inside the branch's hunks or in the base's change. Now only the branch's own +/- lines count, in carry and in `gstack-ship-release finish`; context lines, hunk headers and the base's change are still shown to the reader. A section whose branch +/- lines pass 200 still takes the full loop, and a base change over 2,000 lines in one file still counts in full. (GST-2229)
- **A main merge inside the review fix range no longer makes main's files count as the PR's fix.** On harness-evals#354, the PR's own change since the last targeted read was a changelog fragment, one docs line and one manifest line, but `gstack-review-plan` measured the fix range as `--range-base` to HEAD, so the 68-line bootstrap script and the `.gstack/pregate.json` edit a main merge brought in made the range gate class. With the owner's grant it printed `next=voices` and re-reviewed code main had already shipped, instead of the one targeted-only read. The fix range, and the check for gate-class edits after the last fix-check, now read only the PR's own change since the range base, the same change the receipt already reads (GST-1593). A gate-class edit in the PR's own change still escalates. (GST-2229)
@@ -1 +0,0 @@
- **An agent's own words no longer settle a user challenge at the /autoplan gate.** `gstack-autoplan answer --words-by agent:<id>` let a Captain choose a `uc` item (a user challenge both reviewers raised, or a round-cap approval) on its own authority, though only the owner may settle one. Such a reply, whether `rec`, `as-is` or a `uc` token, is now refused with `OWNER_REPLY_REQUIRED`, naming the item and asking for the owner's words, and the gate stays open. The agent can still answer the `d` items in its own words and leave the `uc` items to the owner. The rule that agent words are never the owner's now lives in one place, shared by the answer path, carry-forward and plan-sha citations.
@@ -1 +0,0 @@
- **A repository can name its own human-docs folders, so prose there skips the test suites too.** Docs that live outside `docs/` (gstack-evals keeps posts in `wiki/board/`) used to run every suite, and /ship ran them again locally. Add `"docs_only": { "allow": ["wiki/board/**.md"], "tests": ["tests/wiki.test.ts"] }` to `.gstack/ship-policy.json` on the base branch. gstack reads it only from the base, so a pull request can't add itself, and a pull request that edits the policy always runs full CI. Patterns must be narrow Markdown globs: `**`, `*`, `*.md` and anything aimed at agent files are refused with an example of a good one. gstack's own deny list (agent instructions, templates, prompts, preferences, tool configuration, pinned results, generated pages) always wins. A script that reads an allowed folder keeps the class off unless one of the listed tests covers it, and those tests run in the docs check. A listed test missing from the branch keeps the class off too, and land keeps its checks when the base gained a reader of the folder. Unknown keys inside `docs_only` and `land` are now warnings instead of errors. `land.docs_only_skip` names its own release, so `gstack-ship-policy validate` says which `min_gstack_version` turns it on. (GST-1974)
@@ -1 +0,0 @@
- **Free Tests now reports every free-suite result to Trunk Flaky Tests, for detection only.** Each shard keeps bun's JUnit report from every child, the flaky retry included (`--junit-dir`), and a new `trunk-flaky-tests` job uploads them to Trunk's "gstack free suite" collection on same-repository pull requests and main pushes, so Trunk can flag tests that fail and then pass on the same code. Nothing is quarantined or skipped, no job depends on the upload, and an upload failure never turns a check red. A test child that times out or crashes writes no report, so Trunk sees none of that child's results; it also skips the flaky retry, so the flake ledger has no entry for it and the red shard result is the only record. The token lives only in that job, which checks out and runs nothing from the pull request. The flake ledger and the daily flaky pass are unchanged.
@@ -1 +0,0 @@
- **Big results PRs now fit under /ship's review ceiling when their transcripts only name a credential.** Under `review_exclude_paths`, a data file used to stay in review for any line that named a credential, so captured model prompts that mention `${OPENAI_API_KEY}`, `$AGENT_API_TOKEN` or a keychain kept every such file (harness-evals#333: 2,610 files). That many kept files pushed the pattern past the 24,000-byte keep-command limit, so it wasn't applied at all, and readers got over 5 MB against the 2 MB ceiling. A line now keeps its file only when it names a credential and carries a value, meaning the credential scan finds a secret on it or a base64 or token run of 20+ characters follows the name. A key name given a value, such as `OPENAI_API_KEY=sk-…` or `"GH_TOKEN": "…"`, now keeps its file too. Files kept only for a secret line are excerpted, so they no longer count toward the keep-command limit, gstack reads them in git calls under that limit, and `DIFF_KEEP:` leaves them out. On harness-evals#333 the reader's diff drops from 5,458,045 bytes to 1,061,721.
@@ -1 +0,0 @@
- **/ship no longer stalls planning review on a branch that merged in thousands of data files, and `gstack-review-scope classify` reads a branch diff of any size.** `gstack-review-plan` checked each changed data or docs file for a leading `#!` with its own git call, so a fix range that took in another run's 11,749 trial files spent about 200 s per read of that range on a Capy machine (harness-evals #320 waited 25 to 58 minutes); it now finds those files in a few batched git calls. A plan run by itself now stops at the same 180 s limit `gstack-review-dispatch pass` gives it (`GSTACK_REVIEW_PLAN_TIMEOUT_MS` sets it), printing `REVIEW_PLAN_STOPPED` and no plan, and an unreadable fix range lists its first five problems and a count instead of every file. `classify` failed with an empty reason once the branch diff passed 64 MB; it now reads git's diff from a file, and a failure names git's error or the timeout.
@@ -1,3 +0,0 @@
- **Fewer false privacy holds when posting PR descriptions and running the pre-merge checks.** Three kinds of machine-written numbers no longer read as personal data. GitHub Actions run numbers in the JSON summary /ship writes into a PR description (`ci_carried[].run`, `flaky_exception[].runs`, `issues.flakes[].run`) no longer count as phone numbers, so a post with reused CI no longer asks for `--confirm` once per run; only a block exactly as /ship's writer renders it qualifies, and the same number in prose, next to the block, under any other key, in one of those fields holding anything but an 11-to-20-digit run number, or inside a hand-written or broken block still needs confirmation. A four-part version written to or read from a `VERSION` file, or set right beside a three-part version (`path.join(dir, "VERSION"), "1.98.0.0\n"`, `v1.97.2 to 1.98.0.0`), and a `VERSION` file holding only the version, no longer count as public IP addresses; a public IP with no version beside it still reports.
- **`gstack-approvals import` records plans whose autoplan run id looks like a phone or card number.** A run id such as `HARN-64-20261109177601` ends in a 14-digit time stamp, and the scanner could read it, alone or with the digits before it, as a phone or card number, so import refused the approval. Import now skips those two findings only in the time stamp (and the thread number right before it) where it places the run id itself, and only for an id of the minted `<name>-<14 digits>` shape. A phone number in the rest of the run id's name, in the same approval's words or plan title, or the same id typed into `gstack-approvals add`, still refuses.
- Home-directory paths and other low-severity findings stay notes in both `gstack-post` and the pre-merge checks, now pinned by tests, and keys still refuse everywhere, inside /ship's summary block included.
@@ -1 +0,0 @@
- **`gstack-runaway scan` no longer tells a Captain to rotate a crew thread that holds an open PR.** The scan now matches the rule the docs already carried: only a thread the Captain marks standing (a merge lane, a watcher, an automation's standing thread) gets `FLAG: ROTATE`, at 500M own tokens or 3 active days, and never while it holds an open PR; that case prints `DEFER: holds an open PR; rotate after every PR merges or closes (R91)` instead. Every other thread is a crew thread, which gets one `FLAG: CHECK_IN` past 1 billion own tokens and no rotate or own-token warn line. A thread file whose `pull_requests` list is missing or partial counts as holding a PR, so old or truncated input never leads to a rotation. /runaway-stopper's fetch program gains a `standing` list, its line table gains the `CHECK_IN` row, and `SWEEP:` gains a `check_in=` count.
@@ -1,4 +0,0 @@
---
important: security
---
- **The secret check and the pre-push hook now read files git calls binary.** A GitHub token in a file with a NUL byte, in UTF-16 text, or in a file a `.gitattributes` line marks `-diff` (which the same branch can add) used to pass both guards, because git printed "Binary files differ" instead of the added lines. Both now diff with `--text` and read each line of such a file three ways. They read it as little- and big-endian UTF-16 text without its NUL bytes, even when most of it is Cyrillic or CJK, and as bytes where each run of control or non-UTF-8 bytes reads as a space, so a token is found in whichever reading keeps it whole. That catches a token on one line of UTF-16 text. A keyword and its value split across two lines of UTF-16 text, such as an AWS secret key on the line under its YAML key, are not caught yet. UTF-32 text and compressed containers such as zip, docx and PDF files are not read. Only credential findings count in such a file, so a run of digits in a compressed image is not reported as a phone number; this repo's own PNG, JPEG, GIF and ICNS files add no finding. A very large binary is not read in pieces, so it fails closed instead of passing. A file whose scanned text is over the scanner's 16 MiB limit fails the `secrets` check, and a push whose diff is over 64 MiB is blocked; keep such files in Git LFS, whose small pointer files scan as text. The pregate `secrets` check also stops treating a merge-base diff git did not finish (an error, a kill or a full buffer) as complete: it is now `PREGATE_ENVIRONMENT`, never a pass. Its pass line names how many files it read, for example `12 added lines clean in 3 files (1 binary)`.
@@ -1 +0,0 @@
- **The merge queue no longer calls a head green while a check on it failed.** `gstack-ship-queue ready`, `land`, train land, wave and the post-merge watch all read CI through one helper, and it missed two kinds of failed run. GitHub lists only the newest attempt of each workflow run by default, so when a rerun attempt created no jobs, the failed run before it vanished from the list; gstack-evals#303 went READY with `passed=1 failed=0` while its `checks` job was red. The helper now also reads every attempt. A check suite whose latest attempt ran jobs is judged by that attempt alone, so a rerun that dropped a failed matrix job clears it, and a suite whose latest attempt had no jobs is judged by its earlier attempts and its workflow run's result, so a passing first attempt no longer hides a rerun that failed without starting. A workflow run that ended without creating any job (a startup failure, or a run that failed before scheduling one) never appears as a check run at all; it now counts as a failed check named `<workflow> (run <id>, no jobs)` unless a later run of the same workflow at that head replaced it; a replacing run counts only once it has jobs or has finished, and a run cancelled before any job (a concurrency group dropping a pending run for a newer SHA, as the main backstop does) never ran, so it does not count. A green head, a pending or skipped check and a rerun that passed read the same as before (GST-2167).
@@ -1 +0,0 @@
- **`/ship` no longer asks for a money review of files your review policy leaves out.** When a repo's `review_exclude_paths` skipped a file such as a committed SQLite ledger, `gstack-review-plan` correctly said no targeted review was needed, but `gstack-ship-receipt write` at the same commit still refused with `REVIEW_TARGETED: required=money`, so the run could not finish (GST-1900). The receipt write, the receipt carry, the merge queue's check and the plan now all leave excluded files out of that check the same way. A money-looking file the patterns don't cover still needs the money review, and an exclusion that isn't active (no policy key, a pattern only the branch adds, or a gate scan the receipt doesn't bind) still excludes nothing.
@@ -1 +0,0 @@
- **`/ship` no longer keeps a whole results file in review because a model reply says "fit (".** The gate scan's focused-or-skipped-test check (RH-1) read every non-prose file, so English like "don't fit (the gap never drops below one space)" in a committed `turns.jsonl` tagged the file and kept it in the reviewers' diff even under `review_exclude_paths`. On harness-evals#333 that put the reader's diff back at 5,120,181 bytes against the 2 MB ceiling and `/ship` stopped with `REVIEW_DIFF_TOO_LARGE` (GST-2006). RH-1 now skips only data and logs, which no test runner executes: JSON, JSONL, NDJSON, CSV, TSV, `.log` and `.out` files no longer carry RH-1 unless they were renamed from another kind of file. Every other file keeps RH-1, in a test folder or not, and every other gate tag still reads data files, and code under an excluded path with `fit(`, `fdescribe(`, `it.only(` or `describe.skip(` is still tagged and kept.
@@ -1 +0,0 @@
- **QA captures on macOS no longer record a finished command as incomplete.** After a command exited, the cleanup kill of its process group could get `EPERM` from macOS while the group was being torn down, and `gstack-qa-evidence capture` then recorded `status: incomplete`, `exitCode: 2`. On macOS, an `EPERM` now makes the capture check the group directly and count it as gone only when every remaining member is a zombie or is exiting (`ps` state starting with `Z` or carrying the `E` flag); a live member, or a check that cannot run, still fails closed, and `EPERM` on other platforms fails closed as before. The macOS teardown state is inferred from XNU and `ps`, not measured. Reported with measurements by @vilano93 (#4166).
@@ -1,4 +0,0 @@
---
important: blocker
---
- **pregate's `strays` check no longer flags real files whose names only start with `tmp` or `scratch`.** It treated `tmp` and `scratch` as name prefixes, so a real module such as `lib/server/disk-space/tmp-watch.js` failed with `PREGATE_STRAYS`, and `tmpfs-mount.js` or `scratchpad.ts` would have too. A branch can't allowlist its own strays, so nothing on the branch could clear it. `tmp` and `scratch` now count only as the whole name stem, optionally numbered: `tmp`, `tmp.js`, `tmp2.js`, `tmp_1.txt`, `TMP.js`, `scratch.md` and `scratch2.ts` are still flagged, and so is every `zz-*` name and editor leftover. The tradeoff: a stem followed by a word, such as `tmp-repro.ts` or `scratch_notes.md`, is no longer caught either, so name a scratch file `zz-*` to keep it flagged.
@@ -1 +0,0 @@
- **A reader's clean reply, or a sentence denying a severity, no longer stops /ship.** The review collector took a severity word anywhere in a reader's reply as a finding at that label. #4208's pass-2 red team returned `NO FINDINGS` but quoted `CRITICAL (the reviewer wrote …)` in a coverage note, and an alphaclaw fix-check wrote "No fix introduced a CRITICAL defect". Both collected as CRITICAL, judge may not drop a collected finding, and `gstack-review-plan` printed `NEXT: stop` (GST-2122). Now a reply that says `NO FINDINGS` keeps only its structured findings, which still collect at their label. Those are JSON objects (malformed ones included), lead labels, label headings, numbered headings, `Severity` fields, `Blocks: yes`, the native FIXABLE or INVESTIGATE tag, and anything naming a file:line. Its other prose collects nothing. A denial that a change introduced a severity is a no-defect clause, including after a bold lead such as `**1. Coverage:**`. Other prose keeps its label as before, so a real prose blocker such as "Neither fix clears the P0." still blocks.
@@ -1 +0,0 @@
- **The preflight secrets check no longer fails a branch for code that reads an environment variable, or for a self-describing fixture label in a test file.** `env.kv` already let `process.env.X` through, but `token = opts?.env?.AGENT_API_TOKEN;`, `ctx.env.X;`, `import.meta.env.X;`, `process.env["X"];` and `System.getenv("X");` read as credential values and failed `gstack-pregate` (yc-software/qm, GST-1894). An `env` member of any object read by an ALL-CAPS name, and an indexed or call read whose argument is a quoted ALL-CAPS name, are now code, but only when the line ends the expression with `;`, `,` or `)` and holds no fallback or default (`??`, `||`, `or`, a ternary `:`, or a second argument). Any other line, including one whose fallback follows a comment, a blank line or a line break, gets no new exemption. In `*.test.*`, `*.spec.*`, `test_*.py` and `*_test.*` files only, a value made only of lowercase words with a credential word among them (`"personal-secret"`, `"staging-api-key"`) is a fixture label. A literal fallback, the same label outside a test file, and every known key prefix still fail. A quoted name that isn't ALL-CAPS in an indexed or call read now fails where it used to pass: a lowercase name such as `os.environ["db_password"]`, and a literal used as the name such as `os.environ["<secret>"]`, which passed because the check skipped the quote the match had taken.
@@ -1,4 +0,0 @@
---
important: security
---
- **Learnings, timeline events and brain sync no longer store or push Stripe live keys, Slack tokens, npm tokens or database URLs with a real password.** `gstack-learnings-log` and `gstack-timeline-log` now refuse any row whose keys or string values hold a HIGH-tier secret by `lib/redact-engine`, the same gate `gstack-decision-log` already applied: the writer exits 1, names the rule (for example `db.url_with_password`) but never the value, and writes nothing. Before, `gstack-learnings-log` stored `DATABASE_URL=postgres://app:<password>@…` and exited 0, and `gstack-timeline-log` had no secret check at all. Brain sync's push-time scanner, which only knew six families, now also holds a file with a Stripe live or restricted key, a Slack token, an npm token or a database URL with a password, using redact-engine's placeholder rules so `postgres://app:${DB_PASSWORD}@…`, `PASSWORD` placeholders and the local `postgres:postgres@localhost` default still sync. Ordinary rows that mention tokens or passwords in prose still write and sync.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "gstack",
"version": "1.98.0",
"version": "1.99.0",
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
"license": "MIT",
"type": "module",