mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-06 00:44:07 +01:00
Merge origin/main into feat/design-lint; stamp 1.91.251.0
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
commit
4b4cadcb9c
289 files changed
+16151
-2303
No files matched your search
@@ -431,9 +431,9 @@ bun test test/host-config.test.ts
|
||||
The parameterized smoke tests automatically pick up the new host. They verify: output exists, no path leakage, valid frontmatter,
|
||||
freshness check passes, and outside-review skills match each host's exclusions.
|
||||
|
||||
### 6. Update README.md
|
||||
### 6. Update README.md and docs/hosts.md
|
||||
|
||||
Add install instructions for the new host in the appropriate section.
|
||||
Add the new host's row to the README host matrix (tier, install command, first invocation, safety skills, outside-review needs, repair). Per-host notes live in [docs/hosts.md](hosts.md), not the README.
|
||||
|
||||
## Config field reference
|
||||
|
||||
|
||||
@@ -111,7 +111,10 @@ 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) and `lib/redact-engine.ts` (pure `scan()` + `applyRedactions()`).
|
||||
FYI) and `lib/redact-engine.ts` (`scan()` + `applyRedactions()`; no I/O,
|
||||
and `scan()`'s one ambient input is the credential-named variables of
|
||||
`process.env`, read by value for the `env.value` HIGH finding unless
|
||||
`ScanOptions.processEnv` names another environment).
|
||||
Calibration matters: a gate that cries wolf gets ignored, so context-variable
|
||||
shapes (Stripe `pk_live_`, Google `AIza`, JWT, env `*_KEY=`) sit at MEDIUM.
|
||||
- **CLI:** `bin/gstack-redact` (exit 0 clean / 2 MEDIUM / 3 HIGH; `--json`,
|
||||
|
||||
+34
-5
@@ -269,8 +269,8 @@ through the `bun` shim beside it.
|
||||
- `GSTACK_BUN_SHIM=0` at install time writes no shim.
|
||||
- Removing it: delete `~/.local/bin/bun` and `~/.local/bin/bunx`. gstack
|
||||
replaces only a missing file or a file carrying the marker; a foreign file
|
||||
or a dangling link there is left untouched with a note, and the
|
||||
`GSTACK_LAUNCH` prefix stays the way to reach the certified Bun.
|
||||
or a dangling link there is left untouched with a note, and sourcing the
|
||||
launch file (`. <GSTACK_LAUNCH_FILE>`) stays the way to reach the certified Bun.
|
||||
- The doctor's `runtime` row notes the shim; the resolver never treats a
|
||||
shim-marked file as a Bun candidate.
|
||||
|
||||
@@ -345,8 +345,37 @@ Every successful run, `--check` included, prints before the trailer:
|
||||
```
|
||||
GSTACK_LAUNCH: PATH=<dir>:$PATH GSTACK_SESSION_KIND=unattended GSTACK_STATE_ROOT=<path> GSTACK_EPHEMERAL=1 GSTACK_CAPY=1 GSTACK_INSTALL_VERDICT=<file>
|
||||
GSTACK_LAUNCH_PATH: <dir>
|
||||
GSTACK_LAUNCH_FILE: <verdict file without .json>.env
|
||||
```
|
||||
|
||||
`GSTACK_LAUNCH_FILE` names the same assignments as a sourceable script (mode
|
||||
0600, beside the verdict under the state root; the PATH prepend is idempotent).
|
||||
The file is part of the ok verdict: a passing run that cannot write it exits 1
|
||||
with `gstack: fail launch-file — could not write the launch file <path>; fix:
|
||||
make <dir> writable, or set GSTACK_STATE_ROOT to a writable directory
|
||||
(LAUNCH_FILE_UNWRITABLE)`, and a failing run prints no `GSTACK_LAUNCH_FILE` line
|
||||
and removes the file an earlier passing run left beside the verdict it rewrites
|
||||
(a profile check that keeps a fuller ok verdict leaves that verdict's file).
|
||||
`. <that file>` at the start of a shell applies them without ever printing them,
|
||||
which is the only way to hand them to a child agent: pass the path, never the
|
||||
values. None of the assignments is a secret, but during GSTA-96's unattended
|
||||
/ship a docs-audit child re-typed them as `export $(...)`; the substitution came
|
||||
back empty, the bare `export` printed the whole environment, and three API key
|
||||
values landed in its tool output. Never `export $(...)`, `env`, `printenv`, a
|
||||
bare `set` or a bare `export` in a tool fence (`test/launch-env-echo-ratchet.test.ts`
|
||||
keeps them out of every skill fence and shell helper), and the redaction
|
||||
engine now refuses the VALUE of any `*_KEY`, `*_TOKEN`, `*_SECRET`,
|
||||
`*_PASSWORD`, `*_PASSWD`, `*_PASS`, `*_PAT`, `*_APIKEY` or `*_PWD` variable
|
||||
in the writer's environment (any case; names carrying a `PUBLIC` word are
|
||||
framework-public and exempt, gstack's own `GSTACK_BRAIN_LOCK_TOKEN` is not a
|
||||
secret) (`env.value`, HIGH), so a leaked value cannot reach a PR body, receipt,
|
||||
report or Drive file. There is no path exemption: a path-valued `SSL_KEY` that
|
||||
must appear in a text goes through `bin/gstack-redact --allowlist` or the
|
||||
pre-push hook's committed `.gstack-redact-allowlist`; the other writers
|
||||
(`gstack-post`, receipts, reports) take no allowlist, so for one of those
|
||||
commands set the variable empty for that command alone
|
||||
(`SSL_KEY= gstack-post …`): an empty value is below the length floor.
|
||||
|
||||
`PATH=<dir>:$PATH` and `GSTACK_LAUNCH_PATH` appear only when the certified Bun,
|
||||
or the project's resolved Node, is not first on PATH (older Capy images ship
|
||||
`/usr/local/bin/bun` 1.3.14; 57 gstack commands start with `#!/usr/bin/env bun`).
|
||||
@@ -358,8 +387,8 @@ directory holding only `bun`, `bunx` and, when needed, `node`, `npm`, `npx` and
|
||||
never puts that directory's other tools (the image's node) in front of PATH.
|
||||
The pregate's lanes launch through the same directory. A child shell's `export` cannot
|
||||
configure the parent's later commands, so the parent supplies these
|
||||
assignments to every gstack command it launches (prefix the command, or
|
||||
export them once in the shell that runs it). A fresh shell without them keeps
|
||||
assignments to every gstack command it launches by sourcing the launch file
|
||||
(`. <GSTACK_LAUNCH_FILE>`) at the start of the shell that runs it. A fresh shell without them keeps
|
||||
reporting the Bun it would really run: `FAIL runtime — bun 1.3.14 at
|
||||
/usr/local/bin/bun ...; verified bun 1.4.2 found at ~/.bun/bin (not on PATH);
|
||||
fix: export PATH="$HOME/.bun/bin:$PATH" or prefix commands with the
|
||||
@@ -434,6 +463,6 @@ Wrapper 2 only adds optional exports (the trailer's `latest:` note and the
|
||||
| Exit | Meaning |
|
||||
|-----:|---------|
|
||||
| 0 | `gstack: ok` — every required component passed |
|
||||
| 1 | `gstack: fail <ids>` — a required component failed; each row has a fix |
|
||||
| 1 | `gstack: fail <ids>` — a required component failed; each row has a fix. Also `gstack: fail launch-file …` when a passing run could not write its launch file ([LAUNCH_FILE_UNWRITABLE](troubleshooting.md#capy-install-launch-file-unwritable)) |
|
||||
| 2 | usage error |
|
||||
| 3 | preflight stopped before `./setup` (`INSTALL_PREFLIGHT_FAILED`, `RUNTIME_PIN_CONFLICT`); nothing was installed |
|
||||
@@ -178,8 +178,11 @@ draft are cut: the artifact path is tool-owned, a child's self-reported
|
||||
path list is both a pathspec-injection surface and a self-report, and the
|
||||
only history need is calibration, which `--commit <sha>` covers by scanning
|
||||
one commit's first-parent delta without the working tree.) Positional
|
||||
base defaults the way `gstack-diff-scope` does (`origin/HEAD` → `main` →
|
||||
`master`); an already-qualified ref is used as given. stdout: one
|
||||
base defaults the way `gstack-diff-scope` does (the remote's `HEAD` target,
|
||||
origin first, then the other remotes → `main` → `master`); an
|
||||
already-qualified ref is used as given, and a branch name with no local
|
||||
branch resolves through its remote-tracking twin (`origin/main` for `main`:
|
||||
the merge-base is the same commit), noted once on stderr. stdout: one
|
||||
`GATE_SUMMARY: listed=N read=N tagged={RH-1:2,RH-15:1} unmatched=N
|
||||
coverage=<patterns> languages_unlisted=<…> candidate=<sha+wtree>` line, then
|
||||
the read-level listing (bounded; the full listing goes to `--out`).
|
||||
@@ -207,9 +210,10 @@ hunks and 200 KB; `GATE_SUMMARY` reports `eligible`, `inspected`,
|
||||
like UNAVAILABLE, and never as `none detected`.
|
||||
|
||||
**Exit contract.** 0 with or without hunks; 2 only when the base is
|
||||
unresolvable (`GATE_ERROR=no_base ref=<ref>` with the concrete recovery:
|
||||
`git fetch <remote> <branch>` from the detected remote, or "deepen the
|
||||
shallow history") or the tool itself failed. Unmatched paths are reported
|
||||
unresolvable locally and as `<remote>/<ref>` (`GATE_ERROR=no_base ref=<ref>`
|
||||
with the concrete recovery: `git fetch <remote> +<branch>:refs/remotes/<remote>/<branch>`
|
||||
into the remote-tracking ref, `--unshallow` on a shallow clone, or a plain
|
||||
fetch/deepen for a sha or relative ref) or the tool itself failed. Unmatched paths are reported
|
||||
in `GATE_SUMMARY: unmatched=N` and listed, never an exit 2. The template
|
||||
block that calls it branches on the exit explicitly, never
|
||||
`2>/dev/null || true`:
|
||||
@@ -350,9 +354,21 @@ a test count natively.
|
||||
saved log is capped at 2 MB and would truncate the summary) and parses
|
||||
it into `tests_ran` for the supported runners only: bun (reusing
|
||||
`shard-engine`'s classifier; executed = pass + fail, so an all-skip run is
|
||||
ZERO-RUN), jest/vitest, pytest, rspec, and Go's zero-only case (every
|
||||
package `[no test files]`); the last summary per runner wins. Everything
|
||||
else is `unknown`. `check` reports ZERO-RUN as STALE-equivalent and
|
||||
ZERO-RUN), jest/vitest, pytest, rspec, Go's zero-only case (every
|
||||
package `[no test files]`), gstack's shard runner (the sum of its per-shard
|
||||
`[test:free] PASS — N tests` epilogues, or a files-from run's
|
||||
`FILES-FROM RESULT: … tests=N`), and a `gstack-pregate --remote` lane (its
|
||||
`PREGATE_REMOTE: tree=… tests=N failed=M` row or the obligation's
|
||||
`cleared by remote-gate tree=… tests=N` receipt line); when the shard runner
|
||||
spoke its verdict stands for the whole tail (the tail is stdout then stderr,
|
||||
so position cannot order bun's stderr counts against the runner's stdout
|
||||
epilogues), and any summary when it did not speak follows the
|
||||
last-summary-wins rule. Everything else is `unknown`. The shard runner and the
|
||||
remote gate print only bun's terminal totals, which count skipped tests, so
|
||||
for those two `tests_ran` is ran-with-skips rather than executed: the shard
|
||||
runner refuses a shard whose bun summary counts fewer files than planned, but
|
||||
a shard whose files all loaded and executed nothing still passes with that
|
||||
total. `check` reports ZERO-RUN as STALE-equivalent and
|
||||
unknown as non-blocking. The TRANSPARENCY INVARIANT holds: `run`'s exit is
|
||||
always the child's; detection lives in the receipt and in `check`.
|
||||
- After Step 7's test-generation child returns and after Step 9.4's parent
|
||||
|
||||
@@ -87,6 +87,8 @@ After weighing Codex's argument, we chose to roll back CEO EXPANSION and ship an
|
||||
"inferred": {
|
||||
"values": {"scope_appetite": 0.72, "risk_tolerance": 0.58, "...": "..."},
|
||||
"sample_size": 47,
|
||||
"signal_events": 19,
|
||||
"contributing_events": {"scope_appetite": 9, "risk_tolerance": 6, "...": "..."},
|
||||
"diversity": {
|
||||
"skills_covered": 5,
|
||||
"question_ids_covered": 14,
|
||||
@@ -103,6 +105,8 @@ After weighing Codex's argument, we chose to roll back CEO EXPANSION and ship an
|
||||
}
|
||||
```
|
||||
|
||||
`signal_events` counts the logged answers that moved any dimension, and `contributing_events` counts, per dimension, the logged answers that moved it (an answer that writes two deltas to one dimension counts once), so a dimension with no evidence (0) is visible beside one whose evidence balances out; both print 0.5 (#2623). Each value is the sigmoid of its accumulated deltas, and each UTC day's sum per dimension (by the row's `ts`) is clamped to the largest single delta in the signal map (currently ±0.06), so one burst of answers can't pin a dimension; `--trace <dim>` lists every contributing answer, each clamped day and the total. The bound keys on the day, not on `session_id`: the PostToolUse hook and the agent log different ids for the same review (the host's conversation id and the preamble's run id), and an agent can leave the `SESSION_ID` placeholder unsubstituted, so no logged id reliably marks one review. The cost is that several reviews on one day count as one. `contributing_events` still counts an answer logged by both writers twice. Only an ISO 8601 date or date-time `ts` counts as dated (a time without a zone is read as UTC), every other row shares one undated bucket, and `diversity.days_span` counts the same UTC days. Nothing displays `contributing_events` yet (#3436).
|
||||
|
||||
**Diversity check** (Codex #13): `inferred` is considered "enough data" only when `sample_size >= 20 AND skills_covered >= 3 AND question_ids_covered >= 8 AND days_span >= 7`. Below this, `/plan-tune profile` shows "not enough observed data yet" instead of a potentially-misleading inferred value.
|
||||
|
||||
## Data flow (v1)
|
||||
|
||||
@@ -123,3 +123,12 @@ following events are written to `~/.gstack/analytics/browse-telemetry.jsonl`:
|
||||
|
||||
Hostname only — no body content, no agent text. Disable entirely with
|
||||
`gstack-config set telemetry off` or `GSTACK_TELEMETRY_OFF=1`.
|
||||
|
||||
## In brief: domain skills and the raw CDP escape hatch
|
||||
|
||||
Two browser primitives in gstack's own engine (the fallback path when Aside isn't there) compound the agent over time:
|
||||
|
||||
- **`$B domain-skill save`** — agent saves a per-site note (e.g., "LinkedIn's Apply button lives in an iframe") that fires automatically next time it visits that hostname. Quarantined → active after 3 successful uses → optional cross-project promotion via `$B domain-skill promote-to-global`. Storage lives alongside `/learn`'s per-project learnings file.
|
||||
- **`$B cdp <Domain.method>`** — raw Chrome DevTools Protocol escape hatch for the rare case curated commands miss. Deny-default: methods must be explicitly added to `browse/src/cdp-allowlist.ts` with a one-line justification. Two-tier mutex serializes browser-scoped CDP calls against per-tab work. Output for data-exfil methods is wrapped in the UNTRUSTED envelope.
|
||||
|
||||
> Want raw CDP with no rails, no allowlist, no daemon — just thin transport from agent to Chrome? [browser-use/browser-harness-js](https://github.com/browser-use/browser-harness-js) is a different philosophy (agent-authored helpers vs gstack's curated commands) and a good fit if you don't want gstack's security stack. The two can coexist: gstack's `$B cdp` and harness can both attach to the same Chrome via Playwright's `newCDPSession`.
|
||||
@@ -0,0 +1,34 @@
|
||||
# GBrain with gstack: the quick tour
|
||||
|
||||
The short version of [USING_GBRAIN_WITH_GSTACK.md](../USING_GBRAIN_WITH_GSTACK.md), which has every scenario, flag, helper bin and troubleshooting step. This page is what the README used to say.
|
||||
|
||||
```bash
|
||||
/setup-gbrain
|
||||
```
|
||||
|
||||
Four paths, pick one:
|
||||
|
||||
- **Supabase, existing URL** — your cloud agent already provisioned a brain; paste the Session Pooler URL, now this laptop uses the same data.
|
||||
- **Supabase, auto-provision** — paste a Supabase Personal Access Token; the skill creates a new project, polls to healthy, fetches the pooler URL, hands it to `gbrain init`. ~90 seconds end-to-end.
|
||||
- **PGLite local** — zero accounts, zero network, ~30 seconds. Isolated brain on this Mac only. Great for try-first; migrate to Supabase later with `/setup-gbrain --switch`.
|
||||
- **Remote gbrain MCP** — your brain runs on another machine (Tailscale, ngrok, internal LAN) or a teammate's server; paste an MCP URL and bearer token. Optionally pair with a local PGLite for symbol-aware code search in split-engine mode. Best for cross-machine memory without standing up a local DB.
|
||||
|
||||
After init, the skill offers to register gbrain as an MCP server for Claude Code (`claude mcp add gbrain -- gbrain serve`) so `gbrain search`, `gbrain put`, etc. show up as first-class typed tools — not bash shell-outs.
|
||||
|
||||
**Keeping the brain current.** Run `/sync-gbrain` from any repo to re-index its code into gbrain (incremental by default, `--full` for a full reindex, `--dry-run` to preview). The skill registers the cwd as a federated source via `gbrain sources add`, runs `gbrain sync --strategy code`, and writes a `## GBrain Search Guidance` block to your project's CLAUDE.md so the agent prefers `gbrain search`/`code-def`/`code-refs` over Grep. The block is removed automatically if the capability check fails — no stale guidance pointing at tools that aren't installed.
|
||||
|
||||
**Per-remote trust policy.** Each repo on your machine gets one of three tiers:
|
||||
|
||||
- `read-write` — agent can search the brain AND write new pages back from this repo
|
||||
- `read-only` — agent can search but never writes (best for multi-client consultants: search the shared brain, don't contaminate it with Client A's work while in Client B's repo)
|
||||
- `deny` — no gbrain interaction at all
|
||||
|
||||
The skill asks once per repo. The decision is sticky across worktrees and branches of the same remote.
|
||||
|
||||
**GStack memory sync (different feature, same private-repo infra).** Optionally pushes your gstack state (learnings, CEO plans, design docs, retros, developer profile) to a private git repo so your memory follows you across machines, with a one-time privacy prompt (everything allowlisted / artifacts only / off) and a defense-in-depth secret scanner that blocks AWS keys, tokens, PEM blocks, and JWTs before they leave your machine.
|
||||
|
||||
```bash
|
||||
gstack-artifacts-init
|
||||
```
|
||||
|
||||
**Running gstack in Conductor?** Conductor explicitly strips `ANTHROPIC_API_KEY` and `OPENAI_API_KEY` from every workspace's process env, so paid evals and gbrain embeddings won't work out of the box. Set `GSTACK_ANTHROPIC_API_KEY` and `GSTACK_OPENAI_API_KEY` in Conductor's workspace env config instead — gstack's TS entry points promote them to canonical names at runtime. Full details and the contributor checklist for adding the import to new entry points: [Conductor + GSTACK_* env vars](../USING_GBRAIN_WITH_GSTACK.md#conductor--gstack_-env-vars).
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
# Running gstack on other agents
|
||||
|
||||
The README has the host matrix: one row per agent with its tier, install
|
||||
command and first invocation. This page has the rest: OpenClaw, how a checkout
|
||||
decides which agents it renders for, the per-host notes, outside-review
|
||||
providers, the Codex behavioral profile, model tiers, and the design binary's
|
||||
models.
|
||||
|
||||
## OpenClaw
|
||||
|
||||
OpenClaw spawns Claude Code sessions via ACP, so every gstack skill just works
|
||||
when Claude Code has gstack installed. Paste this to your OpenClaw agent:
|
||||
|
||||
> Install gstack: run `git clone --single-branch --depth 1 https://github.com/garrytan/gstack.git ~/.claude/skills/gstack && cd ~/.claude/skills/gstack && ./setup` to install gstack for Claude Code. Then add a "Coding Tasks" section to AGENTS.md that says: when spawning Claude Code sessions for coding work, tell the session to use gstack skills. Include these examples — security audit: "Load gstack. Run /cso", code review: "Load gstack. Run /review", QA test a URL: "Load gstack. Run /qa https://...", build a feature end-to-end: "Load gstack. Run /autoplan, implement the plan, then run /ship", plan before building: "Load gstack. Run /office-hours then /autoplan. Save the plan, don't implement."
|
||||
|
||||
**After setup, just talk to your OpenClaw agent naturally:**
|
||||
|
||||
| You say | What happens |
|
||||
|---------|-------------|
|
||||
| "Fix the typo in README" | Simple — Claude Code session, no gstack needed |
|
||||
| "Run a security audit on this repo" | Spawns Claude Code with `Run /cso` |
|
||||
| "Build me a notifications feature" | Spawns Claude Code with /autoplan → implement → /ship |
|
||||
| "Help me plan the v2 API redesign" | Spawns Claude Code with /office-hours → /autoplan, saves plan |
|
||||
|
||||
See [docs/OPENCLAW.md](OPENCLAW.md) for advanced dispatch routing and
|
||||
the gstack-lite/gstack-full prompt templates.
|
||||
|
||||
## Native OpenClaw Skills (via ClawHub)
|
||||
|
||||
Four methodology skills that work directly in your OpenClaw agent, no Claude Code
|
||||
session needed. Install from ClawHub:
|
||||
|
||||
```
|
||||
clawhub install gstack-openclaw-office-hours gstack-openclaw-ceo-review gstack-openclaw-investigate gstack-openclaw-retro
|
||||
```
|
||||
|
||||
| Skill | What it does |
|
||||
|-------|-------------|
|
||||
| `gstack-openclaw-office-hours` | Product interrogation with 6 forcing questions |
|
||||
| `gstack-openclaw-ceo-review` | Strategic challenge with 4 scope modes |
|
||||
| `gstack-openclaw-investigate` | Root cause debugging methodology |
|
||||
| `gstack-openclaw-retro` | Weekly engineering retrospective |
|
||||
|
||||
These are conversational skills. Your OpenClaw agent runs them directly via chat.
|
||||
|
||||
## Which agents a checkout renders for
|
||||
|
||||
An explicit `--host X` installs for X only and never changes another agent's
|
||||
install. `/gstack-upgrade` refreshes every install it registered, one row per
|
||||
host, and says which ones failed.
|
||||
|
||||
A checkout carries skills only for the agents installed from it. Setup records
|
||||
them in `.gstack-installed-hosts` and never drops one, so adding an agent later
|
||||
is just `./setup --host <name>`. Copies rendered for other agents are pruned:
|
||||
generated files move to `~/.gstack/backups/host-renders/`, and files gstack
|
||||
can't prove it generated stay where they are. A global Claude install went
|
||||
from 632 `SKILL.md` files (34.7 MB) to its own 63 (2.5 MB), which keeps Cursor-agent
|
||||
from freezing on the skills tree (#1694). In a development checkout,
|
||||
`bun run build` still renders every agent; inside an install it renders the
|
||||
recorded ones, and `GSTACK_RENDER_HOSTS=all bun run build` renders them all.
|
||||
|
||||
## Per-host notes
|
||||
|
||||
Copilot invokes gstack skills by their prefixed names (`/gstack-review`) because
|
||||
`/review` is a Copilot built-in. Copilot ignores skill hooks, so `/careful` and
|
||||
`/freeze` only advise there, and `COPILOT_HOME` other than `~/.copilot` is not
|
||||
supported yet (setup refuses and changes nothing).
|
||||
|
||||
Antigravity CLI also invokes skills by prefixed name (`/gstack-review`). Setup
|
||||
writes only `~/.gemini/antigravity-cli/skills/gstack*`: Gemini CLI's
|
||||
`~/.gemini/skills` and settings are never touched, and `--host auto` selects
|
||||
Antigravity only when `agy` is on `PATH` or `~/.gemini/antigravity-cli` exists,
|
||||
never for a Gemini-CLI-only `~/.gemini`. The Antigravity 2.0 app and IDE read
|
||||
`~/.gemini/config/skills`, which this host does not install.
|
||||
|
||||
Outside reviews require the selected CLI to be installed and authenticated: Claude Code when using gstack in Codex, or Codex on other harnesses. External harnesses discover these commands as `/gstack-claude-code` and `/gstack-codex`; each harness omits its own wrapper. Explicit provider requests keep that provider. The existing `codex_reviews` setting controls automatic outside reviews where supported, regardless of the provider selected.
|
||||
|
||||
`/claude` has been renamed to `/claude-code`. Re-run `./setup --host <name>` to migrate managed installations, including other harnesses sharing the checkout. Setup preserves the previous installation if replacement generation or installation fails and prints repair instructions.
|
||||
|
||||
## Instruction-only agents
|
||||
|
||||
**Instruction-only tier (any rules-reading agent — Zed, Amp, Jules, side projects):**
|
||||
copy the 2KB digest at [`agents-digest/gstack-AGENTS.md`](../agents-digest/gstack-AGENTS.md)
|
||||
into a location your agent reads (for example, append it to your project's `AGENTS.md`).
|
||||
It carries gstack's ethos, reuse ladder, and voice rules — no install required. The
|
||||
digest's first line shows its gstack version; re-copy it after upgrading.
|
||||
|
||||
## Codex: the behavioral profile and model selection
|
||||
|
||||
For Codex, setup reads the top-level `model` from
|
||||
`${CODEX_HOME:-~/.codex}/config.toml` and generates the matching behavioral
|
||||
profile, falling back to `gpt-6-astra` when no usable model is configured.
|
||||
`gpt-5.6-sol` automatically receives bounded-scope instructions that
|
||||
finish the requested lake without expanding into adjacent cleanup or speculative
|
||||
hardening. The Sol profile is exact-match only: dated snapshots and other 5.6
|
||||
variants get the generic GPT profile, and setup warns on near-misses like
|
||||
`gpt-5.6-sol-2026-08-01`. Override detection with `./setup --host codex --model <id>` — the
|
||||
override applies to that run only; set `model` in your Codex `config.toml` to
|
||||
make it stick across upgrades. After changing your Codex model, rerun
|
||||
`./setup --host codex` to regenerate the skills.
|
||||
|
||||
**Plan-review and implementation tiers.** Plan workflows use an independent
|
||||
`frontier` reviewer: Fable 5.1 from Codex, or GPT-6 Astra from Claude Code.
|
||||
Implementation handoffs recommend the `smart` tier: Opus 5.5 or GPT-6.1 Sol.
|
||||
Choose a different tier, pin either provider's model, or use `host` mode to
|
||||
preserve native model settings. The read-only `gstack-models` command explains
|
||||
the effective choices; changing these settings does not require regenerating
|
||||
skills or switch your running session. A weekly maintenance check flags changed
|
||||
official recommendations and retirement notices without automatically changing
|
||||
models. See [model policy: setup, overrides and freshness](model-policy.md).
|
||||
|
||||
**Other Codex calls keep their existing selection.** Without the explicit
|
||||
`plan-review` role (including ordinary review and ship adversarial passes), gstack picks the model in this
|
||||
order: a model you name for that request, then `GSTACK_CODEX_MODEL`, then
|
||||
`model` in your Codex `config.toml` (for native `codex review`, `review_model`
|
||||
first; a custom `CODEX_HOME` is honored), and only then gstack's default,
|
||||
`gpt-6-astra`. Before spending anything, it prints the choice and where it came
|
||||
from, for example `CODEX_MODEL: gpt-5.6-terra (exec; source: ~/.codex/config.toml model)`.
|
||||
If your choice is invalid or your account cannot use it, gstack stops with a
|
||||
repair message and reports the outside review as unavailable. It never silently
|
||||
switches to its default. Nested Codex reviews also run with installed skills
|
||||
hidden (`-c skills.include_instructions=false`), so a review cannot turn into a
|
||||
whole nested skill run. Runtime model selection is separate from the setup-time
|
||||
behavioral profile above. Without the plan-review role, `/claude-code` (`gstack-claude-code`
|
||||
on Codex) preserves Claude's configured model. Set `GSTACK_CLAUDE_MODEL=<model>`
|
||||
or name a model in your request to override it for the invocation, including
|
||||
resumed consultations. See [eval defaults and overrides](../CONTRIBUTING.md#testing--evals)
|
||||
for capture, judge, and benchmark model selection.
|
||||
|
||||
## The design binary's models
|
||||
|
||||
The design binary (`$D`) uses OpenAI's `gpt-5.5` by default, with image
|
||||
generation through `gpt-image-2`. Set `GSTACK_DESIGN_MODEL=<model>` to use
|
||||
another gpt-5-class model for both image generation and screenshot analysis;
|
||||
if OpenAI rejects it, the error names `GSTACK_DESIGN_MODEL`. Set
|
||||
`GSTACK_DESIGN_IMAGE_MODEL=<gpt-image model>` to change the image tool model; a
|
||||
value that is not a gpt-image model name is refused before any request. Check a
|
||||
key against the defaults with `bun run design/scripts/live-model-check.ts`,
|
||||
which always tests the default models and ignores both overrides; the weekly
|
||||
periodic census runs the same check. Set `OPENAI_BASE_URL` to send every `$D`
|
||||
call to an OpenAI-compatible gateway instead of `api.openai.com`; egress
|
||||
receipts record the gateway host. On Codex, `/design-shotgun` generates mockups
|
||||
with Codex's built-in `$imagegen` skill instead, so it needs no
|
||||
`OPENAI_API_KEY`; `$D` still builds and serves the comparison board.
|
||||
|
||||
## Adding a host
|
||||
|
||||
**Want to add support for another agent?** See [docs/ADDING_A_HOST.md](ADDING_A_HOST.md).
|
||||
Rendering a new agent is one TypeScript config file; installing it also needs a
|
||||
setup arm and the conformance kit, and it starts as experimental.
|
||||
@@ -0,0 +1,52 @@
|
||||
# Install details
|
||||
|
||||
The README covers the two-step install. This page holds the details: what
|
||||
setup needs and how long it takes, the team-mode hook, the hooks setup
|
||||
registers in Claude Code, and how to remove gstack.
|
||||
|
||||
## How long setup takes
|
||||
|
||||
`./setup` took 31 seconds after the clone on a 4-vCPU Linux cloud machine (v1.91.13.0, clean HOME, including the binary build and the Chromium download). Slower networks and laptops take longer.
|
||||
|
||||
## Requirements, in detail
|
||||
|
||||
**Requirements:** [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Git](https://git-scm.com/), [Bun](https://bun.sh/) v1.4.2+ (the tested version; setup refuses Bun older than 1.3.3, which would let compiled tools read a project's `.env`, and warns in between), [Node.js](https://nodejs.org/) (Windows only). **Recommended on macOS:** the [Aside](https://aside.com) browser (macOS 15+) — browser skills, `/make-pdf`, and `/diagram` drive it first, with your real logged-in sessions. Without it, `./setup` builds gstack's own bundled browser and the same skills use that. `/cso` additionally needs a Bun release with all four `--no-compile-autoload-*` build flags plus a native toolchain: a static-capable C compiler on Linux, Xcode command-line tools on macOS, or Visual Studio 2022 Build Tools with Desktop development with C++ on Windows. If those are absent, setup installs everything else, removes stale CSO helpers, and `/cso` reports `not assessed` with the prerequisite.
|
||||
|
||||
When qualified CSO runtime images are published, setup gives each automatic preload a 30-second window plus a bounded setup allowance for the declared catalog. For slower registries, set an integer such as `GSTACK_CSO_IMAGE_PULL_TIMEOUT_SECONDS=120` (accepted range: 5–300 seconds). One image timing out does not consume the remaining images' windows; setup reports partial progress and a later run resumes from exact digests already present in local Docker. The complete preload is capped at one hour.
|
||||
|
||||
## Team mode
|
||||
|
||||
From inside your repo, `(cd ~/.claude/skills/gstack && ./setup --team) && ~/.claude/skills/gstack/bin/gstack-team-init required` switches you to team mode and bootstraps the repo so teammates get gstack automatically (the README has the full paste, which also commits the change).
|
||||
|
||||
No vendored files in your repo, no version drift, no manual upgrades. Every Claude Code session starts with a fast auto-update check (throttled to once/hour, network-failure-safe, completely silent). Team mode follows the `stable` channel: the weekly tagged release (Monday mornings, plus off-cycle hotfixes), not every merge to main. `gstack-config set update_channel main` follows every merge instead; see [releases.md](releases.md).
|
||||
|
||||
Swap `required` for `optional` if you'd rather nudge teammates than block them.
|
||||
|
||||
`required` blocks skills with a hook that runs on [Node.js](https://nodejs.org/), so every teammate needs Node.js on PATH, on every OS. With Claude Code 2.1.295 or later the hook also blocks when it can't run (no Node.js, or a missing `.claude/hooks/check-gstack.cjs`); older Claude Code lets skills through in that case.
|
||||
|
||||
## Hooks that `./setup` registers
|
||||
|
||||
`./setup` registers up to three hook families in `~/.claude/settings.json`, every one pointing at the canonical install. In team mode it registers the `SessionStart` auto-update hook (`gstack-session-update`; `./setup --no-team` skips it). It asks before registering the AskUserQuestion `PreToolUse` (preference enforcement) and `PostToolUse` (deterministic answer capture) hooks that `/plan-tune` uses at runtime; `./setup` never writes them silently. `docs/uninstall.md` lists the same families when telling you what to remove by hand.
|
||||
|
||||
`./setup` also registers one default-on Stop hook:
|
||||
`gstack-timeline-stop` (closes dangling session-timeline entries when a session
|
||||
is interrupted; fail-open — 2s internal budget, always exits 0, can never block
|
||||
a session). Opt out persistently with `./setup --no-timeline-stop-hook` — the
|
||||
choice lands in the `timeline_stop_hook` config key, survives upgrades, and an
|
||||
explicit "no" removes a live registration. `GSTACK_TIMELINE_STOP_HOOK=no` and
|
||||
`gstack-config set timeline_stop_hook no` work too (flag > env > config).
|
||||
`./setup --no-team` skips it for that run, `gstack-settings-hook remove-source
|
||||
--source gstack-timeline-stop` removes it by hand, and `gstack-uninstall`
|
||||
removes it too.
|
||||
|
||||
Hook registration is canonical-only: every hook command points at the stable
|
||||
`~/.claude/skills/gstack` install, never the tree setup ran from, so deleting
|
||||
a worktree or Conductor workspace can't leave dead hooks erroring in your
|
||||
sessions. Every `./setup` run also heals first: `gstack-settings-hook
|
||||
prune-stale --repoint` removes dead gstack hook entries, re-points stale ones
|
||||
at the stable install, and collapses duplicates, printing one line (and
|
||||
writing a backup beside the file) only when it changed something.
|
||||
|
||||
## Uninstall
|
||||
|
||||
See [uninstall.md](uninstall.md).
|
||||
@@ -194,6 +194,36 @@ Two stages with separate clocks, both printed:
|
||||
issue templates, CODEOWNERS and dependabot config). Stylesheets (they import
|
||||
each other), any other dotfile (`.rspec`, `.husky/` hooks, `.env*`, version
|
||||
pins), CI workflows and actions, and `.gstack/` still need a lane.
|
||||
A release manifest the branch only re-stamped is waived as
|
||||
version-stamp-only (`version-stamp-only (no lane needed): …`): the file's
|
||||
diff against the merge base changes nothing but its version values. Three
|
||||
file kinds qualify, each by its own exact rule (lib/pregate/stamp-only.ts).
|
||||
A JSON file (`*.json`) whose two sides parse to objects of the same shape
|
||||
(the same own keys in the same order in every object, the same length in
|
||||
every array, the same type at every leaf), whose only differing leaf is the
|
||||
document's top-level `version` with a bare version on both sides (`1.2.3`,
|
||||
`v1.2.3.4`, `1.2.3-rc.1`; never a range), and whose text is line-for-line
|
||||
identical once that line has its old quoted version replaced by the new
|
||||
one. A plist whose differing lines are all `<string>` versions under
|
||||
`<key>CFBundleShortVersionString</key>` or `<key>CFBundleVersion</key>`. A
|
||||
`VERSION` or `VERSION.txt` file (any case) holding one bare version, with at
|
||||
most a trailing LF, on both sides. Texts are compared byte for byte outside
|
||||
the stamped value, so a line-ending change or a BOM is content. Nothing
|
||||
nested in JSON qualifies, so a dependency pin or range, a lockfile entry, a
|
||||
catalog or a package named `version` is never a stamp; nor is a changed
|
||||
description, a new, removed or reordered key, a floor such as
|
||||
`minHostVersion`, `schemaVersion` or `MinimumOSVersion`, a reflow, a change
|
||||
the parser would erase (a big integer, a duplicate key), a file that is not
|
||||
valid UTF-8 or is over 1 MiB, a mode change or a symlink on either side, an
|
||||
added, deleted or renamed manifest, or any other file kind: each keeps its
|
||||
`PREGATE_NO_LANE`. The rule runs only after the import graph, the
|
||||
declarations, the regen registry and both empty lanes found nothing, so a
|
||||
manifest a test reads still selects that test, and never under a test,
|
||||
fixture, golden, snapshot, `.gstack/` or CI-workflow tree, whose JSON a test
|
||||
may compare against by a path the graph cannot see.
|
||||
`--explain` prints the verdict per file (`version-stamp-only against
|
||||
<merge-base> (version 1.0.0 → 1.0.1)` or `not version-stamp-only
|
||||
(<reason>)`).
|
||||
In a Django project (a manage.py or code importing django, plus Python
|
||||
code, not a string or comment, that assigns `INSTALLED_APPS` or
|
||||
`ROOT_URLCONF`, or a config file names `DJANGO_SETTINGS_MODULE`), the files
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
# Privacy & Telemetry
|
||||
|
||||
gstack sends nothing about how you use it unless you opt in. Three kinds of
|
||||
network traffic exist, and each has its own switch.
|
||||
|
||||
**Telemetry (opt-in, default off).** On first run gstack asks once. You can say
|
||||
no, and `gstack-config set telemetry off` turns it off at any time.
|
||||
|
||||
- **What's sent, per skill run:** skill name, event type, outcome
|
||||
(success/error/abort), duration, gstack version, OS and CPU architecture,
|
||||
whether the browser was used, whether the run was live or a test, a session
|
||||
id, the count of live session markers (below), the error class, the failed step, and an error message passed through gstack's
|
||||
secret redaction (cut to 200 characters). `community` adds a stable device
|
||||
ID; `anonymous` sends no ID. Prompt-injection detections add the attacking
|
||||
page's domain, a payload hash, a confidence score, the detection layer and
|
||||
the verdict.
|
||||
- **What's never sent:** code, file paths, repo names, branch names, prompts,
|
||||
or any other content you write.
|
||||
- **What stays on your machine:** opted in, each run is also logged to
|
||||
`~/.gstack/analytics/skill-usage.jsonl` (under your state root; see
|
||||
[docs/state-root.md](state-root.md)) with the repo name and branch, so
|
||||
`gstack-analytics` can show your own dashboard. `gstack-telemetry-sync`
|
||||
deletes those two fields (and, for `anonymous`, the device ID) before
|
||||
anything is uploaded. With telemetry off, gstack logs no usage analytics at
|
||||
all; nothing new is written there.
|
||||
- **Where it goes:** [Supabase](https://supabase.com) (an open-source Firebase
|
||||
alternative). The schema is in [`supabase/migrations/`](../supabase/migrations/),
|
||||
so you can verify what's collected. The publishable key in the repo is a
|
||||
public key; row-level security denies direct access, and edge functions
|
||||
enforce schema checks, event allowlists and field length limits.
|
||||
|
||||
**Update check (on by default, regardless of telemetry).** Every interactive
|
||||
skill start runs `gstack-update-check`. When its cache is stale (60 minutes after an
|
||||
up-to-date result, 12 hours while an upgrade is pending, 10 minutes after a
|
||||
failure) it asks GitHub for the latest version: a `git ls-remote` of
|
||||
`github.com/garrytan/gstack`, a GET of the `VERSION` file from
|
||||
`raw.githubusercontent.com` and, when that version equals yours but `main` has
|
||||
moved past your checkout, a GET of `api.github.com`'s compare endpoint for the
|
||||
two commit SHAs. GitHub sees your IP address; nothing about you or your code is
|
||||
sent. With telemetry on, the same check also posts your gstack
|
||||
version and OS to gstack's Supabase. Turn the check off with
|
||||
`gstack-config set update_check false`.
|
||||
|
||||
**Outside reviews (on by default; the first one prints a notice).** Plan
|
||||
reviews, `/autoplan`, `/review` and `/ship` adversarial passes, `/ship`'s design
|
||||
check, `/design-review`, `/design-consultation`, `/office-hours`,
|
||||
`/document-release` and `/spec` can
|
||||
ask a second model for an independent review: Codex under Claude Code, Claude
|
||||
Code under Codex. That sends the review prompt and the relevant code, diff or
|
||||
plan to that provider, using your own Codex or Claude Code login. The first
|
||||
send on a machine says which provider and which account. Turn it off with
|
||||
`gstack-config set codex_reviews disabled`; the binaries that send then refuse
|
||||
(`not_run`, `consent_missing`) and start nothing, and an unreadable config
|
||||
counts as disabled. `/codex` and `/claude-code` still run when you ask for
|
||||
them by name.
|
||||
|
||||
**Local state that is not telemetry.** gstack keeps a few local files whatever
|
||||
your telemetry setting. They are functional state, not usage logs, and the
|
||||
files are never sent:
|
||||
|
||||
- **Session markers:** `~/.gstack/sessions/<pid>`, one empty file per live agent
|
||||
session, touched at skill start and deleted after 120 minutes. The count lets
|
||||
skills notice several sessions on one machine; with telemetry on, that number
|
||||
is the session count above.
|
||||
- **Project timeline:** `~/.gstack/projects/<repo>/timeline.jsonl` records skill
|
||||
starts and finishes with the branch name, for `/context-restore` and `/retro`.
|
||||
It leaves the machine only if you turn on `artifacts_sync_mode full`
|
||||
([docs/gbrain-sync.md](gbrain-sync.md)).
|
||||
|
||||
**Every off-machine send is receipted.** Any gstack-initiated network send,
|
||||
telemetry included, writes a hash-chained, tamper-evident receipt to
|
||||
`~/.gstack/security/egress.jsonl` before the send; sensitive sinks refuse to
|
||||
send at all if the receipt can't be written. Audit with `gstack-egress list`,
|
||||
verify the chain with `gstack-egress verify` (exit 3 on tamper), and see the
|
||||
standing consent settings with `gstack-egress grants`. The ledger records
|
||||
attempted sends so accidents are auditable; it's an audit trail, not a network
|
||||
firewall.
|
||||
|
||||
**Optional third-party bridges are off by default and receipted too.** The one
|
||||
that exists today, the [Memorable recall bridge](memorable-workflow-memory.md),
|
||||
hands your prompt to a locally installed vendor binary only after you run
|
||||
`gstack-memorable enable`; every hand-off writes a receipt first, the consent
|
||||
shows up in `gstack-egress grants`, and what the vendor then sends is
|
||||
documented as the vendor's claim.
|
||||
@@ -0,0 +1,58 @@
|
||||
# Proactive skill suggestions
|
||||
|
||||
gstack notices what stage you are in (a new idea, a diff to review, "does this
|
||||
work?", time to land it) and can invoke the matching skill without you typing
|
||||
its slash command. The `proactive` config key says how far that goes.
|
||||
|
||||
| Value | What auto-invokes | Who it is for |
|
||||
|-------|-------------------|---------------|
|
||||
| `core` (default) | `/office-hours`, `/review`, `/qa` and `/ship` only. Every other skill waits for you to type it, unless a skill you ran hands off to it (`/office-hours` offering the next review). | Most people, and anyone running gstack beside another skill pack: the four core skills are the ones users praise, and the others no longer compete with another pack's niche skills. |
|
||||
| `all` | Any skill whose purpose matches your request. | You use gstack on its own and want the whole sprint suggested. |
|
||||
| `off` | Nothing. gstack never suggests or auto-invokes a skill, not even by asking "want me to run /X?". | You type every command yourself. |
|
||||
|
||||
`true` and `false` still work as the older spellings of `all` and `off`, so a
|
||||
config written before the `core` default keeps its behavior; `gstack-skill-start`
|
||||
reports them to the skill as `all` and `off`. A hand edit may be quoted, upper-case
|
||||
or commented (`proactive: "OFF" # mine`), and the YAML yes/no family (`yes`, `on`,
|
||||
`1`; `no`, `none`, `0`, `disabled`, `never`) reads as `all` or `off`, in
|
||||
`gstack-config set` as well, so a typed opt-out never fails open.
|
||||
|
||||
```bash
|
||||
gstack-config get proactive # core, all, off (or a legacy true/false)
|
||||
gstack-config set proactive all # suggest every matching skill
|
||||
gstack-config set proactive off # suggest nothing
|
||||
gstack-config set proactive core # back to the default
|
||||
```
|
||||
|
||||
A skill you name is always invoked, whatever the setting, and so is a skill that
|
||||
a running skill invokes itself (`/ship` dispatching `/document-release`). The
|
||||
setting governs unasked suggestions, and an in-skill *offer* to run another skill
|
||||
is one: under `off`, `/office-hours` recommends the next review in one line
|
||||
instead of offering to launch it, and the one-time first-run
|
||||
tips stay quiet. The change takes effect on the next skill run; no `./setup` is
|
||||
needed, because every skill reads `PROACTIVE` from `gstack-skill-start` at the
|
||||
start of each run and the router (`/gstack`) applies it to its routing rules. A
|
||||
value gstack does not know (a hand-edited `proactive: maybe`) reads as `core`.
|
||||
|
||||
Two surfaces follow the setting only in part. The first is the `## Skill routing`
|
||||
section gstack offers to append to your project's CLAUDE.md. Under `off` the offer is
|
||||
not made, because those rules are the auto-invocation you turned off (a section
|
||||
already in CLAUDE.md stays; delete it yourself). Under `core` the offered
|
||||
section still lists every routing rule and tells the host to invoke any
|
||||
matching skill. If you accepted it before the `core` default, trim that section
|
||||
to the four core rules (or delete it and let gstack offer it again); making the
|
||||
offered section follow `core` is tracked in #3446.
|
||||
|
||||
The second is the full skill descriptions, which a `--catalog-mode=full` build
|
||||
and the non-Claude hosts keep. Non-core skills still say "Proactively suggest
|
||||
when ..." there, and that text does not change with the setting, so a host
|
||||
that picks skills from those descriptions can still reach them under `core`.
|
||||
The default Claude build uses one-line descriptions without that sentence.
|
||||
#3446 tracks that too.
|
||||
|
||||
gstack asks once, after the telemetry question, which of the three you want;
|
||||
if you stored `off` before that, it takes the answer as given and does not ask.
|
||||
"Say stop suggesting" in a session runs `gstack-config set proactive off` for you.
|
||||
|
||||
If collisions with other skill packs keep coming up under `core`, the fallback
|
||||
Garry approved on 2026-10-25 is `off` by default.
|
||||
@@ -0,0 +1,44 @@
|
||||
# QA without a webpage
|
||||
|
||||
Use the same commands for browser and non-browser software. Start in a repository
|
||||
with its documented native command and an isolated local fixture; name the target
|
||||
and the behavior you want checked. For example:
|
||||
|
||||
```text
|
||||
/qa-only Test this repo's CLI using its documented local fixture. Check valid and invalid input, exit codes, stdout/stderr, and cancellation. Report only; do not change code or tests. For each finding, include the exact command, expected and actual results, and which checks remain untested. Keep requests inside the fixture; ask before contacting an external service.
|
||||
|
||||
/qa Test this repo's local webhook and worker fixture. Explore duplicate deliveries and recovery after a partial failure. Keep all effects inside the fixture; preserve reproduced bugs in native regression tests before repairing them.
|
||||
```
|
||||
|
||||
QA first tells you which surface, tools and write permissions it will use. A CLI or
|
||||
API does not need a browser. Browser targets keep real browser testing; developer
|
||||
experience audits load only when onboarding, installation or ergonomics are in scope.
|
||||
If the native tools or safe fixture are unavailable, the report names the blocker
|
||||
and untested contracts instead of inventing a pass or installing another framework.
|
||||
|
||||
Exploration means learning from each result and choosing the next useful challenge,
|
||||
not running random commands. Before the next discovery probe, QA saves a short
|
||||
`exploration-NNN.json` evidence note with the previous result, the assumption being
|
||||
tested and the next command. The final report links these notes; they do not require
|
||||
an extra chat message between probes. A discovered bug must be reproducible, and its new test
|
||||
must fail for the bug before the repair and pass afterward. Unit tests protect logic;
|
||||
integration and end-to-end tests protect real boundaries that mocks would hide.
|
||||
`/qa-only` proposes those tests without writing them.
|
||||
|
||||
Normal `/review` and `/ship` run a bounded version on changed behavior and nearby
|
||||
risks automatically, including small diffs without a plan or web server. Existing
|
||||
fix/test approval rules still apply. Missing dependencies, denied actions and time
|
||||
limits remain visible coverage gaps; a short smoke pass never means exhaustive QA.
|
||||
Production access and destructive or external effects require specific permission.
|
||||
|
||||
Bounded exploration uses an executable deadline guard, not an estimated clock: it
|
||||
refuses late probes and stops owned foreground work at the limit. Unfinished checks
|
||||
stay visible in the report. Required plan checks remain outside the review/ship smoke
|
||||
budget. See [QA deadlines](reference-qa-deadlines.md) for command, platform and
|
||||
cleanup limits.
|
||||
|
||||
Every ship also runs the existing documentation audit, including repeat ships and
|
||||
existing PR updates. Clear factual corrections join the final checked change; risky
|
||||
rewrites need approval. A failed audit stops for recovery or explicit acceptance of
|
||||
the named risk rather than silently dropping its result. Ship owns versioning, Git
|
||||
and PR publication; the docs helper does not commit or push independently.
|
||||
@@ -0,0 +1,144 @@
|
||||
# Releases and update channels
|
||||
|
||||
gstack stamps a version at every merge to main, about a dozen a day. Since
|
||||
late October 2026 there is also a weekly tagged release, and team-mode
|
||||
installs follow it instead of every merge.
|
||||
|
||||
## What a release is
|
||||
|
||||
Every Monday at 07:00 Pacific (06:00 in winter) the **Weekly release**
|
||||
workflow (`.github/workflows/weekly-release.yml`, which runs
|
||||
`scripts/release-cut.ts`) takes the newest green commit on main and:
|
||||
|
||||
1. tags it `v<VERSION>` (the VERSION file at that commit, an annotated tag);
|
||||
2. publishes a GitHub Release whose notes are one line per CHANGELOG version
|
||||
since the previous release, with a compare link and a link to the full
|
||||
CHANGELOG at that tag;
|
||||
3. fast-forwards the `stable` branch to it.
|
||||
|
||||
A tag and its release never change. `stable` is a moving pointer: always a
|
||||
commit on main's first-parent history, moved only by this workflow, only
|
||||
forward.
|
||||
|
||||
**Green** means, on that exact commit: the `free-tests` check run succeeded
|
||||
(the status main requires), the `main-backstop` check run succeeded, every
|
||||
check run on it has completed and none failed, and `refs/notes/gstack-trains`
|
||||
holds no active freeze. The Main backstop gate workflow publishes
|
||||
`main-backstop` on each push to main from a job of its own: success when a
|
||||
green train record covers the tree or the backstop gated it green, neutral
|
||||
when main moved past the push before it was gated (superseded) or a freeze's
|
||||
own backstop run owns it (deferred), failure when it froze the queue or could
|
||||
not finish. Neutral is never green: a commit whose own tree was never gated is
|
||||
not released. A push that gets no backstop run at all (a dependabot push, a
|
||||
policy without a train block) has no `main-backstop` check run and is not
|
||||
green either; a cancelled check run counts as red. A commit with a check run still going is skipped and the next
|
||||
older one is considered, back through 7 days or 200 first-parent commits. The
|
||||
workflow runs no tests and no paid evals of its own; it only reads what the
|
||||
trains and the backstop already recorded. With nothing green in the window it
|
||||
fails and lists why each commit was skipped.
|
||||
|
||||
A cut that stopped half-way (tag and `stable` pushed, release not created) is
|
||||
finished by the next run, by cron or by hand: `stable` itself stays a
|
||||
candidate until its release exists, and its notes compare against the release
|
||||
tag before it.
|
||||
|
||||
## Channels
|
||||
|
||||
| Channel | Follows | Who |
|
||||
|---|---|---|
|
||||
| `stable` | `refs/heads/stable`: the weekly release, plus any hotfix cut off-cycle | team mode (`./setup --team`) by default |
|
||||
| `main` | every merge to main | plain `./setup`, contributors, Capy crew machines |
|
||||
|
||||
The config key is `update_channel` (`bin/gstack-config`):
|
||||
|
||||
```bash
|
||||
gstack-config get update_channel # auto (default) | stable | main
|
||||
gstack-config set update_channel main # a team install that wants every merge
|
||||
gstack-config set update_channel stable # a solo install that wants weekly releases
|
||||
gstack-config unset update_channel # back to auto
|
||||
```
|
||||
|
||||
`auto` resolves to `stable` when `team_mode` is true and to `main` otherwise,
|
||||
so existing team installs moved to weekly releases with nothing to migrate and
|
||||
contributor installs did not move at all. `GSTACK_UPDATE_CHANNEL=stable|main`
|
||||
overrides the key for one command. The resolution lives in one helper,
|
||||
`bin/gstack-update-channel.sh`, used by:
|
||||
|
||||
- `bin/gstack-session-update`, the team-mode session-start hook: it fetches
|
||||
the channel ref and fast-forwards to it. Its log
|
||||
(`<state root>/analytics/session-update.log`) carries `CHANNEL stable target=<sha>`
|
||||
when it follows a release.
|
||||
- `bin/gstack-update-check`, the "update available" line in every skill
|
||||
preamble: it reads `<channel>/VERSION` and compares with `refs/heads/<channel>`,
|
||||
so a stable install is not told every hour that main moved.
|
||||
- `bin/gstack-upgrade` and `/gstack-upgrade`: they fast-forward to the channel
|
||||
ref (`UPSTREAM_REF=origin/stable` in the skill's output) and say which channel
|
||||
they followed.
|
||||
|
||||
Two edge cases are handled on purpose:
|
||||
|
||||
- **Before the first release exists** origin has no `stable`. Every caller then
|
||||
follows main and says so (`CHANNEL_FALLBACK stable->main` in the hook log,
|
||||
`channel stable has no release on origin yet` from `gstack-upgrade`).
|
||||
- **An install already past the release** (it followed main until now, or a
|
||||
hotfix was cut below it) stays where it is until stable passes it. The
|
||||
updater never moves a checkout backwards. A depth-1 clone cannot see that an
|
||||
older release is its ancestor, so the VERSIONs decide: main stamps a higher
|
||||
version at every merge and stable is always a main commit.
|
||||
|
||||
The local branch stays `main` either way. A stable install is a `main`
|
||||
checkout parked at the last released commit, which is why fast-forward-only
|
||||
still holds and nothing about version stamping changed.
|
||||
|
||||
## Cutting a release by hand (hotfixes)
|
||||
|
||||
A hotfix is an ordinary PR: it merges to main through a train and gets its
|
||||
version like any other change. To put it on stable before Monday, run the
|
||||
workflow by hand with the merge commit's SHA:
|
||||
|
||||
```bash
|
||||
gh workflow run weekly-release.yml -f ref=<full sha on main> -f dry_run=false
|
||||
```
|
||||
|
||||
or Actions → **Weekly release** → Run workflow, on main (the cut job runs
|
||||
only from `refs/heads/main`, with main's tooling; `ref` must be the full
|
||||
40-hex SHA). The same green checks apply. The commit is tagged with its VERSION, the release notes say "hotfix release"
|
||||
and list every version since the previous release (everything merged in
|
||||
between ships with it), and stable fast-forwards. Team installs pick it up at
|
||||
their next hourly check.
|
||||
|
||||
There is no release branch and no cherry-picking: stable only ever names main
|
||||
commits. If main carries a change that must not ship yet, revert it on main
|
||||
first, then promote.
|
||||
|
||||
## Dry runs
|
||||
|
||||
Run the workflow with `dry_run=true` (the default for a manual run) to see the
|
||||
plan without changing anything: the candidate, its VERSION and tag, the stable
|
||||
move, how many version lines the notes would carry, every skipped commit with
|
||||
its reason, and the exact commands a real cut would run. The scheduled Monday
|
||||
run is never a dry run.
|
||||
|
||||
```bash
|
||||
gh workflow run weekly-release.yml -f dry_run=true
|
||||
gh run watch
|
||||
```
|
||||
|
||||
The step summary carries the same plan plus the full release notes.
|
||||
|
||||
A pull request that touches `.github/workflows/weekly-release.yml` or
|
||||
`scripts/release-cut.ts` runs the same dry run against main as its own job
|
||||
(`Dry run against main`, read-only token), so a change to the release tooling
|
||||
is exercised against real check runs before it merges.
|
||||
|
||||
## Outcomes
|
||||
|
||||
| Line | Meaning |
|
||||
|---|---|
|
||||
| `CANDIDATE: <sha> (<date>)` | the commit that will be (or was) released |
|
||||
| `NOTHING_NEW: origin/main <sha> is already released as v<X>` | nothing merged since the last release; exit 0 |
|
||||
| `NO_GREEN_COMMIT: ...` with `SKIPPED:` lines | nothing in the window passed the gate; exit 1 (a dry run exits 0: the skips are its answer) |
|
||||
| `REFUSED: non-fast-forward: ...` | the candidate is behind stable; stable only moves forward |
|
||||
| `REFUSED: queue frozen since train ...` | the main backstop froze the queue; clear it first (docs/ship-policy.md) |
|
||||
| `REFUSED: tag v<X> already names <other sha>` | a version was stamped twice; needs a look before anything is cut |
|
||||
| `TAG:`, `STABLE:`, `RELEASE:` | what the cut did; a re-run reports each step as already done and finishes a step an earlier run did not reach |
|
||||
+101
-7
@@ -168,9 +168,51 @@ gate: command="<gate_command>" full_e2e=no gate-ahead=none
|
||||
SHIP_RECEIPT: pr=42 head=<head12> …
|
||||
```
|
||||
|
||||
CI's Version Gate (`scripts/compare-pr-version.ts`) accepts that head as
|
||||
unstamped when `VERSION` and `package.json`'s version match its merge base, the
|
||||
CHANGELOG change is confined to `[Unreleased]`, and any changed release output
|
||||
is byte for byte what `release_tool` generates at the head's merge base
|
||||
`VERSION` (the head's own, so a later stamp on main changes nothing), so a
|
||||
generator change ships with its regenerated output and the slot's restamp
|
||||
regenerates it again. A release output that differs from that (a hand edit, one generated at a
|
||||
bumped version, or one the tool never writes), a `VERSION` bump or a
|
||||
`package.json` version change classifies the head as stamped, and it must then
|
||||
carry base + bump. The tool runs as head code in a throwaway worktree with a
|
||||
bare environment and no tokens; one that fails at the head is an error, never
|
||||
a pass.
|
||||
|
||||
**A PR that conflicts with the base on CHANGELOG.md alone.** GitHub shows every
|
||||
READY PR as conflicting each time main stamps; the train merges the CHANGELOG
|
||||
by section, so the lane never merges main for it. The conflict does mean GitHub
|
||||
has no merge ref for the PR, so no `pull_request` CI runs on the head and
|
||||
`ready` prints `ci: none at <head12>` with a `ci-hint:` line. The lane then
|
||||
dispatches the free workflows on the branch (`gh workflow run free-tests.yml
|
||||
--ref <branch> && gh workflow run windows-free-tests.yml --ref <branch>`),
|
||||
clears the requires-remote pregate lanes with those runs
|
||||
(`gstack-pregate clear --remote <lane> --run-url <run>`) and reruns `ready`,
|
||||
which passes because the dispatched check runs attach to the commit; the
|
||||
`ci-hint:` line then says those non-pull_request runs count. The `ci-hint:` line is
|
||||
advisory text for the lane; nothing parses it, and a PR whose head is on a fork
|
||||
gets a hint that leaves the dispatch to the coordinator. `ready --screen` on such a
|
||||
PR refuses, because there is no merge ref to screen (`QUEUE_SCREEN_STALE_REF`,
|
||||
`<remote> has no refs/pull/<n>/merge`); that refusal's "merge <base> into the
|
||||
branch" advice does not apply to a CHANGELOG-only conflict, so the lane READYs
|
||||
with `--no-screen` after the skill's own paid cases ran focused.
|
||||
|
||||
`ci:` counts the latest run per check (one app's check name), as GitHub's
|
||||
rollup does: a check that was re-run or superseded on the same commit (a
|
||||
cancelled run beside a later pass of the same name from a second workflow run)
|
||||
is judged by its newest run (latest `completed_at`, then id; a run still in
|
||||
progress is newest); two apps reporting the same name stay separate; a run with
|
||||
no name or no readable `completed_at` counts on its own; commit-status contexts
|
||||
are counted one each.
|
||||
|
||||
It refuses (`QUEUE_NOT_READY`) when the working tree has tracked edits, HEAD
|
||||
is not the PR's pushed head, CI at that exact head is not green, or the pregate
|
||||
result is not a current pass. Send the block as printed.
|
||||
result is not a current pass, and (`QUEUE_RECEIPT_MISSING`,
|
||||
`QUEUE_RECEIPT_STALE`) when the PR body carries no /ship receipt for that
|
||||
head: /ship runs before every READY ([details](#ship-before-ready)). Send the
|
||||
block as printed.
|
||||
|
||||
**2. Lane behind another PR: gate ahead.** Gate the tree the queue will have
|
||||
once the predecessor lands, while it is still open:
|
||||
@@ -304,13 +346,53 @@ are mirrored to origin as git notes on `refs/notes/gstack-trains` before any
|
||||
remote effect, so a lost machine loses nothing: `train status` on a clean
|
||||
state root rebuilds them. `GSTACK_SHIP_QUEUE_NOW` pins the clock for tests.
|
||||
|
||||
<a id="ship-before-ready"></a>
|
||||
**0. Lane: /ship before READY.** Every READY follows a /ship run on the branch
|
||||
at the head being READY'd, including a re-READY after a push (Garry,
|
||||
2026-10-25: "we should really be using the gstack ship skill before merging
|
||||
PRs"). /ship runs the tests, both reviews, the gate-integrity list and the docs
|
||||
audit, and ends by writing its receipt into the PR body
|
||||
(`gstack-ship-receipt write --pr <n> … --body <file>`, posted with
|
||||
`gstack-post pr-body`). Under `queue_mode: stamp-at-merge` it bumps no version
|
||||
and never merges or merges main into the branch; the train still stamps at the
|
||||
slot. The queue enforces this at every step from the PR body alone:
|
||||
|
||||
- `ready` refuses with `QUEUE_RECEIPT_MISSING` when the body has no valid
|
||||
receipt and with `QUEUE_RECEIPT_STALE` when the receipt's `head` is not the
|
||||
head being READY'd (or its `pr` is another PR); the informational
|
||||
`SHIP_RECEIPT:` line still prints. There is no flag around it.
|
||||
- `train build` refuses a pin whose PR has no current receipt with the same
|
||||
codes, naming the PR, before anything is written, pushed or dispatched,
|
||||
`--dry-run` included.
|
||||
- `verify --train` refuses the slot with the same codes before printing
|
||||
`MERGE_CONDITION`.
|
||||
|
||||
**The stamp keeps the pin's receipt.** The slot's stamp commit changes only
|
||||
stamp files (and, from slot 2 on, merges main into the pin), so no /ship runs
|
||||
on it: the receipt written for the pinned head stays the binding one. `verify
|
||||
--train` accepts a receipt whose `head` is either the slot's pinned head or
|
||||
the current head, and the re-READY after the stamp push (`ready` at the stamp
|
||||
commit) accepts the receipt for the pin when HEAD is the pin plus stamp files,
|
||||
or (from slot 2 on) the pin merged with main plus stamp files on top of the
|
||||
merge; any other head is `QUEUE_RECEIPT_STALE`, including a merge of main with
|
||||
no stamp above it (a lane's own push under rule 14) and a merge whose pin side
|
||||
carries an edit made after /ship.
|
||||
|
||||
This is a process gate, not an authenticity check: the receipt is a block in
|
||||
the PR body, which the PR author writes, so the codes prove that /ship was run
|
||||
and recorded for this head, not that nobody typed the block by hand. Fork heads
|
||||
are already refused (`QUEUE_TRAIN_UNTRUSTED`). The `ship-run.json` that
|
||||
`gstack-ship-receipt result` writes under the lane's state root is a
|
||||
normalized copy of that same block bound to the head it was run at, not an
|
||||
authenticity proof either.
|
||||
|
||||
**1. Lane: READY with a screen choice.** Under a train policy READY needs an
|
||||
explicit choice:
|
||||
|
||||
```bash
|
||||
gstack-ship-queue ready --pr 42 # prints SCREEN: lines and the recommendation, refuses
|
||||
gstack-ship-queue ready --pr 42 --no-screen # READY receipt; the PR rides unscreened
|
||||
gstack-ship-queue ready --pr 42 --screen [--broad] # dispatches one paid screen on the PR's merge SHA
|
||||
gstack-ship-queue ready --pr 42 --screen [--broad] # dispatches one paid screen on the PR's merge SHA (QUEUE_SCREEN_STALE_REF when GitHub's merge ref lags the head)
|
||||
```
|
||||
|
||||
Without a choice it prints the `SCREEN:` selection (with
|
||||
@@ -326,7 +408,17 @@ dispatches `gate_workflow` with `screen_pr`, `screen_merge_sha` (the
|
||||
the policy, so a repeat follows the same run and a broad screen never adopts
|
||||
a narrow one. The READY block gains `screen: …`, and the receipt is written
|
||||
locally and to the notes ref. `--dry-run` prints the dispatch without sending
|
||||
it.
|
||||
it. Before dispatching, `ready` fetches the merge ref and requires its second
|
||||
parent to be the PR head: a missing or lagging merge ref (GitHub stops
|
||||
recomputing it while the PR conflicts with the base, CHANGELOG-only conflicts
|
||||
included) is `QUEUE_SCREEN_STALE_REF`, because a screen dispatched there plans
|
||||
no cases and reports success. A finished screen that planned no cases (no
|
||||
`paid-plan` artifact, which a superseded run never uploads, or a plan manifest
|
||||
with nothing planned) is never followed as a screen: `ready --screen`
|
||||
dispatches a fresh one and `train build` prints `SCREEN: #<n> run <id> planned
|
||||
no cases …; counted as not-run` and seats the slot unscreened. A plan the
|
||||
queue cannot read keeps the slot screened, with `SCREEN: #<n> <reason>;
|
||||
counted as screened, unverified`.
|
||||
|
||||
**2. Coordinator: build.** Name the pins exactly as READY reported them:
|
||||
|
||||
@@ -350,8 +442,10 @@ has waited `fill_hours` (`QUEUE_TRAIN_WAITING`); `--urgent` dispatches a
|
||||
one-PR train at once. The same pins on the same base return the live train
|
||||
instead of a second one; `train build --train <nonce>` resumes a train from
|
||||
its record. Refusals, all before any spend: `QUEUE_TRAIN_UNTRUSTED` (a fork
|
||||
head), `QUEUE_TRAIN_MOVED` (a head moved from its pin), `QUEUE_NOT_READY` (no
|
||||
READY receipt at that head), `QUEUE_TRAIN_CONFLICT` (the printed
|
||||
head), `QUEUE_TRAIN_MOVED` (a head moved from its pin),
|
||||
`QUEUE_RECEIPT_MISSING` and `QUEUE_RECEIPT_STALE` (the PR body has no /ship
|
||||
receipt for the pin; see [/ship before READY](#ship-before-ready)),
|
||||
`QUEUE_NOT_READY` (no READY receipt at that head), `QUEUE_TRAIN_CONFLICT` (the printed
|
||||
`QUEUE_TRAIN_CONFLICT: train <id> stopped at #<pr> (conflicts: <files>) after
|
||||
merging #<a>,#<b>; nothing dispatched, no branch pushed; fix: …` line names
|
||||
the build without that PR) and `QUEUE_TRAIN_FREE_RED` (free tests red,
|
||||
@@ -468,7 +562,7 @@ which carry the workflow consequences the skill prose would otherwise spell out:
|
||||
| Line | Effect |
|
||||
|---|---|
|
||||
| `ship: history merge-only: …` | never rebase, squash or force-push; `check-history --plan <merge\|squash\|rebase>` refuses a rewrite plan (`HISTORY_POLICY_VIOLATION`, exit 3) |
|
||||
| `ship: queue_mode stamp-at-merge: …` | Step 12 items 1–4 are replaced by `gstack-restamp --next --base <base> --pr <n> --expect-base <sha>` when the PR is next, `--after <predecessor-pr>` when it is not; the restamp's own `next:` line says what follows (NEW_VERSION, CHANGELOG entry text only, the merge's expected head) |
|
||||
| `ship: queue_mode stamp-at-merge: …` | Step 12 items 1–4 are replaced by `gstack-restamp --next --base <base> --pr <n> --expect-base <sha>` when the PR is next, `--after <predecessor-pr>` when it is not; the restamp's own `next:` line says what follows: under `stamp-at-merge`, `commit and push only the stamp diff above (FILES), with no edit of your own` and the merge's expected head (the stamp commit holds restamp's output alone); under the per-PR queue, NEW_VERSION and the CHANGELOG entry text |
|
||||
| `ship: shared merge queue: …` | in a coordinator-run queue, `gstack-ship-queue ready`, `gate-ahead` and `stamp` replace the restamp calls; see [Run a shared merge queue](#run-a-shared-merge-queue) |
|
||||
| `ship: gate-ahead: …` | with `.gstack/tmp/gate-ahead.json` present, Step 16 reuses only the lanes `gstack-tree-receipt … --json > .gstack/tmp/tree-receipt.json` marks reusable; stamp-sensitive and not-eligible lanes run live; `--force-gate <reason>` is the coordinator's call |
|
||||
| `ship: bump patch: …` | Step 12 never asks for MINOR or MAJOR; PATCH or MICRO by size |
|
||||
@@ -517,7 +611,7 @@ Stages, each journaled in `.gstack/tmp/restamp-journal.json`:
|
||||
0. **restamp_command** (only when the policy sets it): the repo's own restamp, see [restamp_command](#restamp-command).
|
||||
1. **version**: `gstack-version-bump write --version <X>` in the staging worktree (VERSION, the manifest, npm lockfiles); skipped when `restamp_command` owns the version.
|
||||
2. **release_tool**: the policy command, under the contract below.
|
||||
3. **changelog**: the PR's `[Unreleased]` or old-version section moves to the top under `<X>`, above its predecessor's entry. Duplicate headings (`CHANGELOG_DUPLICATE_HEADING`) and a top entry that is not `<X>` (`CHANGELOG_TOP_MISMATCH`) are errors.
|
||||
3. **changelog**: the PR's `[Unreleased]` or old-version section moves to the top under `<X>`, above its predecessor's entry. A duplicate heading the PR introduces (`CHANGELOG_DUPLICATE_HEADING`: the headings are counted against the base's CHANGELOG at `origin/<base>` and at the fork point, so a version the base already heads twice passes as long as the PR adds no further copy) and a top entry that is not `<X>` (`CHANGELOG_TOP_MISMATCH`) are errors; the section being re-headed itself may exist only once.
|
||||
4. **stamp_paths**: exact `(was X)`, `since: X` and `vX` rewrites, plus every `stamp_patterns` template, within `stamp_paths` and `stamp_files`. `X` is the branch's own tentative stamp (its VERSION when it moved past base), else `--was <X>`, else the version an unstamped branch would have taken next (`base + bump`); mentions of a version the base never released (not a CHANGELOG heading at base, not a tag) are rewritten too, so a queue reorder that moves the base under a branch still finds its `since:` fields. Only the repo's own version width counts, so a 3-digit dependency pin in a 4-digit repo is never a candidate.
|
||||
5. **mirror**: tier 2; declared and validated, reported as not run.
|
||||
6. **apply**: every changed file is written to the branch only after all stages validated, each against the fingerprinted preimage; a file edited meanwhile is `RESTAMP_CONFLICT` and the applied writes roll back (rollback refuses to overwrite a file whose bytes differ from the recorded postimage).
|
||||
|
||||
+6
-2
@@ -462,7 +462,7 @@ You know the feeling. You have a feature, a page, a landing screen... and you're
|
||||
3. It generates 3 distinct design variants as PNGs
|
||||
4. A comparison board opens in your browser with all 3 side-by-side
|
||||
5. You click "Approve" on the one you like, or give feedback for another round
|
||||
6. The approved variant saves to `~/.gstack/projects/$SLUG/designs/` with an `approved.json`
|
||||
6. The approved variant saves to `~/.gstack/projects/$SLUG/designs/` with an `approved.json`; for a landing, pricing or campaign screen, it also records the page archetype you picked (`classic-hero`, `long-form-story`, `minimal-conversion` or `comparison`) and its section outline
|
||||
|
||||
That `approved.json` is one way to feed `/design-html`. The design pipeline chains: shotgun picks the direction, design-html renders it as working code. But `/design-html` also works with CEO plans, design reviews, or just a description.
|
||||
|
||||
@@ -512,7 +512,7 @@ Not every page needs the full Pretext engine. The skill reads the design and pic
|
||||
|
||||
### The refinement loop
|
||||
|
||||
1. Reads the approved mockup from `approved.json`
|
||||
1. Reads the approved mockup from `approved.json`, plus any page archetype and section outline you picked on the board, which it builds as given instead of asking again
|
||||
2. Uses GPT-4o vision to extract implementation spec (colors, typography, layout)
|
||||
3. Generates self-contained HTML with Pretext inlined (15KB, zero network dependency)
|
||||
4. Spins up a live-reload server so you see changes instantly
|
||||
@@ -1236,6 +1236,10 @@ Outside-review routing follows the harness, independently of the configured mode
|
||||
|
||||
`/claude` was renamed to `/claude-code` without an alias. Run `./setup --host <name>` to migrate managed installations, including installations sharing that checkout. Setup retains a working old installation when replacement generation or installation fails.
|
||||
|
||||
## Proactive skill suggestions
|
||||
|
||||
gstack can invoke a skill for you when your request matches one. By default (`proactive: core`) only `/office-hours`, `/review`, `/qa` and `/ship` auto-invoke; `all` frees every skill and `off` suggests nothing. See [docs/proactive.md](proactive.md).
|
||||
|
||||
## Safety & Guardrails
|
||||
|
||||
Four skills that add safety rails to any Claude Code session. They work via Claude Code's PreToolUse hooks — transparent, session-scoped, no configuration required.
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# Standalone binaries
|
||||
|
||||
Beyond the slash-command skills, gstack ships standalone CLIs for workflows that don't belong inside a session:
|
||||
|
||||
| Command | What it does |
|
||||
|---------|-------------|
|
||||
| `gstack-model-benchmark` | **Cross-model benchmark** — run the same prompt through Claude, GPT (via Codex CLI), and Gemini; compare latency, tokens, cost, and (optionally) LLM-judge quality score. Auth detected per provider, unavailable providers skip cleanly. Output as table, JSON, or markdown. `--dry-run` validates flags + auth without spending API calls. |
|
||||
| `gstack-taste-update` | **Design taste learning** — writes approvals and rejections from `/design-shotgun` into a persistent per-project taste profile. Decays 5%/week. Feeds back into future variant generation so the system learns what you actually pick. |
|
||||
| `gstack-egress` | **Egress receipt auditor** — every gstack-initiated off-machine send writes a tamper-evident, hash-chained receipt to `~/.gstack/security/egress.jsonl` before the send. `list` shows what gstack attempted to send and to which host, `grants` shows the standing consent settings plus the exact command that revokes each, `verify` recomputes the hash chain and exits 3 on tamper (catches edits, reordering, and mid-chain deletion; truncating or deleting the ledger itself is out of scope — it's a forensic log, not tamper-proof storage). |
|
||||
| `gstack-context-bill` | **Token bill-of-materials** — read-only, offline audit of what an installed skills tree costs in tokens: always-on frontmatter every session pays vs per-invocation SKILL.md + forced references. `--diff` compares two trees, `--budget` enforces a ceiling, `--exact` opts into Anthropic `count_tokens` (sends the text of the billed `.md` files inside the tree, the linked gstack checkout or gstack's render folder off-machine, names and estimates anything else a symlink reaches, labels a part-estimated bill `mixed`; writes an egress receipt first, degrades to the offline estimate if the receipt can't be written). |
|
||||
| `gstack-code-intelligence` | **Code-intelligence provider picker** — wraps GBrain, Sourcebot, and Graphify behind one interface: `options`/`status` to see what's available, `select` to pick one, `index`/`search` to use it, `suggest` to check whether the one-time indexing offer should fire here. The offer triggers on large repos (1,000+ tracked files; a decline is persisted). Non-local providers refuse to index *or search* until you record per-repo consent (`consent <repo> yes\|no` — the query text is repo-derived content), the per-repo trust policy's deny and read-only tiers veto write-class operations regardless of consent, and every off-machine send writes an egress receipt. Fully optional — with nothing selected, gstack falls back to grep. |
|
||||
| `gstack-verify-gate` | **Verification stop hook (opt-in)** — blocks a Claude Code turn from ending until the project's declared verify command passes (after 3 blocked re-entries it yields with a loud still-RED warning instead of looping forever). Declare it on one line in CLAUDE.md: `<!-- gstack:verify: bun test -->`. Hooks bypass the permission system, so a declared command never runs until you trust it once per repo (`gstack-verify-gate --trust`); editing the command invalidates trust until re-granted, and every grant is audit-logged. `./setup` never registers it for you — opt in with `gstack-settings-hook add-event --event Stop --command ~/.claude/skills/gstack/bin/gstack-verify-gate --source verify-gate`, remove with `gstack-settings-hook remove-source --source verify-gate`. |
|
||||
| `gstack-memorable` | **Memorable recall bridge (opt-in, third party, Claude Code only)** — connects Claude Code to the external [Memorable](https://memorable.sh) CLI *through gstack* instead of the vendor's own installer, so the hook gets gstack's guarantees: an explicit consent key (`memorable_recall`, off by default, listed by `gstack-egress grants`), a fail-closed egress receipt for every prompt handed over (`gstack-egress list --sink memorable-recall`), a HIGH-tier secret pre-scan, a trust envelope and 8 KiB cap on whatever comes back, an allowlisted environment and process-group containment for the vendor process, and clean removal. `enable` registers the hook at the stable install with a 5 s timeout and never runs the vendor's own consent command; `disable` revokes the gate first and removes the entry by identity even after Claude Code strips the tag; `status` is read-only. gstack never installs Memorable, and what its binary sends is the vendor's claim, not gstack's. Not available on Windows yet. [Full guide](memorable-workflow-memory.md). |
|
||||
| `gstack-wtree` | **Working-tree fingerprint** — prints a content hash of what's actually on disk (temp index seeded from the stat cache, ~40x cheaper than a full re-hash; untracked source counts, gitignored scratch doesn't). Identical content fingerprints identically through commits, rebases, amends, and squashes — it's what binds reviews and test evidence to content instead of commit SHAs. |
|
||||
| `gstack-review-log` | **Review-pass receipts** — `--start <skill>` captures the working-tree fingerprint before a diff review; `'<JSON>' --finish <token>` consumes that single-use, repository/branch/skill-scoped receipt. Binding requires matching start/end content and reviewer-reported `completed:true` and `converged:true`; it is not independent proof that a model read the source. `--check-shared-libs <token>` reads a current finding as JSON on stdin and checks prior Skip decisions against the actual branch, capture and eligible raw source blobs. It returns `reusable:false` when proof is missing or unsafe, including older records without logger-versioned coverage. Final review logging computes shared-code fingerprints and coverage rather than trusting supplied proof. |
|
||||
| `gstack-review-read` | **Review freshness** — emits review records with computed `review_freshness.status` and `reason`: CURRENT, STALE, or UNVERIFIED for diff reviews. `/ship` and `/land-and-deploy` use the same grade; a matching commit alone never certifies a diff review. [Dashboard rules](skills.md#review-readiness-dashboard). |
|
||||
| `gstack-evidence` | **Verification-evidence ledger** — `run --label <lane> -- <cmd>` transparently wraps any test command (the child's exit code always passes through) and records what ran against which working-tree fingerprint; `check` grades each label FRESH/STALE/MISSING with `--expect-cmd`, `--max-age`, and `--allow-paths` binding. /ship and /land-and-deploy cite fresh evidence instead of re-running suites. Per-run logs are 0600, capped at 2MB, pruned after 30 days; the ledger and logs stay machine-local by design. |
|
||||
| `gstack-issue-guard` | **Tracker-text trust envelope** — fetches GitHub issue/PR text (`issue <n>`, `pr-body`, `pr-comments`, or `--stdin`) and wraps it in a labeled envelope so agents treat it as data: injection-shaped lines get labeled even through fullwidth and invisible-character evasion, and forged envelope banners are defused. Every tracker-text ingress in gstack routes through it, enforced by a CI scanner. |
|
||||
| `gstack-ios-qa-daemon` | **iOS QA daemon** — Mac-side broker between an agent and a connected iPhone or iPad over USB CoreDevice. Loopback by default; `--tailnet` opens a Tailscale-facing listener with identity-gated capability tiers. Single-instance via flock on `~/.gstack/ios-qa-daemon.pid`. See [docs/howto-ios-testing-with-gstack.md](howto-ios-testing-with-gstack.md). |
|
||||
| `gstack-ios-qa-mint` | **iOS allowlist manager** — owner-grant CLI for the tailnet allowlist. `grant`/`revoke`/`list` against `~/.gstack/ios-qa-allowlist.json` (mode 0600). Remote agents never auto-allowlist; this is the explicit-intent path. |
|
||||
| `gstack-ios-qa-regen` | **iOS bridge regenerator** — deterministically installs the canonical DebugBridge package, generates typed state accessors, and records the installed gstack version. Safe to rerun after source changes or upgrades. |
|
||||
|
||||
The private paid CSO evaluation producer is a packaging contract, not an ordinary `bun run build` artifact. On macOS or Linux, a release operator compiles `cso-eval-producer` with the documented hardened Bun flags in the same clean build session as `bun run build:cso`, then installs it beside `gstack-cso-launcher`, `gstack-cso-core`, `gstack-cso-watchdog`, and the hidden `.gstack-cso-generation` manifest as one root-owned, nonwritable five-artifact unit. The producer rejects root execution, writable/symlinked/incomplete installations, and unreviewed provider CLI versions; each receipt binds all five artifact hashes, and collection rejects receipts from different unit identities. See the [clean producer procedure](../test/fixtures/cso-eval/README.md#trusted-five-artifact-producer-unit). Paid producer evaluation remains unavailable on Windows because the detached watchdog has no Windows build.
|
||||
|
||||
Each producer gets a curated one-cell source copy with directories sealed to `0555` and files to `0444`, plus pre/post content, Git, and mode checks. Claude receives no source add-directory, because an add-directory is a sandbox write root where Claude Code's bubblewrap leaves an empty `.mcp.json` mount point. Its Bash runs inside Claude Code's sandbox with one exception: the exact absolute launcher path that `--allowed-tools` names, which runs outside it as it does for users. The launcher is the boundary there. It replaces its environment with a fixed allowlist, uses only the local Docker Unix socket, and runs every target in hardened containers. The producer passes that policy through `--settings` (sandbox enabled, fail if unavailable, no unsandboxed fallback, no auto-allow, and `excludedCommands` naming only the launcher). It refuses to start the agent (`CLAUDE_PRODUCER_SANDBOX_POLICY_MISMATCH`) unless the generated arguments and environment carry exactly that policy. `CLAUDE_CODE_SUBPROCESS_ENV_SCRUB` stays unset, because it would force the launcher back into the sandbox. Every other Bash command is denied by `--allowed-tools`, and Write is confined to the provider work directory and `cso-home`. Gemini receives no source working directory or include-directory. Codex technically receives read-only filesystem access to the exact curated source root because the trusted helper inherits the Codex permission profile; its private provider work directory and exact `cso-home` artifact directory are the only write roots, every other root path remains denied, and the producer prompt requires source access through the helper. This is evaluation containment for an immutable public fixture, not a claim that Codex cannot directly read that fixture.
|
||||
|
||||
Copy each completed cell's receipt together with `state/cso-home/security/cso/` to the trusted adjudication host. Receipt entries are sorted paths relative to that directory and bind every retained file's size and SHA-256 plus an aggregate inventory hash. Provider homes, settings, sessions, and credentials live under separate disposable directories and are removed after the cell; they are never part of the retained artifact tree. The adjudicator must re-hash the transported tree against the receipt before trusting reports or repair bundles.
|
||||
|
||||
Paid producer qualification uses this explicit host matrix:
|
||||
|
||||
| Producer host | Daily/static cells | Comprehensive target execution |
|
||||
|---|---|---|
|
||||
| Codex CLI 0.153.4 | Supported under the custom permission profile | Fails closed because Docker/socket access is not granted to the model command sandbox; report the setup gap as partial |
|
||||
| Claude Code 2.1.263 | Supported under restricted safe mode | Fails closed when the restricted launcher child cannot reach Docker; report the setup gap as partial |
|
||||
| Gemini CLI 0.59.0 | Supported with isolated home/settings | Private release qualification host: the exact `run_shell_command(<launcher>)` policy can run the trusted helper without exposing a general shell |
|
||||
|
||||
Every setup-blocked comprehensive cell remains a miss in release-gate denominators. The evaluator does not silently count a host or workflow as supported when its containment policy prevents required setup.
|
||||
+7
-3
@@ -166,14 +166,18 @@ option and never writes there.
|
||||
A child shell's `export` cannot configure the parent's later commands
|
||||
(`lib/state-root.ts` reads each process's own environment), so the installer
|
||||
ends with one machine-readable line the parent supplies to every gstack
|
||||
command it launches:
|
||||
command it launches, and the same assignments as a sourceable file:
|
||||
|
||||
```
|
||||
GSTACK_LAUNCH: GSTACK_SESSION_KIND=unattended GSTACK_STATE_ROOT=<path> GSTACK_EPHEMERAL=1
|
||||
GSTACK_LAUNCH_FILE: <verdict file without .json>.env
|
||||
```
|
||||
|
||||
Prefix each command with those assignments (or export them once in the shell
|
||||
that runs them). See [docs/capy-install.md](capy-install.md).
|
||||
Apply them with `. <that file>` at the start of the shell that runs each
|
||||
command; never re-type them as `export $(...)`, and never run `env`, `printenv`,
|
||||
a bare `set` or a bare `export` in a tool fence (a bare `export` prints every
|
||||
variable, credentials included). See
|
||||
[docs/capy-install.md](capy-install.md#the-launch-line).
|
||||
|
||||
## Uninstall
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
# Issue triage
|
||||
|
||||
How gstack issues move between open, parked and closed, and what each state
|
||||
promises the reporter. The rule exists because a closed issue reads as "fixed"
|
||||
or "rejected" to the person who filed it. On 2026-10-11 a triage pass closed 75
|
||||
feature requests into TODOS.md themes with a "not rejected" note, and reporters
|
||||
still saw a closed issue with no fix. Those were reopened on 2026-10-25 under
|
||||
the `parked` label, and this page is the rule going forward.
|
||||
|
||||
## The three states
|
||||
|
||||
| State | Meaning | How it looks on GitHub |
|
||||
|-------|---------|------------------------|
|
||||
| **Open** | We intend to act on it and nothing is decided yet, or a lane is on it now. | Open, no `parked` label. `next-wave` marks issues accepted for the next fix wave. |
|
||||
| **Parked** | We intend to fix it later, not now. | Open, labelled `parked`, with one comment that says where it is tracked and what fixes it. |
|
||||
| **Closed** | Fixed, duplicate, or won't fix. Nothing else. | Closed with the matching reason (below). |
|
||||
|
||||
An issue we still intend to fix is never closed. Grouping it into a theme,
|
||||
folding it into a tracking issue, or deciding it waits for other work are all
|
||||
"parked", not "closed".
|
||||
|
||||
## Parking an issue
|
||||
|
||||
1. Add the `parked` label.
|
||||
2. Post one comment with two facts: **where it is tracked** (the TODOS.md
|
||||
theme, by link, or the tracking issue such as #3137, #3138 or #3139) and
|
||||
**what fixes it** (the work that, when it ships, closes this issue). One or
|
||||
two sentences. No other comment is needed.
|
||||
3. Add the issue number to the TODOS.md entry so the theme credits the reporter
|
||||
when it is picked up.
|
||||
|
||||
When the work ships, close the issue as fixed with the version or PR. Whatever
|
||||
the close reason, remove the `parked` label when the issue closes
|
||||
(`gh issue edit <n> --remove-label parked`), so a `label:parked` search lists
|
||||
only open parked issues. GitHub leaves labels alone when a merged PR's
|
||||
`Closes #<n>` closes the issue, so the lane that ships the fix removes the label
|
||||
itself. When a lane picks a parked issue up, it removes the label while it works
|
||||
on it (the issue is open again, with the lane on it); an issue the lane later
|
||||
drops goes back to parked with an updated comment.
|
||||
|
||||
## Closing an issue
|
||||
|
||||
Each close names its reason in the closing comment. GitHub's close reason and
|
||||
the labels match:
|
||||
|
||||
| Reason | Comment says | Close reason | Label |
|
||||
|--------|--------------|--------------|-------|
|
||||
| **Fixed** | the version, commit or PR that fixed it, and what changed | completed | — |
|
||||
| **Duplicate** | the issue that tracks it (and that issue is open or parked; if it was already fixed, close this one as fixed too, with the same version) | duplicate (`gh issue close <n> --duplicate-of <m>`) | `duplicate` |
|
||||
| **Won't fix** | why: out of scope, a different design was chosen, the premise no longer holds | not planned | `wontfix` |
|
||||
|
||||
`gh issue close` sets only the close reason; add the `duplicate` or `wontfix`
|
||||
label separately with `gh issue edit <n> --add-label <label>`.
|
||||
|
||||
A duplicate of a parked issue is still a duplicate: close it with the link,
|
||||
and the parked issue's comment or TODOS.md entry credits both reporters. A
|
||||
report that no longer reproduces on current main closes as fixed when a commit,
|
||||
PR or version explains why (cite it, as `/investigate --backlog` does with
|
||||
`already_fixed <commit>`); when nothing does, it closes as won't fix with the
|
||||
version it was checked against and an invitation to reopen with a repro.
|
||||
|
||||
## Labels this page uses
|
||||
|
||||
- `parked`: not fixed yet, not rejected; stays open, with a comment saying
|
||||
where it is tracked and what fixes it.
|
||||
- `tracking`: a tracking issue for a theme; stays open until the theme ships.
|
||||
- `next-wave`: accepted for the next fix wave.
|
||||
- `duplicate`, `wontfix`: the two non-fix close reasons.
|
||||
- `needs Windows verification`: a merged fix awaiting confirmation on real
|
||||
Windows hardware; the issue stays open until then.
|
||||
|
||||
## Where the themes live
|
||||
|
||||
Parked feature requests are grouped in TODOS.md under "feature themes from the
|
||||
Wave 9 triage": new hosts (#3137), provider pluralism (#3138), install
|
||||
footprint and layout (#3139), new skills, ship/review/safety, planning reviews,
|
||||
state root and project identity, browser and mobile QA, prompt cost and skill
|
||||
shape, customization and localization, retro, and CI supply chain. Parked bugs
|
||||
sit under "parked in Wave 9" in the same file. Pick a theme up as a whole and
|
||||
credit every linked reporter in the CHANGELOG entry.
|
||||
|
||||
Community pull requests follow the wave process in
|
||||
[CONTRIBUTING.md](../CONTRIBUTING.md#community-pr-triage-wave-process); a
|
||||
closed PR gets the same courtesy as a closed issue: a comment saying why and
|
||||
what supersedes it.
|
||||
+191
-6
@@ -1470,6 +1470,20 @@ or nvm), then re-run `bin/gstack-capy-install`. A checksum mismatch means the
|
||||
upstream installer changed: update `GSTACK_BUN_INSTALL_SHA` only after
|
||||
reviewing the new script.
|
||||
|
||||
<a id="capy-install-launch-file-unwritable"></a>
|
||||
### `gstack: fail launch-file — could not write the launch file <path>; fix: ... (LAUNCH_FILE_UNWRITABLE)`
|
||||
|
||||
**Meaning.** The doctor passed, but `bin/gstack-capy-install` could not write
|
||||
the launch file (`GSTACK_LAUNCH_FILE`, the sourceable mode-0600 copy of the
|
||||
`GSTACK_LAUNCH` assignments beside the install verdict). The file is part of the
|
||||
ok verdict: without it a caller would be left with only the line and the
|
||||
re-typing path that leaked an environment in GSTA-96, so the run fails instead.
|
||||
The directory under the state root is unwritable, or a temp file could not be
|
||||
moved into place.
|
||||
|
||||
**Fix.** Make the verdict directory writable, or set `GSTACK_STATE_ROOT` to a
|
||||
writable directory, then re-run `bin/gstack-capy-install`.
|
||||
|
||||
<a id="upgrade-refused"></a>
|
||||
### `gstack-upgrade: <cause>; nothing was changed; fix: <command> (UPGRADE_REFUSED)`
|
||||
|
||||
@@ -1604,8 +1618,9 @@ message lists.
|
||||
|
||||
**Meaning.** On a Capy machine (`GSTACK_CAPY=1`, set by the `GSTACK_LAUNCH`
|
||||
line), an unattended or spawned workflow starts only with a passing install
|
||||
verdict (`<state root>/install-verdicts/<sha256 of the checkout path>.json`,
|
||||
named as `GSTACK_INSTALL_VERDICT`) for this checkout, its current HEAD and a
|
||||
verdict (`<state root>/install-verdicts/<sha256 of the checkout path>/<sha256
|
||||
of the project directory, or none>.json`, named as `GSTACK_INSTALL_VERDICT`)
|
||||
for this checkout, its current HEAD and a
|
||||
workflow at least as wide as the one starting: a verdict for `--for autoplan`
|
||||
does not cover `qa`'s browser, and a planning-only pins SKIP does not cover a
|
||||
workflow that runs the repo's tests. `gstack-skill-start` printed this row and
|
||||
@@ -1613,8 +1628,12 @@ workflow that runs the repo's tests. `gstack-skill-start` printed this row and
|
||||
an unattended run whose Bun on PATH is below the supported minimum
|
||||
(`INSTALL: fail runtime ... (RUNTIME_BELOW_MINIMUM)`) on any host.
|
||||
|
||||
**Fix.** Run the Capy skill installer with `--for <workflow>` on this machine
|
||||
and launch with the `GSTACK_LAUNCH` line it prints.
|
||||
**Fix.** Run the Capy skill installer (`scripts/install.sh --for <workflow>`)
|
||||
from the project directory the workflow will run in, since the verdict is
|
||||
keyed by that directory (GSTA-85), and launch with the `GSTACK_LAUNCH` line it
|
||||
prints. A verdict file that exists but cannot be read (`the install verdict at
|
||||
<file> is unreadable or has an unsupported schema_version`) is repaired the
|
||||
same way, by rerunning the installer.
|
||||
`GSTACK_ALLOW_UNSUPPORTED_RUNTIME=1` admits anyway (printed as
|
||||
`INSTALL_VERDICT: overridden`); record it as a deviation.
|
||||
|
||||
@@ -2239,7 +2258,11 @@ entry that no longer applies; rotate a credential that is real.
|
||||
<a id="redact-version-as-ip"></a>
|
||||
### `pii.ip_public` MEDIUM on a four-part version number
|
||||
|
||||
**Meaning.** The line does not declare the value as a version.
|
||||
**Meaning.** The line does not declare the value as a version. When the
|
||||
text goes through `gstack-post` from inside the repository, four-part versions
|
||||
equal to the root `VERSION` or to a `## [x.y.z.w]` / `## vx.y.z.w` CHANGELOG.md heading are
|
||||
exempt wherever they appear, so there the finding means the quad is not a
|
||||
release of this repository (or the post ran outside the checkout).
|
||||
|
||||
**Fix.** Write it as `version: 1.2.3.4`, `"version": "..."` or `v1.2.3.4`.
|
||||
MEDIUM findings do not block pushes.
|
||||
@@ -2615,8 +2638,22 @@ read the receipt again.
|
||||
|
||||
**Meaning.** Two sections share one version heading, usually a stamp that
|
||||
re-headed `[Unreleased]` beside an entry that already carried the version.
|
||||
In `gstack-restamp` only duplicates the PR introduces count: the headings are
|
||||
compared against the base branch's CHANGELOG as read at `origin/<base>` and at
|
||||
the commit the branch's own lines are counted from (its fork point, or the
|
||||
predecessor's head in a gate-ahead stamp); the larger count per heading wins,
|
||||
so a version the base already heads twice (released history the PR inherited
|
||||
and must keep) passes as long as the PR adds no further copy of it. When a
|
||||
reading is unavailable (the PR adds the CHANGELOG itself, or the file moved),
|
||||
the stage detail and the refusal name it: with no reading left every duplicate
|
||||
counts, otherwise inherited headings are counted at the readable commit only. Two paths keep the strict rule: the section being re-headed itself
|
||||
(`[Unreleased]`, or the branch's earlier stamp) may exist only once, and
|
||||
`gstack-version-bump write --changelog` (the claim-at-open bump) compares the
|
||||
file with itself.
|
||||
|
||||
**Fix.** Merge them into one entry under the new version.
|
||||
**Fix.** Merge the duplicate sections the PR introduced into one entry under
|
||||
the new version. A duplicate that is on the base branch itself is not this
|
||||
PR's to fix in a restamp; leave it and the stamp proceeds.
|
||||
|
||||
<a id="changelog-top-mismatch"></a>
|
||||
### `CHANGELOG_TOP_MISMATCH`
|
||||
@@ -2994,6 +3031,20 @@ test. An unmapped source or code file still fails. A file under a test
|
||||
directory is not this empty lane. Images, fonts and media, and
|
||||
allowlisted tool config no test names, are the assets-and-tool-config empty lane,
|
||||
not this error.
|
||||
A release manifest the branch only re-stamped is not this error either: when a
|
||||
touched `*.json`, `*.plist`, `VERSION` or `VERSION.txt` file differs from the
|
||||
merge base in nothing but its version values, `lanes` waives it as
|
||||
version-stamp-only and prints `version-stamp-only (no lane needed): <files>`.
|
||||
The exact per-kind rule (the document's top-level `version` in JSON of the
|
||||
same shape and text, the two bundle version keys in a plist, one bare version
|
||||
in a VERSION file) is in [docs/pregate.md](pregate.md#gstack-pregate). A
|
||||
restamp PR on a base whose policy declares no lane for its manifests (gbrain's
|
||||
`release:restamp` and its plugin manifests) therefore passes without a
|
||||
policy change. A manifest whose description, keys, key order, nested
|
||||
versions, line endings or any non-version byte changed, one under a test,
|
||||
fixture, golden, `.gstack/` or workflow tree, or one added, deleted, renamed
|
||||
or re-moded, is still this error; `--explain` prints `not version-stamp-only (<reason>)`
|
||||
for it.
|
||||
|
||||
**Fix.** Add a test that names the file, or declare its lane (an explicit `[]`
|
||||
for files with no behaviour) under `dependencies` in `.gstack/pregate.json` (or the user-owned file the message names).
|
||||
@@ -4021,6 +4072,74 @@ trains and other trains' merges are refused until a repair lands.
|
||||
<n>@<sha>`). The freeze clears when verify finds main's tree after that merge
|
||||
in a green record; `NEXT:` then builds a train of the PRs still unmerged.
|
||||
|
||||
<a id="queue-receipt-missing"></a>
|
||||
### `QUEUE_RECEIPT_MISSING`
|
||||
|
||||
**Meaning.** The PR body carries no valid ```gstack-ship-receipt``` block, so
|
||||
/ship never ran on this branch (or its receipt was lost when the body was
|
||||
rewritten). `gstack-ship-queue ready` refuses to READY it, `train build`
|
||||
refuses the pin by PR number before any write, push or dispatch (dry run
|
||||
included), and `verify --train` refuses the slot. The block still prints the
|
||||
informational `SHIP_RECEIPT: none in the PR body` (or `invalid`) line. There
|
||||
is no flag that skips this check (Garry, 2026-10-25: use the gstack ship skill
|
||||
before merging PRs; [ship-policy.md](ship-policy.md#ship-before-ready)).
|
||||
|
||||
**Fix.** Run /ship on the branch. Unattended, it ends by writing the receipt
|
||||
into the PR body (`gstack-ship-receipt write --pr <n> … --body <file>`, then
|
||||
`gstack-post pr-body`) when an open PR exists for the branch at HEAD;
|
||||
otherwise it leaves the receipt in `.gstack/tmp/ship-receipt.md` and the
|
||||
parent posts it ([unattended.md](unattended.md)). Then rerun
|
||||
`gstack-ship-queue ready --pr <n> --screen|--no-screen` and send the new READY
|
||||
block.
|
||||
|
||||
<a id="queue-receipt-stale"></a>
|
||||
### `QUEUE_RECEIPT_STALE`
|
||||
|
||||
**Meaning.** The PR body's ship receipt is valid but names another head than
|
||||
the one being READY'd or merged (a push after /ship, a receipt copied from
|
||||
another PR, or a `pr` field for another PR), so the receipt does not cover
|
||||
what would merge. The one accepted difference is a train slot's stamp: when
|
||||
the current head is the pinned head's stamp commit (same tree modulo stamp
|
||||
files, or the pin with main merged in at slot 2 and later), the receipt
|
||||
written for the pinned head is the binding one and `verify --train` accepts it.
|
||||
|
||||
**Fix.** Rerun /ship on the current head so the receipt names it, then rerun
|
||||
`gstack-ship-queue ready --pr <n> --screen|--no-screen`. A lane never stamps
|
||||
to fix this: the stamp comes only when the slot is called.
|
||||
|
||||
<a id="queue-screen-stale-ref"></a>
|
||||
### `QUEUE_SCREEN_STALE_REF`
|
||||
|
||||
**Meaning.** `gstack-ship-queue ready --screen` dispatches the screen against
|
||||
GitHub's `refs/pull/<n>/merge`, and that ref is missing or still merges an
|
||||
older head than the PR's pushed head. GitHub stops recomputing the merge ref
|
||||
while the PR conflicts with the base (a CHANGELOG-only conflict after a stamp
|
||||
counts), so a screen dispatched there checks out the old merge, sees itself
|
||||
superseded, plans 0 cases and reports success. GSTA-95 hit this on #3431:
|
||||
`ready` printed `SCREEN_DISPATCH:` as if a screen ran. `ready` now compares
|
||||
the merge ref's second parent with the PR head before dispatching, and a
|
||||
finished screen that planned no cases is never adopted as a screen. A screen
|
||||
run uploads no train record, so the evidence is its plan: a superseded run
|
||||
skips `plan-slices` and uploads no `paid-plan` artifact, and a manifest with
|
||||
nothing planned counts the same. `ready --screen` dispatches a fresh one and
|
||||
`train build` prints `SCREEN: #<n> run <id> planned no cases (no paid-plan
|
||||
artifact; …)` or `… planned no cases (an empty plan manifest; …)`, each
|
||||
ending `; counted as not-run, the slot rides unscreened`. A plan it cannot
|
||||
read (`… paid-plan artifact has expired`, `… manifest.json is unreadable`, an
|
||||
unreadable run or artifact list, an unreadable repository slug, or a READY
|
||||
whose run id is not a number) keeps the slot screened and prints
|
||||
`SCREEN: #<n> <reason>; counted as screened, unverified`; `ready --screen`
|
||||
follows such a run. When `ready` cannot list the remote's refs or fetch the
|
||||
merge ref at all, it refuses with the same code and a `could not list …` or
|
||||
`could not fetch …` message whose NEXT line says to check the remote, not to
|
||||
resolve a conflict.
|
||||
|
||||
**Fix.** Resolve the conflict with the base branch (or wait for GitHub to
|
||||
recompute the merge ref), push, and run `gstack-ship-queue ready --pr <n>
|
||||
--screen` again. Under crew rule 14 a CHANGELOG-only conflict is left to the
|
||||
train, so a lane that cannot get a fresh merge ref READYs `--no-screen` only
|
||||
after its own focused paid cases passed (rule 6).
|
||||
|
||||
<a id="ready-screen-choice-required"></a>
|
||||
### `READY_SCREEN_CHOICE_REQUIRED`
|
||||
|
||||
@@ -4218,3 +4337,69 @@ every flag and code.
|
||||
**Meaning.** Unexpected helper failure; the line names the error class and its first stack frame. Exit 1.
|
||||
|
||||
**Fix.** Rerun once; if it repeats, report the printed frame to the gstack maintainers.
|
||||
|
||||
## Quick answers
|
||||
|
||||
The short troubleshooting entries that used to live in the README. Each names the symptom and the fix; the sections above cover every gate message in depth.
|
||||
|
||||
**Skill not showing up?** Run `./setup --status` from your gstack checkout. It
|
||||
prints every install (host, scope, version, skills directory, source checkout)
|
||||
and, for a `stale`, `missing` or `unregistered` row, the exact command that fixes
|
||||
it. The usual fix is to re-run setup from that row's source for that host, e.g.
|
||||
`cd ~/.claude/skills/gstack && ./setup` (Claude) or `cd ~/gstack && ./setup --host codex`.
|
||||
A project install lives in the project's `.claude/skills/gstack` or
|
||||
`.agents/skills/gstack`; run its `setup` from inside the project.
|
||||
|
||||
**Cursor freezes on load, or an agent's gstack skills went missing after an upgrade?** Each checkout now renders skills only for the agents installed from it (#1694). `./setup --status` lists them; `./setup --host <name>` adds one back. Pruned copies are in `~/.gstack/backups/host-renders/<time>-<id>/`, with every path in its `prune.log` ([host renders pruned](#host-renders-pruned)).
|
||||
|
||||
**`/browse` (or `/qa`, `/design-review`) says `NEEDS_ASIDE` or `ASIDE_NOT_RUNNING`?** That's the probe telling you it's about to use the fallback browser. Want Aside? Open the app and sign in — `aside --version` should print a version and `aside repl 'console.log("ok")'` should print `ok` — then re-run. gstack never installs it for you. Want the fallback on purpose while Aside is open? `GSTACK_SKIP_ASIDE=1` makes every skill, the renderer, and `./setup` treat Aside as absent. When Aside is absent the probe prints `NEEDS_ASIDE: <OS>` and skills trust that line for the macOS-only download pitch; `GSTACK_PLATFORM` overrides the OS it names, for tests and unusual hosts (set it in your shell — gstack never reads it from a project `.env`).
|
||||
|
||||
**`/browse` fails on the fallback browser?** `cd ~/.claude/skills/gstack && bun install --production --frozen-lockfile && bun run build`
|
||||
|
||||
**`/make-pdf` or `/diagram` can't render?** Same two paths: with Aside open they print through Aside (`bun run ~/.claude/skills/gstack/bin/gstack-render.ts some.html --screenshot /tmp/out.png` tests it directly, and its first line, `ENGINE=aside` or `ENGINE=browse`, names the browser that actually rendered); without it they use the bundled browser, so `bun run build` is the fix.
|
||||
|
||||
**Stale install?** Run `/gstack-upgrade` — or set `auto_upgrade: true` in `~/.gstack/config.yaml`.
|
||||
On Codex and the other non-Claude hosts, upgrade from a terminal: find the
|
||||
checkout in the `source` column of `./setup --status`, then
|
||||
`cd <source> && git pull && ./setup --host <host>`, and start a new session.
|
||||
|
||||
**Typing into a specific field with the fallback browser?** `browse type --selector '<css>' <text>`
|
||||
types into that element; bare `browse type <text>` types into whatever has focus.
|
||||
|
||||
**State in the wrong place, or a setting that won't stick?** `~/.claude/skills/gstack/bin/gstack-paths --explain` shows which directory gstack uses for its state and why. See [docs/state-root.md](state-root.md).
|
||||
|
||||
**Want shorter commands?** `cd ~/.claude/skills/gstack && ./setup --no-prefix` — switches from `/gstack-qa` to `/qa`. Your choice is remembered for future upgrades.
|
||||
|
||||
**Don't use some skills?** `gstack-config set disabled_skills make-pdf,pair-agent` stops registering them on every host, so their descriptions stop loading into each session. Claude Code updates right away; other hosts on their next `./setup --host <name>` or `/gstack-upgrade`. Their files stay installed, so a skill that calls a disabled one still works (you get a warning). Typos are rejected with the closest skill name, `gstack-upgrade` can't be disabled, and `gstack-config set disabled_skills ""` re-enables everything.
|
||||
|
||||
**Never use plan mode?** `gstack-config set plan_mode_preamble off` leaves the "Plan Mode Safe Operations" and "Skill Invocation During Plan Mode" sections (about 1.6 KB per skill) out of every installed skill on the next `./setup` run; the command prints the exact `./setup --refresh-registered` line that applies it. Claude Code then serves a render under `~/.gstack/render/claude` instead of the committed files. Commands marked "PLAN MODE EXCEPTION — ALWAYS RUN" keep their marker. `gstack-config set plan_mode_preamble on` (the default) and another `./setup` bring the sections back. It matters for Claude Code, Copilot and OpenCode: hosts without plan mode (Codex and others) never carry these sections.
|
||||
|
||||
**Want namespaced commands?** `cd ~/.claude/skills/gstack && ./setup --prefix` — switches from `/qa` to `/gstack-qa`. Useful if you run other skill packs alongside gstack.
|
||||
|
||||
**Codex says "Skipped loading skill(s) due to invalid SKILL.md"?** Your Codex skill descriptions are stale. `${CODEX_HOME:-~/.codex}/skills/gstack` is a runtime directory, not the checkout: `./setup --status` shows the Codex row's source checkout. Fix: `cd <that source> && git pull && ./setup --host codex` — for a repo-local install, run it from inside the project.
|
||||
|
||||
**Chromium install failed or hung during `./setup`?** The bundled browser is
|
||||
best-effort: setup records the reason, finishes registering every skill, and
|
||||
prints which skills are affected (`/qa`, `/qa-only`, `/design-review`,
|
||||
`/browse`, make-pdf, `/diagram`, `/pair-agent`). With Aside open, the browser
|
||||
skills keep running in Aside and only the fallback engine is missing;
|
||||
`/pair-agent` always needs the bundled browser. Fix the cause and re-run
|
||||
`./setup`. Knobs:
|
||||
`GSTACK_PLAYWRIGHT_INSTALL_TIMEOUT=<seconds>` raises the download bound
|
||||
(default 600) on slow links; `GSTACK_SKIP_PLAYWRIGHT=1` skips the Chromium
|
||||
install entirely (CI, no-browser boxes); `GSTACK_CHROMIUM_NO_SANDBOX=1` is the
|
||||
fix when Chromium installs but cannot launch because the host blocks
|
||||
unprivileged user namespaces (Ubuntu 24.04+ AppArmor default, #2157).
|
||||
|
||||
**Setup ended with "Not registered (a skill you own already uses the name; left untouched)"?**
|
||||
gstack only deletes or links over a skill entry it can prove it created: a
|
||||
symlink into gstack, a directory carrying the `.gstack-owned` marker `./setup`
|
||||
writes into every directory it creates, or a SKILL.md that is byte-identical to
|
||||
gstack's or carries the generated `<!-- AUTO-GENERATED from ... -->` banner. A
|
||||
`qa/` or `ship/` you wrote yourself is left untouched by `./setup`,
|
||||
`gstack-relink`, and both prefix-mode flips, and the linker names it in the
|
||||
final summary. Rename or move yours, or switch modes (`./setup --prefix` /
|
||||
`--no-prefix`) so the names stop colliding. If you started your own skill from
|
||||
a generated gstack SKILL.md and then edited it, that file is moved to
|
||||
`~/.gstack/backups/skills/<timestamp>/<skill>/SKILL.md` before gstack's is
|
||||
linked in, never deleted.
|
||||
+44
-10
@@ -16,8 +16,10 @@ it, so `headless` keeps its BLOCK semantics.
|
||||
|
||||
## What the session kind changes
|
||||
|
||||
`gstack-skill-start` prints `SESSION_KIND: unattended` and
|
||||
`UNATTENDED_SESSION: true`, then applies these rules for the whole skill:
|
||||
`gstack-skill-start` prints `SESSION_KIND: unattended`,
|
||||
`UNATTENDED_SESSION: true` and the `unattended-session` instruction block
|
||||
(`GSTACK_INSTRUCTION_BEGIN: unattended-session <session-id>` through
|
||||
`GSTACK_INSTRUCTION_END`), then applies these rules for the whole skill:
|
||||
|
||||
- **No prompts.** The update check, upgrade offer, telemetry and sync consent,
|
||||
onboarding tips and routing blocks are suppressed exactly as they are for
|
||||
@@ -80,7 +82,36 @@ Not every workflow writes this line or a run directory today:
|
||||
record), not a run directory.
|
||||
- **A refused launch.** Any skill whose install check refuses it at startup
|
||||
prints `GSTACK_RESULT: skill=<name> status=refused run=-` and stops.
|
||||
- **Every other workflow skill** (`/ship`, `/review`, `/qa`, `/design-review`,
|
||||
- **`/ship`** ends with `gstack-ship-receipt result --pr <n>` (or `--body
|
||||
.gstack/tmp/ship-receipt.md` after the handoff section). It binds the
|
||||
receipt to the branch head, writes `<state root>/ship/runs/<utc>-<head12>/`
|
||||
(`ship-run.json` with `head`, `pr`, `base`, `version`, `queue_mode`,
|
||||
`session_kind`, `receipt` as the path of the copied `ship-receipt.md`, and
|
||||
`receipt_source`; it is a /ship record, not the `run` v1 manifest below, so
|
||||
read it directly: `gstack-artifact validate` does not apply) and prints
|
||||
`GSTACK_RESULT: skill=ship status=complete run=<dir>`. When gh cannot read
|
||||
the PR it prints `GSTACK_RESULT: skill=ship status=incomplete run=-` (exit 1). A receipt that is missing or names another head prints
|
||||
`GSTACK_RESULT: skill=ship status=refused run=-` with `QUEUE_RECEIPT_MISSING`
|
||||
or `QUEUE_RECEIPT_STALE` on stderr (exit 3); a run that stops before
|
||||
publication prints the same refused line itself. A parent's merge check is
|
||||
then mechanical: take `run=` from the last result line, read
|
||||
`ship-run.json`, and compare its `head` to the PR head. `gstack-ship-queue ready`,
|
||||
`train build` and `verify --train` make the same comparison from the PR body
|
||||
([ship-policy.md](ship-policy.md#ship-before-ready)), which is why an
|
||||
unattended /ship may update the body of a PR that already exists: when an
|
||||
open PR for the branch exists and HEAD is its pushed head, Step 18 takes the
|
||||
pr-body section's existing-PR path and posts the body (receipt first) and
|
||||
title through `gstack-post`. That is the one publication the unattended
|
||||
rules allow; it never runs `pr-create`, never pushes a commit, never runs
|
||||
`gh pr ready` (the existing-PR path's draft-state items concern an early PR
|
||||
an unattended run never opens), and a branch with no open PR, or whose PR
|
||||
head is not HEAD, takes the handoff section. `result --pr <n>` also refuses
|
||||
`QUEUE_RECEIPT_STALE` when HEAD is not the PR's pushed head.
|
||||
When the run committed anything (a CHANGELOG entry, generated tests), the
|
||||
parent pushes those commits itself, then reruns the receipt write, the body
|
||||
post and `result` at the pushed head: a receipt for an unpushed head is
|
||||
`QUEUE_RECEIPT_STALE` to the queue.
|
||||
- **Every other workflow skill** (`/review`, `/qa`, `/design-review`,
|
||||
`/spec`, `/land-and-deploy` and the rest) gets the session rules above (no
|
||||
prompts, approvals stay pending, nothing published), but has no step that
|
||||
writes a run directory or prints the line, so a parent should not wait for
|
||||
@@ -88,14 +119,15 @@ Not every workflow writes this line or a run directory today:
|
||||
helper writes its `gate.json` yet (`gstack-gate` renders and parses a gate
|
||||
list; it does not create one). `/cso` skips the shared startup, so these
|
||||
session rules do not reach it at all. Read each skill's own record instead:
|
||||
the review-log rows (`gstack-review-read`) for `/review`, the
|
||||
`qa-report-*.md` file for `/qa`, and the ship receipt for `/ship`. When
|
||||
the run did not push (an unattended run must not), read the receipt file in
|
||||
the child's checkout with `gstack-ship-receipt read --body .gstack/tmp/ship-receipt.md --json`;
|
||||
the review-log rows (`gstack-review-read`) for `/review` and the
|
||||
`qa-report-*.md` file for `/qa`. For a `/ship` run from before the result
|
||||
line, read the receipt instead: when the run did not push (an unattended run
|
||||
must not), read the receipt file in the child's checkout with
|
||||
`gstack-ship-receipt read --body .gstack/tmp/ship-receipt.md --json`;
|
||||
use `gstack-ship-receipt read --pr <n> --json` only once a PR exists. In both
|
||||
cases compare the receipt's `head` to the branch SHA (`git rev-parse HEAD` in
|
||||
the child's checkout): `read` does not check that the receipt is current, so
|
||||
an older PR's receipt reads as valid.
|
||||
an older PR's receipt reads as valid; `result` does.
|
||||
|
||||
Every workflow command shares one exit table: `0` ok, `1` fail, `2` usage,
|
||||
`3` refused or needs a flag. Error lines end with a result code in parentheses,
|
||||
@@ -125,8 +157,10 @@ JSON Schema for any of `findings`, `decisions`, `tasks`, `timing`, `run`,
|
||||
|
||||
## How a parent consumes a run
|
||||
|
||||
1. **Decide whether the run finished.** `gstack-artifact validate <dir>/run.json`
|
||||
is the deterministic completion check. It exits `0` and prints
|
||||
1. **Decide whether the run finished.** For an `/autoplan` run directory,
|
||||
`gstack-artifact validate <dir>/run.json`
|
||||
is the deterministic completion check (a `/ship` `run=` directory holds
|
||||
`ship-run.json`, read directly as described above). It exits `0` and prints
|
||||
`ARTIFACT_VALID: run=<id> status=<status>` only when the manifest parses and
|
||||
matches its schema, every required phase and each reviewer voice has a
|
||||
terminal outcome, every listed artifact exists inside the run directory with
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# Uninstall
|
||||
|
||||
## Option 1: Run the uninstall script
|
||||
|
||||
If gstack is installed on your machine:
|
||||
|
||||
```bash
|
||||
~/.claude/skills/gstack/bin/gstack-uninstall
|
||||
```
|
||||
|
||||
This handles skills, symlinks, global state (`~/.gstack/`), project-local state, browse daemons, and temp files. Use `--keep-state` to preserve config and analytics. Use `--force` to skip confirmation.
|
||||
|
||||
## Option 2: Manual removal (no local repo)
|
||||
|
||||
If you don't have the repo cloned (e.g. you installed via a Claude Code paste and later deleted the clone):
|
||||
|
||||
```bash
|
||||
# 1. Stop browse daemons
|
||||
pkill -f "gstack.*browse" 2>/dev/null || true
|
||||
|
||||
# 2. Remove per-skill directories whose SKILL.md points into gstack/
|
||||
# (rm -rf, not rmdir — installed dirs also contain runtime-asset links)
|
||||
find ~/.claude/skills -mindepth 1 -maxdepth 1 -type d ! -name gstack 2>/dev/null |
|
||||
while IFS= read -r dir; do
|
||||
link="$dir/SKILL.md"
|
||||
[ -L "$link" ] || continue
|
||||
target=$(readlink "$link" 2>/dev/null) || continue
|
||||
case "$target" in
|
||||
gstack/*|*/gstack/*)
|
||||
rm -rf "$dir"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
# Directories gstack created carry a .gstack-owned marker (the only signal on
|
||||
# Windows, where installs are file copies with no symlink to read)
|
||||
for marker in ~/.claude/skills/*/.gstack-owned; do
|
||||
[ -f "$marker" ] && rm -rf "$(dirname "$marker")"
|
||||
done
|
||||
# Alias skills install as copies (no symlink to detect) — remove by name
|
||||
rm -rf ~/.claude/skills/_gstack-command ~/.claude/skills/connect-chrome 2>/dev/null
|
||||
|
||||
# 3. Remove gstack
|
||||
rm -rf ~/.claude/skills/gstack
|
||||
|
||||
# 4. Remove global state
|
||||
rm -rf ~/.gstack
|
||||
|
||||
# 5. Remove integrations (skip any you never installed)
|
||||
rm -rf "${CODEX_HOME:-$HOME/.codex}/skills/gstack"* 2>/dev/null
|
||||
rm -rf ~/.factory/skills/gstack* 2>/dev/null
|
||||
rm -rf ~/.kiro/skills/gstack* 2>/dev/null
|
||||
rm -rf ~/.openclaw/skills/gstack* 2>/dev/null
|
||||
rm -rf ~/.cursor/skills/gstack* 2>/dev/null
|
||||
rm -rf ~/.config/opencode/skills/gstack* 2>/dev/null
|
||||
rm -rf ~/.gemini/antigravity-cli/skills/gstack* 2>/dev/null
|
||||
|
||||
# 6. Remove temp files
|
||||
rm -f /tmp/gstack-* 2>/dev/null
|
||||
|
||||
# 7. Per-project cleanup (run from each project root)
|
||||
rm -rf .gstack .gstack-worktrees .claude/skills/gstack 2>/dev/null
|
||||
rm -rf .agents/skills/gstack* .factory/skills/gstack* 2>/dev/null
|
||||
```
|
||||
|
||||
Manual removal leaves gstack's hook entries behind in `~/.claude/settings.json`
|
||||
(the uninstall script removes all of them for you, including entries whose
|
||||
`_gstack_source` tag was stripped). Edit that file and delete every hook whose
|
||||
command path points into `.claude/skills/gstack/`: the SessionStart auto-update
|
||||
hook, the AskUserQuestion PreToolUse/PostToolUse hooks, and the Stop hooks
|
||||
(session timeline, plus verify-gate if you opted in). Left in place, they error
|
||||
on every matching event once the install directory is gone.
|
||||
|
||||
## Clean up CLAUDE.md
|
||||
|
||||
The uninstall script does not edit CLAUDE.md. In each project where gstack was added, remove the `## gstack` and `## Skill routing` sections.
|
||||
|
||||
## Playwright
|
||||
|
||||
`~/Library/Caches/ms-playwright/` (macOS) is left in place because other tools may share it. Remove it if nothing else needs it.
|
||||
|
||||
## Aside
|
||||
|
||||
gstack never installed Aside, so it never uninstalls it. Keep it or remove it like any other app.
|
||||
@@ -0,0 +1,14 @@
|
||||
# Voice input (AquaVoice, Whisper, etc.)
|
||||
|
||||
Many gstack skills list speech-to-text aliases (`voice-triggers:` in their
|
||||
templates): "security check" for `/cso`, "run QA" or "test the app" for `/qa`,
|
||||
"tech review" for `/plan-eng-review`. Say one of those naturally and the right
|
||||
skill can be picked without the slash command name. On Claude Code the aliases
|
||||
are routing lines in the root `/gstack` router; on Codex and the other hosts
|
||||
they are appended to each skill's description, which is what those hosts match
|
||||
on.
|
||||
|
||||
The separate `triggers:` list in a skill's frontmatter is gstack metadata, not a
|
||||
matching surface. Claude Code and Codex choose a skill from its name and
|
||||
description, so a phrase that appears only under `triggers:` does not activate
|
||||
the skill (Codex output drops the field; the GBrain host keeps it).
|
||||
@@ -0,0 +1,84 @@
|
||||
# Why gstack, and how I use it
|
||||
|
||||
The story behind gstack, the productivity numbers and their caveats, how the
|
||||
skills map onto Karpathy's failure modes, and what changes when you run ten
|
||||
sprints at once. This page moved out of the README so the README can stay
|
||||
short; nothing here is required to install or use gstack.
|
||||
|
||||
> "I don't think I've typed like a line of code probably since December, basically, which is an extremely large change." — [Andrej Karpathy](https://fortune.com/2026/03/21/andrej-karpathy-openai-cofounder-ai-agents-coding-state-of-psychosis-openclaw/), No Priors podcast, March 2026
|
||||
|
||||
When I heard Karpathy say this, I wanted to find out how. How does one person ship like a team of twenty? Peter Steinberger built [OpenClaw](https://github.com/openclaw/openclaw) — 247K GitHub stars — essentially solo with AI agents. The revolution is here. A single builder with the right tooling can move faster than a traditional team.
|
||||
|
||||
I'm [Garry Tan](https://x.com/garrytan), President & CEO of [Y Combinator](https://www.ycombinator.com/). I've worked with thousands of startups — Coinbase, Instacart, Rippling — when they were one or two people in a garage. Before YC, I was one of the first eng/PM/designers at Palantir, cofounded Posterous (sold to Twitter), and built Bookface, YC's internal social network.
|
||||
|
||||
**gstack is my answer.** I've been building products for twenty years, and right now I'm shipping more products than I ever have. In the last 60 days: 3 production services, 40+ shipped features, part-time, while running YC full-time. On logical code change — not raw LOC, which AI inflates — my 2026 run rate is **~810× my 2013 pace** (11,417 vs 14 logical lines/day). Year-to-date (through April 18), 2026 has already produced **240× the entire 2013 year**. Measured across 40 public + private `garrytan/*` repos including Bookface, after excluding one demo repo. AI wrote most of it. The point isn't who typed it, it's what shipped.
|
||||
|
||||
> The LOC critics aren't wrong that raw line counts inflate with AI. They are wrong that normalized-for-inflation, I'm less productive. I'm more productive, by a lot. Full methodology, caveats, and reproduction script: **[On the LOC Controversy](ON_THE_LOC_CONTROVERSY.md)**.
|
||||
|
||||
**2026 — 1,237 contributions and counting:**
|
||||
|
||||

|
||||
|
||||
**2013 — when I built Bookface at YC (772 contributions):**
|
||||
|
||||

|
||||
|
||||
Same person. Different era. The difference is the tooling.
|
||||
|
||||
**gstack is how I do it.** It turns Claude Code into a virtual engineering team — a CEO who rethinks the product, an eng manager who locks architecture, a designer who catches AI slop, a reviewer who finds production bugs, a QA lead who opens a real browser, a security officer who runs OWASP + STRIDE audits, and a release engineer who ships the PR. Twenty-three specialists and eight power tools, all slash commands, all Markdown, all free, MIT license.
|
||||
|
||||
This is my open source software factory. I use it every day. I'm sharing it because these tools should be available to everyone.
|
||||
|
||||
## Karpathy's four failure modes? Already covered.
|
||||
|
||||
Andrej Karpathy's [AI coding rules](https://github.com/forrestchang/andrej-karpathy-skills) (17K stars) nail four failure modes: wrong assumptions, overcomplexity, orthogonal edits, imperative over declarative. gstack's workflow skills enforce all four. `/office-hours` forces assumptions into the open before code is written. The Confusion Protocol stops Claude from guessing on architectural decisions. `/review` catches unnecessary complexity and drive-by edits. `/ship` transforms tasks into verifiable goals with test-first execution. If you already use Karpathy-style CLAUDE.md rules, gstack is the workflow enforcement layer that makes them stick across entire sprints, not just single prompts.
|
||||
|
||||
## Parallel sprints
|
||||
|
||||
gstack works well with one sprint. It gets interesting with ten running at once.
|
||||
|
||||
**Design is at the heart.** `/design-consultation` builds your design system from scratch, researches what's out there, proposes creative risks, and writes `DESIGN.md`. But the real magic is the shotgun-to-HTML pipeline.
|
||||
|
||||
**`/design-shotgun` is how you explore.** You describe what you want. It generates 4-6 AI mockup variants using GPT Image. Then it opens a comparison board in your browser with all variants side by side. You pick favorites, leave feedback ("more whitespace", "bolder headline", "lose the gradient"), and it generates a new round. Repeat until you love something. Taste memory kicks in after a few rounds so it starts biasing toward what you actually like. No more describing your vision in words and hoping the AI gets it. You see options, pick the good ones, and iterate visually.
|
||||
|
||||
**Works with impeccable.** If you use [impeccable](https://impeccable.style) too, gstack does not fight it. gstack runs impeccable's deterministic engine as a pre-pass in `/design-review`, `/review`, `/ship`, and `/design-html` when you have it installed (gstack never runs impeccable's installer or launcher; the first time a design skill finds no engine it asks once whether to download the engine binary, checksum-pinned and logged, into `~/.impeccable`, and remembers your answer), speaks the same 61 rule ids in its own voice, reads `PRODUCT.md`, writes `DESIGN.md` in the open DESIGN.md format both tools read, and hands deferred findings to `/impeccable <command>`. Say no and nothing changes: no nag, no missing step. `gstack-config set design_detector off` turns the pre-pass off. Attribution for the material gstack derived from impeccable and the DESIGN.md spec is in `NOTICE.md`.
|
||||
|
||||
**`/design-html` makes it real.** Take that approved mockup (from `/design-shotgun`, a CEO plan, a design review, or just a description) and turn it into production-quality HTML/CSS. Not the kind of AI HTML that looks fine at one viewport width and breaks everywhere else. This uses Pretext for computed text layout: text actually reflows on resize, heights adjust to content, layouts are dynamic. 30KB overhead, zero dependencies. It detects your framework (React, Svelte, Vue) and outputs the right format. Smart API routing picks different Pretext patterns depending on whether it's a landing page, dashboard, form, or card layout. The output is something you'd actually ship, not a demo.
|
||||
|
||||
**`/qa` was a massive unlock.** It let me go from 6 to 12 parallel workers. Claude Code saying *"I SEE THE ISSUE"* and then actually fixing it, generating a regression test, and verifying the fix — that changed how I work. The agent has eyes now.
|
||||
|
||||
**Smart review routing.** Just like at a well-run startup: CEO doesn't have to look at infra bug fixes, design review isn't needed for backend changes. gstack tracks what reviews are run, figures out what's appropriate, and just does the smart thing. The Review Readiness Dashboard tells you where you stand before you ship.
|
||||
|
||||
**Test everything.** `/ship` bootstraps test frameworks from scratch if your project doesn't have one. Every `/ship` run produces a coverage audit. `/qa` creates native regressions when infrastructure is available and explicitly reports missing test coverage; CSS-only fixes may use browser evidence instead. 100% test coverage is the goal — tests make vibe coding safe instead of yolo coding.
|
||||
|
||||
**`/document-release` is the engineer you never had.** It audits relevant authored docs against the release diff and corrects factual drift before final verification. Risky changes return for approval. `/ship` invokes it on every run and owns TODOS, release metadata, generation and publication; the child reports updated, current or blocked documentation.
|
||||
|
||||
**Aside is the browser gstack drives first.** For browser surfaces, on a Mac with the [Aside](https://aside.com) AI browser open, `/qa`, `/qa-only`, `/design-review`, `/canary`, `/benchmark`, `/scrape`, and `/browse` all run there — your real browser, with your real logged-in sessions, in tabs the agent opens for itself and closes when it's done. No cookie import, no "open the browser" step, no CAPTCHA handoff dance: hit a sign-in wall, sign in inside Aside, say "done", and the agent continues. Anything a page returns is treated as untrusted content — the agent takes syntax from it, never instructions. `/make-pdf`, `/diagram`, and design previews print and screenshot through Aside too (served from your machine on loopback, one render per script), and the planning skills do their web research through Aside's own agent before reaching for a search tool.
|
||||
|
||||
**When Aside isn't there, gstack's own browser takes over — automatically.** Linux, Windows, or a Mac with Aside closed: the same skills use the bundled headless Chromium that `./setup` builds, produce the same evidence, and light up the features below that only make sense when the browser is gstack's rather than yours.
|
||||
|
||||
**Real browser mode.** `/open-gstack-browser` launches GStack Browser, an AI-controlled Chromium with anti-bot stealth, custom branding, and the sidebar extension baked in. Sites like Google and NYTimes work without captchas. The menu bar says "GStack Browser" instead of "Chrome for Testing." Your regular Chrome stays untouched. All existing browse commands work unchanged. `$B disconnect` returns to headless. The browser stays alive as long as the window is open... no idle timeout killing it while you're working.
|
||||
|
||||
**Sidebar agent — your AI browser assistant.** Type natural language in the Chrome side panel and a child Claude instance executes it. "Navigate to the settings page and screenshot it." "Fill out this form with test data." "Go through every item in this list and extract the prices." The sidebar auto-routes to the right model: Sonnet for fast actions (click, navigate, screenshot) and Opus for reading and analysis. Each task gets up to 5 minutes. The sidebar agent runs in an isolated session, so it won't interfere with your main Claude Code window. One-click cookie import right from the sidebar footer.
|
||||
|
||||
**Personal automation.** The sidebar agent isn't just for dev workflows. Example: "Browse my kid's school parent portal and add all the other parents' names, phone numbers, and photos to my Google Contacts." Two ways to get authenticated: (1) log in once in the headed browser, your session persists, or (2) click the "cookies" button in the sidebar footer to import cookies from your real Chrome. Once authenticated, Claude navigates the directory, extracts the data, and creates the contacts.
|
||||
|
||||
**Prompt injection defense.** Hostile web pages try to hijack your sidebar agent. gstack ships a layered defense: content filters (datamarking, hidden-element stripping, ARIA scrubbing, URL blocklist) on every page read, plus a 22MB ML classifier running locally in a sidecar subprocess that scans page-derived content before the agent sees it, with a verdict combiner that requires classifier agreement before blocking (prevents single-model false positives on Stack Overflow-style instruction pages). Everything runs on your machine, no network calls. Emergency kill switch: `GSTACK_SECURITY_OFF=1`. See [ARCHITECTURE.md](../ARCHITECTURE.md#prompt-injection-defense-sidebar-agent) for the full stack.
|
||||
|
||||
**Browser handoff when the AI gets stuck.** Hit a CAPTCHA, auth wall, or MFA prompt? `$B handoff` opens a visible Chrome at the exact same page with all your cookies and tabs intact. Solve the problem, tell Claude you're done, `$B resume` picks up right where it left off. The agent even suggests it automatically after 3 consecutive failures.
|
||||
|
||||
**`/pair-agent` is cross-agent coordination.** You're in Claude Code. You also have OpenClaw running. Or Hermes. Or Codex. You want them both looking at the same website. Type `/pair-agent`, pick your agent, and a GStack Browser window opens so you can watch. The skill prints a block of instructions. Paste that block into the other agent's chat. It exchanges a one-time setup key for a session token, creates its own tab, and starts browsing. You see both agents working in the same browser, each in their own tab, neither able to interfere with the other. If ngrok is installed, the tunnel starts automatically so the other agent can be on a completely different machine. Same-machine agents get a zero-friction shortcut that writes credentials directly. This is the first time AI agents from different vendors can coordinate through a shared browser with real security: scoped tokens, tab isolation, rate limiting, domain restrictions, and activity attribution.
|
||||
|
||||
**Multi-AI second opinion.** In Codex, gstack sends outside reviews to Claude Code through `/claude-code`. In Claude Code, `/codex` sends them to OpenAI Codex. Other harnesses expose both skills and use Codex where automatic outside reviews are supported. Each skill supports code review, adversarial challenge, and consultation with session continuity. Routing follows the harness, so changing your configured model does not change the outside reviewer. Reports identify the provider that actually completed each review; unavailable outside coverage remains visible.
|
||||
|
||||
**Safety guardrails on demand.** Say "be careful" and `/careful` warns before any destructive command — rm -rf, DROP TABLE, force-push, git reset --hard. `/freeze` locks edits to one directory while debugging so Claude can't accidentally "fix" unrelated code. `/guard` activates both. `/investigate` auto-freezes to the module being investigated.
|
||||
|
||||
**Proactive skill suggestions.** gstack notices what stage you're in and, by default, suggests only the four core skills: `/office-hours` for a new idea, `/review` for a diff, `/qa` for "does this work?", `/ship` to land it. `gstack-config set proactive all` suggests every matching skill, `off` suggests nothing, and "stop suggesting" in a session turns it off and remembers across sessions (see [docs/proactive.md](proactive.md)).
|
||||
|
||||
## 10-15 parallel sprints
|
||||
|
||||
gstack is powerful with one sprint. It is transformative with ten running at once.
|
||||
|
||||
[Conductor](https://conductor.build) runs multiple Claude Code sessions in parallel — each in its own isolated workspace. One session running `/office-hours` on a new idea, another doing `/review` on a PR, a third implementing a feature, a fourth running `/qa` on staging, and six more on other branches. All at the same time. I regularly run 10-15 parallel sprints — that's the practical max right now.
|
||||
|
||||
The sprint structure is what makes parallelism work. Without a process, ten agents is ten sources of chaos. With a process — think, plan, build, review, test, ship — each agent knows exactly what to do and when to stop. You manage them the way a CEO manages a team: check in on the decisions that matter, let the rest run.
|
||||
Reference in new issue
Block a user