Third port review round (outside voice): admitting every crate past the cap
made a non-member run as 'cargo test -p <name>' from the workspace root,
where a same-named member answered and its passing tests stood in for the
orphan's failing one (a false green). Keep the conservative membership for
dependency edges, but leave such a crate's workspace undecided (null):
planRuns leaves its files unclaimed and the lane incomplete.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Second port review round (outside voice): the length bound from the first
round dropped a valid cfg(any(test, feature = "<257 chars>")), so its crate's
tests were no longer selected (a false green). Read each attribute once to
its closing ']' and resume after it, which keeps the scan linear without a
bound; an attribute the scan cannot close (an unterminated string, no ']')
counts as a test, which selects more, never fewer.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Second port review round (outside voice): the matcher counted UTF-16 units, so
'crates/?' missed a crate directory named with a supplementary character and
dropped its inherited dependencies (a false green); and each unmatched '['
rescanned the rest of the glob (5 s for one crate with a million of them).
Tokenize by code point with a precomputed next-']' table, and past 4096
characters of members globs admit every crate under the workspace: a wrong
member fails loudly in cargo, a dropped one is silent. The new supervised
probe kills a super-linear matcher at 30 s instead of hanging the file.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Port review (outside voice): only #[cfg(test)] modules and tests/ files
counted, so a dependent crate whose tests are plain #[test] or #[tokio::test]
functions in src never ran when its dependency changed (a false green). The
cfg scan also went quadratic on a run of unclosed '#[cfg(' (about 3 s on
120 KB). Test attributes now count, and the cfg predicate and its strings are
length-bounded so the scan stays linear.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Port review (outside voice): members globs went through ship-policy's
matchesAny, which escapes [...] classes (crates/[ab] matched nothing, so the
crate was no member, lost its inherited [workspace.dependencies] path and its
dependents' tests were never selected: a false green) and backtracks
exponentially on a hostile glob such as '*a*a...*ab' (over 5 s for one crate,
before any runner deadline). A small dynamic-programming matcher handles *,
?, [..] with negation and ranges, and ** across components, in
O(pattern x path).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Port review (outside voice): quoting alone was not enough, because the lane
expands {files} after the runner string is built, so a crate named
'evil{files}' spliced the quoted file list into the quoted name and a file
name like tests/$(cmd).rs then ran. A name outside Cargo's grammar
(letters, digits, _ and -) now gets no runner: its files are unclaimed and
the lane is incomplete, as Cargo itself would refuse the manifest. A valid
name, Unicode included, still runs quoted.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
'a touched file in a nested pnpm+vitest package runs vitest there' ran the
host's real pnpm wherever Bun and pnpm share a directory (/usr/local/bin on
Capy's image): the lane puts Bun's directory first, so pnpm installed vitest
from the network, dirtied the fixture tree and the check came back
incomplete. Put the shim first again in the lane's bash through BASH_ENV,
as the Rust fixture does.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
The crate name is read from the repo's Cargo.toml and was interpolated into
'cargo test -p <name>', which the lane runs through bash. A name like
'evil;touch pwned' ran the second command. Quote it like every file and cwd
the lane already passes; a new end-to-end case checks the name reaches cargo
as one argument and runs nothing.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
windows-free-shard (5) failed 'a touched src file runs its crate...' and
'zero tests is ZERO-RUN...' deterministically: the shim PATH was joined with
':' so Git Bash never saw the shim dir, and the runner's real cargo ran
against the fixture (exit 1, no test result lines). On POSIX hosts where
Bun and cargo share a directory (/usr/local/bin on Capy's image), the lane
puts Bun's directory first and the real cargo wins the same way.
Join with path.delimiter, add cargo.cmd for Bun.which on Windows, and on
POSIX put the shim first again in the lane's own bash through BASH_ENV.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
The line scan of Cargo.toml missed dependents (a false green) and picked the
wrong workspace:
- an inherited renamed dependency (`base = { workspace = true }` with
`[workspace.dependencies] base = { package = "alpha", path = ... }`);
- dotted keys (`alpha.path = "../a"`), `[dependencies.<name>]` tables and
target-specific tables, matched by key name only;
- a crate the workspace excludes ran `cargo test -p` from the workspace root.
Manifests are now parsed with Bun.TOML. A dependency resolves by path
(relative to the manifest declaring it, or to the workspace root when
inherited), else by package name. A crate runs from the workspace it is a
member of under Cargo's rules (package.workspace, the nearest [workspace],
members globs, exclude, literal members, path dependencies of members),
else from its own directory. Nothing runs cargo.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lanes reported PREGATE_NO_LANE for every touched .rs file and ran a nested
pnpm+vitest package's tests as `bun test` from the repo root.
- Rust: a .rs file of a crate (nearest Cargo.toml with [package]) is a test
under <crate>/tests/ or when it holds a #[cfg(test)]; every .rs file of a
crate and its Cargo.toml select the crate's tests and those of every crate
depending on it (lib/pregate/rust.ts). Selected tests run per crate as
`cargo test -p <name>` from the workspace root, judged on the summed
`test result:` lines (doc-tests included); zero tests stays ZERO-RUN and a
missing cargo is incomplete, like a missing Dart SDK.
- JS/TS: a test owned by a nested package.json that declares vitest or jest
runs from that package with package-relative paths, through the package
manager its packageManager field or nearest lockfile names (pnpm exec,
yarn, bun x, else npx). Root detection and a declared runner are unchanged.
- init notes that Rust tests run per crate; docs/pregate.md updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(evals): plan-design focus rating in plain words, docsync own-call rule at the candidate --help, floor Create menu cropped at its rule (GSTA-65)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(evals): floor actor answers Step 0D's focus menu in the template's own words (GSTA-65)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(evals): floor actor identifies Step 0D's focus menu by its structure, not one wording (GSTA-65)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs(changelog): GSTA-65 paid-side flaky repairs under [Unreleased]
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(evals): the 0D focus picker moves into plan-design-finding-floor's owned fixture; no harness series reset (GSTA-65)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(evals): the 0D picker rows join the floor case's owner test (test-of-test ratchet)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* v1.91.185.0 fix(evals): plan-design focus rating in plain words, docsync own-call rule, floor Create crop and 0D menu (GSTA-65) (#3358)
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(evals): a rejected API key is INFRA, never 131 assertion reds and reruns
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test: register the credential-rejection fixture as free
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test: keep the credential-rejection CLI replay off the Windows subset, like paid-rerun
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test: name the Windows run in the curation reason
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* v1.91.183.0 fix(evals): a rejected API key is INFRA, never 131 assertion reds and reruns (#3361)
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(cso): name the qualified runtime and scanner catalogs instead of 'static assessment only'
Ships with the runtime catalog promotion PR: AGENTS.md, README.md and
docs/cso-release-qualification.md said no qualified runtime or scanner
image existed. The scanner catalog is qualified since #3042; the runtime
catalog is qualified by the promotion PR this trains with.
* v1.91.180.0 docs(cso): say what the qualified runtime and scanner catalogs allow
* fix(qa): materialize names the next write; fixture states the write boundary (GSTA-63)
qa-functional-webhook-fix (gate 39646086155) and qa-functional-cli-fix
(gate 39166935531) each wrote a stray placeholder beside the fixture
right after a fail verdict that read "final for this report root".
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* v1.91.178.0 stamp
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
---------
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* ci(cso): give the promote candidate step GH_TOKEN, and fail any workflow step that runs gh without a token
Promote run 39691485727 failed after its cso-runtime-release approval:
"Generate a fail-closed catalog candidate" calls gh api .../compare with no
GH_TOKEN, and gh on a hosted runner exits 4. test/workflow-gh-token.test.ts
scans every workflow step that invokes gh for a token in step, job or
workflow env.
* changelog: promote workflow GH_TOKEN fix
* v1.91.177.0 stamp
* cso: ignore the compiled helpers' leading #! line in preparationSha256, on both sides of the release contract
S1 (#3312) rewrote only line 1 of lib/cso/verifier.ts and
lib/cso/preparation-container.ts. That shebang is an unused string in the
compiled helper binaries, but it moved preparationSha256, so qualify run
39535948607 refused the run-5 statements staged at 133cb36 with
STALE_RUNTIME_QUALIFICATION. Garry chose this narrow rule (option 2).
The release contract re-measures a recorded preparationSha256 that differs
from the checkout's at the staged commit (git history), accepts a record
that matches it under the current or the legacy rule, and compares and
records the current-rule value. Both release workflows fetch full history.
* test(cso): prove the release-contract fingerprint rule without git history
Free-suite checkouts are depth 1, so the tests no longer read 133cb36 or
6b2b5f3. They rebuild the staged commit in a scratch repository from this
tree with both helpers' S1 shebang reverted. Its legacy fingerprint equals
the real run-5 record (b069f7ef...), so it is 133cb36's image source byte
for byte, and the contract accepts that record under the current rule.
* v1.91.176.0 stamp
* fix(test-free): a flaky pass keeps the failing assertion in the epilogue and the flake ledger
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* v1.91.175.0 stamp
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
---------
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(ios-qa): a restarted daemon resumes the persisted session bearer instead of relaunching the app
The boot token is one-use, so a new daemon process could only get back in by
relaunching the app with --terminate-existing, wiping its QA state (#1796).
The daemon now persists the rotated bearer to
$GSTACK_STATE_ROOT/ios-qa-session.json (0600, fresh temp file + rename),
bound to UDID, bundle ID and port, and resumes it once per process through
recoverTunnel's new pinned-address mode: devicectl-reported address only,
/healthz bundle check first. Mismatched binding, a foreign device or app, no
devicectl address, or a non-private file bootstraps without sending the
bearer. The file is cleared before every bootstrap, so a rejected bearer is
never replayed. /ios-qa Phase 0 now describes the real file.
Diagnosis and reuse scenarios from #1796 by @Bmathews721.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore: take draft #3132 off the version slot (VERSION back to main's 1.91.84.0, CHANGELOG [Unreleased])
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* v1.91.171.0 stamp
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
---------
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(evals): the gate reruns failed cases once and reports a flaky green (GSTA-49 F1)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* ci: retrigger after retargeting to main
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(evals): a case red twice stays a real red for the report, the run, reuse and the train verdict (GSTA-49 F1)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(evals): keep a lone rerun-plan artifact off the plan manifest (run 39363705692)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore: stamp v1.91.163.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(pregate): Dart import graph and test lanes (#3285)
Port of arlophoenix's #3285. The import graph reads Dart import/export/part
directives and resolves package: URIs through every tracked pubspec.yaml;
_test.dart files under test/ are tests. lanes runs each Dart package's
selected tests from its own root with flutter test or dart test (through fvm
when the package pins it). flutter/dart/melos/very_good CI jobs are lanes,
and run-step test paths resolve against working-directory and literal cd.
A missing Dart SDK or a .dart file outside any package leaves the lane
incomplete with a fix line; device tests (integration_test/, test_driver/)
are not unit lanes.
Co-authored-by: arlophoenix <arlo.phoenix@gmail.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(pregate): a --base that names a commit gets a branch fix, not an impossible fetch (#3269)
gstack-pregate --base <sha> refused with "fix: git fetch origin <sha>
--depth=1". That fetch can never create origin/<sha>: a short SHA fails
with "couldn't find remote ref", and a full SHA only reaches FETCH_HEAD.
A fork clone hits the same wall with --base upstream/main.
When origin/<base> does not resolve but <base> names a commit here and
no local branch (a SHA, a tag, another remote's ref), the refusal now
says --base takes a branch name on origin and suggests origin/HEAD's
branch. A local branch, or a name that resolves nowhere, keeps the
fetch fix: that is the unfetched or shallow checkout it repairs. Same
code, same exit 3, and pin files are still read only from the reviewed
origin/<base>.
Fixes#3269
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(pregate): Django, asset and tool-config lanes; init drafts from the repo (#3266, #3267, #3268)
- Django: settings modules, settings strings, include(), app migrations,
admin, templates, static files and project templates get framework edges
(lib/pregate/django.ts).
- Assets, stylesheets and dotfile tool config take a no-tests empty lane;
licence text under any name is documentation.
- gstack-pregate init drafts languages, runner notes and nearest-test
dependencies; never writes [].
- Review fixes on the #3285 port: untracked shell scopes leave a lane
unknown; Dart directives read to their semicolon; nested comments.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(pregate): init draft API is exercised directly, so init-draft.ts has a lane
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs(changelog): restore the history a truncated rewrite dropped
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(pregate): outside-voice review fixes for the non-JS lanes
- Tool config is an allowlist: unknown dotfiles (.rspec, .husky hooks) and
stylesheets still need a lane; snapshot, golden and fixture trees and
.gstack/ are never inert.
- Django edges need Django evidence (manage.py or code importing django)
and settings assignments in code, not strings or comments.
- A cd after ||, &, control flow or braces leaves a lane unknown.
- Dart directives allow metadata and quoted semicolons.
- init drafts at most 256 dependencies (the schema limit); the rest are TODO.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(windows): pregate-dart and pregate-non-js join the POSIX-run pregate fixtures
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore: release v1.91.161.0
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
---------
Co-authored-by: arlophoenix <arlo.phoenix@gmail.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: y$un_ <forrest.sun527@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(scope,review,ship): recognise Dart and Flutter in diff scope and test detection (#3284)
Port of arlophoenix's #3284 onto current main, squashed:
- gstack-diff-scope: .dart is frontend and backend; pubspec.lock is config.
- lib/frontend-scope.ts mirrors the frontend half; gstack-design-detect
never sends .dart to the impeccable engine.
- Test bootstrap, coverage audits and review-army detect Dart/Flutter
(RUNTIME:dart, FRAMEWORK:flutter, dart_test.yaml, *_test.dart).
- Adversarial DIFF_SIZE sums git diff --numstat.
On top of the contributor's diff: shorten the bootstrap's ecosystem-marker
comment so /design-review stays at its SKILL.md size pin, lower the 31 pins
that shrank, and add the CHANGELOG entry.
Co-authored-by: arlophoenix <arlo.phoenix@gmail.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* v1.91.160.0 fix(scope,review,ship): recognise Dart and Flutter in diff scope and test detection (#3300)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
---------
Co-authored-by: arlophoenix <arlo.phoenix@gmail.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* security: isolate every gstack Bun entry point from the audited checkout's .env and bunfig.toml
Bun auto-loads .env, .env.local and .env.<NODE_ENV> from its cwd and runs a
cwd bunfig.toml preload before the program starts. gstack runs inside the repo
it audits, so a committed .env could steer state roots, the claude binary or
spend caps, and a bunfig preload ran arbitrary code in every bin and hook.
Every bun shebang, shell and skill caller, TypeScript spawn and compiled build
now disables both (--no-env-file --config=/dev/null, bunIsolationArgs(),
--no-compile-autoload-dotenv/bunfig), with a ratchet and hostile-fixture
regressions.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test: name the directly executed scripts in the isolation ratchet
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test: follow the isolation flags in touchfiles, copy lists, recipe assertions and the harness pin
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix: keep browse/src/bun-child-config.ts unchanged so the native cookie qualification still binds
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(windows): the empty bunfig outlives its process so the timeline hook's detached repair child can read it
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* v1.91.159.0 security: gstack's Bun entry points ignore the audited repo's .env and bunfig.toml (S1)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test: plan-mode render pattern accepts the isolation flags
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(gen-skill-docs): add --plan-mode=off to omit the plan-mode preamble (#3168)
Generated skills ship a ~1.8 KB "## Plan Mode Safe Operations" +
"## Skill Invocation During Plan Mode" block on every skill, for hosts
and users who never enter plan mode. This adds a --plan-mode=off flag to
gen-skill-docs that drops that block at gen time. Default builds
(planMode undefined or true) keep the block byte-identically.
The "PLAN MODE EXCEPTION — ALWAYS RUN" markers inside skill bodies
still apply when the flag is off.
(cherry picked from commit 51edee9915)
* style(types): fix stray indentation on TemplateContext.installRoot
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(config): plan_mode_preamble key wires --plan-mode=off into setup and every installed render
gstack-config plan_mode_preamble (on|off, default on) makes ./setup pass
--plan-mode=off to every gen:skill-docs render, gbrain-refresh and the
gstack-relink router render. The default Claude install, which otherwise
serves the committed SKILL.md files, gets a plain render with the key off;
the activation record notes plan_mode=off so switching back on removes the
render, including on the gbrain=unknown keep branch. With the key on or
unset every path and record is byte-for-byte unchanged.
Fixes#3168. Finishes #3213 by @xingzihai.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(setup,relink): plan_mode_preamble=off survives builds and failed renders; alias follows the router
Review findings on the plan_mode_preamble wiring:
- a build renders env hosts without --plan-mode=off, so with the key off
setup no longer skips their re-render after a build;
- with the key off the gbrain-absent branch leaves the render to the plain
re-render, so a failed render keeps the previous one;
- gstack-relink refreshes the _gstack-command alias after re-rendering the
router (also fixes the same staleness for disabled_skills).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
---------
Co-authored-by: xingzihai <xingzihai@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* perf(gen-skill-docs): stop emitting the "When to invoke" body section
The Claude catalog trim moved each skill's routing prose ("Use when...",
"Proactively invoke...", voice triggers) out of the frontmatter description
into a "## When to invoke this skill" section at the top of the body. Claude
Code reads only name + description to choose a skill and loads the body after
the skill is invoked, so that text cannot route anything and costs tokens on
every invocation. Keep the one-line trimmed description; drop the section.
The routing prose stays in each template's description, which
--catalog-mode=full and the other hosts still render. Tests that pinned the
section now assert its absence, or check the template instead.
(cherry picked from commit 707ccd6f38)
* feat(gen-skill-docs): keep voice triggers in the root routing block
The Claude catalog trim drops each skill's "Voice triggers (speech-to-text
aliases)" line from its description, and the body section that used to hold
it is gone. {{VOICE_TRIGGER_ROUTING}} renders them in the root router's
routing block instead: one "Voice trigger ... → invoke /<skill>" line per
skill, read from the templates' voice-triggers lists, so disabled-skill
filtering applies to them like any other routing line. Other hosts keep the
line in each description and render nothing here.
extractVoiceTriggers moves to the new resolver so the generator and the
resolver share one parser.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 645e75b1e8)
* chore: regenerate SKILL.md files without the "When to invoke" section
Output of `bun run gen:skill-docs --host all` after the previous commits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit ff4c813337)
* test: refresh context-budget ceilings for the smaller skill bodies
`bun test/helpers/capture-context-budget.ts`. Against a recapture of the base
commit, 55 skills that lost the section get a lower ceiling and the root
gstack router rises by 532 tokens for the voice-trigger routing lines (5,369
tokens lower in total). devex-review and eval-plan differ from the committed
fixture only because it already lagged main. alwaysOnTotal is unchanged: the
catalog descriptions did not change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit d886fcecc1)
* test: refresh the claude ship golden without the "When to invoke" section
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit f1789dca5c)
* test: lower the claude design-review size pin
The size guard (#3199) asks for it: claude/design-review shrank by 437 chars
without its "When to invoke" section.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 3a446fcc62)
* test: skip alias symlinks in the voice-routing check, keep unrelated budget ceilings, add #3129 CHANGELOG entry
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test: rebuild the historical cookie approval with the dropped section, record the skill-fixture change as non-behavioral
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* v1.91.157.0 perf(gen-skill-docs): drop the "When to invoke" body section from Claude SKILL.md files (port of #3129)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
---------
Co-authored-by: thanhnguyentung95 <17350411+thanhnguyentung95@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(browse): /health status is daemon liveness; active tab reported apart (#3247)
/health answered `status` from a page.evaluate('1') race on the active
tab, so one page stuck in a long script made the whole daemon read as
unhealthy. The CLI gates every command on that answer, and its 2000 ms
fetch timeout equalled the server's 2000 ms race, so it could not even
read the reply: status, tabs, tab, newtab and stop all failed with
"Daemon busy" until --force-restart threw the session away.
- BrowserManager.isHealthy() is liveness only (Chromium connected).
- probeActiveTab() reports { id, responsive }, bounded by
TAB_PROBE_TIMEOUT_MS (1s); /health returns it as `activeTab`.
- `status` adds a line naming a not-responding active tab and the
commands that leave it.
- The CLI reads /health through one helper whose timeout,
HEALTH_REQUEST_TIMEOUT_MS (2s), stays above the probe.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wb6Wntntmwau19fSuSbuaL
(cherry picked from commit 322d255354)
* fix(browse): bound per-tab reads in `tabs` and `state save` (#3247)
A renderer stuck in a long script never answers page.title() or
page.evaluate(). `tabs` awaited each title in turn and `state save`
read each tab's storage unbounded, so both hung on one busy tab even
though neither needs it.
- getTabListWithTitles() reads titles in parallel, each bounded by
TAB_PROBE_TIMEOUT_MS; a tab that misses it is listed as
"(not responding)".
- saveState() skips the storage of a tab that does not answer within
5s, the same outcome as a tab whose read fails.
busy-active-tab.test.ts holds a real Chromium tab in a busy loop next
to an idle one and checks /health, tabs, status, saveState and
closetab against it. On main, /health takes 2004 ms and says
unhealthy, `tabs` hangs past the test timeout and `status` never
names the tab.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wb6Wntntmwau19fSuSbuaL
(cherry picked from commit d010d7b243)
* fix(browse): name the busy tab when a command times out (#3247)
A command that needs a stuck tab still waits out the CLI's 30s reply
timeout, and the report should say why. It never ran: Bun rejects an
AbortSignal.timeout() fetch with TimeoutError, the generic branch only
matched AbortError, so a timeout fell through to "[browse] The
operation timed out."
- isFetchTimeout() accepts both names; the cookie-import branch uses it
too.
- commandTimeoutReport() reads /health: when `activeTab.responsive` is
false it names the tab and points at `browse tab <id>` and
`browse closetab <id>`; a live daemon otherwise keeps the
busy-not-restarting line and a dead one the plain timeout line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wb6Wntntmwau19fSuSbuaL
(cherry picked from commit ef70b7d880)
* docs(browse): /health liveness vs active-tab responsiveness (#3247)
BROWSER.md's busy-vs-dead lifecycle note, ARCHITECTURE.md's daemon
model and the REMOTE_BROWSER_ACCESS /health reference now say that
`status` is daemon liveness, that `activeTab` reports the active tab
separately, and which commands keep working while a tab is busy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wb6Wntntmwau19fSuSbuaL
(cherry picked from commit 3dbb256796)
* fix(browse): one pending probe per stuck tab, not one per poll (#3247)
Review on #3250: the 1s race stops waiting but leaves page.evaluate()
queued in the stuck renderer, so every /health poll (the extension
polls every 10s), `status` and `tabs` call added another pending read,
all drained at once when the page recovered. Playwright cannot cancel
an in-flight evaluate, so the active-tab probe and the `tabs` title
read now join the page's pending read of the same kind instead of
starting a new one.
busy-active-tab.test.ts counts page.evaluate calls across three probes
of the stuck tab: 3 before this change, at most 1 after.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wb6Wntntmwau19fSuSbuaL
(cherry picked from commit 19fc9b9709)
* fix(browse): keep a busy tab's last good storage in saved state (#3247)
Review on #3250: with BROWSE_PERSIST_STATE=1 the periodic snapshot used
to hang on a stuck tab, so session-state.json kept its last good copy.
The 5s storage bound let the snapshot finish and write `storage: null`
for that tab over it, so a restart lost its localStorage and
sessionStorage even with persistence on.
saveState() now records the storage each page answers with, together
with its origin. A read that times out returns that last good storage
when the origin still matches, and null otherwise. A failed read stays
null, as before. The storage read also joins the page's pending read
instead of queueing a new one on every persist tick.
busy-active-tab.test.ts takes a good snapshot, hangs the tab, and checks
that persistSessionState() still writes its localStorage. Live, with
BROWSE_PERSIST_STATE=1 and a 2s interval, session-state.json kept
{"probe":"kept"} for the busy tab, and its savedAt stayed current
through 12s of busy page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wb6Wntntmwau19fSuSbuaL
(cherry picked from commit 60cbaebee5)
* docs(changelog): busy-tab lockup fix from #3250 under [Unreleased]
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
---------
Co-authored-by: Andrei Vershinin <vershinin@intelas.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(cso): withhold excluded credential files from retained history; name failed witness assertions ASSERTION_FAILED
* fix(cso): Rails verify replaces read-only copied config instead of writing into it
* fix(cso): security expectations may list several safe statuses while excluding the vulnerable marker
(cherry picked from commit aa9f45e0ece0e12152e7d02bf88e1559ee42b1eb)
* docs(changelog): safe status sets
* v1.91.154.0 stamp
* style(cso): format merged snapshot history capture
* fix(outside-review): no-findings counts only as the review's conclusion; add regressions/defects (#3294)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(outside-review): a trailing ', and' clause can conclude; elsewhere/otherwise are exceptions; real codex captures
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(outside-review): whitelist the conclusion's qualifier; join wrapped Recommendation clauses; outside-review findings as controls
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(outside-review): join a wrapped line only when it continues in lowercase
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(cso): record a nested repository as an explicit snapshot exclusion
An untracked nested repository makes gstack-cso start fail with
UNSAFE_PATH. Git lists it as one entry with a trailing slash, so
relativePath() rejected the empty segment and aborted the run. A
registered submodule gitlink already failed one step later for the same
reason: it is a directory, and the capture loop only reads regular
files.
Accept the boundary under its own name and record it like a host
dependency tree: one manifest entry, originalHash not-read, an
excluded: transformation that discloses the omission in the report's
snapshot-inputs coverage, no payload copied, and an identity check
during capture so a swapped or symlinked boundary fails closed. Nested
repository contents are never audited with the outer repository because
Git never descends into them.
* fix(cso): keep the newest whole commits when history exceeds the 1 MiB bound (#3255)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(cso): regressions and negative controls for nested repos, long history and macOS sockets (#3255, #3248)
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* docs(changelog): blank line before 1.91.149.0
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test(cso): set GSTACK_HOME in beforeAll, not at module scope
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* chore: stamp v1.91.152.0
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
---------
Co-authored-by: Chao Chen <209239467+surlymochan@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>