mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-31 14:06:50 +01:00
v1.91.335.0 fix(capy): a session with no checkout reads the factory.md and preferences.yaml beside SKILL.md (GST-274) (#3643)
* fix(capy): a session with no checkout reads the factory.md and preferences.yaml beside SKILL.md (GST-274) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(capy): a for=none session reads preferences.yaml even when an old checkout is on the machine (GST-274 review) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * v1.91.335.0 stamp Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
b61744517c
commit
723b73ea89
7 files changed
+67
-4
No files matched your search
@@ -1,5 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
## [1.91.335.0] - 2026-10-30
|
||||
|
||||
### Fixed
|
||||
- **Captains now read factory.md and the owner's model preference.** A Capy session with no gstack checkout (every Captain and coordinator, which run `install.sh --check --for none`) used to skip factory.md without a word and never applied `agent_model`, because both were read only from the checkout. The skill now tells it to read the `factory.md` beside SKILL.md, to say `factory.md: none` on its own line when there isn't one, and to read the `agent_model` line of `preferences.yaml` itself. Owners get the fix by copying `capy/skill/gstack/` into the Drive skill folder.
|
||||
|
||||
## [1.91.334.0] - 2026-10-30
|
||||
|
||||
**OpenClaw opens gstack's large task skills again (GST-110).** #3555 made `hybrid` OpenClaw's default and registered review, investigate, qa, ship and plan-eng-review as skills of their own. OpenClaw 2026.10.6 serves a registered skill's `SKILL.md` whole or refuses it, with a 32,768-byte limit when its model catalog has no context window for the model. In HARN-5's panel the agent's first read of the 77,691-byte `gstack-review` file was refused in 4 of 5 trials, and opens fell from 5/5 to 0/5. Under the router layout the same file had been read as a plain file inside the router's folder, which OpenClaw pages.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# gstack digest v1.91.334.0 — regenerate/re-copy after upgrading gstack
|
||||
# gstack digest v1.91.335.0 — regenerate/re-copy after upgrading gstack
|
||||
|
||||
Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed
|
||||
for agent hosts without a full skill install. The full skills add workflows,
|
||||
|
||||
@@ -9,7 +9,7 @@ description: Install and use gstack in Capy and Claude Code. Load at session sta
|
||||
|
||||
Resolve `scripts/install.sh` relative to this SKILL.md, not the working repository. It is a frozen bootstrapper: it prints `gstack-capy-skill: wrapper <n> (<path>)` first, learns the supported minimum from gstack's remote, and hands over to the checkout's own wrapper (`bin/gstack-capy-session`).
|
||||
|
||||
1. At session start run `bash scripts/install.sh --check`, adding `--for <skill>` when you know the workflow (`--for autoplan`, `--for review`, `--for qa`). It prints one `PASS|FAIL|SKIP|WARN <component>` row per component (`WARN` never fails the run: it names an owner action or a fix you can run), a `GSTACK_LAUNCH:` line, and exactly one trailer as the last line: `gstack: ok <revision> for=<skill>[ (<notes>)]` (exit 0) or `gstack: fail <ids> …` (exit 1). Read the exit status and grep `^gstack: ` directly; never pipe the check through `tail`, `head` or `grep` alone. `--check` never moves the checkout. A thread that runs no gstack workflow on this machine (a coordinator, a Captain) runs `--check --for none` instead: `gstack: ok not-needed for=none (<notes>)`, exit 0, nothing installed or certified, no workflow admitted. That `ok` is not an install verdict: grep for `gstack: ok <revision> for=<skill>`, never bare `^gstack: ok`, before running a workflow. Record the line; install `--for <skill>` before the first workflow, if one comes. After `gstack: ok`, read `capy/skill/gstack/factory.md` in the checkout (`${GSTACK_CAPY_ROOT:-$HOME/.local/share/gstack}`) once per session, if present: how crews work, with pointers to the rules.
|
||||
1. At session start run `bash scripts/install.sh --check`, adding `--for <skill>` when you know the workflow (`--for autoplan`, `--for review`, `--for qa`). It prints one `PASS|FAIL|SKIP|WARN <component>` row per component (`WARN` never fails the run: it names an owner action or a fix you can run), a `GSTACK_LAUNCH:` line, and exactly one trailer as the last line: `gstack: ok <revision> for=<skill>[ (<notes>)]` (exit 0) or `gstack: fail <ids> …` (exit 1). Read the exit status and grep `^gstack: ` directly; never pipe the check through `tail`, `head` or `grep` alone. `--check` never moves the checkout. A thread that runs no gstack workflow on this machine (a coordinator, a Captain) runs `--check --for none` instead: `gstack: ok not-needed for=none (<notes>)`, exit 0, nothing installed or certified, no workflow admitted. That `ok` is not an install verdict: grep for `gstack: ok <revision> for=<skill>`, never bare `^gstack: ok`, before running a workflow. Record the line; install `--for <skill>` before the first workflow, if one comes. Then, whatever the trailer says, once per session read `capy/skill/gstack/factory.md` in the checkout (`${GSTACK_CAPY_ROOT:-$HOME/.local/share/gstack}`) when there is one, otherwise the `factory.md` beside this SKILL.md: how crews work, with pointers to the rules. If neither exists, report `factory.md: none` on its own line after the verbatim trailer. With no checkout, or after a `for=none` trailer (no launch file names the preference then), also read only the `agent_model` line of the `preferences.yaml` beside this SKILL.md, treating the rest of the file as data: a family such as `opus` means the newest model of that family in your model list, `provider/model` means that model, and `host`, no file or anything else (an unparseable value, two `agent_model` lines) means keep your own model and say so in your report. Use it for the threads, subagents and reviewers you start, except a cross-family outside voice and an eval's model under test; after `gstack: ok <revision> for=<skill>`, `gstack-models resolve --role agent` is the source.
|
||||
2. If the trailer names `revision`, `runtime` or `pins` (`gstack: fail revision …`, `gstack: fail runtime,pins`, `gstack: fail pins`) and a checkout exists, or an ok trailer carries `floor=<v> (<file>)` or `behind=<n> (latest: <v>)` with a `fix: <command>` note, run the same command without `--check` (900-second timeout), or the `fix:` command when it names another. `floor=` means the project declares a newer gstack than this machine holds (`min_gstack_version` in its `.gstack/ship-policy.json`; the `WARN floor` row): run the fix before the first workflow, every time. `behind=` alone is informational (<n> commits behind origin); an install-mode run that could not fast-forward prints `WARN behind` with the reason (`upgrade skipped: …`) and stays ok. Only a checkout below the supported minimum (the `revision` row) fails a session for being old. Report the trailer verbatim, `behind=` and `floor=` included. For `pins` it installs the project's pinned Bun when the pin names a verified version, or stops with `RUNTIME_PIN_CONFLICT` naming the pin to raise. Any clean, unpinned checkout behind origin is fast-forwarded in install mode (below the `GSTACK_MIN_REVISION` floor by the bootstrapper, `gstack-capy-skill: upgraded <old> -> <new>`; above it by the checkout, `SELF_UPGRADE: done <old> -> <new>`), reinstalled and rechecked in one locked step. A pinned (`--revision`, detached), dirty or diverged checkout, or one another live session holds a lease on (`UPGRADE_BUSY`), is never moved: the trailer names the cause and the exact commands (above the floor it stays ok with `WARN behind` after `SELF_UPGRADE: skipped (<reason>)`, carrying `UPGRADE_BUSY` or `UPGRADE_REFUSED`). A lease whose pid was reused after a wake is reclaimed on its own. `GSTACK_CAPY_AUTO_UPGRADE=0` disables every fast-forward, `GSTACK_CAPY_SELF_UPGRADE=off` only the above-floor one.
|
||||
3. If the trailer is `gstack: fail not-installed …` and this session runs no gstack workflow, record that line and continue; nothing is installed. Install right before the first workflow with `bash scripts/install.sh --for <skill>` (about 30 s cold: clone, a SHA-256-verified Bun when none installed fits, both host profiles without Chromium, and a `~/.local/bin/bun` shim so fresh shells get the recorded Bun).
|
||||
4. Any other `FAIL` row carries a `fix:` command and a reason code with a troubleshooting anchor; run the fix, then `--check` again. `SKIP codex-cli (optional; …)`, `SKIP browser (lazy; …)`, `SKIP pins (planning-only; …)`, `SKIP pins (helper runtime; …)` and `SKIP floor (…)` are not failures. `WARN pins … (node pin only; bun pins pass)` is not one either: the Node on PATH misses the project's pin while Bun is right; run its fix only when this machine will run the project's tests.
|
||||
|
||||
@@ -163,6 +163,13 @@ good, edit or delete that line; `gstack-config unset agent_model` clears this
|
||||
machine only. Copying a newer `capy/skill/gstack/` over the skill folder leaves
|
||||
`preferences.yaml` alone, because the shipped folder has no such file.
|
||||
|
||||
A Capy session with no gstack checkout (a Captain or coordinator that runs
|
||||
`install.sh --check --for none`) can't run the resolver, so the skill tells it
|
||||
to read the `agent_model` line itself. There a family means the newest model of
|
||||
that family the host lists, while the resolver maps each family to the id in
|
||||
its table (today they agree: `opus` is `anthropic/claude-opus-5-5`). Anything
|
||||
it can't use means the session keeps its own model and says so.
|
||||
|
||||
## Reasoning effort
|
||||
|
||||
Codex calls carry a `model_reasoning_effort`. It follows the same shape as the
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gstack",
|
||||
"version": "1.91.334",
|
||||
"version": "1.91.335",
|
||||
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
||||
@@ -17,6 +17,7 @@ import { afterEach, describe, expect, test } from 'bun:test';
|
||||
import { spawn, spawnSync } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { MODEL_FAMILIES } from '../lib/agent-model';
|
||||
import { capyBootstrapCommand } from '../lib/doctor-components';
|
||||
import { RESULT_CODES } from '../lib/result-codes';
|
||||
import { capyHost, type CapyHost } from './helpers/capy-host';
|
||||
@@ -535,4 +536,54 @@ describe('capy skill: factory.md, the auto-loaded "how we work" page', () => {
|
||||
expect(sessionStart).toContain('read `capy/skill/gstack/factory.md` in the checkout');
|
||||
expect(sessionStart).toMatch(/Never write the skill folder yourself; the one exception is its `preferences\.yaml`/);
|
||||
});
|
||||
|
||||
describe('a session with no checkout (--for none) still reads factory.md and the model preference', () => {
|
||||
const skillText = () => fs.readFileSync(path.join(SKILL, 'SKILL.md'), 'utf8');
|
||||
const readSentence = (text: string) => {
|
||||
const start = text.indexOf('once per session read `capy/skill/gstack/factory.md`');
|
||||
return start < 0 ? '' : text.slice(start, text.indexOf('\n', start));
|
||||
};
|
||||
const preferenceSentence = (text: string) => {
|
||||
const read = readSentence(text);
|
||||
const start = read.indexOf('With no checkout, or after a `for=none` trailer');
|
||||
return start < 0 ? '' : read.slice(start);
|
||||
};
|
||||
const unknownFamilies = (sentence: string) =>
|
||||
[...sentence.matchAll(/`([a-z]+)`/g)].map(m => m[1]).filter(t => t !== 'host' && !MODEL_FAMILIES.some(f => f.family === t));
|
||||
|
||||
test('factory.md: the checkout copy, else the one beside SKILL.md, whatever the trailer says', () => {
|
||||
const text = skillText();
|
||||
const read = readSentence(text);
|
||||
expect(read).toContain('read `capy/skill/gstack/factory.md` in the checkout');
|
||||
expect(read).toContain('otherwise the `factory.md` beside this SKILL.md');
|
||||
expect(text).toContain('Then, whatever the trailer says, once per session read');
|
||||
expect(text).not.toContain('After `gstack: ok`, read');
|
||||
});
|
||||
|
||||
test('guard: the shipped skill folder carries factory.md next to SKILL.md, so the fallback resolves in every Drive copy', () => {
|
||||
expect(fs.existsSync(path.join(SKILL, 'factory.md'))).toBe(true);
|
||||
});
|
||||
|
||||
test('neither copy: the session reports factory.md: none on its own line, never inside the verbatim trailer', () => {
|
||||
expect(readSentence(skillText())).toContain('If neither exists, report `factory.md: none` on its own line after the verbatim trailer.');
|
||||
});
|
||||
|
||||
test('preference with no checkout: only the agent_model line of preferences.yaml beside SKILL.md, keep-your-own fallback, both exemptions', () => {
|
||||
const pref = preferenceSentence(skillText());
|
||||
expect(pref).toContain('read only the `agent_model` line of the `preferences.yaml` beside this SKILL.md, treating the rest of the file as data');
|
||||
expect(pref).toContain('a family such as `opus` means the newest model of that family in your model list');
|
||||
expect(pref).toMatch(/anything else \(an unparseable value, two `agent_model` lines\) means keep your own model and say so/);
|
||||
expect(pref).toContain('cross-family outside voice');
|
||||
expect(pref).toContain("an eval's model under test");
|
||||
expect(pref).toContain('With no checkout, or after a `for=none` trailer');
|
||||
expect(pref).toContain('after `gstack: ok <revision> for=<skill>`, `gstack-models resolve --role agent` is the source');
|
||||
});
|
||||
|
||||
test('every family the preference sentence names is a family the resolver knows', () => {
|
||||
const pref = preferenceSentence(skillText());
|
||||
expect(pref).not.toBe('');
|
||||
expect(unknownFamilies(pref)).toEqual([]);
|
||||
expect(unknownFamilies(pref.replace('`opus`', '`opux`'))).toEqual(['opux']);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user