* fix(make-pdf,design): real TOC page numbers and anchors; design models move to gpt-5.5 + gpt-image-2 (#2903, #2807) * ci: pin every action to a SHA, Scorecard, checksummed Bun, scoped Codex CLI in the CI image, macOS named-regressions job (#1948, #1706, #1997) * fix(cso,browse): Windows/macOS lease identity, retention skips bad runs, truthful 'not assessed' reports, env.kv false positive, Windows browse spawn fixes (#2894, #2912, #2048, #2637, #2805) * fix(outside-voice): one Codex model precedence for probe and dispatch, bounded timeouts with KILL escalation, probe cache identity, guarded mktemp, skills hidden in nested Codex (#2776, #2914, #2787, #2881, #2847) * feat(setup): install registry, ./setup --status, staged activation, per-scope migrations, host tiers and conformance kit, OpenCode/Hermes fixes, Copilot CLI (experimental), disabled_skills, slimmer CLAUDE.md (#1925, #2879, #2347, #2338, #2626, #2629, #2825, #2015, #393, #1206, #2096) Co-authored-by: Andrey Esipov <61357035+andrey-esipov@users.noreply.github.com> Co-authored-by: Matthew Williams <5863886+ridermw@users.noreply.github.com> Co-authored-by: Rocky Wu <92764750+lolisaigao1234@users.noreply.github.com> * fix(preamble): skills start in worktree-isolated Claude Code sessions — one literal skill-start line, context-recovery helper, no eval in skill blocks, $N token tripwire, honest plan-mode precedence (#2763, #2896, #2851) * fix(setup): per-install renders for renamed, vendored and custom-config installs, space-safe alias roots, per-host /cso launcher path (#1882, #2906) * test: free tests never write the real HOME (home-write tripwire), no orphaned browse daemons, symlinked-TMPDIR (macOS) fixes, gbrain-on-PATH isolation (#2895, #2816, #2829, #2841, #2982, #2911, #2909, #2908, #2910) * chore: integrate wave follow-ups — regenerate all hosts, README model docs, TODOS, touchfiles, duration seeds, Bun 1.3 telemetry test fix, replay fixtures pinned to their recorded bytes * v1.91.14.0: release metadata * test(browse): group-kill cleanup falls back to the leader when macOS returns EPERM for a live group * ci: Codex login steps run under bash (the CI container's default sh rejects pipefail) * fix: full-suite repairs — private state roots for 7 real-HOME writers, truthful home-guard attribution with --attribute-home, gstack-paths shim in ship-hook fixtures, Copilot in team-init probes, test-of-test detector, cookie review fixture pinned to the approved excerpt * fix(windows): LF for Dockerfiles, normalized home-guard exclusions, path.relative fixture containment, ship push test spawn timeouts * test(design-detect): widen the whole-scan budget test's timing margins so a loaded macOS runner can't time out the single-batch call * fix(evals): qa protocol names defined for the judge, async PTY judge so overlapping sessions keep their cleanup deadline, bws setup cases use the standard JUDGE_MS tier * test(pty): the non-blocking judge case is POSIX-only (its fake judge is a shebang script, like the PTY harness it covers) * test(evals): ship-coverage-audit names absolute skill paths, uses Read, and skips the Step 7 artifact nothing consumes (removes the mechanism behind 3 of 4 historical timeouts) * test: deflake terminal-agent concurrent startup (write the placeholder record before spawning, like the product) and qa-evidence resumed pipe (barrier on a full high-water mark instead of a sleep) * fix(design): daemon log stream handles open errors instead of crashing the daemon; shutdown test awaits the stubbed exit * test(evals): skillify checks the product's resolved state root (no GSTACK_HOME bypass), negative cases check every root; tpa shims live outside the agent's cwd and answer consistently * test(evals): coverage-audit detector splits parallel columns at │ rails and [+] groups; coverage prompts name absolute skill paths; TODOs from the gate eval census * fix(plan-eng-review): scope gate always sends the menu (no headless 'Scope pending' exit); PTY harness ends idle turn-ends and API-error panels promptly with a precise diagnostic * test(evals): one position-bound coverage-read rule credits read-only && chains (labels, context ranges, git displays in either order) and still rejects forged output * fix(ship): documentation section has one saved source inserted by cat (never retyped); spawned-marker evidence is the child output; refreshed codex/factory ship goldens * test(docsync): the byte-exact insert positive is POSIX-only (the bash-command observer refuses backslash paths by design) * test(evals): plan-design-with-ui focus-title rule accepts the comma-less 'dimensions or focus' wording (replayed on the captured CI run, negative controls kept) * fix(qa-callers): exploratory preflight says the scope/method Reads come after exploratory.md; design-review scan check credits an executed scan proven by one engine run over the changed files --------- Co-authored-by: Andrey Esipov <61357035+andrey-esipov@users.noreply.github.com> Co-authored-by: Matthew Williams <5863886+ridermw@users.noreply.github.com> Co-authored-by: Rocky Wu <92764750+lolisaigao1234@users.noreply.github.com>
10 KiB
Contributor reference
Contributor-only detail moved verbatim from CLAUDE.md, which Claude Code
loads into every session and caps at 40,000 characters (#2096,
test/claude-md-size.test.ts). CLAUDE.md keeps a one-line pointer to each
section here.
Skill size budgets
Token ceiling: Generated SKILL.md files trip a warning above 160KB (~40K tokens).
This is a "watch for feature bloat" guardrail, not a hard gate. Modern flagship
models have 200K-1M context windows, so 40K is 4-20% of window, and prompt caching
makes the marginal cost of larger skills small. The ceiling exists to catch runaway
preamble/resolver growth, not to force compression on carefully-tuned big skills
(ship, plan-ceo-review, office-hours legitimately pack 25-35K tokens of
behavior). If you blow past 40K, the right fix is usually: (1) look at WHAT grew,
(2) if one resolver added 10K+ in a single PR, question whether it belongs inline
or as a reference doc, (3) only compress carefully-tuned prose as a last resort —
cuts to the coverage audit, review army, or voice directive have real quality cost.
A second, harder ceiling guards the DISCOVERY surface: test/catalog-budget.test.ts
caps the aggregate frontmatter name + description across all skills at
CATALOG_BUDGET_TOKEN_EQUIVALENTS (1,194 today; each new skill ratchets it)
token-equivalents (260-byte per-skill sub-cap), counted through the shared census
in test/helpers/skill-census.ts. This one is enforced, not a warning — every
host loads the full catalog every session, so growth here taxes every
conversation. The failure message carries the re-measure + ratchet protocol.
bin/gstack-context-bill shows the full token bill-of-materials for a skills
tree (always-on vs per-invocation, --diff, --budget; --exact opts into the
real tokenizer and POSTs file text to api.anthropic.com with an egress receipt).
The context-budget ratchet (test/context-budget-ratchet.test.ts, free, runs
in bun run test) pins ABSOLUTE ceilings on two more ledgers: the always-on
FULL-frontmatter aggregate (catalog-budget counts only name+description) and
each skill's per-invocation eager tokens (SKILL.md + forced-read references —
size floors and parity ratios guard these relatively, not absolutely), graded
against test/fixtures/context-budget.json. A skill that grows past its
ceiling fails; a new skill fails until it's consciously budgeted. For
legitimate growth or a landed reduction, re-run
bun test/helpers/capture-context-budget.ts and commit the refreshed fixture
in the same commit, so ceilings ratchet down and every win is locked.
Egress receipts
Egress receipts at every off-machine sink (v1.63.0.0+). Every gstack-initiated
send off the machine MUST write a hash-chained receipt to
~/.gstack/security/egress.jsonl BEFORE the send: TypeScript callers use
writeReceipt from lib/egress-receipt.ts; shell scripts source
bin/gstack-egress-lib.sh and use _receipted_curl / _receipted_git. Failure
polarity is per-class: fail-closed for sensitive sinks (brain-sync, memory-ingest,
gbrain-sync, telemetry, ngrok tunnels, mcp-verify, supabase-provision, and the
Memorable bridge's per-prompt memorable-recall hand-off), fail-open
- stderr warning for user-facing ones (design OpenAI calls, update-check,
dashboards, git-class ops). The new-sink scanner in
test/egress-receipt-wiring.test.tsfails CI on an unreceiptedcurl/git push/fetchto a non-loopback host unless the file carries a reasoned entry in itsSCANNER_EXEMPTlist (user-directed page fetches, reachability probes, instruction strings, skill prose) — if you add a new off-machine sink, wire it through the helpers and add it to the enumerated sink list.aside exec(a gstack-composed prompt sent to Aside's agent) is a fail-open user-facing sink: skills call it through the_aside_execwrapper thatscripts/resolvers/aside.tsrenders, never bare. Inspect withbin/gstack-egress(list|verify, exit 3 on tamper |grants). Threat model: forensic observability of ATTEMPTED egress, not an exfiltration control.
Skill linking and ownership
Prefix setting: Setup creates real directories (not symlinks) at the top level
with a SKILL.md symlink inside (e.g., qa/SKILL.md -> gstack/qa/SKILL.md), plus
links to each skill's runtime assets (sections/, templates, checklists — everything
except SKILL.md, tests, build output, and .tmpl sources). Alias skills
(_gstack-command, connect-chrome) install as rewritten copies, never symlinks.
This ensures Claude discovers them as top-level skills, not nested under gstack/.
Names are either short (qa) or namespaced (gstack-qa), controlled by
skill_prefix in ~/.gstack/config.yaml. Pass --no-prefix or --prefix to
skip the interactive prompt.
Ownership gate (#2119): setup writes a .gstack-owned marker into every
skill directory it creates, and setup (the linker, the alias installer, both
prefix-flip cleanups, and the retired-skill prune) and bin/gstack-relink only
delete or link over an entry they can prove is gstack's. Strong proof (a
symlink resolving into gstack, or the marker) allows deleting or refreshing the
whole directory. Weak proof (a
real SKILL.md byte-identical to the source, or carrying gen-skill-docs' two-line
banner) covers only that one file, and a weakly-proven file that differs is
moved to ~/.gstack/backups/skills/<ts>/<skill>/SKILL.md before gstack links
over it. Anything else is a foreign skill: skipped, and named in setup's final
summary. The rule lives in two copies (setup and bin/gstack-relink); keep
them in sync until the shared helper filed in TODOS.md lands. The retired-skill
prune (_prune_stale_generated) applies the same strong/weak split to renders
of skills that no longer exist, through its own gate
(_owned_for_windows_refresh: a real host directory is a candidate only when
its SKILL.md carries the generated banner; the marker and byte identity are not
consulted): it scans the render tree and every host skills dir,
deletes a real render directory, removes a host symlink only when it resolves
into gstack, cleans a bannered real directory through _cleanup_weak_dir,
never follows a symlink inside the render tree, and recognizes a skill renamed
through its frontmatter name:. Pinned by test/setup-link-ownership.test.ts,
test/setup-cleanup-orphans.test.ts, test/setup-prune-stale-generated.test.ts,
and test/relink.test.ts.
Redaction guard
Shared redaction engine catches credentials, PII, and legal/damaging content
before it reaches an external sink (codex dispatch, GitHub issue/PR body, pushed
commit). It is a guardrail, not airtight enforcement — git push --no-verify,
direct gh issue create, and GSTACK_REDACT_PREPUSH=skip all bypass it. It
catches accidents and carelessness, the 99% case. Do not claim it stops a
determined leaker (a CHANGELOG line that does would fail a hostile screenshotter).
- Engine + taxonomy:
lib/redact-patterns.ts(the single source of truth — 3 tiers; HIGH = genuinely-secret credentials that block, MEDIUM = PII/legal/ internal + high-FP credential shapes that confirm via AskUserQuestion, LOW = FYI) andlib/redact-engine.ts(purescan()+applyRedactions()). Calibration matters: a gate that cries wolf gets ignored, so context-variable shapes (Stripepk_live_, GoogleAIza, JWT, env*_KEY=) sit at MEDIUM. - CLI:
bin/gstack-redact(exit 0 clean / 2 MEDIUM / 3 HIGH;--json,--auto-redact,--repo-visibility,--from-file).bin/gstack-redact-prepushis the opt-in git hook. - Skill docs are generated from
scripts/resolvers/redact-doc.ts({{REDACT_INVOCATION_BLOCK:<sink>}}) so /spec, /cso, /ship, /document-release, /document-generate never drift from the engine. - Scan-at-sink: always scan the EXACT bytes that will be sent — write to a
temp file, scan that file, pass the SAME file to
gh/git. Never scan a string then re-render (that reopens a scan-vs-send gap). - Visibility (no tier promotion): resolve once per run, order = local config
(
gstack-config get redact_repo_visibility, ~/.gstack so never committed) → gh → glab → unknown(=public-strict). Public repos get STERNER per-finding confirmation (no batch-acknowledge, no silent-proceed); MEDIUM is never auto-promoted to HIGH. - Tool-attributed fences: wrap Codex/Greptile/eval output in
```codex-review/```greptilefences so example credentials those tools quote WARN-degrade instead of blocking. A live-format credential inside the fence still blocks. - Config keys:
redact_repo_visibility(public|private|unknown, local-only override for repos gh/glab can't read),redact_prepush_hook(true|false). There is intentionally NO key to disable HIGH blocking. - Audit: the /spec semantic pass appends a content-free record (categories +
body sha256, no spec text) to
~/.gstack/security/semantic-reviews.jsonl(0600).
Checking out PRs from garrytan-agents
When the user says "check out " and the PR is from garrytan-agents/gstack
(or any other fork that is NOT a collaborator on garrytan/gstack), do NOT just
gh pr checkout. Fork PRs don't receive base-repo secrets (ANTHROPIC_API_KEY,
OPENAI_API_KEY, etc.), so the eval/E2E CI jobs fail with empty-env auth errors
regardless of what's set on the base repo.
Workflow: push the branch to garrytan/gstack (the base repo) and re-target
the PR from there.
Concretely, after gh pr checkout <N>:
- Note the original PR number and head branch name.
- Push the same branch to the base repo:
git push origin HEAD:<branch-name>(origin =garrytan/gstack, since the worktree is set up with that remote). - Close the fork PR (
gh pr close <N> --comment "moving to base-repo branch for secret access"). - Open a new PR from the base-repo branch:
gh pr create --base main --head <branch-name>. - New PR's workflows will get secrets automatically.
Why not fix it on the fork side? garrytan-agents isn't a collaborator on
garrytan/gstack. Adding it as a collaborator (option A) or flipping the
repo-wide "send secrets to fork PRs" toggle (option B) would let secrets reach
fork PRs from anyone — broader blast radius than just moving this one branch.
Option C (this section) keeps secret-distribution scope tight.
If the user asks you to skip the move (e.g., "just leave it as a fork PR"), respect that — eval CI will fail with empty-env auth, but check-freshness, workflow-lint, and windows-tests will still pass on the fork PR.