* fix(pr-selection): scan shebang and .ts imports, fail closed on scan errors, classify repo config safely
A1-1: strip a leading #! before scanImports at every site, treat bun/node
shebang scripts as code, pick the loader by extension (the TSX loader
misread 15 .ts files), and restore the full gate when any other import
scan fails. Broadens selection only: 0 (file, case) pairs lost vs main,
41 files move from no-consumer to consumed.
A1-2: FREE_ONLY_PR_FILES gains six repository-config files, capy/ is
derivable, and a full-gate file is labelled 'full-gate input' instead of
'real unknown dependency'. The replay MISS check honours the same audited
free-only exemptions as the selector.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(test-health): report gate cases and known cost per PR run, lead with cost per merged PR (A1-3)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs(testing): correct the PR fallback section, document the lockfile decision (A1-3, A1-4)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore(harness): record the A1 test-pr-profile.ts change as non-behavioral (selection only)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(pr-selection): exercised-path consumers and a shadow selection block (A2-1, A2-2, A2-3)
exercisedConsumers() unions touchfiles, the test-file import closure, executable
skill shell and the bins recorded trials ran (test/helpers/exercised-commands.json).
selectPrProfile reports what that map would select in prCoverage.shadow and never
changes the real selection; a map-only diff selects no paid case.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(exercised-map): union-only refresh with a fail-closed contract, census refresh, MAP_GAP and the replay receipt (A2-1, A2-4)
bun run test:exercised-map refreshes from a census run or local artifacts and
writes nothing on unavailable (exit 3) or partial (exit 4) input. The paid report
prints non-blocking MAP_GAP lines; --replay pins 0 misses over the 47 recorded
gate reds.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(test-health): report shadow savings as a lower bound and split red drops into activation misses and flaky drops (A2-3)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs(testing): document the exercised-path map, its refresh contract and shadow metrics
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore(harness): record the A2 test-pr-profile.ts and paid-report.ts changes as non-behavioral
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(fixtures): register the exercised-map replay fixture as free (read by scripts/exercised-map.ts --replay)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore(release): v1.91.127.0
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* Revert "chore(release): v1.91.127.0"
This reverts commit 79e3ef6c63.
* chore(release): v1.91.130.0
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>
* fix(pr-selection): scan shebang and .ts imports, fail closed on scan errors, classify repo config safely
A1-1: strip a leading #! before scanImports at every site, treat bun/node
shebang scripts as code, pick the loader by extension (the TSX loader
misread 15 .ts files), and restore the full gate when any other import
scan fails. Broadens selection only: 0 (file, case) pairs lost vs main,
41 files move from no-consumer to consumed.
A1-2: FREE_ONLY_PR_FILES gains six repository-config files, capy/ is
derivable, and a full-gate file is labelled 'full-gate input' instead of
'real unknown dependency'. The replay MISS check honours the same audited
free-only exemptions as the selector.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(test-health): report gate cases and known cost per PR run, lead with cost per merged PR (A1-3)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs(testing): correct the PR fallback section, document the lockfile decision (A1-3, A1-4)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore(harness): record the A1 test-pr-profile.ts change as non-behavioral (selection only)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore(release): v1.91.127.0
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* Revert "chore(release): v1.91.127.0"
This reverts commit 79e3ef6c63.
* chore(release): v1.91.129.0
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>
* ci(seed): weekly standing recorder for the Linux and Windows duration seeds (W4)
free-durations-record.yml runs Mondays 04:00 UTC (before test-health) and on
dispatch. Linux records on ubicloud-standard-8, the free-suite shard runner,
with scripts/ubicloud/setup-free-suite.sh; Windows records through
windows-free-tests.yml's record_durations path (now also workflow_call).
When either seed's file set drifts from main, the propose job force-updates
bot/free-duration-seeds, opens or refreshes one seed PR stating the seed
ratchet result and the files that failed while recording, and dispatches the
free lanes on it. A red file keeps its duration; an interrupted recording
uploads no seed (this also stops the Windows dispatch from uploading a stale
copy), and more than 5 red files on either OS proposes nothing. The bot never
edits the allowlist. open_pr=false on dispatch records and reports only.
The workflow is classified free-lane only in scripts/test-pr-profile.ts, with
a non-behavioral HARNESS_VERSION decision.
Intentional pin change: test/windows-native-workflows.test.ts now expects
workflow_call and the complete-recording guard on the Windows record job.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* ci(seed): keep maintainer commits on the open seed PR, publish only from main, survive long ratchet reports
/review on #3253 (gpt-6.1-sol): a refresh force-pushed over maintainer repairs
on the seed PR, a dispatch from a feature branch would publish that branch's
commits, and grep | head under pipefail could abort the step on a long ratchet
report. The refresh now leaves an open seed PR alone when it holds any commit
besides the bot's one refresh commit and pushes with an exact lease; only main
publishes; the report is cut with sed. Each has a functional test that fails on
the old workflow.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs(changelog): unreleased entry for the weekly duration-seed recorder
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore: stamp v1.91.128.0
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>
* test(qa-callers): bind QA section names in the caller prompt instead of teaching helper forms (W3-4)
The caller session prompt taught full capture/checkpoint syntax but only
mentioned materialize, so two ship-exploratory-small-cli sessions (paid
gate runs 38973329985, 38993076767) skipped qa/sections/exploratory.md and
system-functional.md and guessed a bare materialize. The prompt now
declares only fixture bindings (DEADLINE_TOOL, EVIDENCE_TOOL, PROBE_DIR,
DEADLINE_FILE, --public approval, --timeout-ms 10000 for plan probes) and
points to the section for every form. The read requirement is unchanged;
both captured transcripts are kept as negative controls.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(review-army): run the state-machine detector in the scope block and check its exit status (W3-5)
Specialist 9 named gstack-state-matrix detect only in selection prose, and
small diffs keep only State Machines, so agents ran an undeclared command
(paid gate run 39002269281). The generated scope block now runs the
detector with its exit status checked: on failure it keeps the detector's
diagnostics and sets SCOPE_STATE_MACHINE=unknown, and selection dispatches
State Machines when the signal is true or unknown. The check step becomes a
declared heredoc block. The clarity test requires every non-diff-scope
signal to come from a detect helper the scope block runs; a free test runs
the block against a failing detector stub; the captured standalone call
stays outside the caller interface.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(third-party-actions): letter the absent-Aside consent options B/C/D (W3-7)
The contract fixed letters only when Aside is detected, so a tpa-absent-linux
agent numbered its consent question 1/2/3 (paid run 39011101113), which the
lettered-offer inspection cannot read. The absent branch now offers
B) gstack's own visible browser, C) manual instructions, D) defer and omits
A). The paid test's assertion is unchanged; the captured numbered answer is
a documented fail example in the unit test.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(plan-reality): say tier 1 rows are never n/a (W3-8)
The plan-review reality block offered pass|finding|n/a without saying tier 1
rows may never be n/a, so a plan-mode-no-op session wrote
`REALITY: deferred-asks n/a ...`, check returned repair, and the repair loop
ran past the 300 s capture (paid run 39004466422). The block now says tier
1 rows are never n/a and that with no linked issue deferred-asks is pass
with a receipt showing none. The captured line is pinned through check as
missing=deferred-asks verdict=repair.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(qa-functional): webhook fix runs re-verify the repaired invariant and rerun superseded captures (W3-6, W3-14)
W3-6: stage 2 of the fix-run prompt asked only for the original, happy,
cancel and dependency probes while verifyQANativeRegression reruns all
eight, so a status-only repair that left partial broken reached the verifier
(paid run 38986120559). The webhook fix prompt now states the capture budget
up front (at most 5 pre-repair captures, then the 7 post-repair scenarios:
duplicate, partial, concurrent-ab, concurrent-ba, happy, cancel, dependency;
12 in total; report incomplete if 5 cannot establish the defect). That list
feeds the capture nudge (GSTACK_QA_REQUIRED_PROBES);
QA_WEBHOOK_REQUIRED_SCENARIOS and the verdict are unchanged.
W3-14: the fix prompt now says to rerun every pre-repair capture the
verdict relies on after the final repair, and that a superseded row stays
open until its command reran on the repaired source (paid run 39053431488
labelled a pre-repair duplicate superseded and never reran it). The
detector and its negative test are unchanged; the captured shape joins them
as a negative control.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(qa): tie the exploratory stop rule to per-rule input probes; fix runs close superseded captures (W3-9, W3-14)
W3-9: two qa-functional-cli-report sessions stopped at 5-7 of 12 probes
without probing a non-digit amount (paid runs 38983306684, 39001107574),
though system-functional.md requires one probe per documented input rule.
The single stop rule shared by both exploratory templates now says each
documented input rule needs its own probe breaking only that rule, and
rules left uncovered at a probe or time cap go in limits.
W3-14: for fix runs (not qa-only), every capture taken before a fix is
superseded even when its client prints no snapshot; before completion the
agent reruns each one its verdict relies on or reports it as an open
remainder. Production superseded/open semantics are unchanged.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(qa-functional): keep the captured webhook-report cat attempts as denied negative controls (Q3)
Paid run 39006896632 (qa-functional-webhook-report) attempted
`cat package.json probe.ts .gitignore` and `cat src/*.ts test/*.ts`; the
PreToolUse hook denied both. This is a model miss, not a detector defect, so
both commands are pinned as denied by the registered hook and outside
qaCommandAllowed. No detector change.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(docs-candidate): per-subcommand --help and a compare reject that names the fix (W3-2)
snapshot --help and compare --help print the usage and exit 0 before any side
effect; the docsync observer admits exactly those two forms. compare names the
record path, each missing or wrong field (schema_version, base, hashes) and
the snapshot command that writes a valid record, keeping exit 1. The captured
ship-docsync-stale-after commands from PR gate run 38990111230 replay inside
the interface; help with any other argument stays rejected.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(docsync-fault): prepare writes the production candidate through the installed snapshot (W3-1)
The fault actor prepare wrote its own {base_sha, content_hashes, ...} record,
which the installed gstack-docs-candidate compare rejects, while
ship/sections/documentation.md requires running compare; 79 of 198 docsync
captures (and the red in PR gate run 38990111230) were pushed off the
documented path by it.
prepare now runs the installed snapshot under a 30 s timeout for the same
selection, returns its exact command as snapshot_command so the post-child
rerun is literal, publishes prompt and event only after a valid record
exists, and removes a record its own failed snapshot left. dispatch observes
the candidate with the same installed snapshot; inspect returns base, index
and per-file git blob ids in the record representation; the legacy re-audit
check reads selected/hashes. The fault prompt names snapshot_command and the
new field names.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs(todos): file the qa-quick capture headroom follow-up (fix wave 3, T)
qa-quick timed out once at 300 s (paid run 38980005793) against passing
runs of 164-272 s. Trimming what the Quick tier loads is a product call, so
it is filed, not repaired; no budget change.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(docsync): keep the two docsync model misses as negative controls (W3-3)
The Bash grep of ship-docsync-missing-asset (PR gate run 38983306684,
bash#13) and the composed cat; cat of ship-docsync-failure (38984977679,
bash#12) were correct rejections. They join the stored known_bad commands
(with a {home} placeholder for the fixture home); no detector change.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(golden): refresh the ship goldens for the W3-5 scope block and W3-7 consent letters
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(third-party-actions): state the absent-Aside letters in fewer characters (W3-7)
Same contract (B), C), D) when Aside is not detected; the next sentence
already omits the Aside option), 67 fewer characters on the pinned
land-and-deploy and office-hours skills.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(skill-md-size): re-pin 31 over-limit skills grown by fix wave 3 prompt repairs (owner sign-off needed)
CREW rule 11: these skills were already over 100,000 characters and grow
by the minimum text each repair needs; none crosses the limit for the
first time.
- gstack-review on cursor, factory, kiro, opencode, slate (+657..+676):
W3-5 runs gstack-state-matrix detect inside the scope block with its exit
status checked and declares the check step as a heredoc block; the new
fence carries the env-var-host runtime prelude (about 360 characters).
- gstack-plan-eng-review on 12 hosts (+104): W3-8 says tier 1 reality rows
are never n/a (paid run 39004466422 looped on a rejected n/a row).
- gstack-land-and-deploy on 12 hosts and gstack-office-hours on 4 hosts
(+21): W3-7 letters the absent-Aside consent options B), C), D).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(skills): fit the W3-5, W3-7 and W3-8 prompt repairs inside the parity budgets
The parity suite left 2-42 bytes of headroom on ship, review,
plan-eng-review, plan-ceo-review and land-and-deploy, so each repair now
pays for itself in the same resolver:
- W3-5: the scope block runs `source <(gstack-state-matrix detect --base
<base> || echo SCOPE_STATE_MACHINE=unknown)` (a failed detector's
stdout is overridden by the later unknown assignment; its stderr still
shows), the check block uses a short heredoc, and DIFF_LINES comes from one
`git diff --numstat | awk` sum instead of two --stat greps (same count:
insertions plus deletions, binary files zero).
- W3-7: the absent branch reads "offer only options B), C) and D)."
- W3-8: "never n/a" joins the tier 1 parenthesis, the no-linked-issue rule
joins the receipt sentence, and the deep header says the same in fewer
words; the redundant "(it prints the PLAN_REALITY: line)" is dropped.
Size pins: land-and-deploy (12 hosts, -20), office-hours (4 hosts, -20)
and plan-eng-review (12 hosts, -10) are lowered to their new sizes;
gstack-review on cursor, factory, kiro, opencode and slate stays above its
main pin (+355..+374) because the declared check block is a new fence that
carries the env-var-host runtime prelude. Raising those five pins needs
owner sign-off.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(design-e2e): the shim-dom scan check resolves the command's literal _REPORT/_RUN (W3-10)
PR gate run 38980005793 went red because the agent ran the generated DOM
block, scanning "$_REPORT/dom/$_RUN" with _RUN="run1" assigned in the same
command, and the check looked for the literal dom/run1 anywhere in a command.
domScanOfRun substitutes that command's own literal _REPORT=/_RUN=
assignments and credits a scan only when one of its own arguments is the run
directory. scan --changed, another directory, quoted heredoc report text and
non-literal or conflicting assignments stay negative controls. All 15
captured shim-dom runs in the window pass the new check (14 passed the old).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(pty): keep the last non-blank viewport for failure dumps (W3-11)
autoplan-guard-pty (PR gate run 38976253774) stalled at 489 s and its screen
dump, the tail of visibleText(), was only spinner redraws. Live PTY sessions
now remember the last non-blank viewport a screen read returned
(session.lastScreen()) and the harness wait-timeout dumps append it.
Diagnostic only: visibleText(), matching and verdicts are unchanged.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(autoplan): explicit 30 s timeout for each answer-invalidation test (W3-12)
The eng-only override test ran 4.1-4.4 s on a 4-core machine against Bun's
5 s default (7.8-9.4 s per file). Each test now names a 30 s budget.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(review-army): size the nested bun test budgets for windows-latest (W3-15)
windows-free-tests run 39053431430 (shard 5): the nested bun test exited 1 in
5.58 s because Bun's default 5 s hook timeout fired on the real Red Team
beforeAll (8 git spawns plus skill-section copies). Both nested argvs pass
--timeout 30000, which raises only the nested default; the registrations
under test keep their explicit timeouts. Spawn timeouts move from 8 s to
40 s and outer test timeouts to 45 s, including the consensus test that had
none. With a 6 s beforeAll injected, the lifecycle still matches; the old
argv reproduces the Windows hook timeout.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs(todos): close the createQAFunctionalFixture entry and file D6 (W3-13)
The git auto-maintenance entry was fixed by 0bf9d50 (#3190). D6: nested bun
test output pollutes free-runner failure attribution in
scripts/lib/shard-engine.ts (windows-free-tests 39053431430).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(free-fixtures): declare the W3-4 captured caller transcript as a free-only fixture
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(pty): strip cursor-visibility escapes and print the last screen on autoplan-guard-pty failure (W3-11)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(review-army): keep the state-machine detector's output only on exit 0 (W3-5 confirm round)
A failing gstack-state-matrix detect prints SCOPE_STATE_MACHINE=false before
exiting non-zero. The scope block now captures its output and sources it only
when the detector succeeds; otherwise it discards the output, sets
SCOPE_STATE_MACHINE=unknown (dispatching the State Machines specialist) and
leaves the detector's stderr visible. Free tests cover false-output/non-zero,
empty-output/non-zero and a late failure after printing true. The stack
comment is shortened so gstack-review stays 4 chars under its pin; the five
host pins are lowered to match.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(qa-functional): reserve webhook post-repair reruns first inside the unchanged 12-capture ceiling (W3-6/W3-14 confirm round)
The fix prompt now states precedence: post-repair reruns (the 7 stage-2
scenarios plus each distinct pre-repair command the verdict relies on, such as
a reject probe) are reserved first, pre-repair exploration gets what remains,
and reruns that cannot fit are reported as open. A free test fits a pre-repair
reject plus the reproduced defect and its replay within 12 (11 captures).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(docs-candidate): the compare reject never sends an agent to re-snapshot after the child audit (W3-2)
A post-audit snapshot over the candidate replaces the before-audit baseline, so
the ownership check would compare current files with themselves. The fix line
now says the candidate must be the record the Prepare snapshot wrote, that the
Prepare snapshot is rerun only before dispatching the child audit, and that the
post-child snapshot uses a different --out (<audit-id>-post.json). The helper
test pins the text.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(docsync-fault): diagnosable prepare snapshot failures with one fresh-id retry (W3-1)
A failed installed snapshot now returns exit 25 with a stable reason code
(snapshot_exit, snapshot_timeout, snapshot_spawn_error, snapshot_invalid_record),
the helper's stderr tail redacted through lib/redact-engine with the fixture
root as <HOME> and bounded to 800 characters, and keeps the attempt's files: the
candidate the helper left, if any, and prepare-<audit-id>-failure.json. The
failure is a prepare-failed event, not a rejected interaction. One retry is
allowed under a fresh audit id; reusing the failed id, dispatching it or a third
prepare is rejected. Tested before and after candidate creation. The two
snapshot tests get an explicit 30 s timeout: the compare/inspect agreement
test runs 4.3-4.8 s alone and timed out at Bun's 5 s default under load.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(qa-evidence): count required post-repair probes only after the fixture-declared final source edit (W3-6/W3-14 confirm round)
GSTACK_QA_REQUIRED_PROBES reminders counted any complete capture, so a
pre-repair duplicate or partial capture looked like post-repair coverage. The
caller may now declare GSTACK_QA_REQUIRED_AFTER_EDIT with absolute paths and a
clean-fixture time: only captures started after the last edit under those
paths count, none count before the first edit, and a malformed declaration
counts none. Without the declaration every capture counts as before. The
webhook fix fixture declares its src/ directory. A free test covers
pre-repair-only coverage (nudges), complete post-repair coverage (no nudge)
and a second edit (nudges again).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(harness): bump HARNESS_VERSION to 5 for the W3-11 stripAnsi change
stripAnsi now removes cursor-visibility escapes (ESC[?25h/l). Every PTY case
matches patterns against its output, so a pattern spanning one of those escapes
can now match where it did not before: a verdict can change, so this is a
behavioral bump, not a non-behavioral entry. The launch.ts change (last
non-blank screen in failure messages) is diagnostic only.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(review-army): pass the state-machines output to check as a written file, not a heredoc placeholder (W3-5)
The generated-bash lint (env-host-fence-conformance INV-3) rejects free text in
a heredoc body: <its complete output> reached the shell. The agent now writes
the specialist's full output to .gstack/tmp/state-machines.md and the fence
runs gstack-state-matrix check on that file. Two stack-detection comments and
the parenthetical are trimmed to stay inside the review/ship parity ratios;
gstack-review shrinks by 33 on the five pinned hosts and their pins are
lowered to match.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(autoplan-guard-pty): the scripted actor ends its turn after a background CEO dispatch before reading the close packet
PR gate 39070276914 (dc739be) failed at the close-packet check while gate
39069171159 (b789898) passed with byte-identical autoplan inputs. The evidence
differs only in order: the failing actor read the close packet in the dispatch
turn, so the first tool call after the CEO notice was the publication. The
product guard allowed all four guarded calls in both runs. The fixture step 5
told the actor to end its turn only conditionally; step 5 now ends the turn as
soon as the result says the agent runs in the background, and step 6 reads the
close packet only after the reviewer finished. The close-packet check moves to
closePacketReadAfterNotice, replayed in a free test against both captured
orders; a second free test pins the actor's steps 5 and 6.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore(release): v1.91.127.0 fix wave 3 paid-gate reliability round 2
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(qa): input-rule probes are per field, and a value breaking two rules covers neither (W3-9)
PR gate 39073934326 (93449bc) failed qa-functional-cli-report with no observed
unannounced defect, the same class as 38983306684 and 39001107574. The agent
read 'allowed characters' as the ID rule only, counted -5 as its sign probe
and never probed the amount's digits-only rule with an otherwise valid value,
so the seeded parseInt defect stayed hidden. The stop rule now says each
field's documented input rule needs a probe breaking only it, and that a
signed amount breaks sign and digits-only and covers neither; the functional
table row names input rules per field. The qa union stays inside its parity
ratio.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(qa): state the input-rule principle instead of a worked example (W3-9)
The signed-amount example in 0fe616e was misread in the focused rerun as 'a
signed amount breaks only the digits-only rule', and the run again missed
the seeded non-digit defect. The stop rule now states the principle: each
field's documented input rule needs a probe that breaks only it and satisfies
every other rule.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(tpa): the Darwin pitch detector accepts the canonical phrase anywhere, short forms only at a sentence start
PR gate 39075977549 (d6d82fb) failed tpa-absent-darwin on a correct pitch:
'One-time note: you can download Aside (macOS 15+) at aside.com, then open it,
sign in, and re-run'. The contract is to deliver the pitch once; the
sentence-start anchor exists only so narration is not counted. The full
canonical phrase now counts anywhere unless a negation precedes it, and the
short forms (download it / download Aside at aside.com) keep the
sentence-start rule. The detector moves to test/helpers/third-party-actions.ts
with a count helper. Free tests replay the captured red (counts once), the
four captured passing Darwin pitches (once each), five Linux and five READY
narrations plus both recovery-public responses (zero), a mid-sentence short
form and negated phrases (zero), and a duplicated phrase (two).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(qa-callers): only a required plan check may swap the deadline for --timeout-ms; reruns keep the deadline
PR gate 39077524802 (0f71a55) failed ship-exploratory-late-input: after the
late input change the actor reran captures 001 and 002 with --timeout-ms
10000, reading the fixture's 'explicit plan probes outside the smoke budget'
clause as covering revalidation reruns, and the verifier reported both as
smoke probes missing a trusted deadline run. The binding now says only an
explicit plan check this phase requires may use --timeout-ms, and every other
capture, including a rerun or revalidation, uses the deadline.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(third-party-actions): count the Darwin pitch only at a recommendation boundary
/review delta on 51f1e02 (Claude and GPT-6.1 Sol voices): the 0f71a55
detector accepted the canonical phrase anywhere unless the adjacent word was
a negation, so a Darwin refusal or a quoted policy mention ('I will not ask
you to download Aside ...', 'The Darwin policy says to "download Aside ..."',
'You cannot download ...') satisfied the paid case's positive match, and a
free assertion even expected "You don't need to download ..." to count.
The canonical phrase now counts only where a recommendation can start: a
sentence, clause (, : ; dash), bullet, line or bold start, optionally led by
then/please/just/so/first/now and you can/could/may/should/need to, with no
negation directly before it across a line break. Quoted, parenthesised,
reported and clause-negated mentions fall outside that boundary. Every probe
from both voices is a free test; the five captured Darwin pitches (including
the 39075977549 red) still count 1 each and the Linux/READY captures 0.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(review-army): write the state-machines output to a fresh private file from gstack-state-matrix outfile
/review delta on 51f1e02 (Claude F2): dc739be wrote the specialist output to
a fixed .gstack/tmp/state-machines.md with no exclude. On a second review
Claude Code's Write refuses an existing unread file, so check could validate
the previous run's output, and git add -A could commit it.
gstack-state-matrix outfile creates .gstack/tmp (0700) at the repo top,
adds /.gstack/tmp/ to info/exclude once, and prints a new empty 0600 file
per call; the selection prose has the agent Read that file, write the full
output there and check that path. The prose moved the check command inline,
so gstack-review shrinks and its pins are lowered (cursor 141010->140637,
factory 144792->144416, kiro 140816->140449, opencode 143258->142872,
slate 140913->140543).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(qa-evidence): pin the post-repair edit's mtime to capture 002's completion
The GPT voice's /review run on 51f1e02 failed qa-evidence.test.ts:547 once:
capture 003 reported no required probe remaining, so capture 002 (started
before the edit) counted as post-edit. The test wrote the edit right after
002 returned, a gap of one child run (7-27 ms measured here; the voice's
machine ran this test in 59 ms against 183 ms here, so a few ms there),
while an ext4 mtime comes from the kernel's coarse clock and lags
Date.now() by up to a tick (0.5-4.5 ms measured here).
The edit's mtime is now set to 002's recorded completedAt, so 002 started
strictly before the boundary and 003 starts after it on any machine. The
product boundary is unchanged: tick-scale imprecision only matters when a
capture starts within milliseconds before an edit, and a stricter margin
would refuse a real edit && capture chain.
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>
* 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>
The field wave's residual gaps are closed. v1.91.96.0 through v1.91.100.0
shipped the install-honesty and unattended-`/autoplan` wave
(`docs/designs/GSTACK_FIELD_WAVE_2026_10_12.md`); running that release on a
Capy machine left six gaps, each reproduced here with a test that failed
first.
* docs(designs): gstack field wave plan (2026-10-12) and the shared simulated Capy host fixture
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(doctor): --min-revision, a launch Bun found off PATH, planning-only pins SKIP
gstack-doctor --check gains --min-revision <v>|unknown: below it the revision
row FAILs with REVISION_BELOW_MINIMUM naming the Capy wrapper or
bin/gstack-upgrade; unknown says so and never claims the minimum. The runtime
and pins rows name a supported Bun found in $BUN_INSTALL/bin (not on PATH)
with the export/GSTACK_LAUNCH fix (gstack_bun_launch_dir, one helper in
bin/gstack-bun-version.sh). --for a planning-only workflow (autoplan, the plan
reviews, office-hours) reports a repo test-pin mismatch as SKIP pins
(planning-only; ...). New result codes UPGRADE_REFUSED, UPGRADE_FETCH_FAILED,
REVISION_BELOW_MINIMUM, CAPY_WRAPPER_STALE, INSTALL_UNVERIFIED with
troubleshooting anchors; REVISION_UNMET's fix names bin/gstack-upgrade.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(install): install verdict and install admission at skill start
bin/gstack-install-verdict.sh (Bash) and lib/install-verdict.ts (TypeScript)
are the one reader of the install verdict gstack-capy-install writes to
<state root>/install-verdicts/<sha256(checkout realpath)>.json (checkout,
HEAD, workflow and required components, planning-only pins, trailer). The
source checkout and capy/support.env resolve through gstack_source_root /
sourceRoot, which follow an installed root's .source-path.
gstack-skill-start, after SESSION_KIND: an unattended session (any host)
refuses on a PATH Bun below the supported minimum; with GSTACK_CAPY=1 an
unattended or explicit spawned session also refuses without a passing
verdict for this checkout, HEAD and workflow (INSTALL_UNVERIFIED). A refusal
prints the INSTALL: row, an install-refused instruction block bound to its
SESSION_ID, GSTACK_RESULT ... status=refused run=- and exits 3. Ambient
spawned sessions warn; GSTACK_ALLOW_UNSUPPORTED_RUNTIME=1 admits and says so.
capy/support.env declares GSTACK_MIN_REVISION and GSTACK_CAPY_MIN_WRAPPER.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(capy-install): launch PATH, Bun reuse and CI pins, setup skip on HEAD, verdict, --check
The GSTACK_LAUNCH line carries PATH=<dir>:$PATH when the certified Bun is not
first on PATH, plus GSTACK_CAPY=1 and GSTACK_INSTALL_VERDICT, and
GSTACK_LAUNCH_PATH: <dir>; the doctor runs with that PATH. A supported Bun in
$BUN_INSTALL/bin is reused instead of downloaded again. Project pins are read
with bin/gstack-runtime-pins.ts (CI lanes included): when every exact pin is
gstack's verified version the archive is installed alongside a different PATH
Bun; any other exact pin is RUNTIME_PIN_CONFLICT. Both ./setup runs are
skipped when this HEAD was set up with the same hosts, model and browser flag
and the check passes (never after a fast-forward); a failed setup ends with
gstack: fail setup and is retried next run. --check certifies only;
GSTACK_CAPY_LOCK_HELD=1 accepts the bootstrapper's held lock; result anchors
come from the generated components table; REV_NOTE names bin/gstack-upgrade.
Intentional pin change: test/gstack-capy-install.test.ts --revision trailer
now names <root>/bin/gstack-upgrade (the old text named a command that never
existed).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(upgrade): bin/gstack-upgrade, the unattended fast-forward every fix line names
--check fetches and prints current|behind <installed> -> <remote> (<n>
commits); the default fast-forwards to the remote default branch tip and
reinstalls (bin/gstack-capy-install on a Capy install, else ./setup
--refresh-registered), completing a pending reinstall on the next run. It
refuses (UPGRADE_REFUSED, exit 3, nothing changed) a dirty tracked byte, a
diverged commit, a pinned checkout, the incoming Bun floor and a held
.setup-lock; never stashes or resets; an unreachable remote is
UPGRADE_FETCH_FAILED. Fetches are receipted (fail-open). The unattended
skill-end UPGRADE_AVAILABLE line is followed by UPGRADE_COMMAND naming it.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(capy): vendored Capy skill, frozen bootstrapper and full wrapper bin/gstack-capy-session
capy/skill/gstack/ is the Drive skill's source of truth. scripts/install.sh
is a frozen bootstrapper: it prints gstack-capy-skill: wrapper <n> (<path>),
clones or fetches under .gstack-capy-install.lock, reads the remote
capy/support.env (FETCH_HEAD), fast-forwards a clean unpinned checkout below
GSTACK_MIN_REVISION (refusing dirty, diverged or pinned checkouts with the
exact commands; GSTACK_CAPY_AUTO_UPGRADE=0 disables), and execs the
checkout's own bin/gstack-capy-session with GSTACK_CAPY_LOCK_HELD=1 so the
held lock is never taken twice. Offline it says the remote minimum is
unknown. A Drive copy older than GSTACK_CAPY_MIN_WRAPPER prints an owner
action (CAPY_WRAPPER_STALE). SKILL.md: check at session start, install
lazily before the first workflow, the GSTACK_LAUNCH line, the trailer rule,
the subagent first-report line, gstack-pregate sealed before any push, and
the autoplan/DRIVER.md pointer. The context-budget fixture gains the skill's
entry.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs: Capy install layers, lazy install, snapshot bake, forward-only recovery, raising the minimum
docs/capy-install.md describes the three layers (Drive bootstrapper, full
wrapper, installer), the lazy install, the GSTACK_LAUNCH line, install
admission, baking gstack into a Capy snapshot, and that recovery after an
interrupted fast-forward completes or refuses the pending install stages
(checking out an older commit is not a rollback). docs/CHANGELOG_STYLE.md
(the release doc; there is no docs/RELEASING.md) says when
GSTACK_MIN_REVISION may be raised.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* refactor(capy): the stale-copy owner action moves from the frozen bootstrapper to bin/gstack-capy-session
The checkout-owned full wrapper reads GSTACK_CAPY_MIN_WRAPPER through
gstack_support_env_get(gstack_source_root) and compares it with the
bootstrapper revision it was handed, so the rule can change without
recopying the Drive skill. The bootstrapper keeps only what a stale checkout
cannot do: clone, fetch, read the remote minimum, fast-forward, exec.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* v1.91.96.0: honest install and self-upgrade on Capy; supported minimum 1.91.96.0
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test: drop the helper-only Capy host fixture test (test-of-test ratchet); lane tests exercise the helper
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test: bound the new sync spawns in the install-verdict and wrapper tests (spawnSync tripwire)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* ci(paid-pr-profile): the vendored Capy skill is free-only; free-only files are not prompts needing paid coverage
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore(harness): non-behavioral HARNESS_VERSION decision for the PR-profile free-only rule
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(doctor): lane A runtime and pins cases are platform-neutral (bash paths; a lane-free .bun-version pin)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs(designs): field wave plan with amendments and the gate rev 2-3 review record (ARTIFACT_VALID, 3 gate revisions)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(capy-install): follow a project's exact Bun pin above the minimum when gstack has its verified archive (adds Bun 1.4.3)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs: the installer follows a verified exact project Bun pin (capy-install, troubleshooting, changelog)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(capy-install): the install reason names the project's pin and gstack's real minimum
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>
* v1.91.77.0 feat(ship): two-stage gstack-pregate, gstack-regen registry, intent-based issue links (multi-agent wave C-pregate)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(pregate): keep the bin-driving pregate tests off the Windows subset; pin files are free-only for eval selection
The three C-pregate tests that spawn bin/gstack-regen, bin/gstack-pregate,
bin/gstack-issue-links and bin/gstack-ship-receipt do so through
test/helpers/pregate-fixture.ts runBin, so the Windows curation's source
scan of the test file never saw a bin/ spawn and shipped them to
windows-latest, where CreateProcess cannot exec a shebang (14 failures in
windows-free-shard 2 and 5). Each is now a KNOWN_WINDOWS_INCOMPATIBLE entry
with the precise reason. .gstack/generated.json and .gstack/pregate.json
join FREE_ONLY_PR_FILES: no paid case reads gstack's own pin files (the
tools read origin/<base> of the repo they run in, and paid cases run in
fixture repos), so touching them no longer restores every gate case.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(harness-version): record the FREE_ONLY_PR_FILES pin-file entries as non-behavioral
scripts/test-pr-profile.ts is a pinned shared-harness file; the two
.gstack pin-file entries change selection only, never a case verdict.
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>
* fix(timeline): reject a missing or malformed --limit with exit 2 (#1723)
gstack-timeline-read parsed --limit with parseInt and fell back to 20, so
'--limit 1abc' became 1, 'nope', 0 and -5 silently became the default, and a
bare --limit crashed on an unbound $2. Validate the argument as a positive
integer before the no-timeline early exit and exit 2 with an example. No flag
still means 20.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(skill-start): echo SESSIONS, the live session-marker count (#1651)
The sessions directory is touched and expired at skill start, but the count
was never printed, so CLAUDE.md rules that read the preamble's session count
had nothing to read. Echo SESSIONS: N (markers fresher than 120 minutes,
this session included). Idea from @0xDevNinja's PR #1747, redone in the
consolidated skill-start script.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(office-hours): pick the closing tier from the profile read before this session is logged (#2801)
Phase 4.5 appended this session to the builder profile and Phase 6 then read
the profile to choose its closing, so every run saw itself as history: a
first-time user got welcome_back and was asked how the assignment issued
minutes earlier went. Phase 4.5 now reads the profile before --log-session and
records the prior tier, count and last-session fields in the transcript;
Phase 6 uses those values (this session is prior + 1) and re-reads only the
running totals. A failed read proceeds as a first session and says so. Tier
headers now name the session numbers each tier actually covers.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(office-hours): print the full design doc inline before asking for approval (#879)
Tool output collapses in Claude Code, so users were asked to approve a doc
they could not see. Port of @RyanAlberts's PR #1116 into the carved
design-and-handoff section.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(office-hours): report success only when this run wrote its design doc (#1049)
Before telemetry, check the design doc this run wrote, by its exact path under
the state root, never the newest file in the folder. Success needs a
non-empty doc there; otherwise the outcome is abort or error with failed step
design_doc. Rewrite of @walton-chris's PR #1049, which matched the newest
global session marker and hardcoded ~/.gstack.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(plan reviews): skip the /office-hours prerequisite offer in spawned and headless sessions (#1958)
The BENEFITS_FROM offer had no recommended option, so a spawned run had
nothing defined to auto-pick and a headless run could only block. Skip it
there and continue with the standard review.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore: regenerate SKILL.md files
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test: office-hours tier order, failed-read fallback, inline doc, design-doc check and offer skip
Rendered-block tests run the Phase 4.5 read and log on a temp state root
(first session reads introduction, failed read is marked) and the
pre-telemetry check against this run's doc, an empty doc, another session's
doc and a path outside the state root. Parity caps take the measured growth.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore(release): v1.91.37.0 changelog and TODOS
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(browse): run watchdog test servers under a private HOME
The headed-mode case starts a real server with BROWSE_HEADED=1, and its
headed launch writes ~/.gstack/.auth.json for the extension. With the
caller's HOME, a slow kill let that write reach the real home and failed the
free runner's home-containment check (free-suite shard 5 on #3075). Give each
spawned server a private HOME and GSTACK_HOME, keeping Playwright's browser
cache path.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(evals): name the office-hours-auto-mode case so the PR lane can select it
The PR lane runs each short probe with a Bun name pattern ending in its case
id. The office-hours AskUserQuestion-blocked smoke had a descriptive test
name and no CASE_TEST_NAMES label, so whenever office-hours changed its shard
matched 0 tests and failed in 0 s (E2E Evals slice 6 on #3075). Name the
test after its id, keep the description on the describe, and check that
every literal-named PR probe has a test the pattern selects.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(office-hours): skip a last-session greeting clause whose value is empty
A profile backfilled from the timeline can carry a welcome_back or regular
tier with empty LAST_ASSIGNMENT and LAST_PROJECT. The welcome_back and
regular greetings now drop the "last time" clause and its question when the
value it names is empty, and say only "Welcome back."
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>
* fix(careful): check PowerShell tool commands and nested pwsh/cmd (#3067)
/careful and /guard matched only the Bash tool, so on Windows, where
Claude Code's PowerShell tool is the primary shell, the guard never ran.
check-careful.sh also had no PowerShell or cmd patterns.
- careful/guard register a PowerShell matcher beside Bash.
- hook-extract.sh gains gstack_hook_extract_tool: one parser call reads
tool_name with the field, so dispatch adds no fork.
- check-careful.sh runs one PowerShell/cmd pattern table on PowerShell
tool commands and on the tail of any Bash command that launches
pwsh/powershell/cmd: Remove-Item and aliases with -Recurse/-Force
prefixes, rd/rmdir/del/erase /s, Format-Volume, Clear-Disk,
Clear-Content, [IO.Directory]/[IO.File]::Delete, and asks with an
explanation for -EncodedCommand, iex, Start-Process of a shell and
& $cmd. Command-position anchors keep Bash negative controls allowed.
- /careful documents the coverage as best-effort and points to Claude
Code permission deny rules.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(freeze): NotebookEdit respects the freeze boundary (#3067)
NotebookEdit was in no freeze/guard/investigate matcher, and
check-freeze.sh read only file_path, so notebook edits ignored /freeze on
every platform. NotebookEdit carries tool_input.notebook_path.
- freeze, guard and investigate register a NotebookEdit matcher beside
Edit and Write.
- check-freeze.sh reads file_path, then notebook_path, through the shared
extractor; denials name the tool, the field, the boundary and
/unfreeze. A helper without the combined reader denies as out of date.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test: run hook-scripts on the Windows lane (#3067)
The careful/freeze hooks are Windows coverage, but test/hook-scripts.test.ts
was excluded from windows-free-tests by the raw /tmp/ pattern; its /tmp/
literals are payload strings that are never opened. Admit it by name and
gate the three cases Windows runners cannot host: a symlink (no Developer
Mode), a newline in a directory name, and a POSIX ':' PATH prefix.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore(evals): check-careful.sh is free-only for PR selection
No paid fixture copies or runs careful/bin/check-careful.sh (the
investigate boundary fixture copies hook-extract.sh and check-freeze.sh
only); test/hook-scripts.test.ts covers it. As an unknown dependency it
restored the full paid gate on every /careful hook change.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(careful): pin the #3067 Invoke-Sqlcmd DROP TABLE example on the PowerShell tool
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore(evals): record the check-careful.sh selection change as non-behavioral
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs: /careful covers PowerShell (best-effort), /freeze covers NotebookEdit (#3067)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore: release v1.91.37.0 (CHANGELOG, TODOS for deferred hook follow-ups)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore: re-slot release to v1.91.39.0 (v1.91.37.0 and v1.91.38.0 claimed by #3075, #3076)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore: re-slot release to v1.91.41.0 (merge-queue reservation)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(careful): hook payload reader survives Windows text-mode Python and backslash source paths
windows-free-tests (run 37739845723) showed every PowerShell-tool case
falling through: Python's text-mode stdout on Windows wrote tool_name as
"PowerShell\r", so dispatch never matched, and freeze denials carried
stray CRs. gstack_hook_extract_tool now reads and writes bytes.
hook-extract.sh also located gstack-state-root.sh with ${BASH_SOURCE%/*},
which strips nothing when the file is sourced through a C:\... path, so
gstack_hook_state_root vanished (state-root parity case on the same run).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(evals): PR lane selects office-hours-auto-mode by its real test name
The PR paid lane filters audited files with -t built from
CASE_TEST_NAMES[id] ?? id. office-hours-auto-mode's Bun test is named
'AskUserQuestion surfaces when --disallowedTools AskUserQuestion is set',
so the filter matched 0 tests and the shard failed (#3077 eval-slices (6),
run 37741434771). Map the label, and check every audited PR-profile case
is addressable by name. Recorded as non-behavioral for HARNESS_VERSION.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore: CHANGELOG notes for the Windows CRLF and PR-lane name fixes
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore: re-slot release to v1.91.47.0 (merge-queue assignment)
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>
* 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
* 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>
* refactor(eval-history): share pass-rate history loading as loadPassRateHistory()
eval:pass-rates' main block loaded local dirs or the weekly trial-outcomes
artifacts inline. Move that into an exported function returning records,
scope and fetch errors, so the off-ship sweep reads the same history the
same way. Behavior unchanged.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(ship-measure): the measurement bar and the weekly off-ship sweep CLI
Decide each diagnostic measurement with one bar (scripts/lib/measure-bar.ts):
MEETS at 9/10; MEETS-qualified at 8/10 when every red is a mechanical
provider error, judge noise or a cited model miss; EXTEND at 7/10 (or an
unqualified 8/10) for one more batch on identical inputs, decided once on
the pooled 20; BELOW otherwise and for any timeout, hang, regression or
fixable cause. Behavior uses 12 trials (11/10/9; 22/20 of 24); judge counts
median-panel outputs. A batch with more than 30% provider-evidence failures
is void and redispatched once. Below-strict reds nobody classified return
needs-classify.
ship-measure gains classify (append-only records; provider refused without
evidence), decide and extend (refuses a changed identity, names the field,
never a third batch), exit codes 5 and 6, and a pooling identity (frozen
tree, runner, CLI, model, CI image, Bun, policy and harness versions).
ship-measure sweep ranks main's gate cases by their cost to the all-green
probability from eval:pass-rates history, measures the top K with the same
runner and bar under a weekly cap shared with the last 7 days of sweep
reports (ship_measure_sweep_cases, ship_measure_sweep_budget_usd), and
writes a report with the predicted all-green probability before and after.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(ship): the measure loop decides by the bar's exit codes
Step 4 branches on MEETS/MEETS-qualified, needs-classify (with the
qualifying and fix-round classes), EXTEND once and BELOW; the rest of the
loop prose is tightened to offset most of the growth. ship's union size
cap moves from 1.495 to 1.4975 (measured 281,058 / 187,706 = 1.4973).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* ci: weekly eval-sweep workflow on main
Mondays 12:00 UTC and on dispatch (k, cap_usd, dry_run): runs
ship-measure sweep in the published CI image by digest with the census's
paid-eval setup, downloads the last 7 days of sweep reports first so the
weekly cap holds across runs, uploads the report and captures, and writes
the job summary. Read-only grants; it never pushes or opens a pull request.
One sweep at a time repository-wide, never cancelled mid-spend.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs: the measurement bar table and the weekly sweep
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore: v1.91.33.0
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* ci(temp): run eval-sweep on push to this branch (K=2, $25) for end-to-end validation
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* Revert "ci(temp): run eval-sweep on push to this branch (K=2, $25) for end-to-end validation"
This reverts commit f4c58f2869.
* ci: classify eval-sweep.yml, the sweep CLI and actionlint labels as free-only PR inputs
None is read by a PR-gate case, so a change to them selects no paid case
instead of restoring the full gate (test/free-fixtures.test.ts workflow
inventory).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(ship-measure): the selection preflight accepts name-mode cases
A case registered beside others in its file lists its trial shard as
<file>#<id>~t1, not <file> (trial 1), so the preflight refused every
name-mode case (for example ship-exploratory-late-input) before any paid
trial. Accept both forms.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* ci(temp): run eval-sweep on push to this branch (K=2, $25) for end-to-end validation
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* Revert "ci(temp): run eval-sweep on push to this branch (K=2, $25) for end-to-end validation"
This reverts commit 9619734307.
* test: record the test-pr-profile.ts change as non-behavioral for HARNESS_VERSION
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs(changelog): the name-mode preflight fix
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(ship-measure): a partly funded sweep row shows the trials that ran
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(sweep): the fake single-case command is a Bun script, so it runs on Windows
The Windows free lane runs test/ship-measure-sweep.test.ts; CreateProcess
cannot exec a shebang shell stub, so every fake trial errored and the cap
stopped the sweep after one trial.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(plan-mode-no-op): the CEO sub-test reviews a fixed pasted plan
Without a plan, plan-ceo-review reviewed the checkout's branch diff, so its
pre-question work (audit and landscape searches) grew with whatever PR the
gate ran on. On this PR it timed out at 300s in 3 of 10 trials while still
researching the diff (ship-measure baseline 7/10). With a fixed small plan,
the same way the devex sub-test already runs, it measured 10/10 (CEO
sub-test 83-255s). No budget or threshold changed.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(ship-measure): each diagnostic trial gets its own EVALS_RUN_ID
Evidence-retaining cases (functional QA, docs faults) refuse to start
without EVALS_RUN_ID, which only CI sets, so measuring them locally ran
0/10 sessions. qa-functional-cli-report, red once in this PR's gate,
measured 10/10 once the trials started ($11.13).
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>
* 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.
* 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