Commit Graph
140 Commits
Author SHA1 Message Date
Garry Tanandcapy-ai[bot] 6417abf7a6 Move the tools only Capy crews use into a crew/ folder (GST-1833) (#4145)
* Load the Capy crew skills only on Capy machines (GST-1833)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Add changelog entry for Capy-only crew skills (GST-1833)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Move the Capy-only crew tooling into crew/ and run its tests only when needed (GST-1833)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Test that the Capy launch file puts crew/bin on PATH

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Count any ~/.capy as a Capy machine, so a Mac running Capy keeps /captain

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Fix the tests and fixtures that broke when crew tooling moved into crew/

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Plan every crew test when a pull request's merge-base can't be read

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Add a test that checks each crew-only module left the root tree for crew/

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Run crew tests on pull requests that change bin/ commands or workflows they drive

Review-Finding: p1/specialist-batched#1 reader=testing
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Keep running every crew test on every pull request; the skip comes in a later change

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Keep copied installs and older release bisects working after the crew/ move

Setup now copies crew/lib into every host's runtime root beside bin and lib, so Windows installs (which copy instead of linking) can still start gstack-ship-receipt, gstack-pregate and the other commands that import it. The release bisect runs the train-gate script from wherever the checked-out commit has it, so a bisect over commits from before the move still gets a verdict.

Review-Finding: p1/outside-voice#1 reader=outside-voice
Review-Finding: p1/outside-voice#2 reader=outside-voice
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Say in the changelog that Windows installs keep working after the crew/ move

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Point the harness guide's merge-queue rows at crew/lib/ where those modules moved

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Point the plain-title test at the wave module's new home in crew/ (GST-1833)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-11-08 14:26:14 +00:00
Garry Tanandcapy-ai[bot] 118207f0f7 Add /design-review --build, an opt-in loop that checks and fixes new UI while you build it (#4142)
* Hide tokens in URLs that do not parse or that hold punctuation

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Add a build receipt and a fixture preview server so a design state whose API call went unanswered is never reported finished

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Register the preview fixtures as free so editing them does not rerun every paid test

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Add /design-review --build, an opt-in loop that lints, screenshots and fixes UI while you build it

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Recommend the design build loop for UI work in the lane card, routing rules, skills guide and design-html

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Let the design review skill grow by the opt-in build mode's routing line and pointer

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Make the design build loop work when an agent follows it step by step

A real run of /design-review --build turned up four problems:
- Each bash block runs in a fresh shell, so later blocks lost $RUN and the
  helper path, and the first block never set the project slug or state root.
  Blocks now stand alone and later ones use the printed run path.
- The lint command it printed was a bare name that is not on PATH; it now
  prints the full path to the bundled lint.
- gstack's own browser can only save screenshots under /tmp, so the section
  says to save where the browser can write and copy the file into place.
- The --build pointer sat before the design hard rules, which every section
  must follow; it now comes right after them.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Hold the design-review size ratio to the granted 1.357

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Let the free checks find the preview server's test when the server file changes

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Keep the design build loop honest about stale API misses and pages that are not the preview

A missing fixture route that was fixed before the final pass no longer blocks the receipt, an API call that arrived with no scenario now does, preflight only promises fixtures-only mutations when every page carries the preview script, and a bad path escape or a fixture with a bad status no longer stops the preview.

Review-Finding: p1/specialist-batched#1 reader=state-machines
Review-Finding: p1/specialist-batched#2 reader=security
Review-Finding: p1/red-team#1 reader=red-team
Review-Finding: p1/red-team#2 reader=red-team
Review-Finding: p1/red-team#4 reader=red-team
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Make the design build loop fail closed on any missed API call and only trust a page the preview actually serves

Any API call the preview could not answer during a run now keeps the receipt incomplete, and its fix line says to add the route, restart the preview and start again from preflight. Preflight checks every local page whatever hosts were granted, asks the preview's own probe route instead of looking for a marker in the HTML, and treats a redirect as not the preview. The Vite plugin now applies to the dev server only, so built pages never carry the preview script.

Review-Finding: p2v/outside-voice#3 reader=outside-voice
Review-Finding: p2/red-team#1 reader=red-team
Review-Finding: p2v/outside-voice#1 reader=outside-voice
Review-Finding: p2v/outside-voice#2 reader=outside-voice
Review-Loop-Exception: a-0aec1d2672bd
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Link the frontend preview guide from the README and note the preview check in the changelog

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Fix a test type error the CI type check caught in the preview probe test

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Fix the five free-suite failures in the design build loop

Run the slug helper through the shared script launcher, move the preview
server tests into the design build test that imports the product code, let
the single-source check skip generated host copies, write the build-loop
commands without free-text placeholders (trimmed so the skill stays under its
size cap), and add the build routing line to the onboarding fixture.

Review-Loop-Exception: a-b2e2abe1c646
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Document how to waive findings in the design build loop and say which requests the preview log records

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Keep the design build receipt from calling waived, edited or proxied runs clean

The receipt now reads waivers from the lint's grade, hashes every file in a touched folder, refuses an inventory that changed after verify-start, and finds the preview's record of unanswered calls in its default state folder. The preview answers a path the app's own dev proxy would forward with a recorded 501, and refuses a state folder that is a symlink or another user's.

Review-Finding: p1/red-team#1 reader=red-team
Review-Finding: p1/red-team#2 reader=red-team
Review-Finding: p1/red-team#3 reader=red-team
Review-Finding: p1/red-team#4 reader=red-team
Review-Finding: p1/specialist-batched#1 reader=testing
Review-Finding: p1/specialist-batched#2 reader=security
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Give the new receipt tests the state mode the inventory type requires, and say the preview does not stop proxied WebSocket upgrades

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Stop a catch-all dev proxy, an unrecorded preview and passwords with @ from slipping past the design build checks

An empty server.proxy key now gets the recorded 501 too. The preview's probe names its state directory, so the receipt reads a --state or --port 0 preview's record without --preview, and a preview that keeps no record makes the run incomplete. Text redaction drops userinfo up to the last @ and redacts percent-encoded secret parameter names.

Review-Finding: s11/outside-voice#1 reader=outside-voice
Review-Finding: s11/targeted#1 reader=targeted
Review-Finding: s11/adversarial-native#3 reader=adversarial-native
Review-Finding: s11/targeted#2 reader=targeted
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Build the userinfo redaction test's URL from parts so the credential scan does not read it as a real login

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Keep reading a stopped preview's default record when another origin's preview still answers

The receipt now reads the union of --preview, the state directory each answering preview names, and the default gstack-preview-<port> directory for every loopback state origin that did not answer the probe. servedStates reports which origin each answer came from.

Review-Finding: fc3/fix-check#1 reader=fix-check
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* List the design build receipt, its library and the preview template in the project structure guide

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-11-08 02:18:23 +00:00
Garry Tanandcapy-ai[bot] 914e7b6fb3 Write PR titles, commit messages and branch names in plain English (GST-1825) (#4146)
* Add a check that keeps tracker codes and internal jargon out of PR titles and branch names

gstack-plain-title checks the readable front of a title, branch name, PR opening or changelog entry against the repository's .gstack/plain-titles.json; one trailing issue code stays allowed. A Plain title workflow runs it on every new pull request, and /ship's push refuses a never-pushed branch whose name fails it.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Move the PR body's machine-read lines into a closing Agent notes section

The ship receipt, its runs block and the Plan line now live in one collapsed section at the end of the body, and every reader looks there first while still accepting the old receipt-at-top layout. gstack-post refuses to post a title the plain-title check refuses, and land uses the checked PR title as main's commit title.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Teach /ship to write plain titles, PR openings, commit messages and changelog entries

The README now lists what gstack is for, so a PR opening can name the goal it serves.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Keep tests, size budgets and generated files in step with plain titles and Agent notes

Copy lists that install result-code modules now include the plain-title codes, the ship goldens and size ledger are refreshed, the new workflow is classified, and the pinned prose tests read the new title and body layout.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Record the test-profile list change as non-behavioral for the shared test harness

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Keep text people add below the PR notes, and fix three smaller review findings

The notes section now ends at its own closing tag, so a collapsible added after it survives the next post. The opening check splits the body the same way the notes reader does. Wave PR titles no longer name the area, which the plain-title check could refuse. The Capy line in the PR-body steps now puts Approvals in the notes.

Review-Finding: p1/specialist-batched#1 reader=testing
Review-Finding: p1/red-team#1 reader=red-team
Review-Finding: p1/red-team#2 reader=red-team
Review-Finding: p1/red-team#3 reader=red-team
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Mask credentials in refused-title lines and keep older PRs' plan lines readable

A refused title's printed match is masked by the secret scanner first. The Plan line reader falls back to the whole body when the Agent notes section has none, so a body written before this change keeps its plan when its first receipt lands. The notes section also ends at its own closing tag when its opener is split or marked open, or when a collapsible inside it is left unclosed.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Print a fixed placeholder when a credential in a refused title cannot be masked

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Keep a plain-title policy edit from scheduling every paid eval

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Record the paid-eval list change as non-behavioral for the shared test harness

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Recheck the PR title on every edit so a base change cannot turn a refused title green

Review-Finding: s11/targeted#1 reader=targeted
Review-Finding: s11/adversarial-native#1 reader=adversarial-native
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-11-08 00:59:38 +00:00
Garry Tanandcapy-ai[bot] bb3b7d072a Install /captain only on Capy machines (GST-1833) (#4143)
* Load the Capy crew skills only on Capy machines (GST-1833)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Add changelog entry for Capy-only crew skills (GST-1833)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Test that the harness registrar leaves /captain out off Capy

Review-Finding: p1/specialist-batched#2 reader=testing
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Say in the skills guide that /captain installs only on Capy machines

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-11-07 22:18:18 +00:00
Garry Tanandcapy-ai[bot] ef99eb5a55 feat: retire /investigate after a no-gain eval (GST-1717) (#4110)
* feat: retire /investigate after a no-gain eval (GST-1717)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(skill-start): core routing offer drops every non-core rule line (GST-1717)

The retired /investigate rule became 'Bugs/errors → fix them directly', which no
longer matches '→ invoke /', so proactive=core leaked it into the offered section.
Match any '- … → ' rule line.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* revert(review): keep review/checklist.md at its no-loss-pinned bytes (GST-1717)

The pin names replay GSTA-224 H1; a reviewer checklist edit merges only after a
passing no-loss replay (crew rule 15). The /investigate flake line stays until a
replayed repin.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: repair free-suite fallout from retiring /investigate (GST-1717)

CI run 42340956269 at 83a8a9a: drop fixtures, ledger rows, allowlist entries and
counts for the retired skill and its deleted paid files; record the harness
change as non-behavioral; the test-of-test detector accepts path.join(ROOT, 'bin');
uncovered-prompt fixtures use a tracked prompt, since an untracked path now
reads as a deleted one.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(relink): back up a retired skill's real SKILL.md before cleaning it (GST-1717)

With the skill's source gone, the customized-file check never ran, so a
banner-carrying local copy of investigate/SKILL.md was deleted instead of moved
to the backup root (red-team review, pass 1).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(qm): restore the review checklist's flake-line rewrite (GST-1717)

review/checklist.md keeps its pinned /investigate line, so the QM edition needs
its replaceText rule back; qm/ stays fresh against the generator.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(changelog): name the setup flag break, the relink backup and the pinned checklist line (GST-1717)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(setup,relink): back up a retired skill's real SKILL.md instead of deleting it (GST-1717)

Scoped fix round under grant a-360dd62bd46a (outside-voice blocking finding and
adversarial #1, #4). setup's prune compares a copied host entry with the render
before removing that render, and backs up a copy that differs or whose render is
gone; relink no longer takes the marker's rm -rf for a skill whose source is
gone, so a real SKILL.md is backed up and the user's files stay. setup reports a
failed remove-source of the retired gate hooks on stderr.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(pr-profile): a changed prompt counts as deleted only when it is gone from disk (GST-1717)

An untracked prompt file on disk still needs prompt validation (targeted review
finding); harness change recorded as non-behavioral.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(changelog): exact backup wording, the hook-removal warning and GST-1744 (GST-1717)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(setup): one backup folder per host for a retired skill's copies (GST-1717)

Under grant a-c95576b8b1f9 (fix-check#1): every copy host moved its
gstack-investigate/SKILL.md to the same backup path, so a later host's copy
overwrote an earlier host's edits. A sandboxed ./setup --host factory run showed
gen-skill-docs removes the retired render before the prune, so every real copy
is backed up; each host now gets its own folder.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(changelog): say exactly what setup backs up for a retired skill (GST-1717)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(changelog): end the two colon sentences prose-lint flagged (GST-1717)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(retired-skills): name plan-eng-review once behind a pregate-literal waiver (GST-1717)

pregate's literals check read the GSTACK_RETIRED_SKILLS change as an old
value still in the tests. plan-eng-review is still retired; investigate was
appended. The fixture names it once, with the waiver.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-11-07 06:52:04 +00:00
Garry Tanandcapy-ai[bot] 3742ee9527 feat: cut standalone /plan-eng-review; /autoplan keeps its eng phase (GST-1698) (#4108)
* feat: cut standalone /plan-eng-review; /autoplan keeps its eng phase (GST-1698)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: lower shrunk SKILL.md size pins; keep the retired methodology's pins measured beside /autoplan

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: carry /autoplan's plan-eng-review.md into Kiro installs

Kiro's per-skill loop copied only SKILL.md and sections, so ~/.kiro/skills/gstack-autoplan lacked the Phase 3 methodology file its SKILL.md reads. Generated sibling *.md files now travel with SKILL.md under the same ownership guards.

Review-Finding: p1/specialist-batched#1 reader=red-team
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: file /plan-eng-review retirement follow-ups; scope the changelog claim to ./setup

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: route architecture-review requests to /autoplan by its description (journey-plan-eng)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-11-07 04:17:35 +00:00
Garry Tanandcapy-ai[bot] a7585d0e74 setup: new installs default to stable; Capy threads run latest main daily (GST-1642) (#4091)
* setup: new installs default to the stable channel; Capy crews stay on main (GST-1642)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* capy: threads run gstack's latest main at least daily; review fixes (GST-1642, R75)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: raise the capy skill's eager context ceiling for the R75 stale-pin prose (GST-1642)

The R75 rules grew capy/skill/gstack/SKILL.md to 5491 tokens, past its 5315 ceiling; free-tests run 42251052228 failed test/context-budget-ratchet.test.ts. Per the ratchet protocol this records the capture's value (actual x 1.10 = 6041) for this skill only; every other ceiling is unchanged. Gate disposition left open for the owner.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes

A review-voice --check no longer prints the run-the-fix-before-the-first-workflow stale note (its fix would move the lane's shared checkout); a refused fast-forward after the pin-override switch is now tested to restore the pin.

Review-Finding: p1/red-team#2 reader=red-team
Review-Finding: p1/specialist-batched#1 reader=testing
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: new installs follow stable in the recommended harness (GST-1642)

Step 14.5 documentation audit gst1642-docs-1.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: regenerate bin/gstack-doctor-components.sh for PIN_UNREASONED (GST-1642)

bun run gen:skill-docs --host all adds the pin-unreasoned anchor row the new result code needs.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* capy skill: tighten step 2 to fit the 5315-token ceiling; restore the ceiling (reverts 943ff5d) (GST-1642)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-11-06 19:26:10 +00:00
Garry Tanandcapy-ai[bot] cb8f9eaeac release: daily stable check: important gated changes reach stable within a day, forced cut at 7 days (GST-1646, R74) (#4083)
* release: daily stable check cuts important gated changes after 24 h and forces a cut at 7 days (GST-1646, R74)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* release-cut: no release PR is NO_RELEASE_PR, a warning on a dry run, so the PR dry run against main stays green between cuts

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes

release-cut --daily: a plan that clears the 24 h floor while the daily check cannot date stable is held as FLOOR_UNKNOWN, so a null decision never reaches a cut (red-team p1#1).

Review-Finding: p1/red-team#1 reader=red-team
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: name the important: fragment field in changelog.d/README.md and CHANGELOG_FRAGMENT_INVALID

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-11-06 17:16:24 +00:00
Garry Tanandcapy-ai[bot] 56a800da98 feat: /runaway-stopper — detect, stop and prevent runaway threads and subagents (GST-1279) (#3975)
* feat(runaway-stopper): detect, stop and prevent runaway threads and subagents (GST-1279)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(runaway-stopper): autoplan review fixes: input validation, exact /ship task-row meter at 100M, rotation hand-off of subagents and PR watches, bounded fetch program

Skill-Size: 0→6747 bytes (SKILL.md.tmpl)
Evidence: plan docs/evals/runaway-stopper-prereg.md measure_by 2026-11-24
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(ship-budget): describe the task-row reading without a literal thread_id this

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(runaway-stopper): ship review pass 1: DEFER holds rotation and a self-stop only, working-only defer, subagents file validation, scan exit kept, DRIVER and factory.md within their size budgets

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(runaway-stopper): ship voices: list limit clamp, allSettled fetch, waiting /ship and shipping descendants defer, unknown flags refused, GSTACK_SHIP_TASK_TITLE implies the task-row meter

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(ship): leave DRIVER.md unchanged; main left 21 bytes under its 12 KB cap

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-11-04 02:59:50 +00:00
Garry Tanandcapy-ai[bot] 14d7414af3 v1.93.33.0 wave(review): #3705 #3744 #3745 #3764 #3765 #3795 (#3852)
* fix(ship): base merges, adjacent base edits, test-only and audit-requested fixes no longer force a second review loop (GST-407, GST-384, GST-391)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ship driver: trim two phrases for headroom with #3662

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ship driver: keep the Next steps heading phrase the handoff check reads; trim elsewhere

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: regenerate ship goldens

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes

comparePath returns same only when every hunk sits where the base change puts it (patch-ids ignore positions) and treats a file-mode change as changed; the scoped-review packet says when a fence is truncated; --docs-result must be the posted audit's result; the --context-reviewed record goes beside the packet, outside the worktree. Negative controls for each.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pass-2 review fixes

A test edit routes as tests= only when it adds lines with no gate tag; a removed or changed line or an unreadable test diff goes through the full loop. Diffs skip textconv. A hunkless patch (binary, empty, mode-only) is same when its patch-id matches. Without --reviewed-base test files route as before. finish's packet path and attempt's state directory have fallbacks.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: Step 11 review fixes; a version-only stamp_files edit after review is release metadata (GBRA-251)

gateEditedPaths reads removed lines only inside hunks; carry refuses when a path listing fails; docs freshness stops on a failed listing; attempt counts an unreadable state file. releasePaths includes stamp_files paths whose edit is a version swap under stamp_patterns.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: no test edit skips review; guard packet writes; exclusive attempt state

A test-only edit with no gate tag gets the scoped fix-check of its test hunks instead of a lanes-only rerun. Packet write failures print a STOP or RECEIPT_CARRY_REFUSED. attempt's allowance uses an exclusive create.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: attempt prints gstack's install command; carry checks the product diff outside context paths

The attempt STOP no longer echoes the audit child's fix text. A scoped-review carry now requires the product patch-id without the context paths to match at both heads.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: oversized packets take the full loop; exact --docs-requested match; name-only test globs only in test dirs

A scoped-review packet section over 200 lines sends that path to the full loop instead of a truncated packet. The docs-result path match is exact. test_*.py, *Test.java and similar count as test code only under a test directory.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(unattended): carry's scoped-review refusal and what it keeps

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: carry's scoped-review route in ship-policy, review-policy and troubleshooting (docs-a-1)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(windows): curate ship-rereview-triggers out of the Windows lane like ship-release (run 41193336209)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(review-log): record a notes-only /ship pass clean; name the cause and repair when a pass's tree moved (GST-430)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(receipt): carry and write handle PRs with thousands of files; tree receipt fails closed on a failed diff (GST-448)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(ship): preflight routes RECEIPT_CARRY_SCOPED_REVIEW to the scoped review; carry --dry-run writes no packet (GST-442)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(ship): Step 1.4 and Step 16 route the scoped-review refusal and the --docs-requested fix; trims keep the cap (GST-442, GST-444)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: /ship review pass 1: notes-only floor, scoped carry order, named temp and diff refusals

- A notes-only pass is recorded clean only when every finding is fixed, or noted or deferred below the
  blocking floor (never CRITICAL or blocking), by its logger-stamped action, and the findings account
  for the claimed issue and critical counts (GST-430).
- The preflight scoped carry route commits and pushes the base-merge fix before writing the packet (GST-442).
- patchId refuses by name on an unwritable TMPDIR; the tree receipt names why its diff failed (GST-448).
- Step 16 stage 3 spells out --docs-requested with its --docs-result (GST-444).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: pin the Step 16 --docs-requested route with its --docs-result

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: a claimed clean review row with a logger-stamped open gate finding reads UNVERIFIED (red-team, pass 1)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: Step 11 findings: gate rows from --findings are stamped, one open rule, stale patch-id dirs swept

- deriveFromFindings stamps gate rows with the session kind before deriving status, so a findings file cannot
  dispose of a gate finding the unattended logger forces open (outside-voice#1).
- reviewFreshness reads a claimed row's findings with isOpenByAction, the rule the notes-only rewrite uses;
  noted and deferred clear a finding only when it is marked blocking: false and is not CRITICAL.
- patchId sweeps gstack-patch-id-* directories over an hour old that a killed carry left in TMPDIR.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: Step 11 round 2: stamped-open gates win over status and disposition; resolved gate, skipped and below-floor actions stay CURRENT

- isOpenFinding treats action 'open' (the logger's forced-open stamp) as open before a row's status or disposition.
- reviewFreshness flags a claimed clean row only for an 'open' action or a CRITICAL or blocking deferral or note, so
  interactive kept and restored gates, skipped advisories and INFORMATIONAL deferrals read CURRENT as before.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: carry dry run offers the scoped route last; patch-id diff under the git dir; changelog matches the review-log floor

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: a claimed clean review row with any unresolved non-gate CRITICAL finding reads UNVERIFIED (pass 2 red-team)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: carry dry-run scoped route, patch-id directory, tree-receipt refusal (docs-d-1 audit)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(ship): Step 3 names merge conflicts in its STOP sentence again (ship-workflow-clarity pin)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(tree-receipt): keep the gated-to-current diff at the file helper timeout (60 s); pregate literals check

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(carry): large-diff fixture sets core.longpaths so Git for Windows can add its long receipt paths (windows-free-shard 1, run 41210080055)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(review): a stale local base branch yields to its origin twin in gate-diff and diff-scope (GST-462)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(ship): Step 11 saves the targeted record and the receipt writes carry it; Step 9 clears it (GST-464)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* changelog: GST-464 and GST-462

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(review): pass 1: ahead and diverged labels, diff-scope needs shared history; preflight refreshes origin/<base> and stops on a diff far beyond the PR (GST-462, HARN-15)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pass-2 review fixes for base twin, prSized stop and targeted record

Resolve the tracking twin by refs/remotes/<remote>/<ref> and only for a bare
branch name, keep a local base that is ahead or diverged, quote the prSized
stop's base and name the fork case, and have every Step 9 entry create,
exclude and clear .gstack/tmp/targeted.json.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: origin's tracking base wins whenever related; prSized lower bound

The fix-check found that keeping a local base ahead of origin left unpushed
base commits out of the gate scan while review-plan listed them. The tracking
branch now wins whenever it shares history with HEAD, an explicit origin/main
reads refs/remotes/origin/main, the preflight also stops when the diff lists
fewer files than the PR, and the Step 9 exclude line survives a missing
.git/info and an exclude file without a final newline.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* changelog: the tracking-ref claim holds while the tracking branch exists

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: repin the two ship section briefs under no-loss replay GST-464

GST-464 brief-identity check: every reviewer brief lib/review-brief.ts cuts
from ship/sections/review-army.md and adversarial.md stays byte-identical to
the bodies the L and GST-257 replays admitted; only lead instructions outside
the cut spans changed. The check is a test CI runs.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ship: keep Step 9's PASS_HEAD sentence whole; repin review-army under GST-464

The targeted-record line follows the PASS_HEAD/RANGE_BASE sentence instead of
splitting it (test/review-fix-range.test.ts pins that sentence). The brief-body
check still holds: every reviewer brief is byte-identical.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: restore bin/gstack-ship-receipt after the main merge

The merge commit b10e2db wrote a truncated copy of the file. This is the
three-way merge of both sides with the refused() union: main's codes plus
RECEIPT_CARRY_SCOPED_REVIEW.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* review-collect: narrow the consumer scan and give it one 300 s deadline (GST-544)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* review-collect: keep the consumer scan deadline under the 180 s plan timeout (review fix)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* review-collect: plain paths under --literal-pathspecs; whole-millisecond timeout override (review fix)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* review-collect: whole-tree grep for non-UTF-8 names, no argument spread, name spawn errors (review fix)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* review-collect: cap the consumer grep output at 256 MB across calls; per-scan deadline wording (review fix, cycle 4)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* review-collect: read a missing spawn status as null (Bun 1.4.2 returns undefined on ENOENT)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: review-collect whole-tree helper reads a null status as -1 (typecheck:test ratchet)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* review-bots: /review and /ship triage every configured review bot, not only Greptile (#3122)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes (gh before 2.48 pages without --slurp; driver routes Step 10; tests for guards, review-bot fence, REMOTE_SLUG owner)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: drop the review-bot fence test that could not fail (the tool-fence WARN degrade is unreachable: placeholder spans are skipped first)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: Step 11 review fixes (record by comment id so a filename never reaches a shell; lookup failures are errors; tojson pages; longest bot prefix wins)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pass-2 review fixes (record refuses * paths, colliding ids and a missing fetch dir; parent requires id and dir; botOwning doc)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* review: classify the PR type and always check repo conventions (#356)

The checklist now opens with a PR-type line (application, CI/infra,
scripts/tooling, docs-only or mixed). Application and mixed diffs run
every category; other types always run five categories, including the
new Repo Conventions & Platform Fit, and report the rest as not
applicable in one line instead of skipping silently.

No-loss replay: GSTA-224 H1 preregistered no-loss run (run id
gsta224-h1-20261031, Opus 5.5, 81 calls, $9.53) against main's checklist
with a no-gstack arm; every preregistered bar passed. The
review/checklist.md pin in test/review-packets-removed.test.ts moves to
a32963ef with replay 'GSTA-224 H1'.

Refs GSTA-224

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* review-bots: allowlist the record placeholders and match Step 10's id rule in tests

The record fence uses <comment-id>, <fix|fp|already-fixed> and <category>,
each with an enum or numeric grammar; <greptile-dir> becomes <bot-dir>.
review/greptile-triage.md also lists every REVIEW_BOTS skip reason.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: review-bot triage covers every configured bot (#3122)

README, CONTRIBUTING, the contributor reference and standalone-binaries
name gstack-review-bots and the review-bot fence; TODOS drops the #3122
item; the changelog fragment covers record-by-id and lookup errors.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: the PR-type test also pins the compose, Terraform and README/CHANGELOG phrases

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: follow the review-bot rename in the remote free tier's three reds

The cross-skill consistency judge describes review-bot-history.md and its
record helper; the tracker-guard exemption for the removed raw Greptile
fetch in review/greptile-triage.md is dropped (the fetch now lives in
gstack-review-bots, whose bodies still reach context only through
gstack-issue-guard); the Step 10 PR-check test expects 'Review bots: not
run'. HARNESS_VERSION stays 6: skill-fixture.ts and llm-judge.ts are
recorded non-behavioral for every other case.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: keep the cross-skill judge's rubric to one renamed bullet

Name review-bot-history.md beside the older greptile-history.md so the
calibration corpus still matches, and keep the REMOTE_SLUG bullet. One
live 3-sample panel at this tree: consistent 3/3, scores 4/4/4.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* changelog: H1 fragment passes prose lint

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* changelog: name the H1 fragment after its branch; TODOS drops #356

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: /review's PR type step in docs/skills.md (#356)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ship-policy: review_exclude_paths key and loadPolicyAt (GST-508)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* review: apply the base policy's review_exclude_paths and the reviewer diff ceiling (GST-508)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* receipt: bind review_exclude in write, carry and the queue (GST-508)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs, tests and changelog for review_exclude_paths (GST-508)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes (binary by bytes, links kept, untracked files out of the selection)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: keep binary files at code paths under review_exclude_paths

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: name the reasons that keep a binary file under review_exclude_paths

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: keep every review_exclude_paths candidate when the gate scan is unknown; docs example names its version

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: keep the NUL fixture clear of the sync-spawn tripwire

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* changelog: split the ceiling sentence

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: keep a live binary file whose bytes cannot be read; tests render outside voices from a clean checkout and read the ship fixture text

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(review-exclude): explicit +x for the census script; skip the chmod-000 read case on Windows and root

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* refactor: move loadPolicyAt to lib/ship-policy-at.ts so lib/ship-policy.ts stays under the 800-line module cap

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: refuse a base that contains HEAD; skip a self-pointing origin/HEAD (GST-774)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: regenerate gstack-doctor-components.sh for BASE_CONTAINS_HEAD

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: land resumes a merged merge-only PR instead of refusing BASE_CONTAINS_HEAD (GST-774 review)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: changelog fragment for GST-774

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: troubleshooting names diff-scope base_contains_head and the self-pointing no_base case (GST-774 docs audit)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: repin the reviewer briefs to GST-508's no-loss-tested bytes

ship/sections/review-army.md, ship/sections/adversarial.md and lib/review-depth.ts now pin
the bytes the GST-508 preregistered no-loss replay ran at ef2760f (handoffs/GSTA-202/noloss-results.md
in the GStack Minder drive): Arm V 33/41 vs R 33/41, delta 4, exact sign flip, p_NI 0.0279 (pass).
The merge of main 83ae96e leaves these three files byte-identical to ef2760f, and an Arm P unit
(3593) renders byte-identical briefs at ef2760f and at the merge.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: GST-774 review round 1 — merged resume depth, aliased and detached PR checkouts, develop/trunk defaults

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ship: Step 20 says a metrics row, keeping DRIVER.md at 12 KB after the main merge

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: ship-parts resend case leaves room for the first phase-3 part after the Step 9 targeted-record sentence

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: /ship delivers review/checklist.md in two parts so checklist v1 fits the 24576-byte part cap (#3670)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* changelog: drop the GST-407 and GST-430 fragments, already released on main (stacked history)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* wave review-53cfb5bfe011: lib/ship-policy.ts back to 800 lines (module-size ratchet after merging #3744 and #3795)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* wave review-53cfb5bfe011: /ship pass 1 fixes

- lib/ship-receipt-depth.ts: a merged resume (land after a true merge) no longer recomputes the review_exclude digest over the emptied range, so it lands instead of RECEIPT_INVALID (failing-first land test with review_exclude_paths active).
- bin/gstack-review-dispatch render: a standalone targeted render under an active review_exclude_paths refuses and points at the pass manifest voices stage, instead of writing a brief with no selection that leaves verify dirty (failing-first test).
- test/default-base.test.ts: #3795 expectations follow main R51 depth (light for an 80-line code diff; runtime_lines=80 still proves the real base).
- test/review-dispatch.test.ts: the GST-897 fingerprint test renders from the clean checkout, like its neighbor, because CI rebuilds the tracked lib/diagram-render/dist/diagram-render.html and the reviewer diff ceiling refused the ROOT render.

* wave review-53cfb5bfe011: /ship pass 2 fixes

- lib/review-exclude.ts: binaryContent reads each blob's first 8,000 bytes (git cat-file blob, 8 KB buffer) instead of one --batch read capped at 256 MB, so head mode (receipt, carry, queue) and the plan agree past 256 MB of blobs; an unreadable blob is kept, as in live mode. Failing-first test with two 270 MB blobs.
- lib/review-bots.ts: a file-level bot review comment (subject_type file, no line position) is triaged instead of read as outdated. Failing-first test.
- test/ship-queue-land.test.ts: the merged-resume exclusion test runs its gate scan under the fixture state root, not the real home (CI free-suite 6 home containment).

* fix: merge follow-ups for main 3d99c22: origin/* stays qualified in diff-scope's twin step, retro skeleton back under its cap, combined no-loss pin GST-508+R51+GST-911

* fix(review): receipt write gate-scans whenever the base declares review_exclude_paths; collectLive never re-qualifies origin/<base> (#3852 /ship red-team#1, outside-voice#1)

* test: the docs-only receipt digest test keeps its gate scan out of the real home (free-suite shard 5 FREE_SUBSHARD_REJECTED)

* fix(merge fc292e22f): tentative REVIEW_EXCLUDE_KEY_VERSION, classify targeted.json, render main's new outside-voice test from a clean checkout

- lib/ship-policy.ts: REVIEW_EXCLUDE_KEY_VERSION 1.93.0.0 -> 1.94.0.0, the next-minor tentative form stamp_paths moves (docs/ship-policy.md, #3512); 1.93.0.0 is a released heading that never names the key.
- test/gstack-clean-producers.test.ts (new from main): KEPT gains targeted.json, the wave's --review-targeted input.
- test/review-dispatch.test.ts: main's wrong-tree test rendered the outside voice from ROOT; a CI build that dirties ROOT pushes that diff past the wave's REVIEW_DIFF_MAX_BYTES ceiling, so render refused and verify saw no brief (exit 3). It now runs in cleanCheckout like the wave's other outside-voice tests.

* test(merge 68410828f): GST-967's brief-body pin carries the wave's GST-508 placeholders

The merged sectionTexts differ from main's by exactly the wave's GST-508 exclude-pathspec lines (merged minus main equals wave minus fc292e22f); the 1,440-case plan grid still hashes to main's MAIN_PLANS. Checks: GStack Minder drive handoffs/GSTA-202/merge-6841-checks.md.

* fix(merge e85005482): targeted classes since the read accept an empty range; the wave's receipt tests pass main's targeted record

- lib/ship-receipt-depth.ts targetedClassesSince (main, GST-986) reads the range from the commit the targeted reader read to HEAD through headInputs, which since #3795 runs gstack-diff-scope's BASE_CONTAINS_HEAD check. When the reader read HEAD itself the range is empty, diff-scope reported base_contains_head, and every write with --review-targeted was refused (the product patch matched). It now passes merged=true (--merged), which skips only that check; an ancestor check already guards the range.
- test/review-exclude.test.ts and test/review-targeted.test.ts: since #3887 write refuses a money head with no targeted record and requires the record's head, so the wave's two writes that must succeed pass one, as main's own tests do (targetedAtHead in test/ship-receipt-depth.test.ts).

* test(merge e85005482): drop the wave's duplicate targeted.json KEPT entry

Main (#3887) classified .gstack/tmp/targeted.json in KEPT too; the auto-merge kept both keys, which typecheck:test rejects (TS1117). Main's entry stays.

* test(merge e85005482): ship size ledger entry for the merged tree (-232 B)

Main's ship size ledger plus the wave's entries summed to 345,334 B; the merged tree measures 345,102 B because DRIVER.md takes main's shorter Step 19 line. Recorded with scripts/size-ledger.ts record ship.

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-11-03 01:40:07 +00:00
Garry Tanandcapy-ai[bot] 775fe2737c v1.92.43.0 docs: support tiers — full for macOS and Linux, minimal for Windows and Gemini (R50) (#3812)
* docs: support tiers — full for macOS/Linux with Claude and OpenAI, minimal for Windows and Gemini (R50)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(support-tiers): the Windows suite can also be started by hand

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: Windows CI section says what runs today and what changes once windows-smoke lands

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-11-01 22:35:42 +00:00
Garry Tanandcapy-ai[bot] e4e4e42967 v1.92.22.0 feat: Gemini CLI host, and older installs drop the dev toolchain (#3778)
* setup: explicit contributor marker; move pre-v1.91.231.0 installs to production deps (#3447)

./setup used node_modules/typescript as its contributor signal, so every
install made before v1.91.231.0 (a full bun install) kept ~520 MB of test and
eval packages forever. The contributor is now the clone bin/dev-setup marks
with git config gstack.contributor true, or GSTACK_CONTRIBUTOR=1. An unmarked
clone with the dev toolchain moves node_modules aside under a checkout lock,
installs production-only, verifies every dependency, and drops the backup; any
failure puts the full install back with SETUP_DEPS_MIGRATE_FAILED. CI and a
clone whose git config cannot be read never migrate. Bun cache entries for the
pinned Agent SDK are removed only when no other project links to them.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* hosts: add Gemini CLI as an experimental host (#1247)

./setup --host gemini (alias gemini-cli; --host auto when gemini is on PATH)
installs gstack-<skill> directories into ~/.gemini/skills, which Gemini CLI
0.66.0 lists with gemini skills list, plus the ~/.gemini/skills/gstack runtime
root. Renders go to .gemini-cli/ (gitignored) with name+description
frontmatter whose names match their directories, CLAUDE.md rewritten to
GEMINI.md, and a tool glossary for Gemini CLI's names (and Antigravity's, which
can read the same folder). gstack-uninstall removes only gstack-managed entries
there, so the user's own skills stay. Docs: ADDING_A_HOST.md verified facts,
hosts.md, uninstall.md, README.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes (leave Bun's shared cache alone)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* changelog: Gemini CLI host and contributor marker (#1247, #3447)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: host lists name Copilot, Antigravity and Gemini CLI

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: the plan-mode re-render guard count includes the Gemini CLI host

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: troubleshooting's runtime-root guard host list names Gemini CLI

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-11-01 09:35:42 +00:00
Garry Tanandcapy-ai[bot] fd5fcb4318 v1.92.20.0 /retro global counts Kimi Code, OMP and Antigravity sessions (#3740)
* global-discover: scanner registry; Kimi Code, OMP and Antigravity sessions (#2917, #1977)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: /retro global lists Kimi Code, OMP and Antigravity; TODOS marks #2917 shipped

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-11-01 08:12:16 +00:00
Garry Tanandcapy-ai[bot] 923ed3cddc v1.91.359.0 feat(verify): /verify skill and gstack-checks bin (GST-116 PR 1) (#3672)
* verify: add /verify skill and gstack-checks (GST-116 PR 1)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes (pass 2)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: adversarial review fixes

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: win32 NUL discard for the Go build check; unique Health Stack names (rule 23 grant)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: changelog fragment for /verify

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-31 09:54:03 +00:00
Garry Tanandcapy-ai[bot] 959c8a6a4f v1.91.327.0 docs(capy): capy/README.md, the gstack + Capy software factory with measured numbers (#3622)
* docs(capy): capy/README.md, the gstack + Capy software factory: how it fits, measured before/after, tips, how Garry uses it

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(capy/README): footnote's gh pr list query needs --limit to return 197

Without --limit, gh pr list returns at most 30 rows, so the quoted
reproduction printed 30, not 197 or 65.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(capy/README): don't name a private repository's Captain

The Harness Evals Captain runs a private repository; the public README
names only gstack-evals among private repositories.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(capy/README): #3610 is an open fix, not a landed one

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(capy/README): the 2.4-day median covers issues of every kind, not only bugs

The 52 are issues linked to a merged PR by closing references; the
measurement does not classify them as bugs.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(capy/README): point the /eval-plan link at its section in docs/skills.md

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(changelog): [Unreleased] entry for capy/README.md

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(capy/README): the Captain took over on 2026-10-16; 10-15 is the first fix waves

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(capy/README): the 2026-10-15 waves were not gstack's first fix waves

docs/designs/ already holds fix-wave plans from October 3, 6 and 12;
on October 15 Garry approved two more (fix-wave-2026-10-15 and
fix-wave-2-2026-10-15).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(docs/captain.md): keep the controller guide generic; the capy/README.md pointer names no owner

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(capy/README): tighten claims the review noted (stamp rule, UTC days, gbrain attribution, gate-class PRs, severe fixes keep their gates)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(capy/README): everyday words (two weeks, plain-English summary, gut feel, merge commit)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* v1.91.327.0 stamp

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-30 01:34:37 +00:00
Garry Tanandcapy-ai[bot] 9f44f188d1 v1.91.317.0 feat(harness): trigger-first skill descriptions, hybrid OpenClaw layout, opt-in --context block (GSTA-135) (#3555)
* test(harness): pin the GSTACK_SESSION_KIND=spawned marker and its readers (GSTA-135)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(harness): trigger-first skill descriptions, hybrid OpenClaw layout, opt-in --context block (GSTA-135)

Router lines and Hermes leads open with the skill's triggers; OpenClaw
defaults to hybrid (five task skills plus the router); --context writes the
routing block only into a named file, refusing SOUL.md and any new Hermes
rules file that would outrank one Hermes loads now.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(harness): skill-use layout, --context and HARNESS_CONTEXT_REFUSED (GSTA-135)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-29 13:28:08 +00:00
Garry Tanandcapy-ai[bot] 415e343b52 v1.91.298.0 chore: remove Dia from gstack (cookie source and qualification harness) (#3551)
* chore: remove Dia from gstack (cookie source and qualification harness)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: pin the removed Dia harness so pregate selects a lane for its deleted scripts

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: CHANGELOG names every doc the Dia removal touched

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-28 19:37:41 +00:00
Garry Tanandcapy-ai[bot] 219f7488bb v1.91.292.0 Stable channel: a regression exception beside the security one, and the stale weekly wording (#3533)
* feat(release): regression cuts pass the 72-hour floor like security cuts; weekly wording follows the twice-weekly cadence

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: a regression anchor needs a published release; an unmoved exception tag reruns only with its recorded reason; a version may end the sentence

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* RESTAMP: 1.91.291.0 -> 1.91.292.0 (stamped; queue_mode=stamp-at-merge base=origin/main@6f27f0c269cb policy=base origin/main@6f27f0c269cb:.gstack/ship-policy.json)
STAMP_VERSION: 1.91.292.0
stage version: done (wrote 1.91.292.0)
stage release_tool: done (bun scripts/gen-agents-digest.ts)
stage changelog: done (Unreleased -> 1.91.292.0)
stage stamp_paths: skipped (no tentative mention newer than the fork version 1.91.291.0 and none of base 1.91.291.0 on a line this branch added)
stage mirror: skipped (no mirror in policy)
stage apply: done (4 file(s))
FILES: CHANGELOG.md VERSION agents-digest/gstack-AGENTS.md package.json
MERGE_CONDITION: sha=40a8df86de0bda12cedea7dbdc744b90f4b1d5eb
GSTACK_RESULT: skill=restamp status=complete run=/workspace/gstack/.gstack/tmp/restamp-journal.json

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-28 09:59:40 +00:00
Garry Tanandcapy-ai[bot] 44f19d0106 v1.91.290.0 feat(harness): gstack registers its skills in whatever agent harness it is installed into (GSTA-135) (#3526)
* wip: gstack-harness registrar (GSTA-135)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* wip: harness registration in setup, uninstall, tests

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* wip: harness hosts experimental, troubleshooting codes, kit

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: agent harness registration (GSTA-135)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: harness hosts run in place from the checkout in --status

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: bun-free ledger reader

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: CHANGELOG for harness registration (Unreleased)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(harness): keep edits beside themselves, never follow symlinks, cut only gstack's own context block

Review blockers on #3526: uninstall deleted the edit backups under the state root;
unregister followed a symlinked folder below the destination; a lone or edited
context marker made the cut eat user lines; a re-register adding --context left an
unrecorded block. Also: ledger written before the files, --refresh-registered no
longer re-runs harness hosts through setup (gstack-harness refresh keeps their mode),
bash goes through runBashSync, and setup tests follow openclaw/hermes becoming installs.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(harness): a ledger with no destination row touches nothing (no hang)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: harness registration in README, install and hosts (document-release audit)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-28 04:33:53 +00:00
Garry Tanandcapy-ai[bot] 7204a39a27 v1.91.287.0 feat(write-skill): write, change and diet skills; /learn at half its template size (#3524)
* test(skill-evals): /learn before/after tasks, written before any cut (GSTA-133)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(learnings): --help, unknown-flag errors and --stats on the learnings bins; cross-project search no longer drops every project when the state root path contains the slug

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* refactor(learn): diet /learn onto the levels: tool flags replace the inline stats program, one mode table, prune rules kept

Skill-Diet: learn
Skill-Size: 5797→2586 bytes (SKILL.md.tmpl)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(write-skill): /write-skill skill, gstack-skill-bill, static skill-to-bin drift ratchet, shared skill eval contract

Skill-Size: 0→3245 bytes (write-skill/SKILL.md.tmpl)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(learn): guard the state-root lookup in Prune; re-measure the learn ceiling

Skill-Size: 2586→2707 bytes (learn/SKILL.md.tmpl)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes (drift scanner, learnings flags, skill-bill baselines, eval fixtures)

- skill-bin-drift: a redirect no longer ends a command, so `bin/x 2>/dev/null --bogus` is checked
- gstack-learnings-search: a missing value or a non-positive --limit exits 2 like an unknown flag
- gstack-skill-bill: the gstack root skill is a checkout skill; Skill-Diet trailers match the exact name; the baseline counts the committed bytes untrimmed
- skill-evals task files get a free contract test and are registered in FREE_FIXTURES, with the drift allowlist
- learn eval: the no-code-change safety task no longer asks the model to fix code (its check penalised obeying the user)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(learn,write-skill): full bin paths in the mode table; prune asks per entry unless the request names the action

Skill-Diet: learn
Skill-Size: 2707→2955 bytes (learn/SKILL.md.tmpl)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(changelog): /write-skill, gstack-skill-bill, drift ratchet, eval contract, /learn diet

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: drift ratchet in CONTRIBUTING's static tests; /learn prune example asks per entry

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(skill-bill): report a checkout skill's eval task file and check it against the contract

The skill-evals task-file test now exercises lib/skill-bill.ts's checkEvalTasks, with a negative control, instead of a test-only validator (test-of-test ratchet).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(skill-bill): EVAL TASKS path uses forward slashes on every platform; a non-object task entry is a problem, not a crash

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(learn): BASH_COMPAT guard on in-window heredocs; count skill-e2e-write-skill in the paid census

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-28 02:58:29 +00:00
Garry Tanandcapy-ai[bot] 10b77de4f2 v1.91.284.0 feat(absorb-tstack): /modernize skill, per-skill Codex default prompts, gstack-post --attribute, the unattended grant and a CONTRIBUTING porting section (#3497)
* feat(absorb-tstack): /modernize skill, per-skill Codex default prompts, gstack-post --attribute, the unattended grant and a CONTRIBUTING porting section

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(modernize): read the PR text through gstack-issue-guard; re-measure the context ceiling

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes

- gstack-post: with --attribute, report scan findings at the body file's own
  line numbers and a finding inside the callout as PART: attribute (the
  coordinates were shifted by the three callout lines)
- /modernize: route commit messages through gstack-issue-guard with the rest
  of the PR text, read isCrossRepository and maintainerCanModify, fetch a fork
  head from pull/N/head, refuse a PR whose head is the base branch, and list
  moved files NUL-safely
- docs/unattended.md: say that /ship never rebases or force-pushes and an
  unattended /modernize stops at its verdict
- CHANGELOG: seven skills carry a codex_default_prompt, not six

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: /modernize fetches a fork head with a forced refspec and stops when the PR moved

A rerun after the author force-pushed left refs/modernize/pr-N stale (the
non-forced refspec is rejected with exit 0 through the pipe), and the lease
pinned to the current remote head would then have let the rebased stale head
overwrite the author's newer commits. The fetch is forced, Steps 2 and 4 stop
when the head is not the one observed in Step 1, and the head-is-base guard
applies only to same-repository PRs (a fork's main into main is a valid head).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: /modernize stops on merge commits in the PR range and pushes a fork head to the fork

A plain git rebase drops merge commits and git range-diff compares only the
non-merge patches, so a change authored in a PR's merge commit disappeared
while the proof printed = for every patch and Step 5 force-pushed the loss.
Step 4 now counts merges in $MERGE_BASE..$OLD_HEAD and stops when there are
any, switches with -C so a rerun after a refused lease does not fail on the
existing branch, and asks for a cross-repository diff to be read as untrusted
input before the repository's test command runs it. Step 5 pushes a fork head
to the fork's URL with the same pinned lease instead of to origin, where the
lease could never match.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: cover the Codex default-prompt render, the openai.yaml ownership proof and the unattended grant

Adds the /ship coverage audit's tests: test/host-renders-openai-yaml-proof.test.ts
drives bin/gstack-host-renders.sh's weakened proof against a rendered yaml, a
hand-written one and a generic-prompt one; test/unattended-grant.test.ts pins the
docs/unattended.md grant section and the handoff pointer; test/gen-skill-docs.test.ts
adds modernize to the skills whose codex_default_prompt must reach openai.yaml.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: name the static lists a new skill still needs and the Codex metadata file

From the ship documentation audit: CONTRIBUTING's new-skill checklist said no
static list needs a row, which this PR disproved (lib/doctor-components.ts
SKILL_REQUIREMENTS and the catalog rows); the host comparison table and
docs/ADDING_A_HOST.md gain the agents/openai.yaml default_prompt source; a blank
line before the new docs/unattended.md grant heading.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes

CHANGELOG [Unreleased] gate-integrity record: the catalog and context-budget
figures re-measured on top of main (1,314 -> 1,359; modernize 9466;
alwaysOnTotal 7458 -> 7880), as the merge commit's ratchets state.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: name the optional codex_default_prompt key in the new-skill checklist

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(parity): re-pin the ship union cap at 1.7405 after the main merge

GSTA-36 grant 2026-10-27. On the tree merged with f27a7605 the ship union is 326,691 / 187,706 = 1.7404 against #3487's 1.7400; the only ship bytes this PR adds are the handoff section's one-line pointer. Cap 1.7400 -> 1.7405, measurement appended to the comment.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(changelog): the [Unreleased] gate-integrity bullet names the ship cap re-pin

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes

CHANGELOG [Unreleased]: end the Codex default-prompts sentence at
"generator input only." so bin/gstack-prose-lint --unreleased --gate
(main's new quality-gate step) passes, and restate the gate-integrity
bullet's figures for the tree merged with 1f246e35: ship cap
1.7524 -> 1.7528 (329,005 / 187,706), modernize 9466 -> 3772 after the
preamble hoist, alwaysOnTotal 7769 -> 7880, catalog 1,359 unchanged.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes

CHANGELOG [Unreleased] gate-integrity bullet names the current ship cap pin
(1.7525 -> 1.7529, measured 329,024 / 187,706 on the 392f50d4 merge) and keeps
the 1.7528 pin in the earlier-merges clause.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-27 22:23:20 +00:00
Garry Tanandcapy-ai[bot] 6bbd0a536f v1.91.266.0 feat(pr-wait): gstack-pr-wait event watcher (tstack pr-wait design port) and /land-and-deploy Step 3 wiring (#3495)
* feat(pr-wait): gstack-pr-wait event watcher (tstack pr-wait design port) and /land-and-deploy Step 3 wiring

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(pr-wait): keep the result codes in lib/result-codes-release.ts so fixtures that copy the module list keep working

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(pr-wait): function declarations for the fake-gh helpers so the pregate TSX import scanner maps the lib files

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(pr-wait): /ship review pass 1 — every check gates green, --timeout, bot identity, signal disposition

Red Team and specialist findings on #3495, all fixed in the diff:

- green gates on every check on the head, as gstack-ci-gate does, so
  land-and-deploy Step 2 and Step 3 can no longer disagree while a
  non-required check pends; required checks still gate before they appear
- --timeout MINUTES (default 60, 0 = none) ends a watch that met nothing as
  PR_WAIT_BLOCKED naming the still-pending checks; restores Step 3's cap
- a bot owns logins that start with its name (greptile-apps[bot]), not any
  login containing it; a failed gating-bot check is a failed check, never a
  completed review
- ready: green alone when no gating bot is configured (pr_wait_gating_bots
  none no longer polls forever); held while merge state is UNKNOWN
- BEHIND and DIRTY announced once per head, with the short head in the line
- the bin installs no signal handlers: the synchronous loop never ran them
  and they made the process ignore SIGTERM/SIGINT; default disposition kills
  it at once and the next watcher takes over the dead pid's lock
- a gh spawn timeout (ETIMEDOUT) is retried like any transient failure
- a local fault (unwritable state root) is PR_WAIT_REFUSED exit 2, so exit 1
  always carries a PR_WAIT_BLOCKED line
- control characters in GitHub text never start a second event line

Tests: 21 new cases including a real SIGTERM against the bin, gstack-config
set-time validation and default parity, and the 14 state-matrix cells the
state-machines specialist listed (gstack-state-matrix check: ok, 33/33).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(pr-wait): /ship review pass 2 — advisory checks gate too, BEHIND keyed on the head, verdict survives a failed save

- an advisory bot's review is never waited for, but its check gates like any
  other, so gstack-ci-gate (Step 2) and gstack-pr-wait --until green (Step 3)
  read the same check set with no exception
- BEHIND and DIRTY are keyed on the head alone; the pass-1 key also held the
  base oid, so a base that kept moving re-announced BEHIND to a restarted
  watcher on every commit
- finish() keeps the printed verdict's exit code when the state file cannot
  be written; the save error goes to stderr
- PR_WAIT_BLOCKED and PR_WAIT_REFUSED summaries name every exit-1 and exit-2
  path (--timeout, a local error); emitBots docstring matches the gating rule
- CHANGELOG says what a timeout means at land-and-deploy Step 3: a STOP with
  the pending checks named, no wait-more question

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-27 04:45:33 +00:00
Garry Tanandcapy-ai[bot] c2e01e4ba7 v1.91.264.0 feat(design-motion): new /design-motion skill for showcase loops, adapted from Elaya's design-to-motion (GSTA-92 phase 2) (#3430)
* feat(design-html): craft section and free craft check, adapted from Elaya design skills (GSTA-92)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(GSTA-92): restore the full CHANGELOG under the Unreleased entry; drop a $-digit literal from craft.md; count only craft-check executions in design-html-slop-gate

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(design-motion): new /design-motion skill: showcase MP4 and GIF loops from a design, adapted from Elaya design-to-motion (GSTA-92 phase 2)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(design-motion): run design-motion-showcase in the marathon tier; the periodic census is at its 38-slice cap (GSTA-92)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(design-motion): review and QA fixes before ship

- encode: refuse unknown options and --formats tokens, validate --pad (#RRGGBB[AA])
  and --crop (compare too) so nothing but a colour or crop reaches ffmpeg's filter graph
- encode: export refuses a capture with missing window frames; check prints
  MOTION_SETTINGS for any threshold/phase override; contact sheet drops -fps_mode
- capture: portrait-aware scale, --cycle must be whole 60 fps frames, stills with
  CSS animations disabled, pauseAt tolerates slow loads, clears stale manifest/stills
- skill: <motion-dir> placeholder instead of a cross-block shell variable,
  GSTACK_FFMPEG-aware probe, stop points before the first capture, doctrine fixes
- tests: refusal rows, portrait scale, cycle guard

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(design-motion): capture-check-export pipeline on real Chromium and ffmpeg

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(design-motion): second review cycle

- capture: refuse an --out that holds a still or frames but no capture.json
  instead of deleting them; --cycle must be a multiple of 0.05 s so the seam
  frame lands exactly one cycle later for every PRE (nearest value round-trips)
- encode: a capture without a mask ends 'MOTION_RESULT: clean (overflow not checked)'
- skill: Step 5 names window.__mark in the capture contract
- tests: foreign/previous --out handling on real Chromium, cycle guard and PRE sweep

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(design-motion): third review cycle and adversarial findings

- capture: an ownership marker (.gstack-motion-capture) replaces the
  capture.json test, so the rerun after MOTION_CONTRACT_MISSING or a crash
  works and a foreign --out is still refused; offscreen lazy images are made
  eager and font/image loading is bounded at 30 s instead of hanging
- encode: refuse a capture.json whose fields are not what capture writes
  (they reach ffmpeg args and paths); a cards overflow with no story frames
  is a planning error, and a zero-frame job never passes
- tests: retry after a refused page, no-mask clean line, lazy image still,
  manifest validator, empty story span

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(design-motion): mask coverage, manifest relations, Windows stop, Figma token off argv

- check fails overflow when the grown mask covers >90% of the frame (an app
  frame whose clip surfaces fill the stage passed vacuously); --mask-grow is
  0-32; --mask refuses braces and semicolons
- manifestError checks fps/windowFrames >= 1 and that the window fits the frames
- skill: native Windows stops before the first capture call; the Figma token
  reaches curl on stdin (-H @-) instead of argv
- tests: split the --out ownership scenario out of the contract test, full-frame
  mask, --still --scale 2 size and STILL_OK, manifest relations, coverage parse

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(design-motion): bounded frames, atomic exports, full-bleed masks

- capture: each frame is bounded at 10 s (a __tick that never returns fails
  with MOTION_CAPTURE_FAILED instead of hanging); a --mask that matches no
  element stops with MOTION_MASK_EMPTY
- check: a grown mask covering >90% of the frame (full-bleed app frame)
  reports overflow as skipped and ends 'clean (overflow not checked)'
- export: writes .partial- files and renames on success, so a failed
  re-export keeps the last good file; a GIF fps that does not divide the
  cycle is refused; a missing GSTACK_FFMPEG override is named
- fidelity: URL source is untrusted data; full-bleed note
- tests: spinning __tick, empty mask, failed re-export, drifting gif fps

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(design-motion): app-only coverage skip, gif fps cap, early empty mask

- check: the >90% coverage skip applies only to an app frame's own surfaces
  (ungrown mask); in cards, or when --mask-grow pushes coverage over 90%,
  overflow fails with what to change (a cards capture with real overflow
  passed as 'clean (overflow not checked)' after a large grow)
- export: --gif-fps/--small-fps capped at 50 (GIF delays are 1/100 s)
- compare resolves its paths (a colon in a relative path read as a protocol)
- capture: MOTION_MASK_EMPTY is checked before the first frame
- tests: cards vs app full-frame mask, fps cap, no frames on an empty mask

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(design-motion): seek story transitions from their own frame

- capture: an animation first seen in a frame where window.__mark did not
  move (a story's class toggle) is seeked from that frame instead of the
  last mark, so it eases in the export instead of popping ~100 ms ahead
- encode: an empty numeric option is refused instead of read as 0
- figma: only fill a file key and node id that look like Figma's
- tests: class-toggled transition mid-fade at its midpoint, blank --min-db

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: PROJECT_STRUCTURE names Elaya's MIT material and licenses/MIT-elaya.txt

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-27 03:04:06 +00:00
Garry Tanandcapy-ai[bot] 737ba3af40 v1.91.259.0 feat(ubicloud): /ubicloud skill with TTL-named VMs and a reaper, quota preflight, hang watchdog and one committed verify entry point (#3482)
* feat(ubicloud): /ubicloud skill with TTL-named VMs and a reaper, quota preflight, hang watchdog and one committed verify entry point

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: CLAUDE.md names the ratcheted catalog ceiling

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ubicloud: UBI_OWNER example is a literal value, not a free-text placeholder (INV-3 fence lint)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ubicloud: the UBI_OWNER example is a neutral value (no thread codes in product prose)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ubicloud: verify trusts the checkout only, watched commands keep stdin and end the remote session, quota and reap outcomes

verify: .gstack/ubicloud.json is repository content, so its setup script and
pull destinations must resolve inside --src (absolute, ~ and .. refused before
any API call) and a name it lists under pass is forwarded only when the command
line grants it (--pass NAME or --trust-config); --pass never forwards UBICLOUD_*
and refuses non-names. watched: the caller's stdin reaches the remote command,
a command that finished while the clock was read is not a timeout, a hung setup
exits 124, INT/TERM take the ssh session down, and the watchdog ends the
command's session on the VM (setsid + ~/.ubirun-<phase>.sid + pkill -s). Quota:
a cached refusal maximum is trusted for a day, the refusal names its source and
uses the 'vCPU quota exhausted' marker pregate-remote.sh retries on. reap:
returns 1 on a failed list or destroy; TTL suffixes read in base 10 and
UBI_TTL_HOURS takes 0-999. Tests: a fake API that creates plus a fake ssh run
'run' and 'verify' end to end.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ubicloud: the remote session file is per runner invocation and setsid is optional on the image

Two watched commands on one VM wrote the same ~/.ubirun-<phase>.sid, so a cancel
could end the other's session; the file now carries the runner's pid and is
removed when the command ends. An image without setsid runs the command through
plain bash instead of failing with exit 127.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ubicloud: pull destinations are rechecked at pull time, verify parses options by arity, a finished command keeps its status, the remote sid outlives the wrapper; docs audit

Pass-2 (Codex adversarial on the delta): an earlier pull could plant a symlink
where a later config pull lands, so under verify every destination is resolved
again (symlinks followed) right before its pull and a refused pull exits 1;
'--dest --trust-config' made an option value act as the trust flag, so verify
parses by arity; a command that finished in the instant before the watchdog's
kill exited 0 and was still reported as 124; the wrapper removed the sid file
while a background child kept the channel open, so the file now stays until
remote_cancel reads it. Docs audit (document-release, edit mode): TTL suffix and
owner rule in TESTING_INTERNALS, the reaper as the exception to 'no sweep', the
docs/skills.md transcript uses a neutral owner; CHANGELOG and the runner header
corrected on the two points it deferred.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ubicloud: a refused pull destination creates no directory, inside or outside the checkout

inside_dir checked containment after mkdir -p, so a destination under a planted
symlink (escape/new/sub) created the outside directories before being refused.
The nearest existing ancestor is checked first; only an accepted destination is
created, and it is checked again after.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: bin/gstack-ubi has a test lane (it execs the runner and forwards arguments)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ubicloud: pack ships a linked worktree with a self-contained .git

A worktree's .git is a gitdir pointer into the main repository; on the VM git
could not run, so the remote pregate refused with PREGATE_VM_TREE_MISMATCH
vm=unavailable. pack now replaces it with a bare --single-branch clone of the
checked-out branch (core.bare false, the worktree's index copied in), ~45 MB for
gstack; a detached worktree is refused with the fix named.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: take main's host-conformance C3 fix (#3479) so the remote gate can run on this tree

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ubicloud: pack skips a split index (its shared half stays in the original git dir)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* CHANGELOG: restore main's released history under the [Unreleased] block (the merge resolution kept only the conflicted span)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* RESTAMP: 1.91.258.0 -> 1.91.259.0 (stamped; queue_mode=stamp-at-merge base=origin/main@7dc2f23192b7 policy=base origin/main@7dc2f23192b7:.gstack/ship-policy.json)
ship, next in queue at 1.91.259.0
STAMP_VERSION: 1.91.259.0
claims: ignored (stamp-at-merge)
stage version: done (wrote 1.91.259.0)
stage release_tool: done (bun scripts/gen-agents-digest.ts)
stage changelog: done (Unreleased -> 1.91.259.0)
stage stamp_paths: skipped (no stamp_paths in policy)
stage mirror: skipped (no mirror in policy)
stage apply: done (4 file(s))
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3c91fdaf8..e4f4d63ec 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,6 @@
 # Changelog

-## [Unreleased]
+## [1.91.259.0] - 2026-10-26

 **New `/ubicloud` skill: the Ubicloud runner moves into gstack with the discipline a shared vCPU quota needs.** Crews ran heavy suites on Ubicloud through a user-scope skill and per-lane wrappers, and paid for it in stray VMs after a machine slept, a project quota at 528 of 512 vCPUs when lanes verified in parallel, verifies that hung for an hour before anyone noticed, and a stale wrapper that skipped a service start. `scripts/ubicloud/ubi-runner.sh` now carries the fixes and `ubicloud/SKILL.md` (entry point `bin/gstack-ubi`) teaches the watched launch; `bun run test:ubicloud` and the remote pregate are unchanged.

diff --git a/VERSION b/VERSION
index c67e9e442..08528ff87 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.91.258.0
+1.91.259.0
diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md
index 4bd43c922..203312cf5 100644
--- a/agents-digest/gstack-AGENTS.md
+++ b/agents-digest/gstack-AGENTS.md
@@ -1,4 +1,4 @@
-# gstack digest v1.91.258.0 — regenerate/re-copy after upgrading gstack
+# gstack digest v1.91.259.0 — regenerate/re-copy after upgrading gstack

 Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed
 for agent hosts without a full skill install. The full skills add workflows,
diff --git a/node_modules b/node_modules
new file mode 120000
index 000000000..c7990ad12
--- /dev/null
+++ b/node_modules
@@ -0,0 +1 @@
+/workspace/gstack-ubi/node_modules
\ No newline at end of file
diff --git a/package.json b/package.json
index 5e6d56827..0eca459a0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "gstack",
-  "version": "1.91.258",
+  "version": "1.91.259",
   "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
   "license": "MIT",
   "type": "module",
FILES: CHANGELOG.md VERSION agents-digest/gstack-AGENTS.md package.json
MERGE_CONDITION: sha=76e86e08a851fb1a19bef9a947bdf0abeb87956d
next: NEW_VERSION=1.91.259.0; commit and push only the stamp diff above (FILES), with no edit of your own: the train's verify accepts the CHANGELOG only as the base's plus this PR's entry re-headed; merge with the expected head 76e86e08a8 and recompute on a stale queue.
GSTACK_RESULT: skill=restamp status=complete run=/workspace/gstack-ubi/.gstack/tmp/restamp-journal.json

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-26 23:45:45 +00:00
Garry Tanandcapy-ai[bot] 4d48d39e51 v1.91.253.0 feat(captain): /captain skill (also /crew), crew templates and docs/captain.md for running a crew from one controller (#3473)
* feat(captain): /captain skill (also /crew), crew templates and docs/captain.md for running a crew from one controller

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore(captain): regenerate SKILL.md at main's version

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(captain): AGENTS.md and NOTES.md templates, roles, ship lane sequence, owner device and lessons loop in docs/captain.md; owner-brief closes with the do-it reply

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(captain): review pass 1: 'do it' parses as rec, READY template matches the tool's lines, slot-2 and freeze wording, mergeChangelogs named, parameter lists and doc numbers aligned

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(captain): align the slot-call BASE merge, freeze repair and severe-race wording across skill, CREW.md and docs; router routes on phrases; receipt head width as printed

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(captain): CREW.md template becomes pointers plus parameters; every crew rule gets a canonical home in gstack (lane discipline, outside contributions, stale READYs, standing spend, the lane side of flaky reruns in docs/captain.md; the rule-to-home map)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-26 18:04:22 +00:00
Garry Tanandcapy-ai[bot] 8663c3d25f v1.91.252.0 feat(retro): factory scorecard (lead time, gate cost, red-train and flaky rates) and the recommended-harness adoption doc (#3471)
* feat(retro): factory scorecard bin (lead time, gate cost, red-train and flaky rates) and the recommended-harness adoption doc

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(scorecard): register the recorded gh fixture with its free consumer

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(scorecard): cover --gate-event any, an empty window, a failed PR read and a bad --now

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes

Review pass 1 (core checklist, testing, maintainability, security, performance, simplification, state-machines, red team) over the whole diff:
- the flaky line and cost-per-merge depend on every input they read, so a failed free-run or PR read prints unavailable (...) on its half instead of 0
- a PR verdict-comment cost backs one gate run per PR; a READY comment after the merge is ignored
- one unreadable artifact leaves its run without cost or flake data instead of dropping the gate section
- --repo defaults to the origin owner/name and is required without one (no built-in repository)
- --max-artifact-runs, --pass-cap and --pass-marker are validated; --csv creates its directory
- one artifact listing per gate run; PR comments are paged; issues use the exact window instant
- every run appends its rows to trend.csv in --out-dir
- new row: review passes per PR (median, p90, PRs at the cap) from the ship receipt or a comment marker
- /retro: report format gains the Factory scorecard slot; the Step 7.5 fence quotes its placeholder
- tests for each; <window-days> joins the placeholder allowlist

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: review pass 2 (delta): usage text, pass-marker capture group, GitHub-host origin parsing, idempotent trend.csv

- usage() strips the header comment decoration again; --help text asserted
- passCount takes the first all-digit capture group; --pass-marker without a capture group exits 2
- --repo falls back to the origin remote only on github.com or GH_HOST; a local-path origin exits 2
- trend.csv keeps one row set per repo and window (a rerun replaces its rows) and always carries its header
- /retro Step 7.5 points at SCORECARD_TREND for the weeks; the doc cites the per-PR baseline CSV

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(host-conformance): delete a named section in the C3 broken-link case

The case removed the first .md readdirSync returned under gstack-qa/sections.
qa-patterns.md and system-functional.md are referenced only in table form, which
setup --status does not count as section links, so on an ext4 directory order
that listed one of them first the deleted file broke nothing and --status exited
0: deterministic red on remote-gate VMs, green on Actions. Delete scope.md, which
every host's rendered SKILL.md reads.

Closes #3479

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* v1.91.252.0 stamp

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-26 17:50:02 +00:00
Garry Tanandcapy-ai[bot] ab9a854be2 v1.91.246.0 docs(readme): cut the README to the first ten minutes; move everything else to docs/ pages verbatim (GSTA-99) (#3428)
* docs(readme): cut the README to the first ten minutes; move everything else to docs/ pages verbatim (GSTA-99)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes

Restore the /setup-gbrain code block the README split left open: README.md kept
the ```bash opener and docs/gbrain-quick-tour.md got only the closing fence, so
both pages rendered their body as one code block. Drop the self-referential
'Full reference' link in docs/domain-skills.md and make the troubleshooting
quick-answer link an in-page anchor.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: describe the proactive core-set default in why-gstack

The moved 'Proactive skill suggestions' paragraph now documents the four-skill
core set, gstack-config set proactive core|all|off and the docs/proactive.md
page that #3432 (gsta-99-proactive-core) adds; the CHANGELOG names it as the one
non-verbatim paragraph of the move.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes (round 2)

Promote docs/uninstall.md's moved sections from ### to ## so the page outline
matches the other pages the README split created; no inbound link targets
those anchors.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: documentation audit fixes

The README Docs table no longer claims docs/standalone-binaries.md covers
every gstack-* CLI (it documents the standalone ones), and
docs/ADDING_A_HOST.md step 6 points at the README host-matrix row and
docs/hosts.md, where the per-host notes now live.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: disclose the update check fully and list every setup hook

The README privacy summary now names both GitHub requests the update check
makes and the version/OS report it adds with telemetry on, as the full text
it replaced did. docs/install.md's hooks section lists the team-mode
SessionStart hook and the AskUserQuestion PreToolUse/PostToolUse hooks beside
the timeline Stop hook.

* docs: name the update check's conditional compare-API request

bin/gstack-update-check also GETs api.github.com's compare endpoint when the
remote VERSION equals the local one but main has moved; the README privacy
summary and docs/privacy.md now list it beside the ls-remote and VERSION
fetch (documentation audit).

* fix: pre-landing review fixes (merge resolution)

docs/install.md:21 carried main's stable-channel sentence twice after the
merge, the second copy with its backticked spans stripped; the paragraph
again matches README.md:41 verbatim with the relative link.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: say outside reviews are on by default in the README privacy summary

The one-paragraph summary dropped the default-on disclosure the full text
kept; mirror docs/privacy.md's parenthetical so the top-level page says which
path sends code off-machine without an opt-in.

* v1.91.246.0 stamp

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-26 13:22:30 +00:00
Garry Tanandcapy-ai[bot] 6c3cf1baac v1.91.242.0 fix(context-bill): bill installed skills whose SKILL.md is a symlink (GSTA-97) (#3423)
* fix(context-bill): follow symlinked SKILL.md files and skill dirs so a real install is billed (GSTA-97)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(context-bill): walk both links to one directory so the walk never depends on readdir order

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(changelog): describe the loop guard as shipped

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(context-bill): a root named '..x' no longer lets a parent link climb out

encloses() tested rel.startsWith('..'), which also matches a first
segment such as '..skills', so a link to the parent of a root under such
a name was followed out of the root. Compare the '..' segment exactly.
Adds that case plus a loop to a directory below the walk root, which only
the ancestor guard stops (found by /ship pre-landing review).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(context-bill): a .md-named link counts only when its target is markdown

walkMd checked only the link's own name, so a link such as notes.md ->
a non-markdown file was walked and, under --exact, its content was sent
to count_tokens. Require the resolved target to end in .md as well.
Adds tests for --exact on the installed layout (every billed file is
measured; nothing behind gstack/ or a .md-named link to another file is
sent) and for a symlinked foreign-host file (found by /ship review).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(context-bill): walk each real directory once so link fan-out cannot explode

walkMd guarded only the current path, and walked every link to the same
directory, so links that fan out into each other grew the walk
exponentially; under --exact every duplicate path was another
count_tokens POST on the user's key. Expand each real directory and
return each real file once per walk, under the first lexical path
reached with entries sorted. Refuse a walk past 200,000 entries with an
error instead of truncating. --exact sends each real file once and
prices every lexical key that resolves to it from that measurement.
Found by the /ship Step 11 adversarial reviews (Claude and GPT).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(context-bill): fence walks at the billed root, measure every walked frontmatter, count outside files in the --exact notice

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(context-bill): a container skill keeps files a nested skill links to

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(context-bill): --exact names a missing tree before any request

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(context-bill): --exact sends only files the bill prices

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(context-bill): --exact refuses every missing tree before sending, and the notice counts what is sent

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(context-bill): exactSendFiles does not swallow a realpath error

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(context-bill): --exact walks only what the bill walks, sends nothing for a skill-less tree, and measures only priced frontmatter

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(context-bill): --exact says when a tree with skills had nothing to send; doc and alias-test fixes

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(context-bill): a skill-less tree prints no estimate line under --exact

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(context-bill): a container skips only nested dirs whose SKILL.md is a file, as discovery does

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(context-bill): skill discovery refuses a tree past the entry cap too

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(context-bill): walk directories through their realpaths so a deep link chain cannot drop files

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(context-bill): --exact reads only the file each walk approved

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(context-bill): --exact sends only the skills root and the linked gstack checkout; anything else is named and estimated

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes (context-bill run-4 cycle 1)

Checkout detection needs a gstack checkout outside the tree; identity taken during the walk and re-checked by realpath; discovery and calibration read through realpaths; refused-dir collapse is linear; all-refused wording; stale comments; tests for each.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes (context-bill run-4 cycle 2)

The gstack checkout is recognised by .git, VERSION, setup and a SKILL.md named gstack, so the bin/dev-setup layout counts and other repos do not; walk contract documented on walkMdFiles; tests for the same-file parent move, a gstack-shaped folder without .git, another project repo, and dev-setup.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(context-bill): a root container skips a nested skill reached through a directory link at any depth

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(context-bill): --exact refuses a walked file swapped for a FIFO instead of blocking on open

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(context-bill): --exact resolves a billed path one link at a time, so a shared deep chain keeps its measurements

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(context-bill): --exact reuses a measurement only for the same realpath, size and mtime it measured

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(context-bill): a bill with any estimated item under --exact is labelled mixed and keeps its error band, in --json and text

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(context-bill): --exact also sends files inside gstack's render folder ($GSTACK_STATE_ROOT/render), judged by realpath

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(changelog): the repo bill keeps its skills, bytes and rounded tokens; raw token floats differ in the last digit

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(context-bill): CHANGELOG and README for the render folder, mixed labels, measurement identity, FIFO refusal and container fixes

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes (context-bill run-5 cycle 1)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes (context-bill run-5 cycle 2)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes (context-bill run-5 cycle 3)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* v1.91.242.0 stamp

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-26 10:57:52 +00:00
Garry Tanandcapy-ai[bot] 75badf306c v1.91.234.0 feat(releases): weekly tagged releases and a stable update channel for team mode (GSTA-98) (#3429)
* feat(releases): weekly tagged releases and a stable update channel for team mode (GSTA-98)

Every Monday morning .github/workflows/weekly-release.yml tags the newest green
commit on main as v<VERSION>, publishes a GitHub Release from the CHANGELOG
headlines and fast-forwards refs/heads/stable to it (scripts/release-cut.ts;
workflow_dispatch with dry_run, and ref for a hotfix). Team-mode installs
follow stable through the new update_channel key (auto: stable when
team_mode, else main) in bin/gstack-session-update, bin/gstack-update-check,
bin/gstack-upgrade and /gstack-upgrade. Per-merge stamping and the merge
train are unchanged. docs/releases.md explains it.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ci(weekly-release): check out the dispatched ref so a dry run exercises that branch's script

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ci(weekly-release): a PR touching the release tooling runs the dry run against main (read-only); the cut job checks out the dispatched ref

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(gstack-upgrade): no $1 in the rendered skill (Claude Code rewrites $N), trim Step 4 under the size budget, pin the new floor-check literal

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: leave setup untouched (its channel note moves to README and docs/releases.md); the release workflow and script are free-only PR files

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: record the pr-profile free-only classification as non-behavioral (HARNESS_VERSION 6)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(releases): gate the cut on a main-backstop check run, finish half-done cuts, keep team installs on their channel

/ship review fixes (GSTA-98, PR #3429):

- The weekly release read the Main backstop gate run's conclusion, but
  scripts/train-backstop.ts exits 0 when main moved past the push before it
  was gated (superseded), so a never-gated tree read as green. The backstop
  workflow now publishes its verdict on the commit as the `main-backstop`
  check run from a code-free checks:write job (success for covered|gated,
  neutral for inert|superseded|deferred, failure for frozen|failed), fed by
  the new `--outputs` flag; release-cut.ts requires that check run and
  free-tests to succeed and holds a commit whose check runs are still going.
- A cut that pushed the tag and stable but failed to create the release can
  be finished: stable itself stays a candidate until its release exists, the
  notes compare against the release tag behind it, and a dry run with nothing
  green exits 0 with the skips named (the real cut still fails).
- weekly-release.yml: the cut job runs only from refs/heads/main, `ref` must
  be a full 40-hex SHA, and actions:read is dropped (no workflow-runs call).
- /gstack-upgrade: the destructive fallback requires the carried UPSTREAM_REF
  instead of defaulting to origin/main in a fresh shell; a team install past
  the release stops with AHEAD_OF_STABLE instead of FF_REFUSED and a reset
  backwards. gstack-update-channel.sh --fetch-ref keeps origin/stable on any
  fetch failure other than a missing release; dead gstack_update_channel_ref
  removed. gstack-update-check follows main before the first release instead
  of caching CHECK_FAILED; gstack-upgrade treats a failed ls-remote as a
  fetch failure, not an empty answer.
- Tests: release-cut (check-run fixture, partial cut, neutral/pending),
  weekly-release-workflow (new), evals-train-workflow and train-backstop
  (backstop-check job, --outputs), update-channel (no-release fallback,
  offline --fetch-ref), template pins.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(releases): the upgrade skill's ahead-of-stable exit works in depth-1 clones; update-check keeps its cache identity on the pre-release fallback

/ship second-pass review of 73e278d4 (GSTA-98, PR #3429):

- gstack-upgrade/SKILL.md.tmpl: README installs are depth-1 clones, where
  origin/stable never connects to HEAD, so the AHEAD_OF_STABLE shortcut now
  also fires when HEAD is not behind stable and stable's VERSION is not
  higher than the installed one (the order bin/gstack-upgrade uses). The
  fence prints the channel helper's warnings instead of discarding them, and
  a 'fetching origin/stable failed' line is a STOP. Prose trimmed to stay
  under the skill size budget. New behavioral test runs the Step 4 fence on
  a depth-1 install past the release (AHEAD_OF_STABLE, nothing moved) and on
  one behind it (goes on to the fast-forward).
- bin/gstack-update-check: the no-stable fallback keeps #stable in the
  cache identity (so the outcome replays within its TTL instead of two
  ls-remote calls per skill start), triggers only when ls-remote answered
  with no such branch (a failed ls-remote stays CHECK_FAILED on stable), and
  the branch-pinned URL derives from REMOTE_RAW_BASE so tests stay offline.
- scripts/release-cut.ts: a cancelled check run is red; the previous-release
  lookups check .ok before using git output. docs/releases.md says which
  pushes get no main-backstop check run at all.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-26 07:22:44 +00:00
Garry Tanandcapy-ai[bot] be402d695c v1.91.231.0 fix(setup): install production dependencies only (~520 MB smaller install) (GSTA-97) (#3426)
* fix(setup): install production dependencies only; contributors and CI keep the full install (GSTA-97)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(changelog): production-only install (GSTA-97)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes (GSTA-97)

- setup/gstack-browser-ensure: Bun treats --production as frozen, so the
  old fallback could never recover a stale lockfile; refresh it with
  --lockfile-only, then install production-only again.
- gstack-browser-ensure failure line and the README tip no longer suggest a
  full bun install to users.
- Tests: real-Bun stale-lockfile and keep-devDeps contracts, behavioral
  browser-ensure install tests, relative-import closure scan, multi-line and
  re-export negative controls; CHANGELOG matches.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes, pass 2 (GSTA-97)

- gstack-browser-ensure's failure line and the README tip suggest the
  frozen production install (./setup skips the install when the build is
  current, and a bare --production install is frozen anyway).
- setup install guard catches quoted and unquoted $BUN_CMD spellings.
- Replace the exact-text gstack-browser-ensure grep with an invariant: no
  full bun install; behavior is covered by gstack-browser-ensure.test.ts.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(setup): keep a contributor checkout's dev packages on bun.lock's versions (GSTA-97)

/ship Step 11: a production install over a full install reports "no
changes" and leaves devDependencies at old versions after bun.lock moves,
so the session updater's ./setup left contributors on stale TypeScript,
Prettier and Agent SDK. A checkout with node_modules/typescript gets the
full frozen install. The import-closure scan now follows shipped code into
test/helpers (gstack-model-benchmark loads the providers).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes, ship invocation 2 (GSTA-97)

- Tests: contributor-branch failure row; bun.lock production closure never
  reaches the dev-toolchain marker; real-Bun rows isolate HOME,
  XDG_CONFIG_HOME and BUN_CONFIG_*; the import walker follows side-effect
  imports; file header matches the two install branches.
- BROWSER.md quick start uses the frozen production install.
- CHANGELOG: the reclaim recipe also drops the Agent SDK's cache entries
  (Bun hardlinks node_modules into its cache); Tests list is current.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(changelog): stub-bun wording matches what the fixtures check (GSTA-97)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* v1.91.231.0 stamp

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-26 06:41:22 +00:00
Garry Tanandcapy-ai[bot] d73b547066 fix(team-init): register the required Skill hook with onFailure: block (#3319) (#3393)
Without node on PATH the node -e launcher exits 127, and with check-gstack.cjs
missing it exits 1; Claude Code treats both as non-blocking and lets the Skill
call through. Register the handler with onFailure: block (Claude Code 2.1.295+),
upgrade existing registrations in place, and tell the agent and the developer
what the two block reasons mean.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-25 21:21:10 +00:00
Garry Tanandcapy-ai[bot] 632bde77de v1.91.187.0 docs: unattended contract, gate reply grammar, session-kind override and voice triggers match the code (GSTA-69) (#3367)
* docs: unattended contract, gate reply grammar, session-kind override and voice triggers match the code (GSTA-69)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* v1.91.187.0 stamp

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-24 23:55:21 +00:00
Garry Tan 2350c2a5fc v1.91.180.0 docs(cso): name the qualified runtime and scanner catalogs instead of 'static assessment only' (#3337)
* docs(cso): name the qualified runtime and scanner catalogs instead of 'static assessment only'

Ships with the runtime catalog promotion PR: AGENTS.md, README.md and
docs/cso-release-qualification.md said no qualified runtime or scanner
image existed. The scanner catalog is qualified since #3042; the runtime
catalog is qualified by the promotion PR this trains with.

* v1.91.180.0 docs(cso): say what the qualified runtime and scanner catalogs allow
2026-10-23 00:04:25 +00:00
cb9ccf2e80 v1.91.158.0 feat(config): plan_mode_preamble=off drops the plan-mode preamble from installed skills (port of #3213) (#3308)
* feat(gen-skill-docs): add --plan-mode=off to omit the plan-mode preamble (#3168)

Generated skills ship a ~1.8 KB "## Plan Mode Safe Operations" +
"## Skill Invocation During Plan Mode" block on every skill, for hosts
and users who never enter plan mode. This adds a --plan-mode=off flag to
gen-skill-docs that drops that block at gen time. Default builds
(planMode undefined or true) keep the block byte-identically.

The "PLAN MODE EXCEPTION — ALWAYS RUN" markers inside skill bodies
still apply when the flag is off.

(cherry picked from commit 51edee9915)

* style(types): fix stray indentation on TemplateContext.installRoot

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(config): plan_mode_preamble key wires --plan-mode=off into setup and every installed render

gstack-config plan_mode_preamble (on|off, default on) makes ./setup pass
--plan-mode=off to every gen:skill-docs render, gbrain-refresh and the
gstack-relink router render. The default Claude install, which otherwise
serves the committed SKILL.md files, gets a plain render with the key off;
the activation record notes plan_mode=off so switching back on removes the
render, including on the gbrain=unknown keep branch. With the key on or
unset every path and record is byte-for-byte unchanged.

Fixes #3168. Finishes #3213 by @xingzihai.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(setup,relink): plan_mode_preamble=off survives builds and failed renders; alias follows the router

Review findings on the plan_mode_preamble wiring:
- a build renders env hosts without --plan-mode=off, so with the key off
  setup no longer skips their re-render after a build;
- with the key off the gbrain-absent branch leaves the render to the plain
  re-render, so a failed render keeps the previous one;
- gstack-relink refreshes the _gstack-command alias after re-rendering the
  router (also fixes the same staleness for disabled_skills).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: xingzihai <xingzihai@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-19 23:31:59 +00:00
afac8dfd87 v1.91.151.0 fix: contributor ports — plan-eng-review structure id, context checkpoint gaps, first-deploy how-to (#3299)
* fix(plan-eng-review): register the structure question id so never-ask can fire

The complexity gate's Original/Smaller arrangement question had no
registered question_id, so each run invented a per-plan slug (one install
logged ten different ids for it). --check matches ids exactly, so a stored
never-ask could never fire and the question was asked every time.

- Register plan-eng-review-structure as a two-way routing question.
- The template names that exact id and says AUTO_DECIDE picks the
  recommended arrangement; the preamble's check-first rule does the rest.
  Wording is byte-neutral against the parity budget and the per-host pins.
- --check falls back to $GSTACK_HOME/global-question-preferences.json, the
  file the question-preference hook already reads, when the project file
  has no entry for the id, so one setting covers every repo and both
  layers agree. A project entry still wins, the one-way and split-chain
  guards run first, a malformed or non-object global file is ignored with
  a NOTE, and --write stays per-project.
- Tests for the fallback and the template contract; two wording pins
  updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit fb0334eb5c)

* fix(context): pin English provenance markers and ask when branch cannot discriminate

Addresses three gaps from #3217:

1. context-save: markers must stay the five English literals; translated
   tokens break restore sorting. Decisions Made claims about file contents
   need evidence or an assumed Remaining Work item.
2. context-restore: unknown/translated parenthesised markers sort under
   Verify first as (path assumed).
3. When CURRENT_BRANCH cannot discriminate and recent candidates have
   different titles, ask instead of silently loading newest.

Pins the new contract in the provenance prompt-byte test.

Fixes #3217

(cherry picked from commit 2a02647fd4)

* docs: add a neutral how-to for bringing up a first deploy (#3150)

/setup-deploy stops at "Nothing is deployed yet" and writes nothing,
because it has no production URL or deploy trigger to record. The docs
said nothing about the step in between. The maintainers chose to keep
that stop vendor-neutral and asked for a neutral docs page instead
(#3150, option 2).

docs/howto-bring-up-a-first-deploy.md walks through the first deploy:
where the app runs and how deploys start, the services it needs, env
and secrets, DNS and TLS, checking that it works, and running
/setup-deploy again. It names no hosting provider or deploy tool. The
README Docs table and the /setup-deploy section of docs/skills.md link
to it. The stop's own text is unchanged.

Fixes #3150

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 5c5d8ec96f)

* docs(changelog): contributor fixes from #3249, #3244, #3264 under [Unreleased]

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: Trevor Bezdek <bezdekt@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Chao Chen <209239467+surlymochan@users.noreply.github.com>
Co-authored-by: y$un_ <forrest.sun527@gmail.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-19 19:44:10 +00:00
Garry Tanandcapy-ai[bot] bc2ed0cee7 v1.91.114.0 feat(dx): score every error for an AI agent reader (agent-ready error contract + gstack-error-capture) (#3226)
* feat(dx): agent-ready error contract for the DevEx review skills

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(dx): gstack-error-capture runs a CLI with no terminal and prints a redacted record

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(dx): CI-clean fences, runtime-root-safe contract location, path-agnostic launch-check test

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(dx): preregistered behavior case for the agent-ready error contract; README skill rows

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(dx): grader items come from the contract; paid-file count includes the new case

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(dx): outside DX voice uses the agent-ready contract; DX_CAPTURE_* codes join RESULT_CODES with troubleshooting anchors

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(dx): review fixes for gstack-error-capture and the paid grader

- every helper diagnostic passes the record's redaction, fail-closed
- forwarded values are masked before and after sanitizing, plus JSON-escaped forms
- the final secret check reads the record's string fields with byte-based scan limits
- mise/asdf config, trust and env-loading variables need --pass-env; only data locations pass by default
- launch reports whether the target started; --launch-check's probe result is exit_code
- Step 3 captures --help first; help states the containment lifetime limit
- the paid grader validates the bound result with parseReviewResult

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(dx): mask forwarded values before argument validation; protocol strings never match literals; Step 3 captures stay read-only

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* v1.91.114.0 feat(dx): score every error for an AI agent reader (agent-ready error contract + gstack-error-capture)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-17 02:10:12 +00:00
+7 3e8b34d892 v1.91.106.0 fix: the 2026-10-15 issue wave (#3209)
* docs(designs): add fix wave 2026-10-15 plan (revision 3, approved)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(cso): invoke docker as argv0 docker so OrbStack's multicall binary dispatches

executable() returns the canonical path, and OrbStack installs docker as a
symlink to docker-tools, which dispatches on argv[0] and exits 127 with
'unsupported argv0 "docker-tools"'. Every Docker call failed, so doctor
reported local-docker-isolation and every scanner missing and setup could
not preload the qualified images.

runProcess takes an argv0 option; docker.ts routes every Docker call
through runDocker, which passes 'docker'. The native watchdog's execve
calls pass "docker" as argv[0] too, so its container cleanup works on the
same installs. The executed file is still the verified canonical path.

Fixes #3187

* test(cso): cover Docker and watchdog invocation through a multicall binary

A compiled fake that refuses any argv[0] but docker stands in for
OrbStack's docker-tools. Both behavior tests fail on the previous code.

Refs #3187

* fix(cso): bound the argv0 test's socket helper and probe images through OrbStack's multicall docker (#3187)

Follow-up to #3188 by Bryan Stone (@aegixx):
- the python3 spawnSync that binds the fake daemon socket gets a 30 s
  timeout; without it test/spawnsync-timeout-tripwire.test.ts fails.
- reviewer test: dockerExactImagePresent, a production probe, admits an
  image when the endpoint's canonical executable is a multicall binary
  that refuses any argv0 other than "docker". It fails on 47d639a
  (returns false) and passes with #3188.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(autoplan): own the conversation after a Claude Code 2.1.300 compaction

Claude Code 2.1.300 keeps a compaction's last records in place and lists
them in compactMetadata.preservedMessages. The segment's tail, which is
also the boundary's logicalParentUuid, is written after the boundary
under the compact summary, so the parent links form a cycle that the
owned causal order never reaches. Every record after the compaction was
dropped, and the publication guard denied each later phase entry with
close_incomplete.

Such a boundary now continues from the last preserved record that is a
node of the file and not its own descendant. Boundaries without that
metadata, with a foreign preserved uuid, or with nothing kept in place
still supply no ownership.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(autoplan): report a compaction-explained unreachable component as unrecognized_shape (#3194)

Follow-up to #3196 by vilano93. After the ownership walk, an owned node
the walk never reached was silently dropped, and the phase guard answered
the next phase entry with close_incomplete, which the user cannot satisfy.
When the unreachable node's ancestry ends in a cycle through a compact
boundary's logical parent, ownedCausalOrder now returns
unrecognized_shape:compaction_unreachable, the guard's existing
unverified disposition (allow with a visible warning and a guard-log
row). Orphans whose ancestor is missing (not flushed yet) and other roots
keep today's behaviour, so the fail-open surface does not widen beyond
compaction. Each node is walked once.

Gate-integrity edit: a close_incomplete denial becomes an unverified
allow for that shape only. Negative controls kept: the existing guard,
hook, guard-reasons and captured-fixture tests still deny (356 pass), and
a new case with the boundary not yet flushed still denies close_incomplete.

Intentional pin changes in #3196's own test file: the three "fails
closed" cases (tail-only segment, missing preserved metadata, foreign
preserved uuid) and the "without compactMetadata" case now expect
unrecognized_shape:compaction_unreachable instead of close_incomplete or
a silently cut transcript.

Reviewer tests: three compactions in one session stay owned and pass the
next phase entry; a 50k-record history with three compactions is decided
in ~300 ms (budget 1000 ms), so descends needs no memo.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(lib): one echo sanitizer for external strings, with a Bash twin

lib/sanitize-echo.ts and bin/gstack-sanitize-echo.sh flatten CR/LF/TAB to
spaces, remove every other ASCII control byte (ESC included) and DEL,
collapse runs of spaces, and cap the result at 200 bytes of valid UTF-8
with no leading or trailing space. Every external string gstack echoes
into a message, a log line or a key=value state file goes through it
(fix wave 2026-10-15, plan rev 3 DX F8). test/sanitize-echo.test.ts runs
both twins on one table under bash, bash --posix and zsh, including a
"\nstages=" injection, ESC sequences and UTF-8 characters cut by the cap.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(bun-version): judge the Bun floor under zsh and hold on an unreadable version (#3195)

Reported by mochafreddo. _gstack_bun_below word-split $_gb_core, which
zsh does not do: `[ -ne ]` errored and every error path read as "not
below", so /gstack-upgrade Step 4 accepted Bun 1.3.2 in a zsh tool shell.

- _gstack_bun_below copies the release's parsed fields into quoted
  positional parameters and compares with $((10#...)); no new _gb_*
  globals. It returns 2 when either side cannot be parsed.
- gstack_bun_status prints malformed for an unparseable version or floor
  (never ok); gstack_bun_incoming_hold holds on it with
  bun-version-unreadable: ... (BUN_VERSION_UNREADABLE), the bun path and
  its output passed through gstack_sanitize_echo. setup's own malformed
  warning is unchanged.
- /gstack-upgrade Step 4 runs the check through `bash -c` and stops on
  BUN_VERSION_UNREADABLE with a reinstall fix (never "bun upgrade").
- New result code BUN_VERSION_UNREADABLE with its troubleshooting entry;
  bin/gstack-doctor-components.sh regenerated from the table.

test/bun-version-shells.test.ts: one version table (1.3.2,
1.3.3-canary.1, 1.3.3, 1.3.14, 1.4.2, 1.10.0, 01.04.02, 2.0.0, garbage)
drives bash, bash --posix, zsh and the TypeScript twin's below();
`zsh -c '. bin/gstack-bun-version.sh; gstack_bun_status 1.3.2'` printed
ok on 47d639a and prints too-old now.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* style(cso): format #3188's runDocker call so format:cso:check passes (#3187)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(cso): keep scanner JSON reports parseable after redaction (#3185)

Reported in #3185. The scanner path splices redactions into raw JSON, and
redactFindingSpans replaced a bare 10+ digit number
("max_memory_bytes":1145427776, an epoch) with an unquoted marker, so
JSON.parse failed and every non-trivial Semgrep run was INVALID_OUTPUT.

- lib/redact-engine.ts: redactJsonFindingSpans, a string- and
  escape-aware lexical tokenizer over one or more top-level values
  (NDJSON included). A span inside a number replaces that whole token
  (-, fraction, exponent) with the quoted marker, number by number; a
  span inside a string keeps the in-string marker, widened to whole
  escapes; a span touching several tokens, whitespace, a literal or a
  structural character returns null (withhold). Non-JSON text gets the
  plain splice. redactFindingSpans is unchanged for every other caller.
- The scan operation's declared format rides the real capture path:
  scanner-executor (scan stdout, or the report file read for outputPath
  plans) -> DockerGroup.execCapture -> runProcess -> spliceRedaction,
  which redacts the concatenated stdout once and maps each interleaved
  run onto it. Version probes and stderr stay plain; marker-only
  withholding and the cross-stream views check are kept, and the output
  is a fixpoint of that check.

Behavior change: a redacted number in a scanner report is now the string
"<REDACTED-...>" instead of an unquoted marker; non-JSON streams keep the
unquoted form (test/cso-eval.test.ts unchanged).

Intentional pin change: test/cso-scanner-release.test.ts pinned the
report read as capture(['/bin/cat', plan.outputPath]); it now pins
capture(['/bin/cat', plan.outputPath], plan.format), so the read also
carries the declared JSON format (stricter, not weaker).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(browse): recover when the last tab is closed outside the CLI

Closing the last tab outside browse (for example, closing the headed
Chromium window) left the daemon wedged: the page 'close' handler removed
the tab and set activeTabId to 0, and executeCommand called
getActiveSession() first, so every later command (goto, status, even stop)
failed with "No active page" until the daemon PID was killed by hand.

closeTab() already opens a blank tab when the last tab closes; the external
close path never did. Add BrowserManager.ensureActiveTab(), which reselects
a remaining tab or opens one blank tab (concurrent callers share one
recovery), and call it before resolving the active session in
executeCommand.

Fixes #3167

* fix(browse): status and stop answer after an external close; recovery says what it did (#3167)

Follow-up to #3179 by aftabkh4n, which reopens a tab on demand.
- BrowserManager.ensureCommandTab runs before every command: status,
  stop and restart never open a tab, so a daemon whose browser or context
  is gone can still report and be stopped; any other command gets an
  active tab. A failed recovery is BROWSE_TAB_UNRECOVERABLE carrying the
  restart command (cause passed through sanitizeEcho), and a command that
  ran on a newly opened tab prints a one-line warning first.
- server.ts reads the session inside the read and write branches instead
  of before dispatch (2223 lines, cap 2224) and exposes browserManager
  and handleCommandInternal on __testInternals__ for the dispatch test.
- meta-commands.ts: handleMetaCommand no longer reads the active session
  before its switch (only snapshot and resume used it), and status prints
  getCurrentUrl() so it works with zero tabs. This file is outside the
  lane's listed ownership; no other lane or #3190 touches browse/.
- New result code BROWSE_TAB_UNRECOVERABLE and troubleshooting entries
  for it and for the old "No active page" wedge.

Reviewer test browse/test/tab-external-close-dispatch.test.ts drives the
real dispatch in a child process: status, text, goto and stop after an
external close, and the closed-context case. On 47d639a (with only the
test seam added) all five fail with "No active page".

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(troubleshooting): /cso INVALID_OUTPUT on Semgrep reports and OrbStack's argv0 refusal (#3185, #3187)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(session-update): name the reinstall fix when an unreadable Bun holds the auto-update (#3195)

Item 11 makes every caller of gstack_bun_incoming_hold treat a Bun
version it cannot read as a hold (bun-version-unreadable,
BUN_VERSION_UNREADABLE). The session-start banner only knew
bun-too-old, so the new reason fell through to "git pull did not
finish ... gstack is not updating" with a pull-and-setup fix that does
not help.

- bin/gstack-session-update: a bun-version-unreadable banner case with
  the reinstall fix and the troubleshooting anchor; the comment that
  said a malformed version passes is corrected.

Intentional pin change: test/session-update-stages.test.ts "supported
or unparseable Bun (bun-dev (local build)) proceeds: setup decides" is
split. 1.3.14 still proceeds; bun-dev (local build) now holds with
BUN_VERSION_UNREADABLE, leaves the checkout and setup untouched, and
the banner names the reinstall fix. The fixture installs
bin/gstack-sanitize-echo.sh beside the floor helper, as a real install
of this release does. The new case fails on 47d639a.

Ownership exception granted by the coordinator for Lane A
(bin/gstack-session-update is Lane B's file; PR 1b stacks on 1a).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(session-update): recover a conflicting autostash by its unmerged files

The auto-updater fast-forwards with `merge --ff-only --autostash`. When
the carried-over local edits conflict with the incoming revision, git
2.56 still exits 0 from that merge, pops the edits with conflict markers
into the live checkout, and keeps the stash entry. The recovery only ran
on a non-zero exit with "autostash" in stderr, so on git 2.56 it never
ran: the updater logged UPDATED and ran setup over a half-merged tree,
in the directory Claude Code runs hooks from. When it did run, it had two
more problems:
- `git checkout -- .` refuses unmerged paths ("error: path 'f' is
  unmerged", exit 1), so it left the markers in place anyway.
- `git stash drop` dropped stash@{0}, whatever that was. refs/stash is
  shared with every worktree of the repository, and when
  refs/stash.lock is held git cannot store the autostash at all, so the
  top entry can be someone else's.

Now the merge is judged by `git ls-files -u`, whatever its exit code.
On unmerged files after the fast-forward, autostash_recover finds the
autostash commit by the id git prints ("cannot store <id>" first, then
"Created autostash: <id>", with the merge run under LC_ALL=C and
core.abbrev=40). A moved refs/stash is only the fallback. When that id
is not a stash entry, it is anchored under refs/gstack-autostash/<UTC>.
The tree is reset to the fast-forwarded revision, which the hook check
already passed, and the stash is kept. The log line is
AUTOSTASH_CONFLICT_RECOVERED tree_reset=1 kept_stash=<id> kept_ref=<ref>.
The update then continues, so the regenerable patches are re-rendered
and setup runs as for any other move. If the commit cannot be found,
nothing is reset: the markers stay for a person, the run logs
PULL_FAILED, and setup does not run.

Files that are already unmerged before the update, from someone's own
merge, rebase or stash pop, now hold the update with PULL_FAILED
"unmerged files ...". Without that check, the recovery would take them
for its own and reset them away.

core.abbrev=40, not "no": "no" is accepted from git 2.31.0 (its release
notes), and `merge --autostash` exists from 2.27.0, so "no" would fail
every update on git 2.27-2.30.

Files:
- bin/gstack-session-update: autostash_recover, the unmerged-files check
  before the merge, the C-locale merge with its stdout kept for the id,
  and removal of the old grep-and-drop block.
- test/session-update-autostash.test.ts: 7 cases in a new describe block.
  A conflicting pop leaves no markers, keeps the edits and still runs
  setup. A user's own stash entry survives. A held refs/stash.lock gets
  the edits anchored. With a git whose merge prints nothing, a moved
  refs/stash still finds the edits; with the lock held as well, nothing
  is reset. Pre-existing unmerged files hold the checkout. A
  non-conflicting edit rides over the update (control).

Tests, on upstream 15ebd85cd:
- 5 of the 7 new cases fail against the previous session-update; the
  unmerged-files guard and the control pass on both.
- Two mutants are each caught by one new case: dropping the refs/stash
  fallback, and resetting when the edits cannot be found.
- session-update-autostash, session-update-stages,
  session-update-hook-hold and team-mode pass 63/0 with
  `--timeout 60000`. At bun's 5 s default under heavy load (one
  reading: load average 169), the three shared files timed out 8 tests
  on this branch and 17 with the previous script.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(session-update): trust only git's autostash id; owe setup on a stop

An independent review of the autostash recovery (Codex gpt-6-astra,
high effort, verdict BLOCK high=1 med=2) found three defects on its rare
paths. All three are fixed here, each with a case that failed first.

- [high] The refs/stash fallback could erase local edits. When git's
  messages were unavailable, recovery took any change to refs/stash as
  its own autostash. refs/stash is shared with every worktree: if a
  sibling pushed a stash while refs/stash.lock kept git from storing
  this one, recovery took the sibling's entry and reset the checkout,
  erasing edits nothing held. Now only git's own printed id counts. A
  missing id resets nothing: the markers stay, the run logs
  PULL_FAILED. Recovery needs git's messages, so when the temp file for
  them cannot be created the update is held before the merge
  (HELD temp-file-unavailable), not merged blind.
- [med] A stop after the checkout moved lost the setup it owed. The
  failure recorded only the pull reason. Once the person resolved the
  conflict, the next check saw an unchanged HEAD and no pending stage,
  logged UP_TO_DATE, and never set up the new revision. Now a failure
  after HEAD moved records `setup` as a pending stage, retried at once.
  The unmerged-files check holds it until the conflict is resolved, and
  the pull reason is the stage's reason, so the session-start line says
  what to resolve.
- [med] An unreadable index read as "no conflicts". Both `git ls-files
  -u` checks discarded the exit status, so a failed read after a
  conflicting pop (which git exits 0 for) let setup run over the markers.
  Now a failed read stops the update with PULL_FAILED "cannot read the
  index", before the merge or after it.

Files:
- bin/gstack-session-update: autostash_recover drops the refs/stash
  fallback (and STASH_BEFORE). The temp-file hold. The ls-files exit
  status checked at both points. The pending setup stage on a failure
  after HEAD moved.
- test/session-update-autostash.test.ts: a shim(fx, name, tool, body)
  helper; the fallback case now expects a refusal; the refusal case runs
  on through resolution to setup; three new cases (unreadable index
  before and after the merge, no temp file for git's messages).

Tests, on upstream 15ebd85cd:
- The 5 changed or new cases failed on the previous commit, each on its
  defect: refs/stash trusted and recovered; UP_TO_DATE with no setup
  after resolution; UPDATED over an unreadable index, before and after
  the merge; a blind merge with no temp file.
- Against upstream's own session-update, 8 of the block's 10 cases fail.
- session-update-autostash, session-update-stages,
  session-update-hook-hold and team-mode pass 66/0 with
  `--timeout 90000`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(session-update): reap heartbeat and its sleeper on exit

The lock heartbeat subshell left its sleep 300 child orphaned when the
updater exited, so heartbeats accumulated across sessions. Track and
reap the heartbeat job (with a job-table fallback for signals between
fork and $!), give the subshell its own exit trap that kills and waits
its sleeper, and map HUP/INT/TERM to exits so the EXIT trap always runs.
Adds a fixture-driven regression test.

* test(session-update): repin the lock trap and heartbeat to #3191's cleanup shape

Intentional pin change: test/session-update-autostash.test.ts 'reclaim is
TOCTOU-safe' and 'EXIT trap is ownership-checked' pinned the one-line
EXIT trap and the 'while :; do sleep 300; ...' loop that #3191 (Jess
Sullivan) replaces with cleanup_session_update and a 'sleep 300 & wait'
heartbeat. The pins still assert the ownership-checked rm -rf, the EXIT
trap, MYPID, and that the heartbeat is killed.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(session-update): move installs only through gstack-ff-update; extract hooks without tar (#3184)

Item 2 (#3189, #3191 adopted in the previous commits) and item 3 of the
fix-wave plan.

bin/gstack-ff-update <dir> <rev> is now the only code that moves an
install's tree. Inside an install-scoped lock (absolute git common dir,
the updater's TTL and mv-aside reap, a heartbeat reaped on exit) it
captures the working tree and the staged state through private indexes,
carries edits that merge cleanly (as an autostash pop would), lets the
regenerable SKILL.md/sections footprint take the target when it
conflicts, and shelves any other conflicting edit, staged change or
unmerged file into refs/gstack/rescue/<oid> (first parent the pre-update
HEAD, second the staged state; compare-and-create; gstack rescue
identity). The move is a two-way read-tree on the private index, so a
late edit or an untracked file in the way refuses with
autostash-unrecoverable and changes nothing. It sources nothing so the
held-install recovery can fetch it from the new revision.

gstack-session-update calls it instead of merge --autostash, holds a
checkout with unmerged files (unmerged-files:), and names shelved edits
at session start at most once a day with inspect, export-the-delta and
delete commands. The incoming revision is extracted for the hook check
through a private index and checkout-index with core.symlinks=false,
never tar (MSYS tar cannot create connect-chrome before its target), and
the held reason carries the failing command's flattened, control-free
stderr. Held lines print before the stage line, one case per reason
with its anchor, every path shell-quoted, no setup fix while unmerged
files exist. docs/troubleshooting.md gains the held-install recovery
forms (Git Bash, PowerShell 5.1, trial and go-back) and entries for each
new line.

design: kept_ref is refs/gstack/rescue/<oid>, refs/stash is never
touched, the quiet-merge cases become a pre-existing-ref refusal and its
recovery, pre-existing unmerged files are a HELD unmerged-files, and the
git shim pins the real binary through git --exec-path.

Intentional pin changes in existing tests:
- test/session-update-stages.test.ts: printed paths are shell-quoted;
  the bun-too-old fix runs gstack-ff-update instead of git pull.
- test/session-update-hook-hold.test.ts: the order pin looks for
  'bash "$FF_UPDATE"' instead of 'merge --ff-only'; the
  hook-check-unavailable fix no longer recommends git pull --autostash;
  installedUpdater copies gstack-ff-update beside the updater.

Reported by wengweiling (#3184).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(gstack-upgrade): Step 4 fast-forwards through gstack-ff-update (#3189)

Item 2 of the fix-wave plan, kept in its own commit so PR 1b can be
rebased onto PR 1a (which edits only Step 4's Bun floor-check line).

Step 4's `git pull --ff-only --autostash` had the same hole the
auto-updater had: a conflicting autostash exits 0 and leaves conflict
markers in the install, and the skill printed FF_OK. It now runs
bin/gstack-ff-update . origin/main after the fetch and the floor check:
exit 0 runs setup (a rescued line is relayed with its inspect, export
and delete commands), exit 4 (not a fast-forward) is the only way into
the gated reset fallback, and any other refusal stops with nothing
changed. The footprint discard moves to the reset fallback, ahead of
its stash, where the warning text already promised it.

Intentional pin changes:
- test/upgrade-template-pins.test.ts: the ff-before-reset pin looks for
  'bash bin/gstack-ff-update . origin/main'.
- test/gstack-upgrade-fresh-shell.test.ts: the C9 fence marker is
  'bash bin/gstack-ff-update'; the gstack-like fixture ships the bin.
- test/upgrade-setup-recovery.test.ts: the git-path fixtures ship
  gstack-ff-update (a stub where git itself is a stub).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(setup.ps1): run under Windows PowerShell 5.1's legacy argument passing (#3130)

Item 4 of the fix-wave plan. Windows PowerShell 5.1 strips the double
quotes inside native arguments, so the bash -c script setup.ps1 handed
Git Bash arrived broken ("unexpected EOF while looking for matching").
The script now holds no double quote: `set -f; IFS=; mapfile -d '' -t
argv < $1; rm -f -- $1; exec ./setup ${argv[@]}` (empty IFS and no
globbing keep $1 and each argv entry whole). That form drops an empty
argument, so setup.ps1 refuses one before anything runs
(SETUP_PS1_EMPTY_ARGUMENT, with its troubleshooting entry).

test/setup-ps1.test.ts runs the adversarial arguments under legacy
passing (pwsh with $PSNativeCommandArgumentPassing = 'Legacy', and
powershell.exe itself on Windows) with a space in TMPDIR, plus the
empty-argument refusal. Intentional pin change: the pinned bash -c
line in "passes arguments NUL-separated through a private file to a
constant bash script" now names the quote-free script and asserts it
holds no double quote.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(setup): register hooks at the $HOME path when CLAUDE_CONFIG_DIR links back to it

A per-session CLAUDE_CONFIG_DIR whose skills symlink to ~/.claude/skills
passed the split-brain guard, so its temporary path was written into the
global settings.json and the hooks broke once the session directory was
removed. Fall back to $HOME/.claude/skills/gstack whenever both spellings
resolve to the same tree.

Fixes #3180

* fix(setup): one sourced helper picks the hook root for setup and gstack-memorable (#3180)

bin/gstack-memorable carried a verbatim copy of setup's old canonical-root
block, so `gstack-memorable enable` under a per-session CLAUDE_CONFIG_DIR
still baked the session path into settings.json after #3181 fixed setup.
The choice now lives in bin/gstack-claude-root.sh
(gstack_canonical_claude_root), sourced by both.

Intentional test changes:
- test/setup-hook-canonical-paths.test.ts: #3181's source-slicing block
  test is rewritten to source the helper (with a spawnSync timeout, which
  the sliced version lacked and the tripwire flags); the
  "honors CLAUDE_CONFIG_DIR" pin moves from setup to the helper, and a new
  pin requires both callers to source it. Adds a no-$HOME-install case.
- test/setup-hook-syntax-gate.test.ts: the hook-section slice starts at the
  new assignment and the harness sources the helper.
- test/setup-claude-code-migration.test.ts, test/team-mode.test.ts: the
  partial-checkout fixtures copy the new helper setup sources.

Reviewer test: gstack-memorable enable under a per-session config dir that
links back to ~/.claude registers the $HOME path (fails on main).

Builds on #3181 by NetrioGit.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(setup): remove 1.31-era unprefixed Cursor skill folders (#3133)

A 1.31 setup run from a checkout under ~/.cursor/skills linked unprefixed
<skill>/ folders there, each holding a SKILL.md symlink into the checkout.
Current setup links gstack-<skill>/ folders, and the retired-skill prune
only looks at gstack-* names, so the old folders stayed: Cursor loaded
autoplan, review and the rest twice, or kept dangling links once the
checkout moved.

Run the Claude flat-name cleanup on ~/.cursor/skills as well. Cursor
installs only gstack-* names, so no prefix gate is needed, and the helper
removes only entries proven to link into gstack (#2119), so user-owned
folders stay. It runs after linking, so a run that fails before the new
links exist keeps the old folders. Reported by @andrewkjankowski in #3084.

Fixes #3133

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(setup): name the skills dir in the flat-name cleanup line (#3133)

`./setup --host cursor` now runs the flat-name cleanup on
~/.cursor/skills as well as ~/.claude/skills, so the "cleaned up old
entries" line says which directory the folders were removed from.

Reviewer test for #3152 (szsunyuan): a real dir whose SKILL.md links into
gstack loses only that link and keeps the user's file; SKILL.md links into
a sibling repo named gstack-fork, a relative link, and a dangling link
outside any gstack checkout are all kept; the printed line names the
removed folders and the Cursor skills dir.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(setup): install Chromium with `bun x`, and name exit 127 for what it is (#3131)

A Bun installed with winget ships no `bunx`, so setup's
`bunx playwright install chromium` exited 127 and was reported as
"offline, proxy, or blocked download?". setup now runs
`bun_cmd x playwright install chromium` through its existing bun resolver
($BUN_CMD), and bin/gstack-browser-ensure runs the bun it found on PATH
with `x`. Exit 127 gets its own text naming the resolved bun, Bun from
bun.sh, and the skip commands for Git Bash and PowerShell; other codes
keep the network hint. New troubleshooting entry:
#chromium-install-exit-127.

Intentional test changes:
- test/setup-playwright-best-effort.test.ts: the installer stub is now a
  `bun_cmd()` stub (was `bunx()`), its mark renamed bun-x-called and now
  records the args; the block under test calls bun_cmd. New winget-shaped
  cases (bun on PATH, no bunx anywhere) run setup's real bun_cmd.
- test/gstack-browser-ensure.test.ts: the fixture is winget-shaped (the
  `bun` stub handles `x`, no `bunx` on PATH); the exit-7 pin now reads
  `<stub>/bun x playwright install chromium exited 7 (...)`.

--help in gstack-browser-ensure prints the comment header only (3,24p).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ci(windows): prove setup.ps1 on PowerShell 5.1 and the held-install recovery natively (#3130, #3184)

windows-setup job:
- setup.ps1 joins the path filter (with bin/gstack-ff-update,
  bin/gstack-claude-root.sh, bin/gstack-browser-ensure,
  docs/troubleshooting.md and test/setup-ps1.test.ts).
- powershell.exe must be Windows PowerShell 5; test/setup-ps1.test.ts runs
  with a space in TEMP and must report exactly 4 pass, 0 fail, 1 skip
  (the Linux-only missing-prerequisite case).
- A copy of the built checkout under a path with a space installs through
  `powershell.exe -File setup.ps1 --host codex`.

held-install-journey job (new, parallel):
- origin's main is this PR's tree on top of the v1.91.101.0 tree
  (47d639abf); fix-branch adds one file.
- A prefix-mode v1.91.101.0 install at the runner's ~/.claude path in
  three states (clean, dirty-conflicting, already-unmerged), with
  auto_upgrade=true and the throttle cleared: the old updater must log
  `HELD hook-check-unavailable`; the PowerShell recovery form, read
  verbatim from docs/troubleshooting.md and run under powershell.exe,
  must reach main with every local edit (only those) in one rescue ref.
  The dirty state runs it with its .\setup.ps1; the others replace that
  step with a marker.
- The trial and go-back forms move the install to fix-branch (detached)
  and back to main.
- Only then a later release whose symlink sorts before its target is
  pushed, and the new updater must log UPDATED and reach it.
- Both jobs assert the exact list of cases that ran.

The journey steps ran locally on Linux with pwsh in Legacy argument
mode as powershell.exe, a git exec-path whose ../../../bin/bash.exe is
bash, and a failing tar standing in for MSYS tar (all 9 cases passed);
the windows-latest run itself happens on the PR.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(ff-update): a killed step-4 run fails the status check instead of the type check

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ci(windows): the held-install job runs without the bun cache step

The cache key line is a secret-scanner false positive on added lines
(env.kv); the fixture's own setup installs its packages either way.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(fixtures): register #3191's heartbeat fixture as free

test/free-fixtures.test.ts failed on the frozen-branch Ubicloud run:
test/fixtures/session-update-heartbeat.sh (adopted with #3191) was in no
paid touchfile and not in FREE_FIXTURES. Its only consumer is
test/session-update-heartbeat.test.ts.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(session-update): leave the bun-version-unreadable banner case to item 11 (PR 1a)

PR 1a owns the bun-version-unreadable hold (BUN_VERSION_UNREADABLE), its
banner case and its troubleshooting entry; this branch is rebased onto 1a.
Drops the case, the docs entry and the banner test this branch had added.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(setup.ps1): resolve Git Bash in the shared order, with GSTACK_BASH as an alias and a BASH_UNAVAILABLE refusal

Follows lib/script-invocation.ts (Lane C, PR 2): GSTACK_GIT_BASH is the
override and GSTACK_BASH its deprecated alias at the same precedence;
when both are set and differ GSTACK_GIT_BASH wins and one warning prints.
An override that does not exist is refused, never skipped. Without one:
the install git --exec-path belongs to (behind a scoop shim, or a
portable Git), the folders around git.exe, %LOCALAPPDATA%\Programs\Git,
%ProgramFiles%\Git, and last a bash on PATH whose uname -s reports MSYS;
WSL's bash (System32, WindowsApps) is rejected. A miss lists every
candidate with its rejection, then the PowerShell user-variable and
~/.claude/settings.json env recipes (BASH_UNAVAILABLE, #bash-unavailable).

test/setup-ps1.test.ts gains a 14-row pwsh-on-Linux table with every
location injected (override, alias, both set, missing override and alias,
scoop shim, per-user, portable and machine-wide Git, MSYS PATH bash, WSL
and non-MSYS rejections, missing); 11 rows fail on main. The rows mirror
Lane C's table in test/script-invocation.test.ts; they are not wired to
it, since that branch is not available here. The Windows job's expected
skip count for this file moves from 1 to 15 (the table is Linux-only).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(setup.ps1): give the empty-argument refusal its result code (SETUP_PS1_EMPTY_ARGUMENT)

Adds the row to lib/result-codes.ts (anchor #setup-ps1-empty-argument,
which setup.ps1 already prints). bin/gstack-doctor-components.sh is
regenerated from the table (generated; resolved by regeneration at merge).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ci(windows): silence SC2016 on the PowerShell version probe (the $ is PowerShell's, not bash's)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ci(windows-setup): give tar a POSIX path for the spaced checkout

MSYS tar reads `D:\a\_temp/...` as host:path (rsh syntax) and fails with
'Cannot open: No such file or directory' before setup.ps1 runs. The copy
target now goes through cygpath -u; setup.ps1 still gets the Windows form.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ci(windows-setup): dereference symlinks when copying the spaced checkout

MSYS tar cannot extract connect-chrome, a symlink that precedes its target
(the #3184 failure mode), so the copy died before setup.ps1 ran. tar -h
stores the target instead.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(windows): one Git Bash resolver for gstack's shebang bins (#3197)

Windows has no shebangs, and gstack had three conflicting bash lookups:
the hooks fell back to a bare PATH `bash` (often WSL's launcher), the
gbrain sync checked three fixed paths, and setup.ps1 walked up from
git.exe. lib/script-invocation.ts is now the only TypeScript resolver:
GSTACK_GIT_BASH (GSTACK_BASH as a deprecated alias, same precedence,
warning when they differ, a missing value refused), bash next to
`git --exec-path` (scoop, per-user, portable Git), the known install
paths, then a PATH bash only when `uname -s` reports MSYS/MINGW. WSL
launchers are rejected unprobed. A miss is BASH_UNAVAILABLE, listing
every candidate with its rejection reason.

Bun-shebang bins run under Bun (process.execPath, or the PATH bun inside
a compiled binary). Hooks' runBin resolves through a state-root cache
keyed by git.exe path and mtime, written by temp file plus rename, so
the hot path spawns nothing extra; the override is never cached.
wtreeFingerprint({cwd, timeoutMs}) returns {tree} | {error, kind} with
GIT_OPTIONAL_LOCKS=0 and 40-64 hex ids; the consumers move next.

Intentional pin change: test/gbrain-spawn-windows-shell.test.ts expects
forward-slashed bash paths (WIN_BASH and the GSTACK_BASH override), the
form the shared resolver returns because Bun's spawnSync on Windows
returns ENOENT for a backslash exe path containing spaces.
The copied-tree adapters in test/timeline-stop-hook-slug.test.ts and
test/plan-tune-cathedral.test.ts list the two modules spawn-bin.ts now
imports; the new fixture is registered in test/helpers/free-fixtures-data.ts. bin/gstack-doctor-components.sh is
regenerated for the new result-code row.

Reported by @exGeni.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(windows): fingerprint and gstack bins through the shared invocation (#3197)

bin/gstack-wtree was spawned by path in lib/regen.ts, lib/gate-diff/git.ts,
lib/restamp.ts and bin/gstack-evidence; gstack-slug in restamp, gate-diff
and evidence; gstack-redact in the pregate secrets check; and a bare
`bash -c` in regen, the lane selection manifest and gstack-evidence run.
On Windows each one failed to launch. All now go through
lib/script-invocation.ts, and each fingerprint consumer keeps its fail
direction while saying why:

- gstack-regen check: incomplete, `cannot fingerprint the working tree
  (gstack-wtree exited 1: <helper's own repair>)` instead of
  `(gstack-wtree failed)`; write keeps the registry label as its run id.
- gstack-gate-diff: `+nowtree` candidate, with the cause.
- gstack-restamp: RESTAMP_CONFLICT carrying the launch or exit detail
  (Windows printed `()` before).
- gstack-evidence: records without wtree (grades STALE) and warns once;
  SHA-256 repositories now get a 64-hex fingerprint.

lib/regen.ts keeps a string-returning wtreeFingerprint(repoRoot) wrapper
for lib/pregate/context.ts and lib/pregate/index.ts (files #3190 changes);
item 10b migrates them. test/script-invocation-tripwire.test.ts fails on a
new by-path or bare-bash spawn under lib/, hosts/ or a Bun bin; five sites
outside this change are listed with their owners and may only shrink.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(pregate): keep framework route paths from a selection manifest (#3176)

laneFileSet parsed manifest output with `[\w.-]+` path segments, so
`app/(shop)/[id]/page.test.ts` came back as `page.test.ts`. The selected
test then matched no platform lane, its remote receipt obligation
vanished and it fell back to a local run. manifestTestPaths keeps route
segments whole (`(group)`, `[id]`, `[...slug]`, `[[lang]]`, `@slot`,
`$param`, `+page`), reads every path-shaped token on a line (JSON
arrays, quoted, `path:line`, parenthesized), and decides what is a test
file with the import graph's own isTestFile, so a test-file rule change
there (bats, #3177) reaches manifests without a second regex. A manifest
that cannot start is "unknown" (widens), like a non-zero exit.

This is the manifest-parser half of #3176 by @vocodestudios; the
import-graph half (tsconfig aliases, Next.js route mapping) ships in a
separate PR. test/pregate-manifest-paths.test.ts drives the CLI with a
bracketed route test in the fixture's Windows-only lane and asserts its
requires-remote obligation; it failed on main (requires_remote was []).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ci(windows): windows-pregate-e2e drives the #3197 paths natively

A windows-latest job (Bun 1.4.2) builds a fixture repository and runs
gstack-restamp --dry-run, gstack-regen check and write, gstack-evidence
run and gstack-gate-diff from their TypeScript entry points, asserting a
real tree fingerprint each time; times a question-log hook cold and warm
and checks it cached its Git Bash resolution; and checks that a missing
GSTACK_GIT_BASH refuses with BASH_UNAVAILABLE and lists the candidate.
The journey runs twice: under the runner's Git for Windows and under a
pinned PortableGit 2.56.0.2 (SHA-256 verified), found only through
`git --exec-path`. gstack-pregate run joins it with item 10b. The workflow is registered in
scripts/test-pr-profile.ts FREE_ONLY_PR_FILES (no paid case reads it), recorded
as a non-behavioral HARNESS_VERSION decision in scripts/harness-version.json.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(setup.ps1): resolve Git Bash by the shared fixture table (#3197)

test/setup-ps1.test.ts now drives setup.ps1 through every row of
test/fixtures/windows-bash-resolution.json, the table that pins
lib/script-invocation.ts, instead of a hand-mirrored copy. Two rows
failed and are fixed here:
- an override and alias that differ only in slash direction no longer
  print the differ warning;
- the root git --exec-path names is also searched for usr\bin\bash.exe
  (Git for Windows on arm64).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(windows): isolate the no-bash case and launch pregate as the product does (#3197, #3176)

windows-free-tests run 38813751242 failed two tests this layer added. Both
were fixture defects on a real Windows host, not product defects:
- gbrain-spawn-windows-shell "returns null when Windows has no bash" stubbed
  only `exists`. The resolver's PATH step asks the real Bun.which and found
  the runner's MSYS bash, which is correct. The case now also stubs
  `which` and `gitExecPath`, so it really means "no bash anywhere".
- pregate-manifest-paths spawned the extensionless bin/gstack-pregate
  directly. CreateProcess cannot exec a shebang, so nothing ran and stdout
  was "". It now launches through lib/script-invocation's runScriptSync,
  gstack's own Windows invocation, so the case runs the manifest lane end
  to end on Windows instead of being excluded.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ci(windows): the missing-bash step survives the refusal it expects (#3197)

GitHub runs `shell: bash` as `bash -eo pipefail`, and the step's own
`set -uo pipefail` does not turn -e off. So when gstack-restamp refused, as
the step expects, `OUT=$(...)` aborted the script before `CODE=$?` and before
any output was printed: exit 1 after 0.5 s with nothing in the log (PR #3209,
job 116729693492). The capture is now `OUT=$(...) && CODE=0 || CODE=$?`.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(pregate): the manifest-lane case expects the host's own remote lanes (#3176)

Once 8b2f297cc made the case run on Windows, windows-free-tests run
38824886195 (shard 4) showed the expectation itself was Linux-only. pregate
owes a lane remotely when its runner platform is not the host's
(lib/pregate/workflows.ts). On a Windows host the fixture's Windows lane
runs locally, and the Ubuntu `free-tests.yml/unit` lane is the remote
obligation; that is the record Windows produced.

The case now derives the owed lanes from the host the same way. On Windows
it also requires `ran: node --test 'app/(shop)/[id]/page.test.js'`: there the
route reaches the local run only through the manifest's whole path. With a
cut manifest path (page.test.js) the local set is empty, checked through
buildContext({ platform: 'win32' }) + selectLanes.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ci(windows-setup): expect 31 skipped setup.ps1 cases now that the shared table drives it

Layer 2 drives setup.ps1 through the 16 shared rows of
test/fixtures/windows-bash-resolution.json; like the 14 existing resolution
rows they run under pwsh on Linux and skip on Windows. The case list is still
asserted exactly (4 pass, 0 fail, 31 skip).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(ship-measure): give the CLI tests a private HOME (#3162)

Every bun run test left ~/.gstack/projects/ship-measure-cli-*/evals and
sweep-cli-fake-*/evals in the developer's real home, and the free-suite
runner flagged them ("real home changed ... Give the writer a private
HOME/GSTACK_HOME"). ship-measure's diagnostic-dir check calls
getProjectEvalDir(), which builds the eval dir from os.homedir(), and
these two CLI tests passed the real HOME while overriding only the
state root.

Both now set HOME and USERPROFILE to the test's own state dir. The
sweep test also runs on the Windows lane, where os.homedir() reads
USERPROFILE. test/ship-measure.test.ts:79 already gives its config test
a private HOME the same way. getProjectEvalDir() itself is unchanged:
whether eval history should follow GSTACK_HOME is left open in #3162.

Fixes #3162

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(ship-measure): prove the sweep CLI never writes under the parent HOME (#3162)

Reviewer test for #3163 (adopted from y$un_, forrest.sun527): points the
parent HOME at a sentinel with a stub global gstack-slug and asserts no
.gstack dir appears there. Fails on 47d639a (the sentinel gets
.gstack/projects/leaked-slug/evals); passes with #3163.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(redact): isolate fixture git from inherited hooks

Fixture git was spawned with the parent environment, so a developer's
core.hooksPath ran on the local push and rejected it (#2967). The
helper blanks the global and system files and drops GIT_CONFIG_COUNT
and GIT_CONFIG_PARAMETERS. A hooksPath set in the fixture repo still runs.

* test(redact): blank GIT_CONFIG_GLOBAL with /dev/null on every platform (#2967)

Follow-up to #3156 (adopted from Lockyer228): Git for Windows rejects
GIT_CONFIG_GLOBAL=NUL (the repo convention is EMPTY_GLOBAL_GIT_CONFIG =
'/dev/null' in lib/cso/process.ts), so both fixture helpers use /dev/null
everywhere. Reviewer test: a hooksPath in the default global files under
HOME/.gitconfig and XDG_CONFIG_HOME/git/config cannot reject the fixture
push; it fails with the inherited environment (the 47d639a behavior) and
passes with the isolated env.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: gstack-config compares whole root paths and prints a runnable recovery command

- report_root_disagreement compared word-split values, so two variables naming
  the same directory with a space were reported as disagreeing. Compare each
  complete value instead.
- The privacy-key recovery hint single-quoted the root and left the script
  path bare; an apostrophe or space made it unrunnable. Quote with printf %q.
- On Bash 3.2 (macOS /bin/bash) a failed `.` under set -e exits before the
  `||` fallback, so a missing gstack-state-root.sh exited 1 silently and
  test/state-root-resolver-failure.test.ts failed there. Check readability first.
- The merge fixture now lives under a path with a space and an apostrophe,
  executes the printed command, and asserts same-directory variables agree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(config): pin the %q-quoted recovery command and run it from a spaced install path

Follow-up to #3186 (adopted from davidfragnoli-lang). Intentional pin
change: the fix line's GSTACK_STATE_ROOT='<legacy>' pin becomes the exact
bash printf %q form of the legacy root (the PR loosened it to a prefix
check; this restores an exact pin) and the original test name is kept.
Reviewer test: copy bin/ to a path containing a space and an apostrophe;
the printed fix: command runs cleanly and flips the merged value. Fails on
47d639a (unquoted $_SELF), passes with #3186.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(gstack-gbrain-sync): fail when a star gitignore already ignores the pin

ensureGbrainSourceGitignored appends .gbrain-source even when git
check-ignore already excludes that path. The extra line dirties a
tracked ignore-everything gitignore.

* fix(gstack-gbrain-sync): skip the pin line when git already ignores it

ensureGbrainSourceGitignored asked only for a literal .gbrain-source
line. An ignore-everything star already excludes the pin, and the extra
line left the tracked gitignore dirty.

* fix(gbrain-sync): only a tracked .gitignore can stand in for the pin line

Follow-up to #3178 (adopted from KuroChen). The skip now requires
`git -c core.excludesFile= check-ignore -v` to name a tracked .gitignore
(not global excludes, not .git/info/exclude, not a negation), so a pin
ignored only on this machine still gets the shared line teammates need
(#1384). Tests: the two git init spawns get timeouts (they failed the
spawnSync tripwire), fixtures isolate HOME/XDG_CONFIG_HOME, and the PR's
star-gitignore fixture stages its .gitignore (intentional fixture change:
the contract is a tracked file). Reviewer tests: global excludes only,
info/exclude only, an untracked star .gitignore, and a re-include each
still append; all three fail on the PR head.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(skill-end): log the completion event before the final drain (#3198)

gstack-skill-end drained and pushed before it appended its own completed
timeline event, and gstack-timeline-log backgrounded its enqueue, so a
full-tier end boundary returned with its latest event unpushed. The
completion log now runs before the drain and the logger waits for its
enqueue (one config read and a spool write; it exits early when sync is
off or the session is unattended). Every consent, secret-scan and
queue-lock gate is unchanged; the stale "local-only, never sent anywhere"
comment is replaced.

Patch and regression test from Kkkkkuro (#3198): 2 pass / 5 fail on
47d639a, 8/8 with the fix. Reviewer test: with artifacts_sync_mode off the
completion is written locally with no queue entry and no push.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(pregate): map Next.js route paths and tsconfig path aliases to their tests

QUOTED excluded ( ) [ ] +, so a test naming app/(app)/page.tsx in a string
never matched. resolveRef never read tsconfig compilerOptions.paths, so an
import through @/ resolved to nothing. Both were PREGATE_NO_LANE false
negatives in a Next.js App Router repo whose tests did name the files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(pregate): TypeScript alias precedence, relative extends and a no-lane explanation

Follow-up to #3176 (adopted from vocodestudios). Path aliases now follow
TypeScript's choice: an exact pattern, else the wildcard with the longest
prefix, targets tried in order. When the chosen pattern resolves nothing,
every plausible reading (other matching patterns, baseUrl, the repo root)
gains the edge, so uncertainty selects more tests, never fewer. Relative
extends chains are followed up to MAX_TSCONFIG_EXTENDS (4) hops, with
baseUrl and paths resolved against the config that declares them.
Trailing commas are dropped by the JSONC tokenizer (never inside a string)
instead of a whole-text regex.

A touched file with no lane now gets explain lines naming the import
specifiers in test files that resolved to no repo file, any extends hop
that was not followed (with the limit), and the hint to declare the file
under dependencies or report the resolver gap. The lines ride testsFor's
provenance, which lanes already pushes to --explain.

Reviewer tests: overlapping aliases in both key orders, exact vs
wildcard, target fallback, the union case, a two-hop extends chain, a
string containing ',]', and the depth-limit explanation. Five fail on the
PR head; all 12 pass. The manifest-parser half of #3176's follow-up
(lane-selection.ts) ships in PR 2.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(browse): reap PTY children on agent shutdown

* fix(browse): reap a terminal-agent PTY's whole process group at shutdown (#3147)

Follow-up to #3148 (adopted from Y4NN777). The PTY child leads its own
session and process group; its identity (pgid, sid, leader start time) is
recorded at spawn and stays checkable after the leader exits. Disposal
schedules a group reap after PTY_SHUTDOWN_GRACE_MS that, unlike the
direct-child SIGKILL fallback, is not cancelled when the leader exits, and
shutdown reaps every pending group before exiting, so descendants that
ignore SIGHUP no longer outlive the agent. Only proven members are
signalled: same pgid and sid, started no earlier than the leader, and
nothing at all when the leader's PID is held by a process with another
start time. Linux only (/proc); other platforms keep the direct-child
behavior. During the shutdown grace the server answers 503 and no new PTY
is spawned.

Test changes to the PR's own test (intentional): the agent is spawned
with process.execPath and the PTY child traps INT TERM HUP, so the case
proves the SIGKILL fallback instead of passing on the kernel hangup.
Reviewer tests: a HUP-immune grandchild is dead within the grace plus 1 s;
an early-exiting leader's surviving descendant is reaped; a session that
completed before shutdown still has its group reaped; unit cases for a
foreign group and a reused leader PID. All four integration cases fail on
47d639a; three fail on the PR head.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* build(deps): bump the github-actions group with 5 updates

Bumps the github-actions group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [actions/upload-artifact](https://github.com/actions/upload-artifact) | `7.0.1` | `7.0.2` |
| [actions/download-artifact](https://github.com/actions/download-artifact) | `8.0.1` | `8.0.2` |
| [actions/setup-node](https://github.com/actions/setup-node) | `7.0.0` | `7.1.0` |
| [ossf/scorecard-action](https://github.com/ossf/scorecard-action) | `2.4.4` | `2.5.0` |
| [github/codeql-action/upload-sarif](https://github.com/github/codeql-action) | `4.38.2` | `4.38.3` |

Updates `actions/upload-artifact` from 7.0.1 to 7.0.2
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/043fb46d1a93c77aae656e7c1c64a875d1fc6a0a...cf430e030ddbb5b0abf93d22962f4752f3646cd9)

Updates `actions/download-artifact` from 8.0.1 to 8.0.2
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c...9000827ccba6bdab643e8b6fd33ac0654aef8333)

Updates `actions/setup-node` from 7.0.0 to 7.1.0
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/820762786026740c76f36085b0efc47a31fe5020...949feb2413d6458794dcd2491c4babbbce0c15c1)

Updates `ossf/scorecard-action` from 2.4.4 to 2.5.0
- [Release notes](https://github.com/ossf/scorecard-action/releases)
- [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md)
- [Commits](https://github.com/ossf/scorecard-action/compare/2d1146689b8cda280b9bc96326124645441f03bc...fa7117db83c52d787ae5a89f350999701505d78e)

Updates `github/codeql-action/upload-sarif` from 4.38.2 to 4.38.3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2...24c54180a607b1449ed407dd24f251e4e9147c8d)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: actions/download-artifact
  dependency-version: 8.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: actions/setup-node
  dependency-version: 7.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: ossf/scorecard-action
  dependency-version: 2.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: github/codeql-action/upload-sarif
  dependency-version: 4.38.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(model-policy): track the upload-artifact v7.0.2 pin from #3175

Intentional pin change: test/model-policy-workflow.test.ts asserted
actions/upload-artifact@043fb46d…; dependabot #3175 moves the workflow to
cf430e03… (v7.0.2, verified against the upstream tag). The pin tracks the
workflow. #3175's free-tests.yml hunk is left out of this PR (free-tests.yml
is a #3190 file; it is re-bumped in PR 5).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(model-policy): its upload-artifact pin matches the eval workflows' pin

Reviewer test for dependabot #3175: the model-policy workflow's single
upload-artifact pin equals the pin evals.yml, evals-periodic.yml and
windows-free-tests.yml use, so a group bump that misses one workflow (or
the hard-coded pin above) is caught here.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* build(deps): hold Playwright minor/major bumps behind the windowsHide patch (#2923)

Dependabot #2923 bumped playwright to ^1.64.0 and dropped the
patchedDependencies entry keyed to playwright-core@1.62.1, which keeps
windowsHide on Windows (#2160, #1989). The bun ecosystem now ignores only
semver-minor and semver-major updates for playwright and playwright-core;
patch updates still flow. The 1.64 migration (re-key the patch, the
Dia/cookie qualification pins, the arm64 artifact name) is filed with
owner Garry, revisit 2026-11-15. Test fails on 47d639a (no ignore rule).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(readme): drop the stale skill lists users pasted into CLAUDE.md (#3169)

README's install prompt and troubleshooting block each carried a skill
list for CLAUDE.md; both were stale (18 shipped skills missing) and they
disagreed. Claude Code lists installed skills itself, so both now keep
only the /browse rule, and the troubleshooting entry says to delete an
old pasted list. New free test: any comma-separated skill list in
README.md or CLAUDE.md must name exactly the shipped skills (with a
negative control for the detector). Fails on 47d639a; passes now.
Reported by the #3169 issue author. The matching doctor warning for
pasted lists ships in PR 5.

Intentional pin change: test/test-value-bar.test.ts no longer expects the
README skill list to name /test-audit twice; it now expects that list to
be gone (0 occurrences), the registration this commit removes on purpose.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore(osv): image-size has a fix; html-to-docx's ^1.0.0 pin blocks it (#2753)

The image-size ledger said no fixed release exists. OSV lists 2.0.3 as
fixed for GHSA-5p2g-fcmc-qvqq and GHSA-w3rx-r6r6-pgpr (checked against
api.osv.dev), but html-to-docx@1.8.0 pins image-size ^1.0.0 and there is
no fixed 1.x, so the fix is a cross-major override. Both entries now say
so and name the upgrade trigger; the 2026-11-30 expiry is kept. New test
checks the wording and that bun.lock still shows the stated pin (it fails
on 47d639a). Correction first raised on #2753 by sonukapoor.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(ios-qa): forward GSTACK_IOS_LAUNCH_ENV to devicectl launches (#1796)

Reimplements only the launch-environment half of #1796 (credit
Bmathews721; the session-cache half is superseded by #3132's design and
had state-root and symlink-write problems). devicectl.ts parses
GSTACK_IOS_LAUNCH_ENV as a JSON object of string to string;
launchEnvArgs() returns the --environment-variables argv (empty when
unset or {}) and launchApp passes it before the bundle id. Invalid JSON, a
non-object, a non-string value, a bad name or a key in the debug bridge's
reserved GSTACK_ namespace refuses before any launch with the new result
code IOS_LAUNCH_ENV_INVALID (the detail names keys and types, never
values). launchEnvArgs is exported for the forced-relaunch path in
tunnel-bootstrap.ts.

Adds the result-code row (bin/gstack-doctor-components.sh regenerated by
gen:skill-docs), a troubleshooting entry under its exact text, the
pregate-no-lane entry's explain note, and the how-to section with an
example. New test fails on 47d639a (module load: no parseLaunchEnv).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(deps): declare sharp and clear two transitive advisories

sharp is imported in four files, including browse/src/screenshot-size-guard.ts,
and test/dependency-security.test.ts asserts a >=0.35.4 floor on it and loads
it. It was declared in no dependency field, reaching the tree only through
@huggingface/transformers with the overrides pin forcing the version. Declare it
at ^0.35.4, matching the floor that test already asserts.

Upgrade @huggingface/transformers to ^4.3.0, which clears sprintf-js@1.1.3 and
aligns the sharp override with what the parent declares: 4.2.0 declared
sharp ^0.34.5 while the override pins 0.35.5, which is outside that range, and
4.3.0 declares ^0.35.4.

Upgrade @anthropic-ai/claude-agent-sdk to 0.2.141, which clears
@anthropic-ai/sdk@0.81.0.

The existing overrides entry for sharp is left in place.

* test(deps): sharp is declared and its override satisfies every asking range

Reviewer test for #3145 (adopted from sonukapoor): sharp must be a
declared dependency, and the pinned overrides.sharp must satisfy both
that declaration and every lockfile package that depends on sharp
(@huggingface/transformers included). Fails on 47d639a (sharp not
declared; transformers 4.2.0 asked for ^0.34.5 against a 0.35.5 pin).

Classifier smoke on the changed tree (transformers 4.3.0,
onnxruntime-node 1.30.0, sharp 0.35.5, claude-agent-sdk 0.2.141): the real
sidecar entry bundled for Node with a private state root, and the
classifier in-process under Bun, each downloaded the TestSavantAI model
fresh, reported testsavant: ok, and returned scores identical to
47d639a's tree on five inputs, including a >512-token input that
exercises the truncation override. dependency-security, deps-smoke,
overlay-sdk-cancel-eof, security-classifier and screenshot-size-guard
pass; typecheck:test reports no new diagnostics.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(gbrain-sync): the no-bash skip names GSTACK_GIT_BASH and how to set it (#3197)

The brain-sync stage's Windows skip line told users to set GSTACK_BASH.
After the fix wave the canonical override is GSTACK_GIT_BASH, with
GSTACK_BASH as a deprecated alias (plan correction 9). The summary is now
the exported NO_BASH_SUMMARY: it names GSTACK_GIT_BASH first and GSTACK_BASH
as the old name, and gives the two recipes that reach Claude Code (a
PowerShell user environment variable plus a Claude Code restart, or the
"env" block of ~/.claude/settings.json).

Minimal on purpose: once PR 2 lands, this switches to
lib/script-invocation.ts noBashMessage(). Until then lib/gbrain-exec.ts
still reads only GSTACK_BASH, so this line is accurate only with PR 2
merged first.

New test test/gbrain-sync-no-bash-message.test.ts fails on the previous
commit (NO_BASH_SUMMARY not exported) and passes now.
Handoff from Lane C (PR 2).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(ios-qa): the forced relaunch carries GSTACK_IOS_LAUNCH_ENV too (#1796)

bootstrapTunnel's relaunchApp (an app whose boot token an earlier daemon
consumed is relaunched once with --terminate-existing) built its own
devicectl argv and dropped GSTACK_IOS_LAUNCH_ENV, so an env-gated debug
bridge vanished after the one relaunch the howto says carries it. It now
uses launchEnvArgs(); an invalid value refuses the relaunch with
launch_failed and the IOS_LAUNCH_ENV_INVALID detail before anything is
terminated.

Two new tests in ios-qa/daemon/test/devicectl-launch-env.test.ts drive
bootstrapTunnel through a consumed-token device: both fail on the previous
commit (relaunch argv lacks --environment-variables; the invalid value
still terminates and relaunches) and pass now. ios-qa daemon: 167 pass.
tunnel-bootstrap.ts was assigned to Lane D by the coordinator for this fix.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ci(version-gate): check the version queue only on PRs into main

Stacked layer PRs carry no VERSION stamp by design (the release layer stamps
once), so the queue check failed on any layer touching package.json. The
queue and its collision rule only describe main; a PR into main is still
checked exactly as before.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(browse): scope the SIGTERM PTY-reap case to POSIX signals (#3147)

windows-free-tests run 38813751242 (shard 6) failed "SIGTERM reaps every
active PTY child before terminal-agent exits" with "timed out waiting for
pty-child.pid", serially and on retry.

Diagnosis: invalid test expectation on Windows, not an agent defect the
test can observe. The contract under test is the agent's graceful
shutdown, its process.on('SIGTERM'/'SIGINT') cleanup in terminal-agent.ts.
On Windows, Bun's subprocess.kill('SIGTERM') is libuv's TerminateProcess,
so that handler never runs there; and the PTY fixture is an extension-less
`#!/bin/bash` script, which CreateProcess cannot launch, so the child never
starts (the timeout is at the pid file, after the port, token, grant and
WebSocket all succeeded). The file's other integration cases were already
Linux-scoped; this one now skips only on win32 and still runs on Linux and
macOS. The pure ownership unit tests in the file keep running on Windows.

Open, not covered by any test: whether a ConPTY child outlives a
hard-terminated agent on Windows. Testing it needs a Windows-launchable
terminal fixture, which BROWSE_TERMINAL_BINARY (one executable, fixed argv)
cannot express without a product change.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(plan-review): bound the native outside voice without TaskOutput (#3182)

Claude Code 2.1.277 removed TaskOutput, so the plan-review native fallback's
preflight always failed and users without the Codex CLI never got a second
voice. The fallback now uses primitives the host still enforces: a background
`sleep 300` Bash timer (its exit re-invokes the parent; the 2.1.300 sleep block
applies only to foreground commands), a background Plan dispatch whose
completion arrives as a <task-notification>, and TaskStop for whichever task
loses. Only a matching completed notification that beats the timer counts;
interim ("is waiting on its own background work"), turn-limit, foreground and
late results get no credit. The "[Subagent hand-back]" header (2.1.277) is
treated as framing.

The unavailable line now names its cause and fix, ends with
OUTSIDE_VOICE_UNAVAILABLE (new result code + troubleshooting entry), and the
review-log row carries unavailable_reason. The bounded-wait prose was
tightened so plan-ceo-review and plan-eng-review stay under their existing
size ceilings (no ratchet raise for this item).

Intentional pin change: test/run-in-background-guidance.test.ts
hasBoundedOutsideVoiceWait and the dispatch-contract cases now pin the
timer/notification/TaskStop contract instead of TaskOutput's envelope.
bin/gstack-doctor-components.sh is regenerated from lib/result-codes.ts.

Reported by HuellintonMota in #3182.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(bin): gstack-ceo-plan-find locates the CEO plan for a branch (#280)

/plan-ceo-review saves its scope decisions under the state root's
ceo-plans/, but no plan review can find them: only /design-html reads
that directory, newest file first. This helper is the CEO-plan sibling of
gstack-design-doc-find, so /plan-eng-review can call one command instead
of a glob in prose.

It prints the newest top-level plan whose "Branch:" header names the
branch, compared the way gstack-slug writes BRANCH (feat/x matches
feat-x), else the newest plan, else nothing. Subdirectories are not
read. A usage error exits 2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(plan-eng-review): read the CEO plan in the Design Doc Check (#280)

/plan-eng-review never looked in ceo-plans/, so an eng review after
/plan-ceo-review ran without the scope that review accepted, deferred or
skipped. capy-ai closed #414 asking for this focused fix, and TODOS.md
scopes it as a gstack-ceo-plan-find sibling of gstack-design-doc-find.

The Design Doc Check now prints "CEO plan found: <path>" or "No CEO plan
found" after the design doc line, and the prose says what to read the
plan for. The data-not-instructions rule names the CEO plan too, since
another agent may have written it. Only plan-eng-review changes: the
shared {{DESIGN_DOC_DISCOVERY}} block and the branch-scoped review log
are untouched. Reported by @chancheuklap in #280.

main left plan-eng-review 4 bytes under its parity cap, so the +325
bytes raise maxSizeRatio to the measured 1.2196, noted the way earlier
growth was.

Fixes #280

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(plan-eng-review): disclose a CEO plan borrowed from another branch (#280)

Review changes on top of #3159 (szsunyuan / y$un_):
- gstack-ceo-plan-find keeps stdout a bare path or empty; when it falls back
  to another branch's plan it prints one stderr line,
  "CEO plan found (from branch <b>, not <current>): <path>", with control
  characters stripped from the plan header ("unknown" when it has none).
- The Design Doc Check tells an interactive reviewer to ask before using such
  a plan, and a spawned session to use it and quote the disclosure.
- The Design Doc Check prose is tightened so plan-eng-review fits the
  owner-approved 1.2196 ceiling (measured 151,950 / 124,597 = 1.2195).

Reviewer tests: test/ceo-plan-find-cross-branch.test.ts (5 of 7 fail on the

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
#3159 head). The design-doc finder's matching disclosure stays deferred.

* fix(test): credit abutting section chunks whose edge bytes repeat (#3182)

detectSectionReads trims the longest suffix/prefix overlap between
consecutive Bash outputs. Two chunks that abut exactly (tail -c +N after a
chunk ending at N-1) can share an edge byte by chance; the trim then drops a
real byte and a fully printed section reads as incomplete. #3182's
review-sections edit moved plan-ceo-review's 66,999-byte boundary onto
"cur|rent", which turned the existing byte-range test red. The detector now
also checks the plain concatenation; a gap still refuses.

Detector/fixture defect, not a product change. New regression case in
test/section-capture-native-tools.test.ts fails without the fix.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(changelog): fix wave 2026-10-15 release notes

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* v1.91.104.0 fix: the 2026-10-15 issue wave

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(qa): probe each documented input rule, not only its boundary values

The paid gate's qa-functional-cli-report run (38829994639) probed zero and an
unsafe integer but never a non-digit amount, so it missed the planted
parseInt defect ("Amounts contain only ASCII digits") and reported no
defect. The functional contract map's invalid-input row now asks for one
probe per documented input rule (allowed characters, format, sign, range,
arity).

test/qa-functional-prompt.test.ts replays a captured CI Read of
system-functional.md; its completed-method variant now feeds the current
section bytes, so the validator check keeps its omitted/pending negative
controls without pinning the old wording.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* v1.91.106.0: restamp the fix wave above v1.91.105.0

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(release): keep the wave's dependency bumps through the main merge

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: Bryan Stone <aegixx@gmail.com>
Co-authored-by: vilano93 <244097767+vilano93@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: aftabkh4n <akobkz@gmail.com>
Co-authored-by: Benjamin D. Smith <benjamin.smith@binarysword.com>
Co-authored-by: Jess Sullivan <jess@sulliwood.org>
Co-authored-by: NetrioGit <5wngh@naver.com>
Co-authored-by: y$un_ <forrest.sun527@gmail.com>
Co-authored-by: Lockyer <135391289+Lockyer228@users.noreply.github.com>
Co-authored-by: davidfragnoli-lang <249433777+davidfragnoli-lang@users.noreply.github.com>
Co-authored-by: KuroChen <kurochen_macmini@KuroChendeMac-mini.local>
Co-authored-by: AV <vo.quanganh@gmail.com>
Co-authored-by: Y4NN777 <171065166+Y4NN777@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Sonu Kapoor <sonukapoor@gmail.com>
2026-10-16 22:00:32 +00:00
3b7254a54d v1.91.92.0 feat(outside-voice): enforce codex_reviews consent in the send binaries, codex_effort setting, privacy disclosures (Wave 9 Lane C) (#3144)
Outside-review consent (codex_reviews) is enforced in gstack-codex-probe
run-with-timeout, gstack-claude-code, gstack-outside-voice run and the
autoplan runner (not_run / consent_missing, exit 3; #3102). Codex reasoning
effort resolves GSTACK_CODEX_EFFORT, then codex_effort, then each gate's
default inside run-with-timeout --effort (D1-A; #3098, closes #2934). README
and telemetry privacy disclosures corrected (#1081, #1080, #1150).

Co-authored-by: Lockyer <135391289+Lockyer228@users.noreply.github.com>
Co-authored-by: Lubos Buracinsky <159481718+lubosxyz@users.noreply.github.com>
2026-10-11 19:09:12 +00:00
Garry Tanandcapy-ai[bot] 5d647455f2 v1.91.79.0 feat(eval-plan): /eval-plan tier 1, lane-check, evidence verify, coordinator contract, owner brief, contributor-PR mode, banned terms (multi-agent wave PR E) (#3123)
* v1.91.79.0 feat(eval-plan): /eval-plan tier 1, lane-check, evidence verify, coordinator contract, owner brief, contributor-PR mode, banned terms (multi-agent wave PR E)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(spec,review): fail closed when the coordinator contract cannot be read; restore the omitted-Army sentence

The spec --execute spawn captures `gstack-autoplan contract` before piping
the archive, so a lane never starts without the contract; the C9 fixture
installs its gstack-like checkout (bin, lib, a contract stub) at every
host's global root. Review Step 5.8 keeps the pinned
'without claiming specialist coverage' wording.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-11 03:10:06 +00:00
Garry Tanandcapy-ai[bot] 5b9b8372c4 feat: honest work and gate integrity — gate-edit detector, review/ship gate pass, zero-run green, claims-need-evidence kernel (#3114)
* docs(designs): honest work and gate integrity plan (autoplan-reviewed)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(e2e): review-gate-integrity rule case registered in touchfiles and the PR profile

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(honesty): claims-need-evidence kernel, no_claim card field, CEO value test, implementer dispatch rules, ACCEPTANCE_EDITED, Honest Work ethos

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(gate-diff): gate-edit detector lib + CLI, patch fixtures, calibration script, tests_ran in gstack-evidence

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(gate-diff): RH-15? only on CI/config floors after calibration

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(review,ship): Gate Integrity category, GATE_SCAN_BLOCK, one-way gate disposition, actor stamping, ZERO-RUN lanes, draft-while-open

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: re-measure parity and context budgets on the integrated tree

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(touchfiles): gate-diff fixtures and helpers select review-gate-integrity

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: archaeology lint wording, paid-file census, harness-version non-behavioral record

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pin review Step 3.5 rename, plan-path placeholder, mktemp template, per-stream evidence tail

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test: ACCEPTANCE_EDITED fence substitutes the <plan-path> placeholder

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(gate-diff): shallow boundary commits report no_base instead of a whole-tree inventory; recalibrate

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-10 22:33:01 +00:00
Garry Tan 20eb6202fa v1.91.68.0 fix(cso-eval): Claude producers keep the source sealed, run the launcher outside the sandbox, and keep usage on failed checks (#3093)
* v1.91.68.0 fix(cso-eval): failed receipts with usage for post-run integrity failures; Claude producers stop mutating the sealed source

Claude Code adds <dir>/.mcp.json as a sandbox deny path for every --add-dir.
The producer passed the sealed source as one, so bubblewrap left an empty
0444 .mcp.json mount point there that Claude Code cannot unlink from the 0555
directory, and the post-run source check failed with no receipt.

- claudeExecArgs no longer adds the source directory
- post-run integrity failures write status:failed receipts carrying the
  check code/reason plus usage, tokens and estimated cost (no output or
  artifacts)
- INVALID_PRODUCER_SOURCE / PRODUCER_CHANGED_SOURCE_MODE name the first
  offending relative path and the rule it broke

* fix(cso-eval): run the trusted launcher outside Claude's sandbox; keep every other command sandboxed

Claude Code 2.1.263 forces every Bash command into its bubblewrap sandbox
while CLAUDE_CODE_SUBPROCESS_ENV_SCRUB is set, excludedCommands
notwithstanding. Inside it the launcher's owner check sees root as nobody
and the Docker socket is unreachable, so comprehensive cells degraded to
static-only results.

- drop CLAUDE_CODE_SUBPROCESS_ENV_SCRUB for producers; pass --settings with
  sandbox enabled, failIfUnavailable, no unsandboxed fallback, no
  auto-allow, and excludedCommands naming only the exact launcher path
- ClaudeAdapter.run refuses to start the agent
  (CLAUDE_PRODUCER_SANDBOX_POLICY_MISMATCH) unless args and env carry
  exactly that policy
- README containment note and CHANGELOG updated

* test(doctor): split the two multi-run doctor tests so each fits Windows' 30s per-test budget

On windows-latest each bin/gstack-doctor run costs ~4-5s. The cached/live probe test made 7 runs and the artifacts-sync test 5; both hit the 30s per-test timeout, which killed doctor mid-output ("no row codex probe") and then EBUSY on cleanup. Same failure on #3093 and the fix/runtime-root-source-guard PR. Every assertion is kept; each test now makes at most 4 runs.
2026-10-09 05:32:45 +00:00
54efba6dd5 v1.91.67.0 feat(hosts): Antigravity CLI (agy) host (#3089)
* feat(hosts): Antigravity CLI (agy) host

Adds Google's Antigravity CLI as an experimental host through defineHost():
skills in ~/.gemini/antigravity-cli/skills/gstack-*, a runtime root with
.source-path for /gstack-upgrade, names equal to their gstack-<skill>
directories, name+description frontmatter with the 1024-char limit, the
Gemini overlay, and a preamble glossary mapping Claude tool names to
Antigravity's documented tools (run_command, view_file, write_to_file,
replace_file_content, grep_search, find_by_name, invoke_subagent,
ask_question).

Setup: ./setup --host agy (alias antigravity), --host auto on the agy
binary or ~/.gemini/antigravity-cli, never a Gemini-CLI-only ~/.gemini.
Registry tier, --status known roots, uninstall and team-init cover it.
Gemini CLI's ~/.gemini/skills and settings are never touched.

Co-authored-by: Shagun Prasad <109907086+0xshae@users.noreply.github.com>
Co-authored-by: ManchalaSashank <134257068+ManchalaSashank@users.noreply.github.com>
Co-authored-by: VoIP Expert <5742479+voipexpert@users.noreply.github.com>
Co-authored-by: Mattias Petersson <93470851+KiDDarn@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(agy): golden refresh after main merge; agy is recorded as an installed render host

* v1.91.67.0: CHANGELOG, version

---------

Co-authored-by: Shagun Prasad <109907086+0xshae@users.noreply.github.com>
Co-authored-by: ManchalaSashank <134257068+ManchalaSashank@users.noreply.github.com>
Co-authored-by: VoIP Expert <5742479+voipexpert@users.noreply.github.com>
Co-authored-by: Mattias Petersson <93470851+KiDDarn@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-08 20:05:06 +00:00
+22 8bb6d65270 v1.91.66.0 fix: /autoplan without permission prompts after background reviewers, 34 contributor PRs from the backlog triage (#3087)
* docs: fix CONTRIBUTING.md test-tiers link in TESTING_INTERNALS

The link resolved relative to docs/, pointing at a nonexistent
docs/CONTRIBUTING.md. Adopts #3070.

Co-authored-by: thearbajonart <334330110+thearbajonart@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ci: refresh GitHub Actions SHA pins (setup-buildx 4.4.1, build-push 7.4.0, attest 4.2.2, setup-node 7.0.0)

Rewrite of dependabot #2988. Every bumped `uses:` stays pinned to the full
commit SHA (verified against the upstream tags) and now carries its exact
version comment, including the lines that had none. setup-node 6.5.0 -> 7.0.0
is a major bump; its notes list an ESM migration, cache outputs and removal of
a dummy NODE_AUTH_TOKEN export, none of which affect our node-version-only use.
actionlint 1.7.11 (CI image) and 1.7.12 are clean.

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(openclaw): stop advising a per-spawn env for spawned sessions

OpenClaw's sessions_spawn has no env argument (openclaw/openclaw#51654
was closed without adding one), so the documented
env: { OPENCLAW_SESSION: "1" } silently did nothing. Point users at the
ACP harness or Gateway environment instead and guard the old advice.
Adopts #2749.

Co-authored-by: DanCanadian <211164803+DanCanadian@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(benchmark): scope /benchmark routing to web page performance

/benchmark claimed the bare words "benchmark" and "performance", so model
and eval benchmark requests could route to the web performance skill
instead of /benchmark-models. Narrow the description, Use-when list and
triggers to web-specific phrases, point model/skill benchmarks at
/benchmark-models, and split the root router line the same way. The
catalog lead stays within the discovery budget ("Web performance ...").

Adopts #2302 and closes duplicate #2083 (rewritten on current main).

Co-authored-by: Nader Helmy <31414549+creatornader@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(llms.txt): link skills by generated path, drop host-skipped skills

gen-llms-txt built every skill link as <name>/SKILL.md, so the router
pointed at a nonexistent gstack/SKILL.md (it is the root SKILL.md) and
/claude-code pointed at a SKILL.md the Claude host never generates.
Links now use the template's generated output path, and skills the
Claude host skips are left out of the index. Adopts #2279.

Co-authored-by: Nader Helmy <creator.nader@gmail.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(ci): install Bun from the SHA-256-verified release archive in GitLab CI and Ubicloud setup

Rewrite of #1713. .gitlab-ci.yml's .setup-bun anchor and
scripts/ubicloud/setup-free-suite.sh still piped https://bun.sh/install into
bash. Both now download the bun-v$BUN_VERSION linux-x64 release zip, check it
against the same SHA-256 Dockerfile.ci uses (#1706) before unzipping, install
it to ~/.bun/bin and assert `bun --version`. test/ci-image-cli-pin.test.ts runs
curlPipedToShell over both files and binds their hash and BUN_VERSION to
Dockerfile.ci's amd64 arm and package.json's engines floor, so a Bun bump has
to move all of them together. Verified by running the GitLab anchor in
debian:stable-slim (bash and sh) and the Ubicloud block in a temp HOME.

Co-authored-by: Zee Zheng <228040220+spacegeologist@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(browse): viewport auto unpins a pinned viewport without a restart

`viewport WxH` pinned the page with no way back short of `browse restart`,
which also drops a headed /connect-chrome session. `viewport auto` (aliases
`reset`, `unpin`) now undoes the pin:

- headed: the context already launches with `viewport: null`, but Playwright
  cannot drop a page's emulated size (and re-applies it on new frame
  sessions), so the active tab gets a fresh page in the same context at the
  same URL or load-html content. Cookies and storage persist, the tab id and
  ownership stay, refs reset as on navigation, and the page follows the real
  window again.
- headless: back to the 1280x720 launch default, tracked viewport included.

`auto` with --scale is rejected. Fixes #1059. Adopts #2782 and #1881.

Co-authored-by: Jack Thomas <17882510+loulanyue@users.noreply.github.com>
Co-authored-by: Jayesh Betala <jayesh.betala7@gmail.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(browse): screenshot accepts --clip=x,y,w,h and --selector=<css>

Agent-generated commands often use the GNU `--flag=value` form, which threw
"Unknown screenshot flag". Both value flags now accept it, through the same
validation and conflict checks as the space-separated form. Adopts #1563.

Co-authored-by: techcenter68 <75930234+techcenter68@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(benchmark): price Codex cached input at the cache rate, stop calling Gemini OAuth ready

Codex reports cached_input_tokens as a subset of input_tokens, but the
adapter billed all of input_tokens at the full input rate. Split them
into disjoint uncached and cached counts (what estimateCostUsd expects)
and show new+cached input in the table and Markdown reports. A stored
Gemini OAuth file alone is now reported as not ready, with a reason
naming GEMINI_API_KEY; key-based auth stays ready. Adopts the focused
subset of #2283; the capacity error code and stdin change are left out
(no captured failure or reproduced hang).

Co-authored-by: Mattias Petersson <mattias.petersson@gmail.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(review): catch loosened-input consumers and stale user-facing strings

The Enum & Value Completeness check fired only on newly added values, so a
diff that widens what a validator, parser or guard accepts, and breaks an
unchanged downstream consumer outside the diff, was a blind spot. Widen the
check's trigger (heading unchanged) and add a consumer-grep bullet. Teach
the maintainability specialist to flag user-facing strings whose guarding
condition changed while the text did not.

Adopts #2141 (rewritten on current main; the section heading is kept).

Co-authored-by: Arno V <38716+aversini@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(global-discover): bucket Codex sessions by originator

Every Codex rollout counted as one undifferentiated codex bucket, so
/retro global could not tell interactive Codex Desktop work from
codex_exec runs or Claude Code-driven calls. Normalize
session_meta.payload.originator into desktop, exec, claude_code or
other (missing, unknown and non-string values), report the split per
repo as codex_originators and globally as tools.codex.originators, and
print it in the summary. Existing totals are unchanged. Adopts #1488.
Refs #1315.

Co-authored-by: 0xDevNinja <manmit0x@gmail.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(hook-check): cover skill frontmatter hooks, sourced helpers, conflict markers and the caller's tsconfig

Rewrite of #3066. bin/gstack-hook-check only listed setup's hooks plus
/autoplan's by name, so the /careful and /freeze PreToolUse checks (also run
by /guard and /investigate), /plan-ceo-review's hand-off hook and the helpers
they source were never parse-checked before Claude Code ran them.

- The hook list is setup's _hook_command_path arguments plus every gstack path
  on a `command:` line in a generated */SKILL.md frontmatter (9 hooks, up from
  6). The generated files are read rather than the .tmpl sources because the
  /autoplan and /plan-ceo-review commands come from resolvers.
- Each shell hook's gstack-tree helpers (`.`/`source` lines, assignments and
  shellcheck source= directives, followed transitively) are parsed too:
  careful/bin/hook-extract.sh and bin/gstack-state-root.sh for /careful and
  /freeze.
- The shim, its helpers, each TypeScript entry and every local module the
  bundle used (bun build --metafile; entry only when bun lacks it) are scanned
  for conflict-marker lines, which still parse inside heredocs, strings and
  template literals.
- bun build runs from the gstack root, so the caller's broken tsconfig.json or
  bunfig.toml no longer fails every TypeScript hook. Lists are newline
  separated, so a root with spaces works.

Tests in test/setup-hook-syntax-gate.test.ts cover each case; the gstack-doctor
fixture now copies autoplan/SKILL.md, whose frontmatter registers the hook its
hooks-row test breaks. docs/troubleshooting.md describes the wider check.

Co-authored-by: Benjamin D. Smith <7134745+BenjaminDSmithy@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(review): extend defensive-security framing to the security specialist and Red Team

The "authorized defensive-security review" framing and fixture summary mode
lived only in the adversarial subagent prompt. The security specialist and
the Red Team also reason adversarially over the full diff, so repos whose
test corpus is attack payloads could still get those passes refused.

Move both paragraphs into scripts/resolvers/defensive-review.ts (the
adversarial render stays byte-identical), state them once in the specialist
dispatch for the security specialist and the Red Team, and have the Red
Team prompt start from them. Testing and the other specialists keep full
fixture reads; the codex host still strips Review Army. Parity caps for
ship and review rise by the measured 1,380 bytes each (carve-guards notes).

Adopts #1921 (rewritten on current main).

Co-authored-by: Brian Majewski <1693720+bmajewski@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(team-init): make the required-mode hook shell-neutral (node CommonJS, no shell variables)

Rewrite of #2217. Required team mode wrote a bash check-gstack.sh and
registered it as "$CLAUDE_PROJECT_DIR/.claude/hooks/check-gstack.sh", which
only works when the hook runner is a POSIX shell. Under cmd.exe or PowerShell
(Windows, Copilot CLI) the variable is not expanded and the .sh cannot run, so
an intended allow or deny became a hook error.

- The hook is now .claude/hooks/check-gstack.cjs: the same root search
  (GSTACK_ROOT, every host's global root, the migrated repo), with the home
  directory from HOME, then USERPROFILE, then os.homedir(). It prints {} when
  gstack resolves and the PreToolUse deny JSON with exit 2 when not.
- The registered command is `node -e "require(require('path').resolve(
  process.env.CLAUDE_PROJECT_DIR || '.', '.claude', 'hooks',
  'check-gstack.cjs'))"`: no `$` or `%`, so sh, cmd.exe and PowerShell all
  pass it through unchanged.
- An existing check-gstack registration (matcher Skill or Skill|skill) is
  replaced in place; duplicates are dropped and unrelated hooks in the same
  entry kept. The legacy .sh is deleted only after the settings write
  succeeds (a tracked one joins the suggested git add), and is kept when bun
  is missing or the write fails, which now reports instead of claiming success.

Tests: team-mode and the hook-schema test run the command as registered;
test/team-init-hook-shell-neutral.test.ts runs it through the platform's
default shell (cmd.exe on the Windows lane, which curates it in) and through
PowerShell when installed (verified here in the pwsh 7 container). The
routing-probe check for the bash `_GSTACK_ROOT` gate now pins the same
contract in the .cjs: every host root probed, block only when none resolves.

Co-authored-by: neallee2012 <1499497+neallee2012@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(setup-gbrain): register the gbrain MCP through bun.exe on Windows so no console window opens

Rewrite of #2007, folding #1963's MCP notes. On Windows the local stdio
registration stored the bun `gbrain` binstub; gbrain.exe starts bun in a new
console, so every Claude Code session opened a visible terminal for the MCP
(the contributor's process trees show it; bun.exe launched directly attaches
to Claude Code's headless ConPTY).

Under MINGW/MSYS/Cygwin the skill now resolves BUN_ROOT from BUN_INSTALL or
~/.bun and registers `$BUN_ROOT/bin/bun.exe <global node_modules
gbrain/src/cli.ts> serve`. That path also covers a `bun link`ed clone, whose
global node_modules entry links to it; gstack-gbrain-install records no other
clone location. When bun.exe or the entry is missing it registers
`$BUN_ROOT/bin/gbrain.exe` by its full name, because Claude Code does not
apply PATHEXT to a stored path (#1963). POSIX keeps the binstub line. The
skill also notes that `claude mcp list` can show "Failed to connect" while a
live session holds a local PGLite brain's single-writer lock (#1963).

test/setup-gbrain-windows-mcp.test.ts runs the generated fence under bash with
stub uname/claude and a temp HOME for MINGW64, MSYS with BUN_INSTALL, a
missing entry, and Linux. It is listed as Windows-incompatible (POSIX stubs);
the console behavior needs a manual Windows check.

Co-authored-by: Max Hogan <max@maxhogan.dev>
Co-authored-by: maxpetrusenkoagent <280464918+maxpetrusenkoagent@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(hosts): rewrite every "Agent tool" phrasing for agent-runtime hosts

The Agent-tool rewrite for hermes, gbrain, openclaw and factory keyed only
on "use the Agent tool", but templates mostly say "via the Agent tool",
"with the Agent tool", "multiple Agent tool calls" or "Use the Agent tool",
so the Claude tool name leaked into those hosts' skills and sections. Add
ordered rules after the existing key (possessive, "the Agent tool", then
bare "Agent tool") mapping to sessions_spawn (gbrain/openclaw),
delegate_task (hermes) and delegation (factory). No goldens change.

Adopts #1935 (rewritten on current main, without the new goldens).

Co-authored-by: katlun-lgtm <264247399+katlun-lgtm@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(setup-deploy): Vercel auto-deploys only when connected to the Git repo

/setup-deploy stated that Vercel always deploys on push, which is false for
CLI-only projects with no Git integration, and its nothing-detected path did
not say why such a project is invisible to detection. Qualify the Vercel
trigger (ask for and record the CLI deploy command otherwise) and note that
`vercel link` creates .vercel/ so a re-run detects the project.

Adopts the Vercel accuracy part of #2992 (rewritten on current main); the
third-party bring-up tool pointer is not adopted.

Co-authored-by: mikehasa <91774998+mikehasa@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(retro): persist recommendations so the next retro detects follow-through

/retro produced "3 Things to Improve" but never saved them in its JSON
snapshot, so the next retro could not tell whether last week's
recommendations were acted on. Step 13 now drafts the items before saving
and always writes a `recommendations` array ({category, text}, [] when
empty); Step 12 scores each prior recommendation addressed/partial/open
against this window's commits, files and metrics and renders a
follow-through line, skipping silently for snapshots without the key. The
report format ties the items to the saved array. Global mode is unchanged.

Adopts #1853 (rewritten on current main). Refs #1834.

Co-authored-by: Jayesh Betala <jayesh.betala7@gmail.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(global-discover): count the interactive Codex CLI (codex_cli_rs) in its own cli bucket

* feat(developer-profile): --reconcile backfills office-hours tenure from timeline.jsonl

sessions[] is written only at office-hours Phase 4.5, so runs that ended
earlier never counted toward tenure and the tier undercounted. Add a manual
`--reconcile` that scans projects/*/timeline.jsonl for office-hours
`completed` events and appends a count-only row ({date, mode, project_slug,
signal_count: 0, signals: [], backfilled: true}) for each run with no
sessions[] entry for the same project within 60 minutes, extended back by a
duration_s under 8h. It is idempotent, skips malformed lines, and leaves the
file untouched on a no-op. `--read` counts backfilled rows for SESSION_COUNT
and TIER but takes LAST_*, DESIGN_*, CROSS_PROJECT and the nudge from logged
sessions only. Not wired into the preamble yet; the office-hours greeting
guard for backfill-only profiles is left to the office-hours owner.

Adopts #2668 (rewritten on current main). Closes part (b) of #2657.

Co-authored-by: Briac <137724549+briacSck@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(autoplan): the publication hook approves /autoplan's own Reads after the skill turn

Claude Code applies a skill's allowed-tools only to the turn that invoked it.
On 2.1.292 the default fork-subagent mode runs reviewers in the background, and
the completion notice starts a new turn, so later Reads outside the working
directory (the next phase's section file, the close packet) raised permission
cards. Skill hooks stay registered for the session, and a PreToolUse "allow"
skips the card (reproduced in a PTY on 2.1.292).

autoplan/bin/owned-read.ts approves a parent Read only when the guard itself
returned no output, and only for this installation's autoplan/sections/*.md
(canonical path or the gbrain :user render) or an immutable phase artifact in
<repo>/.gstack/tmp/autoplan/autoplan-<phase>-*/ whose repo is the session's
project directory or contains it. Denials, unverified allows, child calls and
every other path are unchanged. renderSectionBase and userRenderRoot move
there with it.

Tests that asserted a verified driver Read as {} now expect the approval;
autoplan-owned-read covers the shipped shim, the close packet without a
journal, verified artifacts through the guard, look-alikes (foreign install,
writable copy, symlink alias, other repository, other names) and a guard
denial staying a denial.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(autoplan): restore point and phase artifacts live in the project's .gstack/tmp/autoplan/

Skill hooks do not run inside subagents, so a reviewer launched after a
background completion notice raised a card for its own input (native-prompt.md
under the state root). Step 1 now puts RESTORE_PATH in the repository's
git-excluded .gstack/tmp/autoplan/ (added to .git/info/exclude, mode 700), and
the snapshot tool writes every phase artifact beside it, so /autoplan and its
reviewers read them from the working directory without a card. The guard's
bindings are unchanged: they anchor on the restore point's directory. Step 1
still prints the slug, which the old RESTORE_PATH carried.

A free test runs the generated Step 1 block twice from a repository
subdirectory: the restore point lands in <repo>/.gstack/tmp/autoplan/, the
exclude line is written once and git status stays clean. The autoplan parity
cap rises by the measured bytes (115,270 / 101,979 = 1.1303).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(evals): autoplan-guard-pty fails on any permission card after the reviewer's notice turn

The scripted boundary now mirrors Step 1's layout (restore point and artifacts
in the project's .gstack/tmp/autoplan/) and continues past the Phase 2 entry to
dispatch the Phase 2 reviewer after the CEO reviewer's completion notice. The
case asserts four verified guard allows, that the close packet Read, the Phase 2
section Read and the Phase 2 dispatch came after a task-notification turn, that
the Phase 2 reviewer read its input, and that no permission card appeared at
all. Still one trial on haiku, about 75-85 seconds.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(autoplan): permission cards after a background reviewer, and where /autoplan keeps its files

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(outside-review): read a trailing '— Medium.' or '(High)' severity on a finding line, the shape Codex now writes

* feat(design): honor OPENAI_BASE_URL for every design API call

One resolver, openaiUrl() in design/src/auth.ts, now builds every OpenAI
URL the design binary, its prototype and its live model check use. Setting
OPENAI_BASE_URL sends image generation and screenshot analysis to an
OpenAI-compatible gateway; unset or blank keeps api.openai.com. The
prototype script now goes through receiptedFetch, so its egress is
receipted like the rest, and receipts already record the gateway host.

A source scan keeps any design file outside auth.ts from hardcoding an
OpenAI API URL again, and a request-shape test proves the gateway reaches
both the vision and image endpoints.

Co-authored-by: JackJin <1037461232@qq.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* review: testing specialist treats negative assertions as decisions

A new assertion that something is absent records a behavior removal and
needs cited intent. A rename or removal in the diff can empty a standing
negative assertion so it passes forever; the specialist now greps the suite
for negative assertions naming the old value.

Adopted from the testing-specialist part of a community PR; the scope-check
hunk targeted text that no longer exists.

Co-authored-by: EmilianoU26 <314541067+EmilianoU26@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* review: security specialist checks authorization a route-guard read misses

Four access-control classes where the guard is correct and the object still
leaks: error pages composed outside the guard, ownership surviving a role
downgrade, list/detail/download paths checked as one, and a session mutated
before the identity-switch callback is validated.

Adopted to review/specialists/security.md only; the /cso hunk targeted a
bullet list that is now an OWASP/ASVS table.

Co-authored-by: EmilianoU26 <314541067+EmilianoU26@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* review: zero findings is valid; suppress six common false positives

The checklist now says a clean diff may come back clean, and suppresses
framework-owned error handling, long exhaustive switches, null derefs a
type guard already narrows, N+1 on fixed-cardinality loops, hardcoded test
fixture values and non-cryptographic Math.random().

The contributor's "missing input validation on an internal function" and
"missing await on fire-and-forget" suppressions are left out: both can hide
real bugs.

Co-authored-by: frosimanuel <frosimanuel@gmail.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(todos): parked contributor ideas by theme; /autoplan permission follow-ups

* design-review: capture code tokens before universal principles

/design-review read only DESIGN.md and otherwise fell back to universal
principles, so a project whose system lives in CSS custom properties,
tailwind.config or a theme file got audited and mocked up against an
invented aesthetic. The skill now reads code tokens before falling back,
and the target-mockup brief reuses the captured colors, fonts, radii and
spacing, changing layout only.

The contributor's optional microcopy checks (domain vocabulary, human
register) are not adopted here.

Co-authored-by: Tom Lam <tomlam0811@berkeley.edu>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(sidebar): add a terminal text-size picker to the toolbar

The sidebar terminal toolbar gets an XS-XL text-size picker beside
Restart. SM (13px) stays the default, so nothing changes until a user
picks a size; the choice persists in chrome.storage.local and applies
to the live terminal, re-fitting xterm and re-sending the PTY size two
frames later so the new character cell is measured first.

A VM test runs the real sidepanel-terminal.js through one auto-connect
and checks the creation size, the stored-size fallback, persistence and
the live resize.

Co-authored-by: Yotam <yotsurfing@gmail.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(design-shotgun): generate with Codex's built-in $imagegen on Codex

On the Codex host, /design-shotgun now generates and edits mockups with
Codex's documented $imagegen skill in its default built-in mode (the
image_gen tool), so users signed in with ChatGPT need no OPENAI_API_KEY.
The design binary stays for the local comparison board only, images are
published with gstack-design-claim as before, and screenshot evolution
loads the captured page with view_image before an $imagegen edit.

Step 3c-3d moved into a DESIGN_SHOTGUN_GENERATION resolver that returns
the existing design-binary text unchanged for every other host; the
Claude output differs only in two host-neutral rewordings of the
evolve-from-screenshot pointers. DESIGN_SETUP and the regenerate step of
the board loop gain matching Codex-only text for design-shotgun.

Co-authored-by: Jinzhe <hwang.phy@gmail.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* voice: ban the "load-bearing" stock phrase

Models overuse "load-bearing"; it joins the AI-vocabulary ban line in both
voice tiers. The tier-2 voice directive is also tightened (the meta
"compressed for runtime" tag and the bounded closer's restated exemption
clause) so the shared preamble shrinks by 163 bytes per tier-2+ skill,
which also pays for the reply-language rule that follows. The ship goldens
are refreshed for the new render.

Co-authored-by: Nader Helmy <creator.nader@gmail.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* voice: reply in the language of the user's latest message

Every skill's voice directive now tells the agent to reply in the language
of the user's latest message unless asked otherwise, so long English skill
text no longer drags a Chinese (or any non-English) session into English.
Mixed-language sessions follow the latest message. Code, commands, paths,
identifiers and quoted output stay verbatim, and at tier 2+ so do the
machine-read question markers (D<N>, option letters, (recommended)).

The rule costs 205 bytes per tier-2+ skill and 144 per tier-1 skill. A
further 25-byte cut to the bounded closer's exemption list leaves the net
for this wave at +17 bytes per tier-2+ skill and +158 per tier-1 skill.
review, qa and browse caps rise with measured notes; the ship goldens are
refreshed.

Co-authored-by: Xianying Tan <shrektan@126.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(redact-prepush): committed exact-span allowlist at .gstack-redact-allowlist

The pre-push hook never fed the engine's allowlist option, so a reviewed,
known-benign value (a public dev-compose URL, a documented sample key) warned
or blocked every push, and the only ways out were a bypass or reshaping code
until the detector stopped matching.

The hook now reads the repo-root .gstack-redact-allowlist from each pushed
commit (never the working tree), one exact matched span per line with #
comments. Only a finding whose matched text equals an entry is suppressed: a password alone, a
substring, or a different key still reports and still blocks, and engine.*
unscanned findings are untouched. Every push that carries entries prints the
entry count and each suppressed finding's rule and file line. Oversized files
(over 64 KiB) are ignored. gstack.redact.allowEmail keeps working as before.

Co-authored-by: Boyard Engels <boyard.engels@gmail.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(brain-sync): opt-in removal publishing with a bulk-removal valve

gstack-brain-sync never published a removal: a deleted synced file, or the
old directory after a project slug migration, stayed on the remote forever
and gbrain indexed both copies.

New setting artifacts_sync_removals (off by default). When on, each drain
commits and pushes, receipted like every push, the removal of tracked files
that are gone from disk and match the sync allowlist. Transcript pages are
never removed here, and a decision log goes only together with its active
snapshot. One valve: a drain that would remove more than 20 files at once
publishes none and says so. `gstack-brain-sync --publish-removals` lists the
pending removals and `--publish-removals --yes` publishes them on demand,
whatever the setting.

Co-authored-by: Jey2311 <5177215+Jey2311@users.noreply.github.com>

* feat(windows): setup.ps1 checks prerequisites and runs ./setup in Git Bash

A PowerShell entry point for Windows users. It checks that Git for Windows,
Bun and Node.js are on PATH (printing the winget command for each missing one
and changing nothing), finds Git Bash from git.exe rather than a PATH bash
that may be WSL's, and runs ./setup from the checkout with every argument
passed as its own argv entry to a constant bash script. No argument is
interpolated into shell source. ./setup itself is unchanged.

Co-authored-by: Jim McKeeth <jim@mckeeth.org>

* v1.91.63.0: CHANGELOG, version

* fix(ci): link bunx after the SHA-verified Bun install in GitLab CI and the Ubicloud setup

* fix(redact-prepush): strip every parent column from combined-diff lines, so a merged release bump is exempt like any other

* fix(hook-check): treat a Windows drive path as absolute (cygpath when present), so --list finds the skill hooks from a native caller

* fix(windows): owned-read requires the read-only attribute on Windows; setup.ps1 passes arguments NUL-separated through a private file; Windows-only tests expect the owned-read approval

* perf(hook-check): bundle every hook's TypeScript entries in one bun process instead of two bun starts per entry (seconds each on Windows)

* perf(hook-check): read all skill frontmatter in one awk and scan conflict markers with one grep per hook (half the process starts)

---------

Co-authored-by: thearbajonart <334330110+thearbajonart@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: DanCanadian <211164803+DanCanadian@users.noreply.github.com>
Co-authored-by: Nader Helmy <31414549+creatornader@users.noreply.github.com>
Co-authored-by: Nader Helmy <creator.nader@gmail.com>
Co-authored-by: Zee Zheng <228040220+spacegeologist@users.noreply.github.com>
Co-authored-by: Jack Thomas <17882510+loulanyue@users.noreply.github.com>
Co-authored-by: Jayesh Betala <jayesh.betala7@gmail.com>
Co-authored-by: techcenter68 <75930234+techcenter68@users.noreply.github.com>
Co-authored-by: Mattias Petersson <mattias.petersson@gmail.com>
Co-authored-by: Arno V <38716+aversini@users.noreply.github.com>
Co-authored-by: 0xDevNinja <manmit0x@gmail.com>
Co-authored-by: Benjamin D. Smith <7134745+BenjaminDSmithy@users.noreply.github.com>
Co-authored-by: Brian Majewski <1693720+bmajewski@users.noreply.github.com>
Co-authored-by: neallee2012 <1499497+neallee2012@users.noreply.github.com>
Co-authored-by: Max Hogan <max@maxhogan.dev>
Co-authored-by: maxpetrusenkoagent <280464918+maxpetrusenkoagent@users.noreply.github.com>
Co-authored-by: katlun-lgtm <264247399+katlun-lgtm@users.noreply.github.com>
Co-authored-by: mikehasa <91774998+mikehasa@users.noreply.github.com>
Co-authored-by: Briac <137724549+briacSck@users.noreply.github.com>
Co-authored-by: JackJin <1037461232@qq.com>
Co-authored-by: EmilianoU26 <314541067+EmilianoU26@users.noreply.github.com>
Co-authored-by: frosimanuel <frosimanuel@gmail.com>
Co-authored-by: Tom Lam <tomlam0811@berkeley.edu>
Co-authored-by: Yotam <yotsurfing@gmail.com>
Co-authored-by: Jinzhe <hwang.phy@gmail.com>
Co-authored-by: Xianying Tan <shrektan@126.com>
Co-authored-by: Boyard Engels <boyard.engels@gmail.com>
Co-authored-by: Jey2311 <5177215+Jey2311@users.noreply.github.com>
Co-authored-by: Jim McKeeth <jim@mckeeth.org>
2026-10-08 17:40:06 +00:00
b357b50378 v1.91.65.0 fix(install): skills only for the agents you installed, every runtime-root file their skills read (#3080)
* Fix spaced-out terminal text on Windows: put a Latin monospace before the CJK fallbacks

On Windows none of the Mac/Linux monospace fonts in the stack are
installed, so the first font that resolves is Malgun Gothic (the Windows
Korean UI font). xterm.js sizes its character cell from that font's
advance width, and Malgun Gothic advances Latin glyphs in a full-width
CJK cell — doubling every cell and rendering the whole terminal (and the
sidepanel mono UI) as spaced-out text.

Insert Consolas (present on every Windows install) and the Cascadia
faces ahead of the CJK fonts in both the xterm fontFamily and the
--font-mono CSS var. The CJK fonts stay last for actual CJK coverage.
No bundled font, no user install needed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKj8Nr8T4GvryTcWBJRMyC

* fix(windows): every compiled binary answers --version before any side effect

browse --version started a server and exited 1, pdf printed usage and
exited 1, gstack-global-discover rejected the flag, and find-browse ran
discovery. A launch probe for Smart App Control (#2595) needs one
side-effect-free command that exits 0 on a healthy build, so browse,
find-browse, make-pdf and gstack-global-discover now answer --version
first (design already did).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(extension): pin Consolas ahead of the CJK fallbacks in both mono stacks (#2287)

Regression test for @tomfluff's fix: the xterm fontFamily and --font-mono
must list Consolas before Noto Sans Mono CJK KR and Malgun Gothic, or
Windows renders the sidebar terminal double-width.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(cso): record each CSO build's outcome and stop arming the parent trap across the native publisher exec (#3071)

scripts/build-cso.sh now writes bin/.gstack-cso-build-result: result,
failed stage (build or publish), reason, this revision, the revision of
the generation still installed, whether its launcher is usable, and the
diagnostic log. An 'interrupted' entry is written before each stage so a
killed run is visible. A failed publish that restores the old generation
records it as still installed.

Before exec'ing the native publisher, the parent re-checks the locker,
clears its EXIT trap and enables execfail: the locked child's own trap
owns the stage. On Git Bash an exec of a native program may not replace
the shell, so the parent's trap could otherwise delete the stage under
the running child (the vanishing files in #3071; root cause unproven).
If the exec itself fails the parent cleans the stage and records it.

The Windows build prefers PowerShell 7 (pwsh) over Windows PowerShell
5.1, which crashed outright on the reporter's machine.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(setup): an optional CSO build or publish failure no longer aborts setup (#3071)

CSO was optional at probe time but fatal at build time: scripts/build.sh
ran build-cso.sh under set -e before the browse server bundle and the
completion stamp, and setup exited when the CSO artifact set was
incomplete. A failed CSO step now prints 'CSO unavailable: the <stage>
step failed' with its log (bin/.gstack-cso-build.log) and retry, and the
build and setup finish. GSTACK_STRICT_BUILD=1 keeps it fatal; the free-tests,
make-pdf-gate and windows-setup-e2e builds set it, so regressions still
fail CI.

setup's summary reads the build-result record instead of the pre-build
availability flag: probe failures keep their prerequisite instruction;
build and publish failures name the stage, the diagnostic and
'bun run build:cso && ./setup', and a restored earlier helper is
reported as 'previous CSO kept: <revision>'. setup records probe
failures in the same file.

The Windows prerequisite probe prefers pwsh, and a probe that fails for
any reason other than a missing toolchain or a compile error keeps its
exit status and first output line (windows-toolchain-probe) instead of
being labeled windows-msvc-toolchain. Reported and diagnosed by
@dviolante.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(windows): detect Smart App Control-blocked binaries in setup and gstack-doctor (#2595, #2124)

Smart App Control refuses the five unsigned binaries gstack compiles
locally, and Git Bash reports it as 'Permission denied', so setup's
[ -x ] gate passed and skills failed later. bin/gstack-launch-probe.sh
runs each binary's side-effect-free --version and classifies it:
native, blocked (spawn error, exit 126/127, or an application-control
message), broken (launched, exited non-zero) or missing. It shows the
raw first output line, since the exact Windows text is unverified.

On Windows, setup names each blocked binary and the skills that need it,
cites #2595, says which shell ran setup and whether WSL is installed,
gives today's workarounds (WSL, or turning Smart App Control off), how to
undo, and the new troubleshooting section. gstack-doctor's browse row now
says 'blocked at launch' instead of passing, a binaries row covers the
other four, and a cso row reads the build-result record.

windows-setup-e2e builds with GSTACK_STRICT_BUILD=1, checks all five
binaries answer --version, and adds two forced CSO failures: fatal under
strict mode, and a completed setup that reports the failure and a doctor
cso warning under GSTACK_STRICT_BUILD=0.

No shim or launcher: it could not be called from PowerShell and would
load the caller's project .env/bunfig. Signing is the follow-up in
TODOS.md. Diagnosis credit: @knetics9000 (#2595, #2596), @smartjelic-sys
(#2595), @pstilwell90 (#2124), @cko32002 (#2265), @salluexez (#2127).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: Windows Smart App Control known issue, optional CSO failures, doctor rows

README's Windows section says /cso failures no longer stop setup
(GSTACK_STRICT_BUILD=1 keeps them fatal), documents Smart App Control as
a known issue with today's workarounds (WSL, or turning it off) and the
Consolas fix. troubleshooting.md gains a Windows section for blocked
binaries and an entry for CSO build/publish failures. TODOS.md replaces
the Node CLI lane item with Windows code signing (needs a signing
account), the #3071 root-cause follow-up and SAC text verification.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(setup): never write the CSO build record into a source checkout that needed no build

Setup wrote bin/.gstack-cso-build-result for every probe-stage outcome,
so a run that built nothing still changed the source checkout's bin/
(caught by setup-codex-scope's unchanged-source checks). A missing
prerequisite now comes from the run's own probe, as before, and setup
only removes a stale record alongside the removed helper.
scripts/build-cso.sh remains the only writer. The doctor's cso row
reports 'native helper not built' with no record.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(extension): move the Consolas font-stack check into sidebar-ux (#2287)

The standalone file read extension sources without importing product
code, which the test-of-test ratchet rejects; sidebar-ux already owns
the sidepanel.css structural checks.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* v1.91.43.0 fix(windows): setup degrades instead of dying on a CSO failure, detects Smart App Control, Consolas terminal font

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(setup): every env-var runtime root carries the files its skills read as $GSTACK_ROOT/<path> (#1077 residue)

Codex, Factory, Kiro, OpenCode, Cursor and Copilot runtime roots linked
bin/, lib/ and the compiled tools but not the files the generated skills
read on demand: the preamble's jargon list and question registry, the
AskUserQuestion split and CJK docs, the DX Hall of Fame, the test-value
bar, the review checklists and specialists, design-html's vendored
pretext.js, VERSION and plan-design-review/SKILL.md. The reads failed
silently on every non-Claude host.

_link_runtime_dists now links the shared list for every root, and
_copy_runtime_skill_refs copies the host-rendered office-hours and
plan-design-review SKILL.md (the #2449 pattern) into each root. The
per-root review loops it replaces had drifted (Cursor shipped two of
four checklists, only OpenCode had specialists). hosts/define-host.ts
mirrors the list in sharedRuntimeRoot().

test/runtime-root-assets.test.ts renders every host, builds each root
with setup's own functions and checks every $GSTACK_ROOT literal on
disk; it fails on the previous setup for all five roots.
scripts/ship-measure.ts stays deferred (it needs the eval harness).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(build): an install renders skills only for the hosts installed from it (#1694)

./setup ran gen:skill-docs --host all inside the install, so a global
Claude install at ~/.claude/skills/gstack carried 632 SKILL.md files
(34.7 MB) where 56 are its own; Claude Code and Cursor-agent scan that
tree, and Cursor-agent freezes above roughly 11 MB.

setup now keeps an additive record of the hosts it installs from a
checkout (.gstack-installed-hosts, owned by bin/gstack-host-renders.sh),
seeded from installs that predate it: registry rows, install roots that
resolve to the checkout, host skills that link into its renders, and
legacy skill copies with no runtime root. scripts/build.sh renders claude
plus the recorded hosts and prints which hosts and why. A checkout with no
record (development) or GSTACK_RENDER_HOSTS=all renders every host. The
Codex .agents/ render runs only when Codex is installed or recorded;
bin/dev-setup records Codex because it serves the repo's .agents/.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(setup): prune skill renders for hosts a checkout does not serve, without losing a file (#1694)

Every ./setup now removes the render dirs of hosts missing from the
installed-hosts record, so an install that rendered every host (632
SKILL.md, 34.7 MB under ~/.claude/skills/gstack) converges on its own
hosts. It runs per install, so two installs sharing one state root are
both cleaned, and never touches a recorded host, so ./setup --host codex
keeps a --host factory render.

The rules follow bin/gstack-relink: a symlink into the checkout (STRONG)
is removed; a file proven generated (WEAK: gen-skill-docs' banner, the
generator's exact openai.yaml shape, byte identity with the checkout, or
setup's runtime pointers naming it) is moved to
$GSTACK_STATE_ROOT/backups/host-renders/<ts>-<id>/; any other file,
including a link outside the checkout or bin/dev-setup's link to the
checkout itself, stays where it is. Only directories left empty are
removed. setup prints each kept path and a per-host summary, and
prune.log in the backup lists every path. A tree with nothing left to
remove is silent, so reruns print nothing.

gstack-upgrade/migrations/v1.91.50.0.sh records the hosts an existing
install serves and runs the same prune; a checkout no install points at
is left alone. The Hermes, OpenClaw and GBrain hints say to add the host
to the record before rendering it by hand.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(build): name the installed-hosts record in the build's host line

'Rendering skills for: claude codex (recorded in <checkout>/.gstack-installed-hosts;
GSTACK_RENDER_HOSTS=all renders every host)' instead of a nested parenthesis.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* v1.91.50.0 fix(install): skills only for the agents you installed, every runtime-root file their skills read (#1694, #1077)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(team-mode): copy bin/gstack-host-renders.sh into the setup fixture

setup sources the new helper (#1694); team-mode's hand-picked fixture file
list lacked it, so its two setup runs exited 1 ('gstack-host-renders.sh is
missing'). Found by the final full free suite on 9b39cc6; product code is
unchanged.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore(release): re-slot C2 to v1.91.61.0 (merge queue); migration renamed to match VERSION

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: Yotam <yotsurfing@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-08 16:17:18 +00:00
4dfd83bf8c v1.91.60.0 fix(windows): setup degrades instead of dying on a CSO failure, detects Smart App Control, Consolas terminal font (#3079)
* Fix spaced-out terminal text on Windows: put a Latin monospace before the CJK fallbacks

On Windows none of the Mac/Linux monospace fonts in the stack are
installed, so the first font that resolves is Malgun Gothic (the Windows
Korean UI font). xterm.js sizes its character cell from that font's
advance width, and Malgun Gothic advances Latin glyphs in a full-width
CJK cell — doubling every cell and rendering the whole terminal (and the
sidepanel mono UI) as spaced-out text.

Insert Consolas (present on every Windows install) and the Cascadia
faces ahead of the CJK fonts in both the xterm fontFamily and the
--font-mono CSS var. The CJK fonts stay last for actual CJK coverage.
No bundled font, no user install needed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKj8Nr8T4GvryTcWBJRMyC

* fix(windows): every compiled binary answers --version before any side effect

browse --version started a server and exited 1, pdf printed usage and
exited 1, gstack-global-discover rejected the flag, and find-browse ran
discovery. A launch probe for Smart App Control (#2595) needs one
side-effect-free command that exits 0 on a healthy build, so browse,
find-browse, make-pdf and gstack-global-discover now answer --version
first (design already did).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(extension): pin Consolas ahead of the CJK fallbacks in both mono stacks (#2287)

Regression test for @tomfluff's fix: the xterm fontFamily and --font-mono
must list Consolas before Noto Sans Mono CJK KR and Malgun Gothic, or
Windows renders the sidebar terminal double-width.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(cso): record each CSO build's outcome and stop arming the parent trap across the native publisher exec (#3071)

scripts/build-cso.sh now writes bin/.gstack-cso-build-result: result,
failed stage (build or publish), reason, this revision, the revision of
the generation still installed, whether its launcher is usable, and the
diagnostic log. An 'interrupted' entry is written before each stage so a
killed run is visible. A failed publish that restores the old generation
records it as still installed.

Before exec'ing the native publisher, the parent re-checks the locker,
clears its EXIT trap and enables execfail: the locked child's own trap
owns the stage. On Git Bash an exec of a native program may not replace
the shell, so the parent's trap could otherwise delete the stage under
the running child (the vanishing files in #3071; root cause unproven).
If the exec itself fails the parent cleans the stage and records it.

The Windows build prefers PowerShell 7 (pwsh) over Windows PowerShell
5.1, which crashed outright on the reporter's machine.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(setup): an optional CSO build or publish failure no longer aborts setup (#3071)

CSO was optional at probe time but fatal at build time: scripts/build.sh
ran build-cso.sh under set -e before the browse server bundle and the
completion stamp, and setup exited when the CSO artifact set was
incomplete. A failed CSO step now prints 'CSO unavailable: the <stage>
step failed' with its log (bin/.gstack-cso-build.log) and retry, and the
build and setup finish. GSTACK_STRICT_BUILD=1 keeps it fatal; the free-tests,
make-pdf-gate and windows-setup-e2e builds set it, so regressions still
fail CI.

setup's summary reads the build-result record instead of the pre-build
availability flag: probe failures keep their prerequisite instruction;
build and publish failures name the stage, the diagnostic and
'bun run build:cso && ./setup', and a restored earlier helper is
reported as 'previous CSO kept: <revision>'. setup records probe
failures in the same file.

The Windows prerequisite probe prefers pwsh, and a probe that fails for
any reason other than a missing toolchain or a compile error keeps its
exit status and first output line (windows-toolchain-probe) instead of
being labeled windows-msvc-toolchain. Reported and diagnosed by
@dviolante.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(windows): detect Smart App Control-blocked binaries in setup and gstack-doctor (#2595, #2124)

Smart App Control refuses the five unsigned binaries gstack compiles
locally, and Git Bash reports it as 'Permission denied', so setup's
[ -x ] gate passed and skills failed later. bin/gstack-launch-probe.sh
runs each binary's side-effect-free --version and classifies it:
native, blocked (spawn error, exit 126/127, or an application-control
message), broken (launched, exited non-zero) or missing. It shows the
raw first output line, since the exact Windows text is unverified.

On Windows, setup names each blocked binary and the skills that need it,
cites #2595, says which shell ran setup and whether WSL is installed,
gives today's workarounds (WSL, or turning Smart App Control off), how to
undo, and the new troubleshooting section. gstack-doctor's browse row now
says 'blocked at launch' instead of passing, a binaries row covers the
other four, and a cso row reads the build-result record.

windows-setup-e2e builds with GSTACK_STRICT_BUILD=1, checks all five
binaries answer --version, and adds two forced CSO failures: fatal under
strict mode, and a completed setup that reports the failure and a doctor
cso warning under GSTACK_STRICT_BUILD=0.

No shim or launcher: it could not be called from PowerShell and would
load the caller's project .env/bunfig. Signing is the follow-up in
TODOS.md. Diagnosis credit: @knetics9000 (#2595, #2596), @smartjelic-sys
(#2595), @pstilwell90 (#2124), @cko32002 (#2265), @salluexez (#2127).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: Windows Smart App Control known issue, optional CSO failures, doctor rows

README's Windows section says /cso failures no longer stop setup
(GSTACK_STRICT_BUILD=1 keeps them fatal), documents Smart App Control as
a known issue with today's workarounds (WSL, or turning it off) and the
Consolas fix. troubleshooting.md gains a Windows section for blocked
binaries and an entry for CSO build/publish failures. TODOS.md replaces
the Node CLI lane item with Windows code signing (needs a signing
account), the #3071 root-cause follow-up and SAC text verification.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(setup): never write the CSO build record into a source checkout that needed no build

Setup wrote bin/.gstack-cso-build-result for every probe-stage outcome,
so a run that built nothing still changed the source checkout's bin/
(caught by setup-codex-scope's unchanged-source checks). A missing
prerequisite now comes from the run's own probe, as before, and setup
only removes a stale record alongside the removed helper.
scripts/build-cso.sh remains the only writer. The doctor's cso row
reports 'native helper not built' with no record.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(extension): move the Consolas font-stack check into sidebar-ux (#2287)

The standalone file read extension sources without importing product
code, which the test-of-test ratchet rejects; sidebar-ux already owns
the sidepanel.css structural checks.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* v1.91.43.0 fix(windows): setup degrades instead of dying on a CSO failure, detects Smart App Control, Consolas terminal font

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: Yotam <yotsurfing@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-08 15:43:32 +00:00
Garry Tan 92cfd07a79 v1.91.45.0 feat: configurable model tiers and advisory freshness (#3073)
* feat: add configurable model tiers and advisory freshness

* fix: disclose role selections before Claude review execution

* fix: reject malformed model policies and keep model pins portable

* fix: separate readiness diagnostics and bound version checks

* fix: preserve actionable model failures and validate review inputs before probes

* fix: retry released probe locks and retain late-rejection repair context

* feat: finalize configurable model tiers and verified release repairs

* fix: preserve strict model policy reads on Windows

* fix: keep fixture probes read-only and clarify pending CEO decisions

* fix: verify native terminal closure before bounded descriptor settlement
2026-10-08 08:00:08 +00:00
c7554824cb v1.91.38.0 fix(ios-qa): iPads work, a dropped route no longer relaunches the app, loud boot-token write (#3076)
* fix(ios-qa): allow physical iPad devices in tunnel bootstrap (#2743)

(cherry picked from commit d6d43934ef)

* fix(ios-qa): iPhone + iPad on USB names each UDID instead of picking one

With GSTACK_IOS_TARGET_UDID unset, tied default ranks used to pick the first
device devicectl listed, so a QA session could drive the wrong device. Tied
candidates now fail with multiple_devices, list each device and UDID, and
print a ready export line. Device selection moves into selectDevice() so the
route-drop recovery can re-resolve with the same rules.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(ios-qa): a dropped CoreDevice route no longer relaunches the app (#1975)

Rotation deletes the app's one-shot boot-token file, so the daemon's old
503/504 path (invalidate + full bootstrap) could only get a new token by
running devicectl launch --terminate-existing, wiping the in-app QA state at
every route blip.

A route failure now goes through recoverTunnel(): re-select the device with
the bootstrap rules, re-resolve the tunnel address for the pinned UDID, check
the unauthenticated /healthz owner, then probe /state/snapshot with the cached
bearer. Outcomes:
- bearer accepted: keep the session (new address if it moved)
- 401: full bootstrap from the fresh boot token (app was relaunched)
- app confirmed absent: bootstrap, which launches it normally once
- different device or an address devicectl cannot tie to the UDID:
  bootstrap; the old bearer is never sent there
- route still down with the app running: surface the error, keep the session

Concurrent failures on one tunnel share a single refresh. Mutations whose
response was lost are still never replayed. The CLI wiring moves into
deviceTunnelSource() so tests drive it against a simulated device.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(ios-qa): a failed boot-token write is loud instead of silent (#1837)

StateServer.start() wrote the boot-token file with try?, so a failed write
left nothing on disk and no trace anywhere, and the daemon then relaunched the
app trying to mint a token that could never be written. The write is now a
do/catch that logs the path and error (never the token), logs a NOT READY
bootstrap line, and reports boot_token_error on /healthz. bootstrapTunnel
reads that field and returns boot_token_unavailable naming the device-side
cause instead of running --terminate-existing.

Also drops the stale comments claiming the token was written to os_log.
Template and fixture stay byte-identical.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(ios-qa): type the iPad acceptance test's mDNS stub

ResolveImpl returns Promise<string[]>; resolving null tripped the test-code
typecheck ratchet. A rejected lookup exercises the same resolve_failed path.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(ios-qa): iPad support, GSTACK_IOS_TARGET_UDID, route-drop recovery, known limits

- iPhone-only description, triggers, and prerequisites now cover iPads, and
  say how to pick one device with GSTACK_IOS_TARGET_UDID.
- Phase 2 no longer tells you to capture a boot token from os_log (it has
  not been logged for many releases); it describes the 0600 tmp/ file and
  the NOT READY signal (#1837, #1735).
- Source-control guidance for the generated DebugBridge/ package (#1735).
- Failure table: route drops keep the session, multiple_devices, unwritable
  token file, and the one relaunch a daemon restart still costs.
- Known limits: SwiftUI DragGesture and /swipe on iOS 26 (#1975), the
  iOS 26.3.1 /elements data from PR #1755, unverified iPad Stage Manager.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: iOS QA how-to and skill index cover iPads and route-drop recovery

The how-to still said the boot token was readable from os_log and that a
route drop re-bootstraps; both are now accurate, with iPad selection,
GSTACK_IOS_TARGET_UDID, and three new failure rows.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* v1.91.37.0 fix(ios-qa): iPads work, a route drop no longer relaunches the app, loud boot-token write

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(ios-qa): keep issue numbers out of model-read skill text

The archaeology lint rejects issue references in generated SKILL.md text;
the Known limits entry keeps the iOS version and drops the number.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* chore: re-slot to v1.91.38.0 (v1.91.37.0 claimed by #3075)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(ios-qa): device-change recovery test without an IPv4-mapped address

The Windows lane could not reach a fake StateServer through
::ffff:127.0.0.1, so the iPad half of the device-change test timed out. The
fake device now swaps identity on its one ::1 listener, which is also closer
to production: every StateServer answers on the same port.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: loulanyue <260355617@qq.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-08 07:02:02 +00:00
f67c478f05 v1.91.34.0 fix: /autoplan on current Claude Code and long sessions, quieter pre-push and sync guards, gstack-post, gstack-doctor (#3069)
* docs: Oct 7 follow-up wave plan (pre-review)

* docs: Oct 7 follow-up wave plan, reviewed through /autoplan

* docs: Oct 7 wave plan, record final gate decisions (UC1 accepted, UC2 rejected)

* docs: credit #1822 to @Mike-E-Log and #1743 to @habassa5 (v1.64.0.0)

The v1.64.0.0 CHANGELOG entry credited the extension token fix (#1822)
and the polyfill exited promise (#1743) to the wrong handle, and the
extension-sender-auth test header repeated the first slip. Commit history
is not rewritten.

Fixes #3054.

Co-authored-by: Mike Ilog <48659643+Mike-E-Log@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(context-restore): offer newer checkpoints from nested repos and task worktrees

/context-restore could present an older checkpoint as the latest with no
error in two layouts:

1. A session started in a non-git parent saved from a nested repo, so the
   checkpoint went to the nested repo's bucket. Restore in the parent now
   lists checkpoints of other buckets whose project_root lies below the cwd
   (BELOW_CWD; the 200 newest are read, and from $HOME only each project's
   newest). /context-save also leaves a JSON pointer (checkpoint path, bucket,
   project_root) in the starting directory's bucket when $CLAUDE_PROJECT_DIR
   is set and files elsewhere; restore lists those as POINTER, in or out of git.
2. A plan checkpoint on the main tree's branch shadowed a newer checkpoint
   saved in a ticket worktree. When another branch holds a newer checkpoint
   for the same task (a worktree below the main tree, read from the new
   worktree: stamp; equal normalized titles; or a shared #123 / ABC-123
   token in titles or branch names), restore prints NEWER_TASK and the skill
   shows both and proposes the newer one. Generic titles (wip, checkpoint,
   save, notes, todo) never match on title alone, and the worktree rule
   applies only when restoring from the main tree, so sibling worktrees
   still never shadow each other.

The branch-first ordering itself is unchanged.

Refs #3065.

Co-authored-by: Thomas Gernhardt <t.gernhardt@softwareprojekt.net>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(redact-prepush): list each MEDIUM finding with rule, file line and fix

The pre-push hook printed only a MEDIUM count, which nobody could review
without re-running the engine by hand. It now prints one line per finding:
rule id, file and file line, and a fix, never the matched value.

Line numbers come from the hunk headers (combined --cc headers included), so
a finding in a later hunk or past a scan-chunk boundary points at the real
file line. Findings from the per-commit catch-up path name the commit whose
file line it is, so content removed before the pushed tip is still locatable.

Plan items: CEO-7 (summary half), DX-9 (fix lines), ENG-8 (line mapping).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(redact-prepush): stop flagging your own email and already-public authors (#3060)

The hook never passed the engine's selfEmail / repoPublicEmails, so every
push that added the pusher's address, or a commit author's, raised a MEDIUM
pii.email warning for an address the destination already shows.

The hook now reads `git config user.email` and one bounded `git log` of
author and committer addresses, raw and mailmapped (%ae %aE %ce %cE): the
history of every pushed tip (those commits become public with the push),
each remote tip being replaced, and, for a new branch or an unknown remote
tip pushed to a configured remote, that remote's own tracking refs. A URL or
unknown destination never borrows tracking refs, and another remote's
history never counts.

The read stops at 50,000 commits (the newest authors are used) and 5 seconds.
An error or the timeout passes no history and, when an email finding is
listed, prints one line saying suppression was limited for this push. The two
inputs fail independently. HIGH findings and every other rule are untouched.

Controls: a stranger's address, an address only another remote knows, a URL
push next to a matching remote, and a first push of a repo with no history
all still report.

Plan items: A3, CEO-7, CEO-23 (A3 half), DX-9 (partial history), ENG-8
(history scope, mailmap).

Co-authored-by: akmandhania <143462686+akmandhania@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(redact-prepush): per-address allowlist for pii.email (gstack.redact.allowEmail)

`git config --add gstack.redact.allowEmail <address>` allows that address for
MEDIUM pii.email findings only, so a team alias or support address that has
to be in the repo stops warning without reaching for --no-verify. It never
affects HIGH findings or any other rule, and the pii.email fix line names it.

Controls: a different address still reports, and a HIGH secret next to an
allowed address still blocks.

Plan item: DX-17.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(troubleshooting): pre-push pii.email findings, exemptions and allowlist

Plan item: DX-12 (pre-push touchpoint).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(redact-prepush): assemble fixture addresses at runtime

The email fixtures were literals, so this branch's own push listed 47
pii.email findings in its test file. Build them from a helper instead, the
way the neighbouring tests build credential and IP fixtures.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(browse): offer the user's signed-in tab, name silent aside repl failures

Rules (rendered into every browsing skill, kept short):
- Rule 1: before the first openTab, offer the user's tabs on the target
  origin (title and origin only; the rest of listBrowserTabs() stays
  private) and attach only after the user confirms one.
- Rule 4: a second sign-in wall after a confirmed sign-in means the session
  is bound to the tab or URL; offer that tab instead of another sign-in.
- Rule 6 no longer suggests an early return; rule 7 says a fast [ok without
  GSTACK_STEP_OK is an abort.

Cookbook (browse, devex-review): a "Silent failures" list (evaluate returns
only serializable values, no top-level return, locator(sel).press(key), an
empty DOM read is about the selector until the screenshot and the request's
response say otherwise, read toggle state before clicking) and a signed-in
tab recipe that prints only target-origin tabs and never closes the user's tab.

Parity caps for design-consultation, land-and-deploy, qa and the browse
skeleton are raised to the measured sizes, with notes.

Refs #3063, #3064.

Co-authored-by: Thomas Gernhardt <t.gernhardt@softwareprojekt.net>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(codex): run gstack-codex-probe as a command, one subcommand per probe

Generated skills now execute bin/gstack-codex-probe (select-model, check-auth,
show-sandbox, check-sandbox, probe-model, check-version, show-first-use-notice,
run-with-timeout, log-event, log-hang, help) and capture its KEY: value lines,
so no helper state crosses into the calling shell and the preflight works from
bash, zsh or sh. select-model keeps --model > GSTACK_CODEX_MODEL > config.toml
precedence; run-with-timeout supervises with the existing gtimeout/timeout/
watchdog chain and returns 124 on a deadline; an unknown subcommand exits 64.

Sourcing keeps working for skills rendered before the upgrade and prints one
deprecation line naming /gstack-upgrade; it and the zsh self-locate block are
removed in a release on or after 2026-10-21 (DX-18). Parity caps raised with
measured notes for the longer capture lines.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(gstack-post): one argv-only, redaction-scanned write path for PR and issue text

bin/gstack-post posts a PR/MR or issue comment, a review-comment reply, a
PR title or body, a new PR/MR or a new issue from agent-written files. Every
value reaches gh/glab as its own argv element in --flag=<value> form or on
stdin, never through a shell string, and a target is a number or a URL of
this repository on the same host.

The helper owns the redaction scan: it reads each file once, scans those
exact bytes (visibility from config, then gh/glab; the pusher's own email
suppressed) and sends the same bytes. HIGH is refused with exit 1 and no
confirmation can post it. MEDIUM exits 2 with RULE:/LINE:/PART: lines and a
TOKEN: digest of the bytes, destination, operation and findings; only
--confirm <token> for those exact bytes posts, so any edit needs a new
confirmation. gh/glab failures exit 3, usage errors 64. It also carries the
projectCards REST fallback and the title read-back that /ship did by hand.

The logic lives in lib/gstack-post.ts behind an injectable runner, so its
tests run in the Windows free lane; the stub-gh executable test is POSIX.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(autoplan): guard verifies dispatches on Claude Code 2.1.29x (schema strip, unflushed call, named causes)

Cause A: Claude Code journals the model's raw Agent input but hands
PreToolUse the schema-parsed input; with the fork-subagent gate on (the
interactive default in 2.1.292) run_in_background is dropped, so the journal
record never deep-equaled the payload. nativeToolInput now drops only that
documented strip for Agent. Agent input is bound on both paths: keys limited
to prompt, description, subagent_type (general-purpose) and
run_in_background; a model override or any other key is denied.

Cause B: when a ready journal lacks the current call, the guard evaluates
the payload placed after the last journaled record instead of polling for a
record that may arrive only with the tool result. A phase transition counts
a report only when a later journaled record follows it (and, on the journal
path, it is not in the current call's message). One batch rule covers both
paths; a background reviewer keeps the invocation armed until its
completion notice.

Every outcome now has a stable reason code, disposition and anchor
(autoplan/bin/guard-reasons.ts). UC1: Claude-Code-caused states
(unrecognized shape, journal lag, rewritten journal, oversized record,
unchecked-version schema strip) allow with a visible warning; integrity
failures stay denials. No denial advises a bare retry. Every guarded
decision appends one content-free line to analytics/autoplan-guard.jsonl,
trimmed to 1,000 lines past 256 KiB.

Co-authored-by: Jared <182125291+yolo-jared@users.noreply.github.com>
Co-authored-by: crblabs <293123359+crblabs@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(autoplan): publish each phase report with a no-op call; foreground requests are conditional

Phase-close step 6 sends the report in a message whose only tool call is
the Bash no-op ': autoplan-published <phase>', and step 7 makes the next
guarded call in a later message, so the guard sees the report flushed
before the next phase entry regardless of Claude Code's journal timing.

Every site that asked for run_in_background: false now asks for it only
when the Agent schema offers it and says how to recover when a dispatch
runs in the background anyway (wait for its completion notice). Parity
caps rise by the measured bytes.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(ship,spec,review): PR and issue text goes out only through gstack-post

/ship: Step 18 writes the title into an agent-written file. An existing PR's
title is piped from gh/glab through gstack-pr-title-rewrite.sh --stdin (new),
so neither the agent nor a shell string carries it; a new title is written
with the file tool and its version prefix checked in the file. Step 19
composes the body as before and publishes with gstack-post pr-create, or
pr-body then pr-title, which scan the exact bytes they send. The separate
title and body scans, the hand-rolled REST fallback and the title self-check
are gone (the helper does all three). Title preparation moved into the
pr-body section, so the skeleton shrinks.

/spec files its issue with gstack-post issue-create and drops the pre-issue
re-scan; Greptile replies use gstack-post reply and pr-comment.
scripts/resolvers/redact-doc.ts loses its PR and issue sinks, and a test
keeps any template from rendering one again.

Tests that pinned the old scan-then-gh wiring now pin the gstack-post wiring;
the publication tests run the real compose and publish blocks against a stub
gh with the real helper (HIGH in title or body refused, MEDIUM token flow).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* ci: pin Claude Code 2.1.292 in the CI image

2.1.292 is the npm latest users run. Its interactive default turns on the
fork-subagent gate, which strips run_in_background from the Agent schema
and backgrounds reviewers; CI on 2.1.284 never saw the /autoplan guard
break. The image tag is a content hash, so every eval workflow picks up
the new image without a workflow edit.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(brain-sync): one drain lock for every writer to the artifacts repo

gstack-artifacts-init, gstack-brain-restore, gstack-gbrain-source-wireup
and the skill-start merge ran git in $GSTACK_HOME without the drain's lock,
so the drain could never tell an abandoned .git/index.lock from a live one.
They now take the same mkdir lock (bin/gstack-brain-lock.sh): the drain and
the skill-start merge skip when it is held, user-run writers wait for it
(GSTACK_BRAIN_LOCK_WAIT, default 120 s), and a child of the holder (the
drain's daily worktree advance, restore's wireup) inherits it by token.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(brain-sync): hold back only the flagged file, keep coupled artifacts whole

One file with secret-shaped text used to block every push, silently. The
drain now attributes scanner hits to files in one pass over the staged diff
(split on its diff --git headers), holds back each flagged file with the
rest of its coupled group, unstages it, and requires the remaining staged
diff to re-scan clean; anything short of that falls back to a full unstage
and status blocked. Held files stay queued and are re-scanned every drain.

- Status file gains held_count, drainable, last_drain_at, last_push_at and
  held[] (path, scanner rule, dependents, both fixes); new status held.
- Coupled groups come from an explicit map (decisions.jsonl and
  decisions.active.json): a queued member stages the whole group, a group
  stages only when its snapshot matches its log in the index, a skipped
  member stops the whole group.
- --unskip-file undoes --skip-file and queues the path again.
- An index lock older than 10 minutes is removed under the drain lock; a
  younger one, a failed git add, a failed commit (hooks included) keep the
  queue and report status error. A verified empty staged diff is the only
  "nothing to commit". git add runs once, batched.
- The scanner reads bytes, so undecodable content no longer crashes it into
  a false clean; matched text never reaches the status file.
- An empty-queue run with unpushed sync commits stays push_failed.
- Privacy-held duplicate records collapse to one per path; --status counts
  a spool too deep for a shell glob.
- --attention prints skill start's fixed attention lines from the sync
  status table in the usage header.

Co-authored-by: v639dragoon <matthewsbrickley@gmail.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(skill-start): ARTIFACTS_SYNC attention lines for a stuck sync

Skill start now prints one fixed attention line per sync-status-table row
that applies (held, blocked, push_failed, error, unknown, stale-push,
stale-drain), each naming gstack-brain-sync by absolute path. Values from
the status file are clamped (status code to the table, counts to digits,
timestamps to their shape), so the status message, file paths and scanner
output never reach the line. The preamble tells the model to surface the
line in one sentence and continue. Skill-start tests are generated from the
table in bin/gstack-brain-sync, and docs/gbrain-sync-errors.md carries the
same table.

Co-authored-by: v639dragoon <matthewsbrickley@gmail.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(outside-review): severity words in label position count as findings

The Oct 7 /autoplan run's Codex CEO and DX reviews labeled seven and eight
findings High/Medium without [Pn] tags, and the classifier printed
VERDICT: clean / FINDINGS: none for both. lib/outside-review-result.ts now
reads severity words in label position (Severity: High, a leading High: /
High — / [High], **High**, a table cell): critical/high block like P0/P1,
medium/low are advisory. Words in prose (high-level, low-risk, no critical
findings) never count. A review or structured response with neither tags,
labels nor an explicit no-findings statement is unverified, never clean
(DX-11, ENG-10). Design-direction proposals move to a new proposal gate so
they keep needing only their Recommendation line, and review prompts ask
for a severity per finding. Fixtures reproduce the CEO/DX label shapes, the
eng [P1] output, a table and prose negative controls; the stored Codex panel
keeps its verdicts. docs/troubleshooting.md explains how the verdict is read.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat: gstack-doctor readiness report and setup --status Codex row (B5)

bin/gstack-doctor prints one row per check without starting a skill:
install root (the registry rows behind ./setup --status), state root, Bun
against the floor, hook parse check, Codex mode and self-locate via
gstack-codex-probe subcommands, the cached model probe with its age
(--live runs the paid probe-model), artifacts-sync health from the status
file with the status code clamped, the browse bundle, Claude Code version,
the largest session journal and the last five schema-1 guard reason codes.
Rows are ok, warn, not configured or fail; only fail exits non-zero; fix
lines name helpers by absolute install-root path.

./setup --status gains a read-only Codex row (bin/gstack-codex-status.sh,
shared with the doctor) and ends with the doctor's absolute path.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: bug-report template asks for doctor output; README troubleshooting entry; doctor test in the Windows lane

The template gives the Claude Code doctor path and a lookup for other
hosts and names no bare gstack- command (asserted in
test/gstack-doctor.test.ts). The doctor test runs every script through
explicit bash argv, so it joins the curated Windows free subset.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs(troubleshooting): readiness check section points at gstack-doctor

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(question-tuning,plan-tune,gbrain): free text reaches helpers only through agent-written files

Question tuning no longer pipes the question summary through printf: a
registered id needs no summary, and for an unregistered id the agent writes
the summary to .gstack/tmp/qt.txt and passes --summary-file (new). The
helper resolves a relative path against the git top level, removes scratch
files under .gstack/tmp, and asks normally when the file is missing.

/plan-tune tune events carry the user's own words again: they go into a
file the agent writes and reach gstack-question-preference --write through
--free-text-file (new), still behind the injection filter and the
user-origin gate.

docs/gbrain-write-surfaces.md drops its heredoc templates for the
agent-written page file on stdin, matching the per-skill save block, and the
generic save block stops showing a quoted --content argument.

The tuning prose was tightened so every preamble stays within its size cap.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(evals): daily autoplan-schema-canary on the latest Claude Code

A periodic rule case installs the latest published Claude Code with
install scripts off, verifies registry signatures, and runs one headless
session with the fork-subagent gate on whose PreToolUse hook records an
Agent and a Read payload. Each payload must match its journal record under
the guard's own comparison, keep the journal's key set apart from the
documented run_in_background strip, and stay inside the Agent allowlist;
a background Agent's completion notice must keep the shape the guard reads.
evals-periodic.yml runs it daily on its own schedule and concurrency group
(read-only token); the weekly census also includes it. A free test proves
the comparison fails on an injected strip.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(doctor): read the artifacts sync held count from held_count, the field the drain writes

* fix(artifacts sync): the init and restore pre-commit hooks catch Bearer/Basic/Token-prefixed JSON auth headers like the drain does

* feat(greptile-early): detect Greptile, ask once on public repos, open the early draft, wait for the review

bin/gstack-greptile-early backs /ship's early Greptile PR:
- detect: a root .greptile/ folder (which takes precedence), greptile.json,
  or a Greptile comment on the repo in the last 90 days; triggerOnDrafts from
  the effective root config; repo visibility; public-repo consent. It prints
  the signal, that /ship will push and open a draft now, the 10-minute wait
  cap and the absolute gstack-config command that turns it off.
- consent yes|no: the public-repo answer, remembered per repo in the
  project's state.
- open: opens the draft through gstack-post with a body saying /ship opened
  it early, and posts one @greptileai comment through gstack-post when the
  config does not set triggerOnDrafts: true. An open PR is left alone.
- wait: polls every 30 s for a completed Greptile check run, a Greptile
  review of the head commit or a summary naming it; each call returns within
  about 90 s and reports timeout 10 minutes after the PR opened.

gstack-config gains ship_greptile_early (default true, invalid values
rejected): on automatically whenever the repo uses Greptile, off per user.
The logic is in lib/greptile-early.ts behind an injectable runner, so its
fixture-repo tests run in the Windows lane; an end-to-end test drives the
real helper and gstack-post against a stub gh.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(ship): early Greptile PR at Step 6.5, bounded wait at Step 10 (#3020)

When the repo uses Greptile, /ship pushes and opens a draft PR right after
its free tests pass (new lazy section greptile-early, Step 6.5), so Greptile
reviews while Steps 7-9 run. A public repo asks once, remembered per repo;
declining keeps today's order. Step 10 waits for that review with
gstack-greptile-early wait and takes the existing unavailable route on a
timeout, never a claim of zero comments. Step 18 replaces the provisional
title, Step 19 marks the draft ready unless a draft was requested, and a
/ship that stops after Step 6.5 leaves the draft open with one comment
saying why.

ship's union cap rises to 1.5275 with the measured note (B4 +836 bytes,
B2 +4,732 bytes); the step-numbering guard allows 6.5; ship goldens are
refreshed for B4 and B2.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(autoplan): the publication no-op is 'true autoplan-published <phase>'

Measured on Claude Code 2.1.292 in a default-settings interactive session:
a bare ':' Bash command asks for approval ('This command requires
approval'), and after a background reviewer's completion notice starts a
new turn the skill's allowed tools no longer cover it, so the phase-close
no-op prompted the user. 'true' with arguments runs without approval and
prints nothing. The guard only needs a later journaled record, so the
command changes everywhere it is named.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(evals): live autoplan-guard-pty case on the pinned Claude Code

A gate-tier rule case (one trial, haiku) drives an interactive PTY
session with default settings through a project skill that carries
/autoplan's own hook block: init, Phase 1 entry, a CEO reviewer dispatch
that sends run_in_background, a prepared close packet, the report
published with the no-op, and the Phase 2 entry in a later message. It
reads outcomes from the guard decision log (three verified allows) and
fails if the no-op raises a permission card; other cards are approved and
printed.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(autoplan): bounded, append-tolerant owned-journal read (A2)

The publication guard no longer reads the whole parent journal or refuses
it at 32 MiB. lib/claude-owned-journal.ts streams the bytes present at open
to the last complete line (pass one), keeps a content-free index per record
and decides ownership, ancestry and causal order on it with the same checks
and reason codes as before. Pass two reloads in full only what the
evaluation reads (autoplan/bin/guard-journal.ts): every literal init and its
result, the current call, and inside the invocation window (from the latest
human turn at or before the bound chain's first init) guarded and
close-packet Read results, structured Bash results, error results, assistant
text after the first close Read and active-plan mutations, plus the earlier
Read a dedup reply cites even before the window (ENG-4).

- lib/claude-journal-records.ts: per-record event extraction, record
  metadata and the causal-order checks, shared by readPlanCountTranscript
  and the bounded reader. The batch reader keeps its whole-file read and cap.
- Appends after open never fail a read; same device and inode and no
  shrink are required (identity). A second read in one hook invocation must
  hash bytes [0, first size) the same, or it is `rewritten` (UC1 unverified);
  the unrecognized-shape double read uses that prefix hash. `changing` is
  removed (REMOVED_CODES; its anchor stays).
- OWNED_RECORD_MAX_BYTES (32 MiB): only one record over it is too_large.
  OWNED_RETAINED_MAX_BYTES (32 MiB): over it is the new oversized_invocation
  fallback denial. GSTACK_TRANSCRIPT_TEST_MAX_BYTES and
  GSTACK_TRANSCRIPT_TEST_RETAINED_BYTES can only lower them.
- ENG-5: evaluation groups uses and results by tool id once (no per-tool
  rescans).

CEO-25 / ENG-5 spike (120 MiB synthetic, 31,242 records, 4 vCPU Linux,
Bun 1.4.2): a full streaming rescan (parse every line, small index, sha256)
took 254-326 ms at 147-161 MiB peak RSS; a pass-one index cache under the
state root (load cached index, verify a 64 KiB tail hash, parse the tail)
took 26-29 ms at 47 MiB. The rescan meets the 1 s budget, so the cache is
not adopted: it would add trusted persistent state and invalidation for no
budget need. A backward tail read cannot prove competing_root or cycle, so
it was not a candidate.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(autoplan): journal-read benchmark and its dispatch-only workflow (CEO-2, CEO-6)

scripts/measure-journal-read.ts builds two synthetic 120 MiB parent journals
around a real /autoplan invocation (mixed: ~28k records, most bytes inside
the invocation; large-invocation: ~89k records with a current invocation of
many small records), keeps appending while the guard reads, and measures one
hook call in a fresh process (wall ms, peak RSS). The workflow runs it on
ubuntu-latest and macos-latest on workflow_dispatch only.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(evals): autoplan-long-session, a resumed >100 MiB session enters Phase 1

CEO-15 with ENG-6's observer: a real session is compacted, its journal
padded past 100 MiB with owned records before the compact boundary, and
the resumed interactive session runs the scripted phase boundary; the
guard decision log must show a verified Phase 1 allow. A free test proves
that observer on a padded journal through the lower-only bound seams
(the PTY counting reader refuses the same journal), with a sidechain
control that must deny.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* v1.91.34.0: CHANGELOG, TODOS, version; harness decision for test-pr-profile; ship title-step test reads its new section

* docs(changelog): use an example.com address in the allowEmail example

* test: assemble GitHub-token fixtures at runtime in the new brain-sync and skill-start tests (CI credential scan)

* fix: integration repairs across the wave (Step 6.5 fence, printed helper paths on Windows, probe usage without a heredoc) and tests pinned to the moved title and gstack-post contracts

* test(doctor): the healthy fixture builds browse.exe on Windows, the name setup uses there

* test(shared-libs): recognize Claude Code 2.1.292's internal git probe prefix (format.pretty and per-hook disables), with its CI receipt

* fix(ship-measure): report lists v1 measurements as an older format instead of crashing

---------

Co-authored-by: Mike Ilog <48659643+Mike-E-Log@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: Thomas Gernhardt <t.gernhardt@softwareprojekt.net>
Co-authored-by: akmandhania <143462686+akmandhania@users.noreply.github.com>
Co-authored-by: Jared <182125291+yolo-jared@users.noreply.github.com>
Co-authored-by: crblabs <293123359+crblabs@users.noreply.github.com>
Co-authored-by: v639dragoon <matthewsbrickley@gmail.com>
2026-10-07 23:29:05 +00:00
Garry Tan 55f8151fbe v1.91.30.0 fix: honest eval failure reports, Bun floor with safe auto-update, make-pdf and /review on Codex (#3033)
* docs: approved reliability follow-ups plan (Oct 2026)

* D1: score overlay wrong answers as comparison results (contract v4)

A completed answer that fails the fixture's output check is now a valid
measurement with taskCorrect false and answerError set, in either arm.
Only execution, scope and metric failures invalidate a trial. ON
correctness still gates the case, so an OFF wrong answer stays in the
comparison. OVERLAY_CONTRACT moves to v4; v3 verdicts are not
reinterpreted. Replays the 37198445662 sonnet trials (OFF-0 wrong).

* C4: score auq-matrix substance with a 3-sample judge panel

Each skill keeps one native capture in the same execution; the
recommendation-substance grader now runs as a JUDGE_PANEL_SAMPLES panel on
that capture and gates on the mean against the unchanged minimum of 4. A
failed sample fails the panel without resampling. Approved at the gate
2026-10-04 for censuses after this change.

* E1: one Bun version source; setup refuses below the 1.3.3 floor, updater checks before advancing

bin/gstack-bun-version.sh holds the security floor (1.3.3) and the tested
version (1.4.0, the CI pin and engines.bun). setup refuses Bun below the
floor before writing anything, warns between floor and tested, warns and
continues on an unparseable version, and prints a 1.4.0 install hint.
Prereleases sort below their release (semver).

gstack-session-update now fetches, reads the incoming release's floor and
checks the bun setup will run before fast-forwarding to that exact
revision; below it the update is held with reason bun-too-old and a
resume command, leaving the revision and installs untouched.

Fixture copies of setup (team-mode, claude-code migration) now include the
sourced file; the slow-git lock test sleeps on fetch, the updater's
network step.

* E3: record the owned Chromium's PID over CDP so the #2709 reap runs on Playwright 1.62

Playwright 1.62's Browser has no process(), so chromiumProcInfo was always
null and browse stop never reaped a surviving headless Chromium. The owned
headless launch now reads the browser PID from CDP SystemInfo.getProcessInfo
(bounded at 2s; a hung or failed call logs once and records nothing). The
reaper's start-time and cmdline identity checks are unchanged, so a recycled
PID or an externally owned browser is never signalled.

* A1: ship docs gate records post-child hashes with gstack-docs-candidate, saves each record once

Stored transcripts (37198445662 gate-census-2) show the parent's reads of
release files take ~1 s; the parent's own model time dominates (completion
268 s of 392 s, store ~270 s of 387 s), and 45-78 s of it is retyping hash
maps, child output and records into several artifacts after the child
returns. The child contract is unchanged.

* A2: /plan-ceo-review sends the mode handoff line before any other tool call

plan-ceo-mode-routing's HOLD SCOPE session spends its 240 s post-selection
window on model work: 37176837432 skipped the handoff helper and paraphrased
the mode, 37198445662 ran it, then wrote the plan, loaded sections and probed
Codex for 114 s before the posture chat. The handoff line is the posture.

* B3: accept wrapped unchanged rows in the cropped Edit preview

PR run 37176835584 timed out with the floor's owned Edit menu on screen:
unchanged diff rows soft-wrap under their gutter with a blank marker
column, and a trimmed empty unchanged row keeps only its number. The
parser accepted wrapped +/- rows only, so it never verified the owned
edit. Both forms are now parsed and still compared with the exact source
lines; the captured viewport is replayed with negative controls.

* A3: record per-phase headroom diagnosis for docsync, plan-mode-no-op, mode-routing and gate slice 11

* E4: /review Step 1 keeps going when the base fetch fails and reports stale base coverage

Under Codex's read-only .git, 'git fetch && merge-base && diff --stat'
stopped at the fetch, so the empty output read as 'Nothing to review'.
The fetch is now separate, prints BASE_REFRESH fresh or stale with the
local base revision, and the review states stale coverage.

* D2: reword the format-approach question prompt

Both plan-ceo-review-format-approach refusals in census 37195203538
(Fable 5.1 model_refusal_no_fallback, category reasoning_extraction:
"duplicating model outputs") were anchored on the session prompt, whose
capture instruction asked for the "verbatim" question and "the exact
prose you would have shown" in a "format-capture test". The approach
case now asks for the same question in the same file as the step's
output, without that wording. The other three plan-format cases keep
their unchanged prompt (no refusals in 72 trials).

* C2: /qa defines its outcome filename fields; QA report templates name the measured timing fields

Low judge samples in failing panels named both: qa workflow (37186854666)
'an undefined {user} in the outcome filename', and qa-only workflow
(37198445662) 'template asks for Duration / durationMs totals while
reporting.md mandates Probe budget and Guarded command time'. The qa
union stays under its 1.11 size ratio (93292 of 93294 bytes).

* 0.1: session ledger written by the claude -p, Agent SDK, PTY and Codex runners

Each armed model session appends one best-effort line to
$GSTACK_EVAL_DIR/session-ledger.jsonl: a stable session key, the timeout it
armed, monotonic elapsed time, its end reason (completed, session_timeout,
observer_timeout, api_error, refusal, aborted, error), one sanitized evidence
line and, for stream-JSON runners, a compact liveness summary (longest
in-request silence with nothing outstanding, last event kind, tool open at
end). Partial messages now stream for every claude -p session and for SDK
sessions without a custom query provider (runSharedInteractive opts in); their
rows feed only the summary, so transcripts keep their size unless
publicStreamDiagnostics is set. Structured refusal and API-error signals win
over phrases, and tool results are never read for them.

* 0.2: failure_cause beside failure_class, from runner facts and the session ledger

One pure failureCauseOf() beside failureClassOf() sets failure_cause
(contract > pre_turn_infra > api_error > refusal > provider_stall >
session_timeout > observer_timeout > assertion > unknown; no machine-set
'detector') with one bounded evidence line, and failureDetailOf() keeps a
Bun matcher's Expected/Received or a judge's failing dimensions in
failure_detail. Both record builders (classifyTrialShard, junitCensus) call
them and attach the trial's ledger sessions. failure_class, error and
TRIAL_ERROR_MAX are computed exactly as before; the new fields are optional,
bounded and validated only when present, and a frozen copy of the v1 reader
accepts every new record. A provider stall needs partial messages and
STALL_WINDOW_MS (120 s, eval-budgets.ts beside HEADROOM_WARN/FAIL) of
in-request silence with nothing outstanding; it stays a failure under
EVAL_POLICY v1.

* E4: fingerprint under a read-only .git via a private object dir owned by review-log

Codex's sandbox mounts .git read-only, so gstack-wtree's git add -A could not
write blobs and /review lost its start capture. gstack-review-log now creates
a private 0700 object directory under TMPDIR, passes it to gstack-wtree (which
writes new objects there with the common-dir object store, resolved before
exporting, as an alternate, so linked worktrees work), exposes it to the
bindReview/checkSharedLibsReuse consumers that read the tree and blobs, and
removes it on EXIT/INT/TERM after they finish. gstack-wtree prints one stderr
line and exits 1 on a failed write or unwritable TMPDIR instead of printing a
partial hash; review-log no longer swallows that line. Standalone gstack-wtree
keeps writing to the repository store so git diff between saved snapshots
(/ship) still works.

* 0.3: red lines name their values, evidence and an after-a-repair command; one published-text sanitizer

Each red line in the census report keeps its stored error and adds the
machine cause with its evidence, the failure_detail values (Expected/Received,
or each failing judge dimension with its mean, sample count and the sample
sentence naming it), the slice artifact and shard directory that hold the
transcript, a fetch command (eval:pass-rates --run <id> --case <id>), and
'after a repair:' with a command that selects only that case (E2E ids via
--case, judges via their selection and an exact test-name filter). A
session that completed and failed a check says so instead of 'assertion
(success)'. Report text that reaches a PR comment, issue or committed file
goes through scripts/lib/published-text.ts: control and bidi characters
stripped, credential-scan patterns redacted, mentions neutralized, HTML and
link brackets escaped inline, blocks in a fence longer than any backtick run,
length capped. Verdicts and collector verdict objects are unchanged.

* E2: one make-pdf resolver for readiness and every $P block on env-var hosts

make-pdf exported $P from its readiness block, which Codex and the other
env-var hosts lose between blocks. binaryAssignment gains 'make-pdf': the
MAKE-PDF SETUP readiness check and the runtime prelude both use it, so every
block that runs $P derives the same make-pdf/dist/pdf binary, with
MAKE_PDF_BIN (dev builds) winning when it is executable. Claude output keeps
its repo-local-first probe; only the false 'subsequent blocks' comment
changed. Root plus P=... fits the 400-byte prelude budget; the fixed
MAKE_PDF_OVERRIDE segment is the one exemption, named in runtime-root.ts and
the conformance test.

* 0.1: eval:pass-rates --headroom, --reds and --run report views; 85% headroom alarm in --gate

A new pure module, scripts/lib/eval-history.ts, reports per case the slowest
armed session against the timeout it armed (censored when it timed out,
insufficient below 3 samples, unknown without a session ledger, with the
case wall shown separately as an upper bound), the census critical path from
the slice jobs' timings, the verdict red ledger per census by failure class
and machine cause with shrunk per-case rates, and the all-green probability
(an approximation: the product of 1 - p_i, with an interval). --run <id>
lists one census's reds with their values and history and downloads only the
slice artifacts those reds name, reporting any over 64 MB instead of skipping
them. eval-flake-rank.ts only routes flags: --help works offline, an unknown
flag or case id exits 2 before any fetch, --case narrows the JSON and the
alarms, every view prints its run/branch scope, and --gate also alarms on a
session above HEADROOM_FAIL (85%) of its budget.

* E5: GSTACK_DESIGN_IMAGE_MODEL override; the live model check always tests the defaults

The image_generation tool model gains an env override validated against a
conservative gpt-image-<version> pattern; a bad value fails before any
request and the error names the variable. Pairing and rejection hints name
the tool model actually requested. design/scripts/live-model-check.ts now
ignores GSTACK_DESIGN_MODEL and GSTACK_DESIGN_IMAGE_MODEL (and says so), so
an override cannot mask a retirement of a default, and its exit 2 (no key)
says nothing was checked.

* C3: /deslop-shared-libs names a concrete risk for each recommendation, never 'none'

Both shared-libs-opportunity-judgment reds (37193478719 t3, 37195203538 t1)
failed adoption_and_risk because the top recommendation said 'Main risk:
none found inside the repo' / 'none in behavior'. The skill asked to 'name
the main risk or uncertainty' without saying what counts.

* B4: --case selects loop-registered cases; an unknown id exits before any process

caseSelection() resolves the one paid file that registers a case and how a
trial selects it: by its literal Bun test name, by the id a registration loop
names its test at runtime (coverage audits, TPA, journeys), or by running the
whole file when it registers no other case (plan-mode-no-op, whose test names
are not the id, so the old name pattern ran nothing). A whole-file case runs
one file shard per trial and is judged by shard status, as CI judges it.
--list prints the selection. Every E2E_TIERS id now resolves except
carve-section-loading, which fifteen files register (CI runs each as its own
shard) and which still fails with the owning files named.

* 0.3: after-a-repair runs the shard file when --case cannot resolve a case

A case that several files register (carve-section-loading) cannot be selected
by --case; its red line now prints the bun test command for the shard file
that went red, so every printed command selects exactly one case.

* C1: pre-register workflow-judge control bundles for qa, qa-only, review and setup-deploy

Positive (production bundle), negative (headings only) and one targeted
execution-blocking defect per judge, one 3-sample panel each, built with the
eval's own reader, prompt builder, options and floors.

* F: cost_known: a trial whose harness captured no billing says so; totals never hide it

cost_usd stays a finite number. Trial and eval records gain an optional
cost_known boolean (absent means known, so old records still parse; the
validator accepts only a boolean). Session-ledger rows record whether the
session's terminal result carried total_cost_usd; PTY and Codex sessions
never do, and SDK captures without a terminal result do not. A trial is
cost-known only when it has eval records and every record and session
captured billing, so JUnit-only PTY cases (125 of 298 trials in run
37198445662 recorded 0) now read cost unknown. The census headline prints the
known sum plus the unknown count, never a bare total over unknown costs, and
no total when every cost is unknown; the history import keeps the flag.
PTY transcripts are not priced: test/helpers/pricing.ts estimates exclude
cache writes, so they cannot give the billed cost.

* B1: replay corpora for the five repeat-offender cases

shared-libs-plan-callers, plan-ceo-section-loading, auto-decide-preserved,
plan-design-review-plan-mode and ship-docsync-late-result each get an
inventory of every wave census in scope (all artifacts still available)
and minimal, redacted detector or actor inputs replayed through the real
detector in the free suite: census passes, every distinct census red, and
derived negative controls labelled as derived. Fixed false reds stay as
expected passes with their history; the two genuine auto-decide product
failures (37176837432, 37179171083) stay refused. A contract test bounds
each entry at 64 KB, scans it for HIGH credentials, requires a pass and a
refusal per case, and reports entries whose series identity is stale.

Building the auto-decide corpus exposed a latent native detector misread:
a bold 'Mode application and rationale.' heading was parsed as a
withdrawn Mode field (37186854666). A field status now ends at its
sentence.

* B2: read approved stale-fill decisions from more valid ledger records

The structured ledger reader is authoritative before the prose fallback,
but it missed two valid approved records in passing censuses
(37186854666: a camelCase writeProfile subject; 37195203538: a
slash-joined 'D1 / STALE-FILL' row id), which then rested on the prose
detector that caused both historical reds. Both forms are accepted; an
unresolved row, an unrelated decision, the copied fixture plan and
non-write camelCase words stay refused.

* C1: record workflow-judge control results (negatives separated; qa and review miss a removed step)

* C2: record every low workflow-judge sample in the wave censuses with its disposition

* 0.3: published lines cannot break a fixed fence

evals.yml wraps collector-outcomes failures in a fixed three-backtick fence
for the PR comment, so those lines (and eval:pass-rates output meant for the
tracking issue) go through sanitizeFixedFenceLine: cleaned like every
published line, with backticks turned into quotes. Inline published text
also neutralizes backticks.

* A1: fit the post-child record wording under ship's 1.428 parity size cap

* E4: inline the Step 1 base refresh to fit review's 1.199 parity size cap

* B2: inventory of gate and periodic phrase-only detector checks

docs/evals/detector-inventory.md lists every pass/fail decision in gate
and periodic E2E cases that rests on phrases in model free text, with
its structured source (if any), whether the contract is user-visible,
and a disposition: already structured-first, convert candidate, phrase
witness required, or no structured source. Convert candidates are
ordered by census red history.

* 0.2: failureCauseOf takes the stall window instead of importing it; the PTY ledger never throws

eval-store.ts imported STALL_WINDOW_MS from eval-budgets.ts, which free tests
replace with mock.module stubs (review-finalization-budget, office-hours,
qa-fix-loop, qa-only), so loading eval-store failed there. failureCauseOf is
now dependency-free: the record builders pass STALL_WINDOW_MS. A PTY session
whose handle cannot show its text (test doubles) records no panel instead of
throwing from runPtySession's cleanup. The 37186854666 replay also runs
through classifyTrialShard.

* A2/auto-decide: the CEO handoff chat repeats the helper's printed line verbatim

Claude Code 2.1.284 collapses the helper's Bash output ('Ran 1 shell
command'), so the 'Auto-decided review mode → …' line reaches the user only
through chat. In lane D's auto-decide-preserved corpus every passing capture's
chat starts with that line, and the reds paraphrased it ('HOLD SCOPE was
auto-selected…', 'Mode decision done…', "I'm defaulting to HOLD SCOPE…").
The template now says verbatim and why.

* C2: record the judge panels rerun on the final ship and review wording

* integrate: touchfiles and module-size ratchet entries for the wave's new modules

GLOBAL_TOUCHFILES gains session-ledger.ts, published-text.ts and
auq-substance-panel.ts; bin/gstack-bun-version.sh joins every entry that
lists setup or the updater; auq-matrix stays a rule case with its C4
evidence beside E2E_KINDS; eval-history, published-text and session-ledger
are registered new modules.

* E1: BROWSE SETUP install hint pins the tested Bun (1.4.0)

* E2: quote "$P" in make-pdf commands so install paths with spaces work

* E1: /gstack-upgrade checks the incoming Bun floor before pulling

gstack_bun_incoming_hold (bin/gstack-bun-version.sh) is now the one
pre-advance check: the auto-updater calls it before its fast-forward and
/gstack-upgrade's git path calls it after fetching. Below the incoming
release's floor the upgrade stops with BUN_TOO_OLD and the checkout,
VERSION and installs stay untouched. Vendored installs were already safe:
setup refuses before writing and the swap restores the previous install.

* 0.3: only main posts or closes tracking issues; reports carry alarms, red ledger, headroom and all-green

A branch-dispatched periodic or marathon run writes its report to the step
summary and a report artifact; the upsert and close steps run only on
refs/heads/main. The periodic body now keeps every pass-rate alarm line (the
old grep kept only the header), adds the red ledger and session headroom read
with an explicit --branch, the all-green estimate and the triage guide link,
and routes report text through scripts/lib/published-text.ts. The marathon
lane gains the matching close-on-green step.

Migrated: evals-workflow-wiring pinned report-summary.md in the upsert step;
the body is now written by 'Write the census report'.

* C3: prosons neutral-posture detector ignores a negated "taste call"

Both plan-review-prosons-neutral-neg reds (37179171083 t3, 37193478719 t3)
recommended A and closed with "a coverage call, not a taste call"; the
old /taste call/i matched the negation. The detector moves to a helper so
the two census captures replay free as negatives, with positive controls.

* B2: credit the CEO mode handoff only when the chat shows its line

Claude Code 2.1.284 collapses the gstack-ceo-mode-handoff Bash result, so
the printed AUTO_DECIDE line is user-visible only when a later chat message
of the session begins with it. The native detector now requires that
witness; the helper's tool result locates it but no longer replaces it, and
a generic mode statement cannot stand in once a handoff ran. Census
37182865432 is relabelled a genuine miss (expected: refused): the chat said
"I'm defaulting to HOLD SCOPE…" and "Mode decided: HOLD SCOPE". Corpus
replay: 5 credited, 3 refused. ceo-mode-handoff tests migrated to the
witness rule (deliberate behavior change).

* test: baseline the wave's approved detector and harness replay tests

B1 corpora, B3, C3, C4 and D2 replay captures through paid-case detectors,
actors or fixtures (test code by design); each entry carries its reason.

* E6: live Codex multi-block periodic case in the host-run Codex job

test/codex-e2e-multiblock-live.test.ts runs ./setup --host codex once from the
job's checkout into a fresh HOME and CODEX_HOME (auth.json copied from the CI
login home), asserts codex debug prompt-input lists the gstack router as a
real file, seeds one learning, and runs /learn's Show recent block through
codex exec: that later block must root itself through its prelude, run the
installed gstack-learnings-search and print the seeded learning; no block may
report a missing install or an unresolved gstack helper path.

The file joins CODEX_CI_FILES so the planner gives it to the host-run Codex
job. paid-test-durations.json carries a 180 s packing estimate (refreshed by
--write-durations after the first census) so the plan keeps every slice
within max-parallel. Oracles live in test/helpers/codex-multiblock.ts with
free replays in test/codex-multiblock.test.ts. Registration (E2E_TOUCHFILES,
E2E_TIERS periodic, E2E_KINDS rule) is the integrator's.

* E5: design live-model check as a periodic case

test/skill-e2e-design-model-smoke.test.ts runs design/scripts/live-model-check.ts
(default models only) under a temp HOME. Without OPENAI_API_KEY the case is a
Bun skip that the census lists as SKIPPED with no coverage credit; with a key,
exit 0 passes and any other exit fails with the script output (exit 2 under a
present key fails as 'nothing was checked'). paid-test-durations.json carries
a 90 s packing estimate. Registration (E2E_TOUCHFILES, E2E_TIERS periodic,
E2E_KINDS rule) is the integrator's.

* A1: docsync parent points children at a saved interface file

The /ship docsync fixture saved its observation interface only in the
parent prompt and told the parent to copy it into every child prompt,
about 30-40 s of parent generation per session (lane S, 37198445662).
docsSessionOptions now writes it to <home>/fixture-interface.md (0600) and
the parent tells each child to Read that file first. Fault-actor prompts,
which prepare already writes to a file, are unchanged. Fixture-only.

* A3: a JUnit case's recorded wall is capped by its shard's wall

junitCensus summed a case's JUnit test times as duration_ms; for
plan-mode-no-op's concurrent PTY tests that is 346 s inside a 176 s shard
(the "432-613 s" history). The case wall headroom shows is now
min(sum, shard elapsed), a true upper bound. Session headroom already
used the ledger's per-session clocks.

* A3: refresh paid-test durations from census 37198445662

Recorded 2026-09-29 estimates under-packed gate slice 11 (17 files
estimated 1078 s ran 1353 s; plan-review-report 76 s -> 198 s,
plan-ceo-plan-mode 37 s -> 112 s, safety-codex-consult 83 s -> 150 s).
Refreshed both tiers with --report --write-durations from the latest
census's slice walls. Old plan re-costed with observed walls: worst slice
~677 s (slice 11); refreshed plan: every slice estimated <= 539 s.

* C2: Browser Quick gets a feasible budget; QA loop names its directory and stop path

Judge rationales on the qa-only panels (2 of the last 3) cited the 30 s
Browser Quick budget: homepage + 5 targets, each a checkpoint write and a
guarded command, left ~5 s per probe. Quick is now 180 s (method text: 3
minutes); the qa-only-no-fix paid check expects the same native deadline.
PROBE_DIR points at its source line, a stopped loop goes to the final
report steps (annotations, materialize, Markdown), and /review and /ship
say that after smoke expiry only plan checks rerun in revalidation.

* C2: name the flows judges found implicit in review resolvers

Structured outside review: MISSING COVERAGE asks no fix question and is
never reported clean (was "preserve the existing user decision flow").
/review's HIGH-impact plan gate: spawned or non-interactive runs report
REQUIREMENTS MISSING and continue, writing no TODOs. Value cards: the byte
clamp applies to card lines; JSON records you write keep full values.

* C1: a free test catches a removed workflow step, which the judges missed

C1 controls showed the qa workflow judge passing /qa without Phase 10 and
the review judge passing /review without Step 3. The new test pins every
step/phase heading of qa, qa-only, review, ship, plan-ceo-review,
plan-eng-review and setup-deploy (derived from the templates' step
headings) and names any that goes missing or out of order; new step
headings must be pinned. The judge rubric is unchanged (EVAL_POLICY).

* size: fit the wave's wording under ship, review and qa parity caps

Measured after all lane merges: ship -79, review -210, qa -68 bytes over.
Trimmed the added wording first (post-expiry rule, MISSING COVERAGE,
plan-gate default, value-card JSON, stop line) and folded /review's
redundant INFORMATIONAL sentence and findings bullet. ship 268,036 (room
8), review 130,118 (room 1). qa needed 11 more bytes: cap 1.11 -> 1.1102
with the measured 93,305 / 84,049 = 1.11013 and its reason beside it.

* docs: troubleshooting sections for the wave's new messages

Bun floor (setup, auto-update, /gstack-upgrade), /review stale base and
fingerprint temp-dir errors, the design image model override, the
unrecorded Chromium PID, and a contributor section for census report
lines (pass-rates flags, failure causes, headroom, cost unknown, overlay
v4, stale corpora, auq substance). A free test checks every
troubleshooting.md#anchor printed by setup or bin scripts exists.

* TODOS: reliability follow-ups deferrals; close the Bun floor, make-pdf $P and #2709 reap

* 0.4: 'A census went red' guide; docs for causes, session ledger, headroom and new messages

docs/evals/census-red.md walks one red line to a diagnosis: prerequisites, the
report fields, eval:pass-rates --run/--reds/--headroom with expected output,
what unknown/insufficient/unrecorded data means, free replay, and the single
'after a repair' paid run allowed by TESTING_INTERNALS. Linked from
CONTRIBUTING, TESTING_INTERNALS and both census reports.

TESTING_INTERNALS documents the failure_cause glossary (one row per
FAILURE_CAUSES value, diagnostic only under EVAL_POLICY v1), failure_detail,
sessions[], cost_known, the session ledger, the pass-rates views and the 85%
headroom alarm, main-only tracking issues, overlay contract v4, auq-matrix's
3-sample substance panel and the E5/E6 periodic cases. troubleshooting.md
gains #bun-too-old and an anchor per new user-facing message from lanes P, D
and F. README names GSTACK_DESIGN_IMAGE_MODEL. test/census-red-docs.test.ts
pins the glossary to FAILURE_CAUSES, parses every guide command and checks the
links.

* v1.91.20.0: CHANGELOG and version for the reliability follow-ups

* regen: SKILL.md and sections for all hosts, digest, context-budget fixture

bun run gen:skill-docs --host all; goldens and llms.txt re-rendered
unchanged; bun test/helpers/capture-context-budget.ts.

* integrate: register codex-multiblock-live and design-model-smoke (periodic, rule)

* 0.4: census report and eval:pass-rates views link the census-red guide

* test: cookie approval fixture reverses the Bun install-hint pin (E1)

The historical approved cookie-workflow prompt carried BUN_VERSION=1.3.10;
the regenerated install hint now pins 1.4.0, so the fixture's reversal
list gains that edit like the earlier waves' entries.

* test: release floor counts the two new periodic cases (E5, E6)

* fix: /freeze, /guard, /unfreeze and /investigate work on Codex and other env-var hosts

Templates quoted "$HOME/.claude/skills/gstack/...", which the env-var hosts'
rewrites turned into "$HOME/.agents/skills/gstack/..." on Codex (a path a
global install never has; same class on the other hosts), and setup's
runtime roots had no freeze/bin. Quoted runtime paths now use
{{RUNTIME_ROOT}}: $GSTACK_ROOT on env-var hosts (the fence gets the
runtime prelude), the unchanged $HOME path on Claude. _link_runtime_dists
links freeze/bin and the careful/bin helper it sources into every
env-var host's root. Also fixes the same guess in /benchmark-models,
/retro and /diagram. test/env-host-runtime-assets.test.ts fails on the
previous code: no fence guesses a $HOME/<dir>/skills/gstack path, every
$GSTACK_ROOT path a fence uses is in the host's runtime root, and the
installed Codex /freeze and /unfreeze fences run from a fresh shell.
Template-reading tests now read the Claude render or expect the
placeholder (deliberate change).

* CHANGELOG/TODOS: lane W's workflow, cases and guide; the env-var host freeze fix

* E6: a block that reads a SKILL.md is not a missing install

The first live codex-multiblock-live run passed its real check (the /learn
block found the install and printed the seeded learning) but the oracle
flagged a sed of the learn SKILL.md, whose prelude source quotes the
'gstack: no install found (tried $_r)' text. Only an emitted line that
starts with the message and names a real root counts now; free replay
negative added.

* test: qa-only fixture keeps its caller-set 30 s deadline

The qa-only-no-fix fixture tells the model to start the guard with 30 s
(a caller limit), so its deadline assertion's default stays 30 000 ms; the
product's Browser Quick default (180 s) does not apply to it. Reverts the
helper and its test to their base.

* C2: keep PROBE_DIR 'owned' (qa-functional-prompt pins it); qa measured 93,311

* test: paid file count and periodic floor include the two new periodic cases

* test: ship docs freshness pin checks meaning after A1's wording trim

The snapshot still hashes selected release paths (--select narrows them),
generated outputs and docs; the pin now checks that instead of the
pre-trim sentence.

* E3: reap the recorded Chromium only after the daemon has exited

With the PID now recorded (E3), browse stop reaped Chromium while the
daemon was still shutting down; the daemon read that as a crash, exited 1
and left its state file (chromium-profile-isolation's headless stop test
failed 3/3 locally, passes on base). The CLI waits up to 5 s for the daemon
to exit before reaping a survivor.

* regen: qa and qa-only exploratory sections (PROBE_DIR wording)

* B1: batching counter reads a target declared mid-line; replay corpus

GSTA-23's census 37228573062 failed plan-eng-multi-finding-batching with
reviewCount=0 after 975 s although D1-D10 were asked one per finding,
each with its saved decision-ledger record. The report wrote 'Report
destination requested by the user. Review target (fixed): `PLAN.md` ...'
and the counter read target fields only at a line start, so no brief
inherited the plan identity. A target field may now open any sentence of
its line; a mid-sentence mention or another file still gives no identity.
Floor (3), budget and kind unchanged. Corpus: 9 earlier passing censuses,
the red (now credited at D5), and a derived batching refusal; 975 s noted
in the headroom diagnosis.

* B1: shared-libs corpus labels its git path and covers the supported shape

Every census entry was recorded in CI's eval image (git 2.43), where
gstack-safe-git falls back because --no-lazy-fetch is rejected; each entry
now says so (git_path: fallback). Two synthetic supported-path entries
(HEAD and a history citation in the brief, as git >= 2.44 produces) keep
the 37193478719 pass and the 37195203538 refusal, and a test asserts both
shapes are covered and the actor reads no git-path-specific text.

* v1.91.21.0: move off v1.91.20.0 (claimed by #3031)

* test: settle nav-guard commands before asserting; make two new tests Windows-safe

navigation-guard: Bun's expect(promise).rejects slows each Playwright round-trip
to ~1s while it waits (50 page.evaluate: 43ms plain vs 46.6s inside the matcher),
so the click's 5s timeout expired before the guard saw the link-local request
(CI: 'click: Timeout 5000ms exceeded ... waiting for scheduled navigations').
Settle the command first, then assert on the refusal; a source check keeps it so.

env-host-runtime-assets: PROBE_FILES now matches either path separator.
detector-corpus ship-docsync-late-result: JSON-escape the <ROOT> substitution so a
Windows temp path's backslashes do not break JSON.parse.

* fix: CI run/job ids are not phone numbers; context-recovery fixture gets the full runtime bin

pii.phone.e164 redacted the run id in the published eval report's fetch command
(--run 37235771700), breaking the command it prints. Digit-only spans in a run/job
flag, gh run argument, actions/runs/ or /job/ URL segment, or run_id/job_id key are
now exempt; the same digits elsewhere still report.

context-recovery-artifacts: the fixture's ./bin/ lacked gstack-skill-start and
gstack-context-recovery (the preamble's runtime scripts since v1.71), so the agent
spent its 10 turns hunting for them and hit error_max_turns with no final message
(run 37235771700, 11 turns). Link the whole runtime bin; diagnostic rerun passed
in 6 turns.

* test: replay run 37237194905's late-result capture through the docs authority detector

The only failure was call 20's write to a mistyped shard root (3GanyY for 3GanyU),
which the model disclosed; the detector is correct and the A1 post-snapshot
--out under the fixture home stays permitted. Named red recorded in TODOS.

* test: keep docsync-authority in the Windows profile

The replay test's literal helper path tripped the Windows curator's shebang-spawn
pattern, dropping the whole file from the Windows profile (test-free-shards
invariant). The snapshot --out case is already covered by docsync-command-grammar,
so it is removed here, and the Linux-path replay skips on win32.

* fix: gstack-docs-candidate --help works; docs gate red lines name the denying tool failure

PR run 37239016858 ship-docsync-completion: the parent ran
'gstack-docs-candidate --help' (the helper's usage error points at --help, which
failed). That undeclared command made docsToolFailures non-empty, which denied
the atomic-replacement proof of the child's legitimate native Edit, so the red
line blamed the Edit's temp file. A replay of the capture proves the atomic
replace once --help is declared.

- the helper prints its usage for --help/-h and exits 0
- the fixture interface and command grammar admit the helper's bare --help
- the atomic-proof denial names its check and the tool failures behind it
  (deny labels renumbered to their lines)
- replay with negative controls (undeclared command, temp left behind, renamed
  elsewhere); diagnostic rerun of ship-docsync-completion passed

* test: retro-base-branch fixture uses main and a shallow report path

PR run 37241493377: the agent wrote retro-output.md one directory above the
requested <tmp>/retro-base/retro-output.md (it dropped the retro-base segment),
so the report check found nothing. The fixture also ran plain git init, which
makes master on CI's git while the prompt says the repo is on main; the agent
logged that mismatch as a learning. Init on main and use the temp dir itself as
the repo. Diagnostic rerun passed (18 turns, 203s).

* test: qa-quick sets a caller probe limit and drains past its session budget

C2 raised Browser Quick's native budget to 180 s. qa-quick is the only gate case
on the native budget, and its wall went 194 s (main periodic, pre-C2) -> 254 s and
274 s (this PR's runs) -> 300 s session timeout (run 37242691913): the agent now
probes every navigation target with a checkpoint, then needs ~80 s to report.
Like qa-only-no-fix, the eval now sets a caller probe limit (30 s), scopes the
fixture, and gives bun's outer timeout the session drain margin instead of racing
the 300 s session. The product's 180 s budget and the session budget are unchanged.
Diagnostic rerun passed in 195 s.

* review/ship: use main's post-expiry smoke sentence; v1.91.24.0

GSTA-23's measurement on the review workflow judge: main's 'Post-expiry smoke
rechecks are not-run.' passed 10/10; the C2 wording passed 6/10 and the
'..., even in 4c.' variant failed in PR run 37248650879. qa-probe-gates pins
main's sentence before step 4c.

* test: record the plan-design corpus test in the test-of-test baseline

#3031's ratchet now counts tests that slice a paid test file's source; the B1
plan-design-review-plan-mode replay corpus reads test/skill-e2e-design.test.ts
like its sibling corpora already listed.

* fix: --case no longer deadlocks importing the running CLI

runCaseDiagnosis loaded runPaidShards with a dynamic import of
scripts/test-paid-shards.ts; when that file is the entry it is still in its
top-level await, so the import never resolved and `--case` hung after its
first line (on main since #3031, found by the per-case measurement runs). The
CLI now passes its runner in; library callers keep the lazy import.

* test: name the in-flight file and the native error when bun dies before its summary (#3037)

* Bun 1.4.2 everywhere CI and setup pin the tested version

Bun 1.4.0 on Windows aborts about 3% of free-suite shards with native
bad-handle errors (main too; #3037); the same head on 1.4.2 had none in 180
shard runs. Every current 1.4.0 pin moves together, as bun-version-drift
requires: setup-bun in every workflow (CSO orchestration included), the eval
image (sha256 from bun-v1.4.2's SHASUMS256.txt, re-hashed locally), GitLab,
the Ubicloud free-suite setup, gstack-bun-version.sh's tested version,
engines.bun and @types/bun, the install hint in generated skills, the Dia and
cookie native-qualification pins, and docs and tests that state the current
pin. Recorded 1.4.0 observations, fixtures, CHANGELOG history and the CSO
images' oven/bun:1.3.10 base stay.

* fix: --case runs a single-case file whole

A session option naming the case (testName: 'office-hours-section-loading')
made caseSelection pick test-name mode, whose filter matched no Bun test, so
every trial would run hollow. A file that registers only this case now runs
whole before any name match is tried.

* fix(office-hours): reviewer returns a hash-bound receipt instead of echoing its verdict

The spec reviewer now Writes its verdict once, seals it with
gstack-office-hours-review seal (validates the saved JSON and prints
OFFICE_HOURS_VERDICT round=N sha256=<hash> path=<verdict>), and returns
only that line. check --receipt recomputes the hash and fails closed on a
missing, malformed, stale-round, wrong-path or mismatched receipt. The
saved file stays the sole findings inventory; validators bind the receipt
to the saved bytes and the judge reads those bytes.

* fix(office-hours): only blocking findings send a design back for another review round

Review findings carry severity blocking|minor (verdict schema v2). PASS when
no blocking findings remain; minor findings are recorded in Reviewer
Concerns and the Spec Review table, never a reason for another round.
CONVERGENCE needs a persisting blocking prior. A persisting or unverified
blocking prior cannot be relabeled minor. The 3-round cap is unchanged.

* fix(office-hours): rounds 2 and 3 are delta re-reviews of the captured design diff

prepare saves each reviewed design as round-N.design.md and gives later
reviewers the exact line diff since the previous round. A new later-round
blocking finding must cite (changed_text) an excerpt of a changed line;
seal, check and the validators verify it against the snapshots and fail
closed when a citation or snapshot is missing. Other new observations are
minor. Severity, anti-downgrade and CONVERGENCE rules are unchanged.

* test(office-hours): preservation judge accepts COMPLETED with recorded minor findings

* fix(office-hours): parent fixes only blocking findings; callers may set --max-rounds

Between rounds the parent edits for blocking findings only; minor findings
stay recorded. gstack-office-hours-review takes --max-rounds 1|2|3 (default
3) on every command; reaching it stops at MAX_ITERATIONS. The marathon
fixture sets 2 rounds so round 1 (full) and round 2 (delta) run live.

* docs(changelog): office-hours review loop changes in v1.91.28.0

* fix(cso): helper JSON keeps run URLs only in the validated workflow slot

The shared redactor now treats GitHub Actions run ids as CI metadata
(c245d1bc2), so main's #3041 invariant that the same run digits stay
redacted outside qualification.workflow held only through the phone
false positive. Make it explicit in the CSO sanitizer.

* test(plan-ceo): split-overflow progress detector accepts the skill's candidate wording

Collection never ended when the model wrote 'E1 Slack:' / 'E1-SLACK:' or
labels like 'Include in this quarter' / 'Defer with flip trigger', so the
session ran the whole review (930-1464 s) and failed on its extra findings
or the case deadline (6 of 21 runs). The candidate check now accepts a
platform-bound E-id and the action is the bucket's leading verb, as the
skill names them (Include/Defer/Cut/Hold). The semantic judge, call floor
and include/defer/cut validation are unchanged. Replays the captured CI
37374526437, t2, t9 and t15 menus with negative controls.

* test(plan-ceo): split-overflow candidate identity is the one E1-E5 token

The skill fixes no header or question wording, and shape-matching kept
missing new forms (header 'D1.1 Slack' with 'SCOPE-E1:', 'D1.1 E1 Slack',
'E1 Slack DM bot ...:', a misspelled 'Mattermst'). Identity is now exactly
one E1-E5 token across the header and the question's first line, with no
other candidate's platform named. Options rules, judge, call floor and
include/defer/cut validation are unchanged. Replays all 31 measured runs;
the quoted-row controls now quote a second ledger id.

* v1.91.30.0: move past #3044's claimed 1.91.29.0

* test: register the split-overflow wording replay as a free fixture

* fix(skills): resolve judge-cited ambiguities in /qa and /plan-eng-review

- tasks JSONL keeps the raw branch as GIT_BRANCH instead of overwriting
  gstack-slug's sanitized BRANCH used for artifact names
- the Codex outside-voice block runs for ready or unverified, matching
  its routing rule
- plan-eng startup names the preamble's own setup prompts; the
  cross-project question waits for Step 0
- /qa 8c: a fix that applies but leaves the finding reproducing gets one
  revised fix, else it is reverted; browser verification names its before
  screenshot; the exploratory loop drops its duplicate 3-step summary

* fix(plan-eng): keep the startup steps in their pinned order

* fix(plan-eng): explicit spawned sessions follow the spawned rule at the scope gate; one announcement rule

A missing tool still sends the prose menu whatever the session type, but a
session the caller or host explicitly marks spawned selects the menu's
Recommendation without prose or BLOCKED. The pre-load announcement
sentence now states Step 0's single check.

* eval policy v3: judge panels gate on the per-dimension median of 3 samples

A judge dimension now passes when at least 2 of its 3 samples meet the
unchanged threshold (judgePanelMedian); any other sample count or a
non-numeric sample fails closed. Thresholds, sample count, judge model and
prompts are unchanged; panel logs show every sample and the mean, and name
the median as the gate. Applies to the skill-llm-eval panels, the
workflow-judge cache, the auq-matrix substance panel and the calibration
replicas. EVAL_POLICY 2 -> 3 and HARNESS_VERSION 1 -> 2 (verdicts change),
so every case starts a new pass-rate series. Replays the captured panels
from jobs 112032744416 and 112049856586 as replayed evidence.
2026-10-06 18:07:50 +00:00
Garry Tan 55fbe77195 v1.91.24.0 test: audit wave — PR evals keep finished work, honest ceilings, EVAL_POLICY v2, faster free suite (#3031)
* chore(scripts): retire hollow and legacy paid package scripts behind one-release stubs (W8a, DX-4, CEO-31)

test:codex*, test:evals*, test:e2e*, test:gate, test:periodic, eval:bg,
eval:bg:all, eval:flake-rank, eval:watch and test:audit now print their
replacement and exit 1 via scripts/retired-command.ts. CONTRIBUTING.md
carries the same table; test/retired-commands.test.ts pins both. Eval CLI
empty-state hints point at eval:bg:pr.

* chore: delete dead update-readme-throughput, eval-watch, orphan fixtures and QA_FUNCTIONAL_INPUTS (W8b, CEO-24)

Retired with named behavior:
- update-readme-throughput.ts + readme-throughput test: replaced a README
  anchor that no longer exists from a JSON file that was never produced;
  the script was a no-op. Guarantee withdrawn (nothing to protect).
- eval-watch.ts + its dashboard tests: rendered ~/.gstack-dev/e2e-live.json
  and _partial-e2e.json, written only by unsharded runs, so it showed
  nothing for every supported (sharded) entry point. Guarantee withdrawn;
  live progress is the gstack-detach log or gh run watch. Manual-review
  rendering stays covered by eval-list and eval-summary tests.
- Orphan fixtures (autoplan-pending-artifact-ae.json, autoplan-existing-app/,
  autoplan-sign-in-ui/, parity baselines v1.47.0.0/v1.57.7.0/v1.69.1.0):
  no test, touchfile or script reads them; superseded baselines stay in
  git history.
- QA_FUNCTIONAL_INPUTS: exported, never imported.

* refactor(test-free): delete dead WORKER_HOSTILE list and buildShardArgs parallel mode

Neither had a production caller: full-suite mode runs shard processes, so the
worker quarantine was inert and --parallel was never passed (W8b).

* docs(test-value): narrow prompt-byte contract to machine-read tokens (D2)

Define prompt-byte contract as machine-read tokens (markers, field names,
command lines, enum values, file paths, security-boundary strings) and add a
sentence-pin glossary entry: convert to structural checks or delete when a
behavioral eval covers the behavior.

* test(ratchet): count free tests that slice a paid test's source

A free test that binds readFileSync(<paid test file>) and cuts it with
.slice() tests the paid harness's code out of context even when it imports a
lib/ symbol. Record the 15 existing files with reasons; new ones fail with fix
text. Self-test covers positive and negative shapes.

* test(browse): un-quarantine compare-board; all 16 board tests pass headless (W8d, CEO-24)

The GSTACK_COMPARE_BOARD_TESTS opt-in hid a headless mismatch that no
longer reproduces: all 16 tests pass on Bun 1.4.0 with the bundled
headless Chromium, solo, repeated, and beside other in-process browser
files. Protected behavior (the comparison-board DOM contract the
plan-design-review/office-hours/design-consultation feedback loop polls)
is checked on every free run again.

* test: replace silent existsSync early returns with explicit skips or assertions (W8d)

- browse/test/build.test.ts: server-node.mjs checks skip only when the
  bundle is absent and GSTACK_EXPECT_BINARIES is unset; the free CI lane
  and test:ubicloud build it and set the flag, so a dropped build step
  fails with the build command instead of passing with no assertions.
- gstack-update-check: VERSION is tracked; the guard could only hide a bug.
- gen-skill-docs host leakage/frontmatter checks assert the host tree
  exists (the module-level render always produces it).
- cso-docker staged-runtime test uses test.skipIf so reports show it as
  skipped, not passed.

* test(ship): convert ship-workflow-clarity sentence pins to structural checks

Add test/helpers/prompt-structure.ts (between, expectTokens, expectAbsent,
expectOrdered, expectMentions) with fix text on failure. ship-workflow-clarity
now pins headings, step order and machine-read tokens (commands, fields, enum
and status strings); safety rules are meaning-level keyword checks. Wording is
judged by the 'ship/SKILL.md workflow' judge in skill-llm-eval.
Sentence pins: 90 -> 0.

* ci: add platform-qualification lane for opt-in ML, gitleaks and Swift tests (W8d, CEO-24)

security-bench + security-live-playwright L4 (SECURITY_BENCH), the real
gitleaks 8.30.1 memory-ingest cases (GSTACK_TEST_GITLEAKS) and the
DebugBridge Swift build invariants (GSTACK_TEST_SWIFT) self-skip in every
lane today. No scheduled lane has their prerequisites, so per CEO-24 they
move to an owned dispatch + quarterly workflow instead of being deleted.
Each job was run locally (ML 10 pass, gitleaks 130 pass; Swift needs
macOS). test/platform-qualification-workflow.test.ts pins gate variable,
job and file together.

* fix(scripts): capture-baseline and preflight-agent-sdk no longer act on --help or typos (W8e)

capture-baseline parses its flags strictly: --help prints usage, an unknown
or valueless flag exits 2, and only a valid invocation writes a fixture
(--help used to write test/fixtures/parity-baseline-current.json).
preflight-agent-sdk gains --help, rejects unknown flags, and runs the live
SDK query (API spend) only with --live.

* test(evals): register the three keyless periodic paid files with case ids (W1b)

skill-e2e-auq-consistency, skill-e2e-auq-verbose-vs-carved-ab and
codex-e2e-recommendation-substance now carry touchfiles, a periodic tier and
kind rule (one trial, unchanged), so editing one selects its own case instead
of restoring the full PR gate. The verbose A/B case also registers the vendored
pre-cut fixture it reads, and review-army-migration-safety registers the SQL
fixture it reads.

* feat(evals): narrow PR fallback for free-only inputs (W1a, CEO-13/20, ENG-13)

The PR profile now exempts the duration seeds, every free-lane workflow
(explicit list; inventory test fails on an unclassified workflow, .yaml
and workflow_call included), scripts/ubicloud/** and fixtures declared in
the explicit FREE_FIXTURES map. Each remaining unknown file carries a label
and fix (needs touchfile entry / add to FREE_ONLY_PR_FILES / real unknown
dependency). evals.yml itself still restores the full gate.

* ci: delete ci-image.yml; the eval workflows already build and push the same content-hash tag (W8f)

evals.yml, evals-periodic.yml and evals-marathon.yml each compute the
identical hashFiles() tag and build/push it (with :buildcache and :latest)
when missing, so ci-image's push trigger and weekly cron were no-ops
(30-day median 0.5 min, 36/36 success). ci-image-tag-binding now pins the
three eval workflows.

* ci(evals): key periodic/marathon concurrency on ref+event; branch periodic dispatch skips the gate census (W2a, ENG-9, CEO-04)

* refactor(paid): drop ~160 unused imports; leaf paid-types/paid-select modules break the paid-* import cycle (W8c)

* ci(quality-gate): per-PR concurrency, free runner, drop unscoped bun audit (W2a, W8f)

- Concurrency keys on pull_request.number || run_id, so back-to-back main
  pushes no longer cancel each other's credential scan.
- ubuntu-24.04 instead of a billed 8-vCPU Ubicloud runner for a ~1 min job.
- bun audit --audit-level=critical ran on every PR regardless of diff, so a
  newly published advisory turned unrelated PRs red, with its own ignore
  policy. dependency-review (lockfile PRs, fail >= high) and the weekly OSV
  sweep remain the dependency gates.

* chore(test-free): refresh the free duration seed from the audit's Ubicloud recording

Adds the 19 files merged after the last refresh, including the 115 s
claude-overlay-setup that packed as ~1 s and doubled a shard (W6a). The seed
is re-recorded on the final tree at the end of this wave.

* ci: move tiny ubicloud-standard-2 jobs to free ubuntu-24.04 runners (W8f)

actionlint, skill-docs, dependency-review, version-gate and pr-title-sync
are sub-minute jobs; GitHub-hosted runners are free for this public repo.

* feat(test-free): pessimistic unseeded packing, seed growth ratchet and CI health summary

- Unseeded files pack at the seed's p99 instead of p75 (W6a).
- --ci-plan warns in the job summary past 5 unseeded files; --ci-verify
  lists shards that outran their prediction by 1.5x (and 30 s) plus every
  flaky pass absorbed by the serial retry (W6a, W7a job summary).
- test/free-seed-ratchet.test.ts fails when a seeded file over 60 s is not
  on scripts/free-test-seed-allowlist.json, when an entry is stale, and when
  the allowlist grows versus its merge-base copy unless the new entry is an
  annotated exception with a todo (CEO-10, DX-7). free-plan runs it with
  full history so the shrink check is real in the required lane.
- Every message names the fix command and docs anchor (DX-6).

* feat(evals): state PR coverage mode, counts, reuse and fallback files in the summary and comment (CEO-13)

* test: make the tracked-tree skill-doc freshness check local-only; tidy skill-docs header (W8f)

Every free CI shard regenerates the tracked SKILL.md files before running
tests, so the --dry-run freshness assertion compared the regenerated tree
with itself and could not fail there. It now skips under GitHub Actions;
skill-docs.yml's git diff --exit-code is CI's freshness gate. Removes a
stale concurrency comment in skill-docs.yml.

* ci(free-tests): install zsh so the bash+zsh portability arms run (W6f)

~42 zsh-arm tests (aside-probe-shell, qa-browser-preservation,
generated-docs-fences) skipped in CI because zsh is not in the runner image.
All 141 tests in those files pass with zsh present.

* feat(evals): executors write negative receipts at the execution boundary (CEO-25, ENG-6)

A completed single-trial rule/judge shard that did not pass now writes its
negative receipt beside the pass receipts in the slice artifact, so a
cancelled run's FAIL still blocks an older PASS when its report never runs.
Receipts merge negatives first and order by (run attempt, completedAt); a
same-second FAIL/PASS tie resolves to FAIL. The runner call site is a
one-line hunk in scripts/test-paid-shards.ts.

* docs(cso): state that contained runtime/scanner execution is unavailable today

The scanner catalog is empty while its build inputs await review and the
runtime catalog holds no qualified profile until the private evaluator
exists, so /cso runs static assessment only. AGENTS.md, README and
docs/cso-release-qualification.md now say so; docs/cso-scanners.md
already did.

* test(helpers): expectMentions splits on sentence ends only

Clauses joined by ';' or ':' are one rule; splitting there made a reworded
rule fail for punctuation.

* test(plan-ceo): convert skill-ceo-section-ordering sentence pins

Safety and authority rules (never/do not/stop/only/without/wait/approval) are
meaning-level expectMentions checks; other sentence pins are dropped. Headings,
labels, option rows, placeholders and machine-read lines (auto-decide
annotation, Mode:/approved decisions format, STATUS/SOURCE fields,
CROSS-MODEL marker, gstack-ceo-mode-handoff) stay as tokens; ordering checks
unchanged. Behavior is covered by the periodic PTY case in
skill-e2e-plan-ceo-review-section-loading.test.ts.
Sentence pins: 210 -> 23.

* test(helpers): expectMentions treats wrapped lines as one sentence

Templates hard-wrap prose; only sentence ends and blank lines separate rules.

* test(gen-skill-docs): convert sentence pins to meaning checks or drop them

Safety/authority sentences become expectMentions keyword checks; other English
sentences are dropped (wording is judged by the per-skill workflow judges in
skill-llm-eval and the E2E cases in touchfiles-data). Commands, config keys,
markers (PLAN MODE EXCEPTION, NO UNRESOLVED DECISIONS, Gate outcome), field
names and alternation-based meaning regexes stay. Removed the now-empty
'contains opt-in AskUserQuestion text' test (only pinned a sentence; the
resolver's AskUserQuestion wiring is covered by the codex second-opinion
tests in the same describe). Lane E's existsSync hunks are untouched.
Sentence pins: 216 -> 65 (remaining lines carry machine tokens).

* ci(windows): shard the Windows lane via --ci-plan/--ci-run/--ci-verify; move native campaigns to native-qualification.yml

- windows-free-tests.yml: a Linux plan job curates and packs the Windows-safe
  files into 6 shards, one strict serial shard per windows-latest job, and a
  Linux aggregate verifies exact coverage and every result (8.1 min on one
  runner today). --windows-only now combines with the CI modes; the plan binds
  the curated file set (W6e).
- Every Windows plan and --list run prints the curated count with its top
  exclusion reasons in the job summary; a free ratchet fails when the curated
  subset drops below its floor, with the fix (W6e).
- The four Dia/cookie diagnostic dispatch campaigns move to the dispatch-only
  native-qualification.yml with one exclusive `mode` choice input, so their
  red dispatch runs no longer read as a red PR lane (W8f). Their workflow pins
  move to test/windows-native-workflows.test.ts; the boolean-exclusivity
  guard is replaced by the choice input.

* feat(paid): per-slice CI job ceilings and an in-process slice deadline with not_run/hung outcomes and checkpoints (W2c, ENG-2)

* fix(pass-rates): segment trial history strictly by EVAL_POLICY version

Reader-compatibility floor for EVAL_POLICY v2 (ENG-14/CEO-35/DX-12). Only
trials under the reader's own policy version feed labels and alarms; older
policy trials stay visible but are never scored or used as a drift baseline;
trials from a newer policy are ignored with a printed count and fix text.
Reverting the later v2 writer commits leaves a reader that cannot pool v2
records into v1 series (the pre-PR reader at 2db0b3a does: it reports a
mixed fixture as FAILING with drift alarms).

* docs: one contributor command table; retired commands, seed sources and current workflows (W8g, DX-5, DX-13, CEO-12)

- CONTRIBUTING.md gains 'Which command do I run?' (task -> command ->
  cost/time -> needs, including branch validation via gh workflow run);
  CLAUDE.md, AGENTS.md and TESTING_INTERNALS.md link to it instead of
  repeating command lists. Tier tables drop retired commands and stale
  per-run dollar estimates.
- Retired names (test:evals, test:e2e, eval:bg, eval:flake-rank, eval:watch,
  ...) are gone from CLAUDE.md, CONTRIBUTING.md, TESTING_INTERNALS.md,
  ARCHITECTURE.md and USING_GBRAIN_WITH_GSTACK.md.
- The flake-ledger claim now says what happens: CI uploads flake-ledger-*
  artifacts; eval:pass-rates reads only the local ledger.
- TESTING_INTERNALS states that only test:ubicloud --record-durations
  produces an acceptable duration seed (DX-13).
- CI section: quality gate no longer runs bun audit; dependency-review's
  real trigger; the platform-qualification lane.
- docs/test-audit-2026-09.md gets a corrections section for its stale claims.
- TODOS.md: audit follow-ups (post-merge metrics, stub removal, name reclaim,
  diff-aware planner); eval-watch shard-awareness and the image freshness
  tripwire closed as won't-do.

* feat(evals): recover receipts of cancelled PR runs (W1c, CEO-14/34, ENG-4/5/7)

A new recover-receipts job runs base-ref code with actions: read, restores
the PR's store, merges the receipts of completed cancelled evals.yml runs
for this PR (provenance-checked, at most 5, oldest first, negatives first),
reconstructs completed panel negatives with panelVerdict() (partial panels
get no credit) and keeps a recovery.json checkpoint that never advances past
an unresolved run. plan-slices keeps contents: read, takes the store as an
artifact, turns reuse off when recovery degraded or a run is unresolved, and
carries the whole store to slices-report so nothing is lost. Any gh problem
degrades to no recovered receipts with a named reason.

* feat(evals): classify this wave's free-only tools and workflows for PR selection

platform-qualification.yml, retired-command.ts, test-health-report.ts,
bump-harness-version.ts and eval-bg.ts never run inside a paid case.

* feat(evals): debounce PR push bursts before planning paid slices (W1d, CEO-15, ENG-11, DX-7)

A tiny ubuntu-24.04 job waits 90 s only on synchronize when another run for
the PR started in the last 15 minutes; cancel-in-progress supersedes, and a
run that outlives the wait reports "superseded by <sha>" and plans nothing.
Labels: evals-no-debounce skips the wait, evals-fresh disables receipt reuse.

* perf(test): move the real-process browser settlement cases out of test-free-shards.test.ts (W6b)

The 19 owned-browser settlement modes were 85 s of the file's 95 s and could
not be packed apart from the fast runner unit cases. They move to a shared
fixture with two files (lifecycle 47 s, ownership 39 s); the unit file drops
to 10 s. Same modes, same assertions.

* chore(evals): delete the finished validation_phase dispatch and its inline planner (W8f)

Retirement (CEO-24): the protected behavior was a one-off cookie-repair
validation subset (eight dependent cases) and a quality/behavior split of
the census; that validation is complete, so the guarantee is withdrawn.
Branch validation dispatches evals.yml with evals_all.

* perf(test): inject the office-hours record grace in non-cooperative deadline tests

* perf(test): shrink aside-render process slack via GSTACK_RENDER_SLACK_MS in timeout tests

* perf(test): make the memorable lock give-up configurable for lock-contention tests

* perf(test): check install-timeout knob normalization through its own setup lines

* perf(test): inject a 2 s storage-reset deadline in the late isolated dispatch test

* perf(test): shorten the slow gbrain fake to 1 s, still past the 300 ms probe timeout

* ci(free-tests): run only cso-docker-integration in the free CSO Docker job (F16)

cso-node-lifecycle-integration and cso-stack-cold-integration need
GSTACK_CSO_TEST_STACK images and always skipped there; they stay in the CSO
runtime workflows through test:cso:docker.

* test(gen-skill-docs,plan-ceo): split mixed sentence pins into tokens and rules

Sentences that wrap a backticked or bold token keep the token (expectTokens)
and, when they state a safety rule, a keyword check; short safety sentences
(25-39 chars) get the same treatment.

* test(plan-ceo): convert ceo-workflow-clarity.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: periodic PTY case skill-e2e-plan-ceo-review-section-loading and the plan-ceo workflow judges.
Sentence pins: 13 -> 1.

* test(plan-eng): convert eng-review-routing.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: plan-eng E2E cases (skill-e2e-plan-eng-*) and the plan-eng workflow judge.
Sentence pins: 28 -> 6.

* test(plan-eng): convert eng-scope-entry-ap.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: plan-eng E2E cases (skill-e2e-plan-eng-*) and the plan-eng workflow judge.
Sentence pins: 16 -> 5.

* test(plan): convert plan-scope-selection.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: plan-review E2E cases and workflow judges in skill-llm-eval.
Sentence pins: 19 -> 2.

* test(qa): convert qa-lazy-sections.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: qa/qa-only E2E cases (skill-e2e-qa-*) and the qa workflow judge.
Sentence pins: 71 -> 11.

* test(qa): convert qa-browser-preservation.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: qa/qa-only E2E cases (skill-e2e-qa-*) and the qa workflow judge.
Sentence pins: 25 -> 11.

* test(qa): convert qa-probe-gates.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: qa/qa-only E2E cases (skill-e2e-qa-*) and the qa workflow judge.
Sentence pins: 20 -> 5.

* test(qa): convert qa-checkpoint-evidence.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: qa/qa-only E2E cases (skill-e2e-qa-*) and the qa workflow judge.
Sentence pins: 7 -> 5.

* test(qa): convert qa-evidence.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: qa/qa-only E2E cases (skill-e2e-qa-*) and the qa workflow judge.
Sentence pins: 4 -> 3.

* test(design): convert design-consultation-contract.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: design-consultation E2E/judge cases in touchfiles-data.
Sentence pins: 44 -> 11.

* test(outside-voice): convert run-in-background-guidance.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: outside-voice E2E cases and plan-review workflow judges.
Sentence pins: 35 -> 5.

* test(spec): convert spec-template-invariants.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: spec E2E/judge cases in touchfiles-data.
Sentence pins: 36 -> 21.

* perf(evals): 420 s census slice budget; case-shard skill-e2e-qa-workflow; seed its case walls (W5c)

* feat(evals): evals.yml dispatch contract for eval:bg (CEO-17/29, DX-1, ENG-8/12)

workflow_dispatch gains base_ref, base_sha, expected_sha, nonce and
pr_receipts. The nonce appears in run-name so the dispatcher resolves its
own run; evals_all=false runs the PR profile against base_sha/base_ref; a
checkout that differs from expected_sha fails before any paid work with a
named fix; pr_receipts reads that PR's receipts read-only through the
recover-receipts artifact path (never the cache) and never saves.

* ci(windows): pack the Windows lane with Windows-measured durations (W6e)

The first sharded run (37221451711) finished in 6.0 min: the Linux seed has
browse/test/cookie-import-native-job.test.ts at ~0 s (its cases skip on
Linux) but it takes 122 s on Windows, so one shard ran 242 s against an 84 s
prediction. The plan now reads scripts/free-test-durations-windows.json; a
record_durations dispatch times every Windows-safe file alone and uploads it.

* test(free): settlement case files import the runner rule they exercise

Keeps the split files out of the test-of-test ratchet: the retry-eligibility
rule is a direct runner import passed to the shared fixture.

* fix(marathon): no hollow plan-decision-classification shard; office-hours marathon scenario scoped to the pilot (W2f)

* test(free): satisfy the typecheck:test ratchet in the seed ratchet test

* refactor(ci-history): one shared GitHub history reader with the CI flake-ledger reader

Extract pass-rates' gh fetch layer into scripts/lib/ci-history.ts (CEO-09) and
add the W7a reader: paged run listings that fail whole on any page error,
per-run artifact downloads (cached, size-capped, data only), flake-ledger
parsing, the pre-registered flaky-file rule (> 5% of main free-tests runs over
a window of at least 20 runs, widened to the newest 20 runs when the window is
thinner), a job-summary table, and named unavailable reasons with next steps.

* feat(test-health): test:health report of the audit success metrics

scripts/test-health-report.ts (CEO-09/06/22/32, DX-8/9/12/13) prints every
success metric with its counting rule from CI history: PR full-fallback rate,
reused PR trial records, PR-lane spend per merged PR and per validated
revision, cancelled slice-minutes per week, push-to-verdict p50/p95, census
expected failed trials per run with raw failure rate per kind/class and
P(lane red) under the recorded panels vs single-trial aggregation, INFRA
re-dispatches, quarantine evidence (insufficient evidence distinct from
healthy), free flaky-pass rate, free/Windows wall, free serial time and the
net-complexity line. An unreadable input prints 'unavailable: <reason>; next:
<step>', never 0. --enforce fails on a flaky file (W7a) or > 5 unseeded free
files (CEO-10); unavailable checks never fail and keep the issue open.

* test(review): convert review-workflow-clarity.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: review E2E cases (skill-e2e-review*) and the review workflow judge in skill-llm-eval.
Sentence pins: 85 -> 17.

* test(review): convert review-entry-and-design-clarity-au.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: review E2E cases (skill-e2e-review*) and the review workflow judge in skill-llm-eval.
Sentence pins: 15 -> 4.

* test(review): convert review-enum-lifecycle.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: review E2E cases (skill-e2e-review*) and the review workflow judge in skill-llm-eval.
Sentence pins: 8 -> 0.

* test(ship): convert ship-control-flow.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: ship E2E cases (skill-e2e-ship*, skill-e2e-workflow) and the 'ship/SKILL.md workflow' judge.
Sentence pins: 74 -> 9.

* test(ship): convert ship-skip-requeue.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: ship E2E cases (skill-e2e-ship*, skill-e2e-workflow) and the 'ship/SKILL.md workflow' judge.
Sentence pins: 28 -> 3.

* test(ship): convert ship-plan-completion-invariants.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: ship E2E cases (skill-e2e-ship*, skill-e2e-workflow) and the 'ship/SKILL.md workflow' judge.
Sentence pins: 28 -> 12.

* test(ship): convert ship-reentry-gates.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: ship E2E cases (skill-e2e-ship*, skill-e2e-workflow) and the 'ship/SKILL.md workflow' judge.
Sentence pins: 16 -> 4.

* test(ship): convert ship-document-release-dispatch.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: ship E2E cases (skill-e2e-ship*, skill-e2e-workflow) and the 'ship/SKILL.md workflow' judge.
Sentence pins: 11 -> 4.

* test(ship): convert ship-apple-gate.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: ship E2E cases (skill-e2e-ship*, skill-e2e-workflow) and the 'ship/SKILL.md workflow' judge.
Sentence pins: 7 -> 0.

* test(ship): convert ship-skip-actor.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: ship E2E cases (skill-e2e-ship*, skill-e2e-workflow) and the 'ship/SKILL.md workflow' judge.
Sentence pins: 6 -> 1.

* test(ship): convert ship-template-redaction.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: ship E2E cases (skill-e2e-ship*, skill-e2e-workflow) and the 'ship/SKILL.md workflow' judge.
Sentence pins: 5 -> 4.

* test(ship): convert ship-publication-gates.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: ship E2E cases (skill-e2e-ship*, skill-e2e-workflow) and the 'ship/SKILL.md workflow' judge.
Sentence pins: 5 -> 2.

* test(ship): convert ship-review-loop.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: ship E2E cases (skill-e2e-ship*, skill-e2e-workflow) and the 'ship/SKILL.md workflow' judge.
Sentence pins: 3 -> 1.

* test(ship): convert ship-version-sync.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: ship E2E cases (skill-e2e-ship*, skill-e2e-workflow) and the 'ship/SKILL.md workflow' judge.
Sentence pins: 5 -> 2.

* test(ship): convert ship-pr-liveness-policy.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: ship E2E cases (skill-e2e-ship*, skill-e2e-workflow) and the 'ship/SKILL.md workflow' judge.
Sentence pins: 4 -> 0.

* test(plan-review): convert plan-review-cases.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: plan-review E2E cases and workflow judges in skill-llm-eval.
Sentence pins: 58 -> 15.

* test(autoplan): convert autoplan-phase-order.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: autoplan E2E cases (skill-e2e-autoplan-*) and the autoplan workflow judge.
Sentence pins: 42 -> 11.

* test(autoplan): convert autoplan-phase-handoff.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: autoplan E2E cases (skill-e2e-autoplan-*) and the autoplan workflow judge.
Sentence pins: 20 -> 4.

* test(autoplan): convert autoplan-snapshot.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: autoplan E2E cases (skill-e2e-autoplan-*) and the autoplan workflow judge.
Sentence pins: 21 -> 5.

* test(outside-voice): convert outside-voice-preflight.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: outside-voice E2E cases and plan-review workflow judges.
Sentence pins: 26 -> 9.

* test(ask-format): convert resolver-ask-user-format.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: AUQ E2E cases (skill-e2e-auq-*); prompt-injection rule kept as a meaning check.
Sentence pins: 32 -> 15.

* test(skills): convert binding-template-drift.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: workflow judges in skill-llm-eval.
Sentence pins: 19 -> 13.

* test(skills): convert carve-plan-fixture.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: workflow judges in skill-llm-eval.
Sentence pins: 20 -> 6.

* test(land-and-deploy): convert land-and-deploy-postfail.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: land-and-deploy E2E cases and its workflow judge.
Sentence pins: 21 -> 14.

* test(land-and-deploy): convert land-and-deploy-ci-gate.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: land-and-deploy E2E cases and its workflow judge.
Sentence pins: 5 -> 4.

* test(land-and-deploy): convert land-and-deploy-flow.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: land-and-deploy E2E cases and its workflow judge.
Sentence pins: 5 -> 3.

* test(office-hours): convert office-hours-completion.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: office-hours E2E cases (skill-e2e-office-hours*).
Sentence pins: 16 -> 1.

* test(plan-ceo): restore the stale closing-route guard

The negative check for 'Closing hooks' / 'return to section' guards a retired
route token, not a sentence; the first conversion pass dropped it.

* ci(test-health): weekly test-health workflow with an honest tracking issue

New test-health.yml (CEO-18/DX-9): Monday 10:00 UTC after the periodic census
plus dispatch, free ubuntu-24.04 runner, permissions contents:read +
actions:read + issues:write only. Runs bun run test:health --since-days 7
--enforce; an enforced failure upserts one tracking issue, a run with
unavailable inputs keeps it open and lists them, and it closes only when every
enforced check has evidence and passes. Only main runs touch the issue.

* feat(evals): an edited paid test file runs its own gate cases in the PR lane (DX-11)

A gate case outside the audited fast profile is selected when its single
owning paid test file changed and the runner can select it by name (its
Bun test is named by the case id). The manifest records directCases and
validation accepts only recorded, name-addressable ones; the runner and
planner read the file->cases map from test-pr-profile. Cases the runner
cannot address stay deferred with the fix. An inventory test covers every
owned gate case. Touches one hunk each in test-paid-shards.ts and
lib/paid-plan.ts, and pr-shared-input-selection.test.ts's ship-skip pin.

* ci(osv): tracking issue for scheduled reds and a 14-day suppression expiry test

F11/W8f: a failed main OSV scan now upserts one tracking issue and a clean
scan closes it (scheduled reds notified no one during the August backlog).
A free test fails 14 days before any .osv-scanner.toml ignoreUntil (all four
expire 2026-11-30), with the fix: upgrade and delete, or re-justify and move
the date.

* test(ship,review): keep guards against retired fail-open instructions

'skip silently' on reviewer failure and 'do not block /ship on subagent
failure' were harmful instructions, not prose style; keep them absent with the
shortest distinctive phrase.

* feat(eval-select): default to the pr profile

DX-10: eval:select previews the PR lane's selection by default, matching
test:pr and eval:bg:pr; --profile full (or EVALS_PROFILE=full) keeps the plain
touchfile selection.

* test(evals): type-clean the recovery gh fake (typecheck:test ratchet)

* chore(windows): record the Windows duration seed (run 37221963236)

573 Windows-safe files timed alone on windows-latest; cookie-import-native-job
is 102 s there. Six shards now predict 154 s each.

* feat(evals): eval:bg dispatches the pushed ref to CI or runs locally under a 1.5x-planned cap (W5d, CEO-17/29, DX-1/2/3/7, ENG-12/19)

* feat(runners): --help on both shard runners via the shared flag loop; paid runner header describes today's planner/executor/report (W8e)

* test(windows): drop the stale Playwright exclusion and force-include probe-proven files (W6e)

A temporary windows-latest probe (run 37221995077) ran each of the 97 files
excluded for 'launches Playwright browser' or 'raw /tmp/ path' alone: all 17
Playwright files pass (Chromium is installed and Bun drives it there), and 41
of the /tmp files pass with executed cases. The Playwright pattern is removed
and the 41 files are force-included with the probe as their reason; the 36
real failures stay excluded. The curated subset grows from 573 to 631 files
and the ratchet floor follows. Windows curation moves to
scripts/lib/windows-curation.ts (the runner drops to 2000 lines).

* test(free): one install fixture cloned from a read-only seed (W6c, ENG-1)

Merge test/helpers/setup-codex-scope-fixture.ts into install-fixture.ts.
The checkout is built once per test file into a read-only seed and cloned
per fixture with copy-on-write where the filesystem has it (full copy
otherwise), restoring each file's own mode; clones never hard-link the
seed because put() and tests write in place. Callers register
afterAll(cleanupSeed); Bun's runner fires no exit event, so seeds of
killed runs are swept by pid. test/install-fixture.test.ts is the ENG-1
tripwire: put/setVersion/chmod in one clone leave the seed and a sibling
unchanged, and clones share no inodes with the read-only seed.

* test(free): split claude-overlay-setup by describe (W6b)

The 115 s serial file becomes default/lifecycle/installs files (5/4/3
tests, unchanged bodies) sharing test/helpers/claude-overlay-fixture.ts,
so the shard packer can balance them.

* test(cso): convert cso-contracts.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: cso E2E/judge cases (skill-e2e-cso*, cso-eval).
Sentence pins: 16 -> 8.

* test(cso): convert cso-preserved.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: cso E2E/judge cases (skill-e2e-cso*, cso-eval).
Sentence pins: 10 -> 3.

* test(cso): convert cso-eval.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: cso E2E/judge cases (skill-e2e-cso*, cso-eval).
Sentence pins: 7 -> 4.

* test(browser): convert aside-driver.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: Aside/browse E2E cases; the installer ban stays a meaning check.
Sentence pins: 27 -> 9.

* test(browser): convert third-party-actions.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: Aside/browse E2E cases; the installer ban stays a meaning check.
Sentence pins: 17 -> 2.

* test(skills): convert skill-validation.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: workflow judges in skill-llm-eval.
Sentence pins: 19 -> 6.

* test(skills): convert workflow-excerpt.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: workflow judges in skill-llm-eval.
Sentence pins: 37 -> 10.

* test(skills): convert question-tuning-registry-path.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: workflow judges in skill-llm-eval.
Sentence pins: 4 -> 0.

* test(skills): convert host-conformance.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: workflow judges in skill-llm-eval.
Sentence pins: 4 -> 4.

* test(memory): convert memory-ingest-landing.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: memory-ingest free tests that run the binary.
Sentence pins: 11 -> 9.

* test(windows): exclude the skip-only claude-overlay setup files from the Windows subset

Their real-setup cases are describe.skipIf(win32); after the split their bin/
spawns live in the shared helper, so the source scan stopped seeing them.

* test(skills): convert agents-digest.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: the owning skills' E2E cases and workflow judges; CLI/hook output and fixture-data pins in the tail were left alone.
Sentence pins: 1 -> 0.

* test(skills): convert brain-preflight.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: the owning skills' E2E cases and workflow judges; CLI/hook output and fixture-data pins in the tail were left alone.
Sentence pins: 2 -> 1.

* test(skills): convert codex-skill-isolation.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: the owning skills' E2E cases and workflow judges; CLI/hook output and fixture-data pins in the tail were left alone.
Sentence pins: 1 -> 0.

* test(skills): convert office-hours-review.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: the owning skills' E2E cases and workflow judges; CLI/hook output and fixture-data pins in the tail were left alone.
Sentence pins: 2 -> 1.

* test(skills): convert onboarding-moved-literals.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: the owning skills' E2E cases and workflow judges; CLI/hook output and fixture-data pins in the tail were left alone.
Sentence pins: 2 -> 2.

* test(skills): convert open-gstack-browser-liveness.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: the owning skills' E2E cases and workflow judges; CLI/hook output and fixture-data pins in the tail were left alone.
Sentence pins: 3 -> 2.

* test(skills): convert plan-floor-review.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: the owning skills' E2E cases and workflow judges; CLI/hook output and fixture-data pins in the tail were left alone.
Sentence pins: 3 -> 2.

* test(skills): convert plan-review-report-recording.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: the owning skills' E2E cases and workflow judges; CLI/hook output and fixture-data pins in the tail were left alone.
Sentence pins: 4 -> 2.

* test(skills): convert regression-1539-review-self-verify.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: the owning skills' E2E cases and workflow judges; CLI/hook output and fixture-data pins in the tail were left alone.
Sentence pins: 4 -> 0.

* test(skills): convert skill-fixture.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: the owning skills' E2E cases and workflow judges; CLI/hook output and fixture-data pins in the tail were left alone.
Sentence pins: 6 -> 3.

* test(skills): convert template-context-parity.test.ts sentence pins

Safety and authority sentences become expectMentions keyword checks (reword
freely, keep the rule); other English sentence pins are dropped. Headings,
labels, placeholders, commands, field names, enum/status tokens and ordering
checks stay. Behavior and wording: the owning skills' E2E cases and workflow judges; CLI/hook output and fixture-data pins in the tail were left alone.
Sentence pins: 1 -> 0.

* test: restore pins on renderer output and paid-fixture inputs

cso-contracts (the report renderer's required empty phrase), memory-ingest
(binary output), carve-plan-fixture, ship-skip-actor and cso-eval (inputs the
paid harness builds) assert product or fixture output, not skill prose, so the
value-bar narrowing does not apply to them.

* feat(eval-policy): v2 series identity, HARNESS_VERSION guard and identity backtest

W4a (CEO-27/ENG-3/DX-6), not yet wired to the writer: caseSeriesIdentitiesV2
keys a case by the bytes it owns (paid test file, fixtures, its skills' prompt
files including the generated SKILL.md) plus HARNESS_VERSION, and records the
full consumed-input fingerprint as provenance. scripts/harness-version.json
pins the blob of every shared-harness file (GLOBAL_TOUCHFILES plus the session
runners, PTY harness, e2e gate and LLM judge), one line per file;
test/harness-version.test.ts fails on drift with both fixes, and
scripts/bump-harness-version.ts records the decision: --bump (new series for
every case) or --non-behavioral (compatibility entry, no reset).
eval-trial-series --backtest replays identities over weekly main revisions
and recorded trials (ENG-3/DX-12).

* feat(pass-rates): weekly history is main's scheduled runs plus main dispatches

W4b: pass-rates reads evals-periodic history from main only, keeping scheduled
runs and main dispatches and never a branch dispatch, so back-to-back branch
validation runs neither feed pass rates nor count as weeks for quarantine
expiry or drift. --branch <name> still reads one branch's runs for inspection.

* docs(test-audit): EVAL_POLICY v2 decision memo with the identity backtest

D1 memo (CEO-33, ENG-3, DX-12, CEO-27): P(green) arithmetic, false-blocking
targets, the v2 identity rule and harness guard, weekly history, W4c evidence
(the named 'stochastic' cases pass 26/27 to 34/34, so they stay rule), the
backtest over 29 weekly main revisions and 14,898 recorded trials, what it
means for quarantine reachability, alternatives (gating vs trended, adaptive
panels not adopted, buy vs build), and the rollback proof on the pre-PR reader.

* feat(eval-policy): EVAL_POLICY v2 writer: case-owned series identity

The v2 writer, last commit of the W4 block (DX-12/ENG-14/ENG-21): the report
job's stamp now writes caseSeriesIdentitiesV2 (case-owned bytes plus
HARNESS_VERSION) as series_identity, the full consumed-input fingerprint as
series_fingerprint and harness_version as provenance, and EVAL_POLICY.version
is 2 (approved 2026-10-04, D1). The v1 touchfile identity is removed.
Rollback = revert this commit (and the W4 commits above the reader-compat
floor); readers then ignore the v2 records with a printed count.

* test: restore pins on paid-harness prompt inputs

review-enum-lifecycle and plan-review-report-recording assert the prompt a
paid case's harness builds (opts.prompt / call.prompt), a fixture contract,
not skill prose.

* test(security): check injected safety lines on meaning, not capitals

content-security, the canary instruction and the arm judge prompt still must
carry their never-follow / never-output / exact-"none" rules; the checks are
case-insensitive keyword checks so emphasis can change. The UNTRUSTED envelope
markers and "none" value stay exact tokens. (Hidden element stripping fails
locally only because Playwright Chromium is not installed; same at base.)

* test(plan-ceo): keep the outside-voice scope-summary rule as a meaning check

The first codemod pass left an empty if body (TS1313).

* chore(windows): re-record the Windows duration seed for the 631-file subset (run 37222668939)

* ci(free-tests): summarize every shard's flake ledger in the aggregate job (W7a)

The aggregate downloads flake-ledger-* (one subdirectory per shard) and
appends lane C's `scripts/test-health-report.ts flake-summary` output to the
job summary before strict verification. The runner's own flaky table is
dropped so the ledger reader stays the single source; --ci-verify keeps the
shard-overrun warning.

* feat(evals): free test files anywhere and the stored-zip helper never restore the full PR gate (W1a)

The CEO-23 replay found design/test free tests forcing fallback (36485436963).

* feat(evals): replay recorded PR pushes through today's selector (CEO-23, ENG-18)

scripts/replay-pr-selection.ts reports per push the recorded vs replayed
mode and case count, dropped gate cases, retired failed cases and every
miss (a failed case that depends on the diff but is no longer selected).

* refactor(evals): one case-sharded carve-section-loading file with a case id and touchfiles per skill (W5a)

* test(free): hold qa-deadline's full-pipe case on a FIFO, not a paused Bun stream

Bun gives a child 'pipe' stdio a unix socketpair, so whether the 2 MB fill
actually blocks depends on the parent's reader and the host's socket buffer
sizes. CI (run 36761671811, both attempts) saw exit 0 after ~130 ms; with
net.core.{w,r}mem_default=4 MiB locally the old case fails 3/3 the same way.
A FIFO the test never reads is bounded by the kernel pipe buffer, so the
receipt write genuinely blocks and the 5 s settlement deadline must fire.
The 7 s bound now starts once the preload has queued the write.

* test(free): event-driven waits in pty-output-wake

close-releases-observers: the 500 ms wall race becomes an ordering check
(the waiter is released while only SIGINT has been sent, before close's
escalation finishes), and the Bun.spawn spy fakes only the PTY launch so a
shard neighbor's in-process spawn cannot add a stray SIGTERM (CI runs
36015568981, 36036582721, 36074636848).

continuous-redraws: the fixture spinner stops once the observer has made
five coalesced reads, so the run reaches its work deadline with no redraw
in flight. Streaming right up to the deadline raced the helper's screen
drain and failed as 'PTY screen parse failed; viewport is incomplete'
(5 CI runs 09-30..10-03; 4/40 locally under 8 busy loops, 0/40 after).

* test(free): session-runner long-capture handshake waits for the collected Read event

The fake CLI's init line arms the runner's work-phase timer, so the worker's
handshake (work-ready + a >200 s timer) could pass before the runner's read
loop had collected the Read event printed just before work-ready. Advancing
the clock then killed the run with the Read lost: readSection false (CI run
37194511124). The worker now also waits for the runner's own progress line
for that Read. Forcing the observed ordering (work-ready 200 ms before the
Read line) fails the old test deterministically and passes the new one.

* test(free): budget-override restores the CI variables it rewrites

Its provenance cases set and then deleted CI, GITHUB_ACTIONS, GITHUB_REF_NAME,
GITHUB_SHA and the GitLab CI_* names without restoring them, so in a CI shard
every later file ran with CI unset. qa-only-cleanup's real browse daemons
then launched Chromium without --no-sandbox, the sandbox failed on the
runner, and both real-Chromium cases timed out with 'QA cleanup fixture did
not become ready' (shard 12 attempt 1 of six runs on 10-04, retry alone
green). Retained startup log: 'Chromium sandboxing failed!', child env with
no CI. Reproduced 3/3 with CI=true, userns denied and budget-override ahead
of qa-only-cleanup in one process; 0/3 after.

* test(free): pr-shared-input-selection names its base and package history

Every selection now passes EVALS_BASE and packageVersionOnly explicitly, so
no case reads the checkout's git state. Before, 12 calls ran
detectBaseBranch and the cumulative case diffed package.json against
merge-base(origin/main, HEAD) — the ambient input that turned main red in
runs 36572856858 and 36594314027. A PATH git spy counts 14 git calls in the
old file and 0 now.

* feat(evals): derive PR-lane dependents for unmapped files instead of restoring the full gate

scripts/pr-dependencies.ts derives, from the checked-out tree, which paid
cases consume each repository file: test import closures, repo paths and
path.join segments in code/shell/skill text (whole-line comments and
selection data excluded), bin script names, compiled binaries by outfile
path (scripts/build.sh), package scripts, template placeholders through
RESOLVERS, and host configs by host id. The global touchfiles' import
closure is global.

PR selection: an unmapped file with derived consumers puts the run in the
new 'dependents' mode (every dependent gate case and judge, no profile cut);
a tracked file under a derivable directory that nothing reaches is exempt;
periodic/marathon workflows, .gitignore and tsconfig.test.json never run
the PR gate. PR_FULL_GATE_FILES is the fail-closed list (deps, CI image,
setup, build, evals.yml and its actions, test preload, tsconfig.json,
.gitattributes, the skill generator and preamble resolvers, the eval
policy). Untracked or underivable files restore the full gate with fix
text. The summary and PR comment list derived dependents.

* feat(evals): replay misses count derived dependents; report wave vs ordinary pushes (CEO-23)

* test: drop FREE_FIXTURES entries for fixtures deleted in W8b

* test: re-shape the census attribution fixture to qa-workflow and carve case shards

* test: census fixture case order

* ci: 420 s PR-lane slices with per-slice ceilings; periodic tracking issue only on main

evals.yml joins the census lanes at the 420 s slice budget (W5c/CEO-15) and
indexes each eval slice's job timeout from .plan.sliceCiTimeoutMinutes with
the slice-deadline clock as its first step (W2c/ENG-2). evals-periodic.yml's
tracking-issue upsert and close run only on refs/heads/main, so a branch
dispatch whose gate census is skipped (CEO-04) never opens or closes #2755.

* docs: integrate lane docs for the audit wave (eval-bg backends, PR fallback, policy v2, ceilings, evidence)

- CLAUDE.md: 'Running evals as an agent' describes scripts/eval-bg.ts's
  dispatch and local backends, forcing flags, the sentinel and status;
  eval:select's PR-profile default; prompt-structure helper and value-bar
  terms. instruction-facts now requires scripts/eval-bg.ts in that section.
- CONTRIBUTING.md: decision table gains test:health, eval:select default,
  eval:bg backends and the include_gate_census / evals_all dispatch lines;
  420 s runners and per-runner ceilings; flake ledger, OSV tracking issue
  and expiry test, weekly test health; prompt-structure guidance.
- AGENTS.md: build commands name the eval:bg backend choice and test:health.
- docs/TESTING_INTERNALS.md: anchors the fix texts point to
  (#pr-paid-lane-fallback, #pass-rate-policy-versions, #harness-version,
  #flake-ledger, #duration-seed), receipts/recovery/debounce, per-slice
  ceilings, the slice deadline with not_run/hung and checkpoints, the eval-bg
  cap formula in place of the detach floors, EVAL_POLICY v2 history, carve
  case shards via --case.
- docs/test-audit-2026-10.md: CEO-23 replay (165/168 -> 164/168
  full-fallback, 0 misses), ENG-17 slice table (re-measured on this tree),
  D2 prose-pin metric.
- TODOS.md: progress note on 'Let pass-rate history decide the rest'.
- test/testing-internals-anchors.test.ts: every TESTING_INTERNALS.md#anchor
  named in code must match a heading.

* test(free): hold qa-deadline's blocked-output-pipe receipts on a FIFO

The five 'guard %s receipts survive a blocked output pipe' cases filled a
paused Bun child pipe with 2 MiB. That pipe is a unix socketpair, so the
fill only blocks while the host's socket buffers are smaller than the write:
with net.core.{w,r}mem_default=4 MiB the old cases fail 13 of 15 case runs
(the guard finishes before the test resumes the stream). The held stream is
now a FIFO the test drains only after resume(), bounded by the kernel pipe
buffer; Windows keeps the paused pipe. Bounds and assertions are unchanged.

* test(free): qa-deadline waits for a written pid, not just the pid file

DRIVER and LEAF write their pid with writeFileSync, so under load ready()
can see the file before its digits and fail 'expected > 0, received 0'
(seen once in 150 loaded runs). After any early failure the finally
SIGKILLs the CLI, which orphans the detached command group; a leaf whose pid
file is not yet readable then keeps the output stream open and the case
hangs to its 15 s timeout (2/80 old-file runs and 3/310 FIFO-only runs
under 8 busy loops; the instrumented hang showed the CLI already SIGKILLed
by the finally). ready() now polls within the same 3 s bound until the file
holds a positive pid.

* fix(evals): a deleted file selects the dependents of the live files that still reference it

PR selection treated every path absent from the head tree as an unknown
dependency, so any PR that deleted a file forced the full gate. A deleted
path is now placed like a live one, against the head tree: a matching
touchfile selects its cases; live code, skill text or a full-gate input that
still references it (path, extensionless import target, relative import,
bin name, ancestor directory, compiled-binary outfile; whole-line comments
and data files excluded) contributes its dependents, a full-gate referencer
restores the full gate with a 'deleted but still referenced' fix, and an
unreferenced deleted path has no dependents. Deleted PR_FULL_GATE_FILES
entries still force the full gate. Tests that used absent paths as stand-ins
for new unmapped files now use tracked root files (or an explicit tracked
set); the CEO-23 replay counts misses through those references too.

* docs: integrate free-suite lane docs (seed anchor, Windows lane, native qualification, install fixture, flake rules)

- docs/TESTING_INTERNALS.md: '### Free suite duration seed' (the anchor the
  seed messages link) with DX-13, the 99th-percentile packing, --ci-plan
  warning, --ci-verify overruns and the CEO-10 seed ratchet; the Windows
  6-job lane, its Windows-measured seed and refresh dispatch, curation and
  WINDOWS_CURATED_FLOOR; native-qualification.yml modes; the merged
  install fixture; test-only timing knobs; the flake-ledger job summary,
  Windows ledger names and three free-suite flake rules; zsh in the
  Ubicloud mirror.
- scripts/test-health-report.ts links #free-suite-duration-seed, so the
  two seed messages share one heading (the anchor test caught the split).
- CONTRIBUTING.md: Windows seed refresh and native qualification rows;
  seed-source pointer. AGENTS.md: Windows six-job lane.
- docs/cso-release-qualification.md: the free CSO Docker job runs only
  cso-docker-integration.test.ts.
- docs/test-audit-2026-10.md: free-suite evidence lines.
- TODOS.md: CEO-11b progress note on the pre-push hook hang.

* test: final duration seeds on the integrated tree; retire the emptied cookie-validation-phases file

Linux seed re-recorded on Ubicloud standard-16 and the Windows seed from
dispatch run 37227140871, both at c903310. The seed allowlist drops the three
entries that are no longer over the ceiling. cookie-validation-phases lost its
validation-phase pins with W8f and its Windows/Dia pins moved to
windows-native-workflows with native-qualification.yml, so the file is
deleted and the curated Windows floor rises to 638. The Windows recorder writes
the seed at its default path so GSTACK_FREE_TEST_DURATIONS never leaks into
timed tests.

* test: re-express stale PR-lane pins against the new evals.yml contract

- ci-eval-cache: the profile/fresh expressions now include evals_all=false
  dispatches and pr_receipts/evals-fresh; the PR store is restored only by
  recover-receipts (still scoped to repository_id + PR, PR events only),
  plan-slices takes it as an artifact and never touches the cache, saves
  stay PR-only, EVALS_CACHE_PR binds pr_receipts dispatches, and the report
  verdict artifact carries report receipts.
- ci-paid-coordination: plan-slices needs only the two ubuntu coordination
  jobs (never the image); the fork/Dependabot clause is unchanged on
  plan-slices and also gates debounce and recover-receipts; only recovery
  holds actions: read.
- qa-supervision-selection: auq-consistency and auq-verbose-vs-carved-ab
  (W1b) reach claude-pty-runner and pty-screen through auq-sdk-capture,
  exactly as auq-format-gate does.
- paid-retry-supervision: the full-validation plan uses a tracked full-gate
  input (setup); an absent path is now a deletion placed by its references.

* ci(windows): record the Windows seed at the default path

* chore: HARNESS_VERSION non-behavioral decision for the audit-wave integration

* v1.91.20.0 release metadata: test, eval and CI audit wave

* test(compare-board): wait for the post-submit state the feedback POST settles

The two post-submit assertions read the board once, but the board disables
the submit button and shows the message only after its feedback POST
resolves. On a loaded Ubicloud shard the read beat the round trip (FLAKY-PASS
in the final free-suite run). Delaying the POST by 400 ms reproduces both
failures at HEAD; polling for the settled state passes.

* test(plan-eng plan-mode): run the seeded STOP-gate smoke in its own fixture project

The seeded plan proposes a custom test runner, so the review's Step 0 reads
the working tree's test setup. In the gstack checkout that is gstack's own
sharded runner, and the case timed out at 300 s in census 37228573062 after
this wave reshaped the runner. It now runs in a four-file Bun project with
git history, with the product-notice suppression auto-decide-preserved's
standalone fixture already uses. Local pass on Bun 1.4.0 + Claude Code
2.1.284: asked in 121 s, seed tokens observed.

* test(plan-mode evidence): pin the seeded eng smoke's isolated fixture cwd and env

* ci(image): Git >= 2.44 so paid shared-libs cases run the supported safe-git path

bin/gstack-safe-git prefixes every /deslop-shared-libs Git read with
--no-lazy-fetch, which noble's git 2.43 rejects (exit 129). The eval image
therefore ran every paid shared-libs case on the unsupported-Git fallback:
local history unavailable, branch HEAD unknown. shared-libs-read-only failed
on exactly that in PR run 37230639146 ("Current branch HEAD: unknown. The
safe-git probe failed (unknown option: --no-lazy-fetch, git 2.43.0)") and in
2 of 74 earlier PR runs. The image installs current Git from the Ubuntu Git
Maintainers PPA and fails its build if --no-lazy-fetch is missing; the
deliberate unsupported-Git case keeps its fake Git.

* eval: pool matching branch census trials into main's pass-rate series (D1 option b)

Approved 2026-10-05. eval:pass-rates also reads completed branch census runs
in the weekly window. A branch trial counts only toward a series a main run
also has (same case-owned bytes, HARNESS_VERSION, model and CLI), so a branch
extends main's history but never starts or becomes a case's current series;
weeks for quarantine expiry still count main runs only. The D1 backtest put
this at 67 of 210 cases with enough trials for the entry rule, against 0 of
195 on main alone.

* C2: /qa defines its outcome filename fields; QA report templates name the measured timing fields

Low judge samples in failing panels named both: qa workflow (37186854666)
'an undefined {user} in the outcome filename', and qa-only workflow
(37198445662) 'template asks for Duration / durationMs totals while
reporting.md mandates Probe budget and Guarded command time'. The qa
union stays under its 1.11 size ratio (93292 of 93294 bytes).

(cherry picked from commit e14c0c1de1)

* C2: Browser Quick gets a feasible budget; QA loop names its directory and stop path

Judge rationales on the qa-only panels (2 of the last 3) cited the 30 s
Browser Quick budget: homepage + 5 targets, each a checkpoint write and a
guarded command, left ~5 s per probe. Quick is now 180 s (method text: 3
minutes); the qa-only-no-fix paid check expects the same native deadline.
PROBE_DIR points at its source line, a stopped loop goes to the final
report steps (annotations, materialize, Markdown), and /review and /ship
say that after smoke expiry only plan checks rerun in revalidation.

(cherry picked from commit 78d4a2a343)

* qa: fit the C2 judge fixes under the qa and review size caps

Brings GSTA-21's qa.ts trims (3bd625d, ce73af0) for the cherry-picked C2
wording: PROBE_DIR keeps 'owned', the stop line points at §4, and the
post-expiry rule is one sentence, so /review fits its cap. qa measures
93,303 / 84,049 = 1.11010 against the same 1.1102 cap GSTA-21 set. The
qa-only workflow judge passed 10 of 10 local trials on this wording (7 of 10
before the C2 fixes).

* test: qa-only fixture keeps its caller-set 30 s deadline

The qa-only-no-fix fixture tells the model to start the guard with 30 s
(a caller limit), so its deadline assertion's default stays 30 000 ms; the
product's Browser Quick default (180 s) does not apply to it. Reverts the
helper and its test to their base.

(cherry picked from commit e36e622add)

* test: qa-quick sets a caller probe limit and drains past its session budget

C2 raised Browser Quick's native budget to 180 s. qa-quick is the only gate case
on the native budget, and its wall went 194 s (main periodic, pre-C2) -> 254 s and
274 s (this PR's runs) -> 300 s session timeout (run 37242691913): the agent now
probes every navigation target with a checkpoint, then needs ~80 s to report.
Like qa-only-no-fix, the eval now sets a caller probe limit (30 s), scopes the
fixture, and gives bun's outer timeout the session drain margin instead of racing
the 300 s session. The product's 180 s budget and the session budget are unchanged.
Diagnostic rerun passed in 195 s.

(cherry picked from commit f8dc64031e)

* chore: restore the audit wave's package scripts after the main merge (version 1.91.23)

* qa: keep main's one-sentence post-expiry rule for /review and /ship

The C2 variants ("step 4c reruns plan checks only" and "even in 4c") read
to the review workflow judge as conflicting with Step 5.8's repeat-pass
reruns: 6 of 10 local trials passed with the longer sentence and PR run
37248650879 failed on the shorter one. Main's "Post-expiry smoke rechecks are
not-run." passed 10 of 10. qa (9/10), qa-only (10/10) and
ship-managed-hook-refresh (10/10) were measured on the same tree.

* v1.91.24.0: move off the slot #3036 claims
2026-10-05 01:29:54 +00:00