From bb539ba4d5c4a411e35e9fdedfdabb1359e38462 Mon Sep 17 00:00:00 2001 From: garrytan Date: Sat, 31 Oct 2026 14:17:49 +0000 Subject: [PATCH] fix: pre-landing review fixes (Capy skill passes the model as GSTACK_MODEL so an older installer ignores it) (GSTA-161) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- bin/gstack-capy-install | 3 ++- capy/skill/gstack/SKILL.md | 2 +- changelog.d/gsta161-opus-lite-loadout.md | 2 +- docs/loadouts.md | 4 ++-- test/gstack-capy-install.test.ts | 3 +++ 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/bin/gstack-capy-install b/bin/gstack-capy-install index 68e8d0805..9116ef568 100755 --- a/bin/gstack-capy-install +++ b/bin/gstack-capy-install @@ -42,7 +42,7 @@ # mode-0600 file beside the verdict; callers and their children `. ` it, # never re-type it) and the doctor's trailer (`gstack: ok ...` or `gstack: fail ...`; # grep that, never pipe through tail). -# --model (GSTA-161, docs/loadouts.md) names the model this session runs: +# --model , else GSTACK_MODEL (GSTA-161, docs/loadouts.md), names the model this session runs: # bin/gstack-modules install picks its loadout, publishes a non-full one beside # the full Claude install and prints LOADOUT and SKILL_ROOT lines before the # launch lines; a non-full loadout gets its own launch file, keyed by loadout @@ -75,6 +75,7 @@ while [ $# -gt 0 ]; do esac shift done +[ -n "$MODEL" ] || MODEL=${GSTACK_MODEL:-} [ -n "$FOR" ] && [ -n "$REQUIRE" ] && { echo "gstack-capy-install: --for and --require are exclusive" >&2; exit 2; } [ -z "$FOR" ] && [ -z "$REQUIRE" ] && REQUIRE="claude,codex,patch" diff --git a/capy/skill/gstack/SKILL.md b/capy/skill/gstack/SKILL.md index 25a087975..43c5bb9eb 100644 --- a/capy/skill/gstack/SKILL.md +++ b/capy/skill/gstack/SKILL.md @@ -30,7 +30,7 @@ With `GSTACK_CAPY=1`, a workflow started in an unattended or spawned session che For Capy running GPT-6 Astra, read `${CODEX_HOME:-$HOME/.codex}/skills/gstack/SKILL.md` for routing, then `${CODEX_HOME:-$HOME/.codex}/skills/gstack-/SKILL.md` for the selected workflow. These are the generated GPT-6 Astra instructions, not the Claude source files. -For Claude Code, read `~/.claude/skills/gstack/SKILL.md` for routing and `~/.claude/skills//SKILL.md` for the selected workflow. When launching a Claude Code session, tell it to load gstack and name the intended workflow. For Capy running a Claude model, the Claude instruction files can be read directly with the Capy adaptations below; that does not turn Capy into the Claude Code harness. Pass your model id as `--model ` to `scripts/install.sh`; when it prints `SKILL_ROOT: `, read skills from `` instead (`docs/loadouts.md`). +For Claude Code, read `~/.claude/skills/gstack/SKILL.md` for routing and `~/.claude/skills//SKILL.md` for the selected workflow. When launching a Claude Code session, tell it to load gstack and name the intended workflow. For Capy running a Claude model, the Claude instruction files can be read directly with the Capy adaptations below; that does not turn Capy into the Claude Code harness. Run `scripts/install.sh` with `GSTACK_MODEL=` in front (an installer that predates it ignores the variable); when it prints `SKILL_ROOT: `, read skills from `` instead (`docs/loadouts.md`). If no specific skill was requested, use the router's task-to-skill mapping. Read only the selected workflow and its referenced files. For other models, use the closest applicable instruction profile without claiming a model-specific profile was generated or evaluated for them. diff --git a/changelog.d/gsta161-opus-lite-loadout.md b/changelog.d/gsta161-opus-lite-loadout.md index 640bdb6d6..a80a11f0f 100644 --- a/changelog.d/gsta161-opus-lite-loadout.md +++ b/changelog.d/gsta161-opus-lite-loadout.md @@ -3,7 +3,7 @@ ### Added - **`lite` loadout and a model map** in `modules/loadouts.json`. `lite` drops the voice, writing-style, completeness, confusion, evidence, context-health, question-tuning, search-before-building, repo-mode, brain-health and context-recovery pieces and keeps the upgrade check and brain sync. `models` maps only `claude-opus-5-5` (and its dated snapshots) to it; a point release or any other suffix gets full. - **Explicit model detection** (`lib/model-detect.ts`): an explicit loadout, then `--model`, then `GSTACK_MODEL`, else full. An unmapped, invalid or unreadable model id serves full and never errors (`LOADOUT_INVALID_MODEL` names an id that is not a plain model id). -- **`gstack-capy-install --model `** (passed through `scripts/install.sh`): prints `LOADOUT` and `SKILL_ROOT` lines and, for lite, writes a second launch file that adds `GSTACK_LOADOUT_SKILL_ROOT`. The install verdict and its key do not change. +- **`gstack-capy-install --model `** (passed through `scripts/install.sh`), or `GSTACK_MODEL=` in its environment, which the Capy skill uses because an older installer ignores it: prints `LOADOUT` and `SKILL_ROOT` lines and, for lite, writes a second launch file that adds `GSTACK_LOADOUT_SKILL_ROOT`. The install verdict and its key do not change. - **`gstack-modules install` and `resolve`**: `install` publishes the lite tree under `/loadouts/-/` as a complete skill tree (rendered files plus every tracked runtime asset), keyed by the selection, model, full root, host and source tree, reused only after its files re-hash against the lock, and never replaced in place. A failure serves full with `LOADOUT_FALLBACK`. `render --bind-model --full-root ` renders the same start lines for eval arms. - **`./setup --loadout `** for Claude Code: saved as `claude_loadout` and rendered through setup's own Claude render into the untracked render dir; `--loadout full` returns to the committed files. - **The start line carries the model.** A lite skill's start command names its model and the full install root, so `gstack-skill-start` prints `LOADOUT` and `LOADOUT_RULE` lines, plus `LOADOUT_MISMATCH` when `GSTACK_MODEL` names another model, telling that session to read the full SKILL.md instead. diff --git a/docs/loadouts.md b/docs/loadouts.md index 8d5701e2e..0bd5e9de9 100644 --- a/docs/loadouts.md +++ b/docs/loadouts.md @@ -45,14 +45,14 @@ bin/gstack-modules render --host claude \ gstack never guesses the model. It uses, in order: 1. An explicit loadout: `./setup --loadout ` on Claude Code, or `bin/gstack-modules install --loadout `. It is served as asked and bound to no model. -2. An explicit model id: `--model ` on the Capy installer (`scripts/install.sh --model `, which passes it to `bin/gstack-capy-install`), else `GSTACK_MODEL`. +2. An explicit model id: `--model ` on the Capy installer (`scripts/install.sh --model `, which passes it to `bin/gstack-capy-install`), else `GSTACK_MODEL`, which the installer also reads. 3. Nothing: `full`. A model id is lowercased, its `provider/` prefix and a trailing `[…]` are dropped, and it must then be a plain id (`^[a-z0-9][a-z0-9._-]{0,79}$`). An unmapped id serves `full`; an id that is not plain also serves `full` and prints `LOADOUT_INVALID_MODEL`. Nothing reads Claude Code settings or an inherited loadout variable, so a Sonnet subagent of an Opus session gets `full` unless its own install names Opus. ### Capy -The Capy skill passes the thread's model id: `bash scripts/install.sh --check --for --model `. The installer prints two lines before the launch lines: +The Capy skill passes the thread's model id in the environment: `GSTACK_MODEL= bash scripts/install.sh --check --for `. The variable, unlike `--model`, is safe on a checkout whose installer predates loadouts: that installer ignores it instead of refusing an unknown argument. The installer prints two lines before the launch lines: ``` LOADOUT: lite model=claude-opus-5-5 source=model-flag digest=3f2a9c01d4e7 reused=yes diff --git a/test/gstack-capy-install.test.ts b/test/gstack-capy-install.test.ts index 36ce9ec12..37374a282 100644 --- a/test/gstack-capy-install.test.ts +++ b/test/gstack-capy-install.test.ts @@ -561,6 +561,9 @@ describe('gstack-capy-install: launch PATH, Bun reuse, CI pins, setup skip, verd const plain = run(f, ['--for', 'ship']); expect(plain.stdout).not.toMatch(/^(LOADOUT|SKILL_ROOT):/m); expect(launchOf(plain.stdout)).toBe(base); + const fromEnv = run(f, ['--for', 'ship'], { GSTACK_MODEL: 'claude-opus-5-5' }); + expect(fromEnv.status, fromEnv.stdout + fromEnv.stderr).toBe(0); + expect(launchOf(fromEnv.stdout)).toBe(lite); expect(verdicts(f)).toEqual([verdict]); expect(fs.existsSync(lite)).toBe(true); });