diff --git a/bin/gstack-capy-install b/bin/gstack-capy-install index 6cb5155ff..4f9145641 100755 --- a/bin/gstack-capy-install +++ b/bin/gstack-capy-install @@ -629,8 +629,9 @@ EOF_SHIM # A checkout more than 24 hours behind (bin/gstack-capy-session exports # GSTACK_CAPY_STALE) adds `stale=h` after `behind=`, or `stale=h (pinned: )` # for a pin with a recorded reason; a failed fetch with no successful one in 24 -# hours adds `stale=no fetch from origin in h` on its own. Unlike a bare -# `behind=`, a `stale=` with no pin means: run the fix before the first workflow. +# hours adds `stale=no fetch from origin in h` on its own. Like any +# `behind=` of 1 or more, a `stale=` with no pin means: run the fix before the +# next workflow. # A project floor the doctor could not meet (`WARN floor`, PROJECT_FLOOR_UNMET) # adds `floor= ()` and a `fix:` note the same way; when no release # meets the floor (the row says `no release meets this floor yet`), the note is diff --git a/capy/skill/gstack/SKILL.md b/capy/skill/gstack/SKILL.md index de4a3adb2..fae074887 100644 --- a/capy/skill/gstack/SKILL.md +++ b/capy/skill/gstack/SKILL.md @@ -10,7 +10,7 @@ description: Install and use gstack in Capy and Claude Code. Load at session sta **Crew lanes:** after this check passes, read [lane-card.md](lane-card.md), not the rest of this file; it says what to read before /ship. Resolve `scripts/install.sh` relative to this SKILL.md, not the working repository. It is a frozen bootstrapper: it prints `gstack-capy-skill: wrapper ()` 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 ` when you know the workflow (`--for autoplan`, `--for review`, `--for qa`). It prints one `PASS|FAIL|SKIP|WARN ` 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 for=[ ()]` (exit 0) or `gstack: fail …` (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 ()`, exit 0, nothing installed or certified, no workflow admitted. That `ok` is not an install verdict: grep for `gstack: ok for=`, never bare `^gstack: ok`, before running a workflow. Record the line; install `--for ` 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 for=`, `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= ()`, `behind= (latest: )` or `stale=` with a `fix: ` 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), and `stale=h` that the checkout has lacked an origin commit for over 24 hours: run the fix once before the first workflow; a `floor=` note saying `no release meets this floor yet` has no fix: report it. `behind=` alone is informational; `stale=h (pinned: )` is a pin with a recorded reason and stays. A thread that lives past a day reruns the check before a workflow once its last check is over 24 hours old. Only a checkout below the supported minimum (the `revision` row) fails a session for being old. Report the trailer verbatim. 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. Install mode fast-forwards a clean checkout behind origin, reinstalls and rechecks in one locked step. A dirty or diverged checkout, or one a live session's lease or a live eval run holds (`UPGRADE_BUSY`), is never moved; a pin (`--revision`, detached, another branch, `GSTACK_CAPY_SELF_UPGRADE=off`, `GSTACK_CAPY_AUTO_UPGRADE=0`) holds past 24 hours only with a recorded reason (`GSTACK_PIN_REASON=` or `git -C config gstack.pinReason `), else the install overrides it (`PIN_UNREASONED`). A refused run stays ok with `WARN behind` after `SELF_UPGRADE: skipped ()` and names the exact commands. Details: `docs/capy-install.md` in the checkout. +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= ()`, `behind= (latest: )` or `stale=` with a `fix: ` note, run the same command without `--check` (900 s timeout), or the `fix:` command when it names another, once before the next workflow; `behind=1` or more always calls for it. `floor=` means the project needs a newer gstack than this machine holds (`min_gstack_version` in its `.gstack/ship-policy.json`), and `stale=h` that the checkout has lacked an origin commit for over 24h; a `floor=` note saying `no release meets this floor yet` has no fix: report it. A pin with a recorded reason (below) holds: `stale=h (pinned: )`, or a `fix:` starting `unset GSTACK_CAPY_`, which you skip. So does `upgrade skipped: … ()`, a refused install naming the commands: run them once its cause clears, working on the certified revision meanwhile. A thread older than a day reruns the check before its next workflow. Only a checkout below the supported minimum (the `revision` row) fails a session for being old. Report the trailer verbatim. 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. Install mode fast-forwards a clean checkout, reinstalls and rechecks in one locked step, but never moves a dirty or diverged checkout or one a live session lease or eval run holds (`UPGRADE_BUSY`); a pin (`--revision`, detached, another branch, `GSTACK_CAPY_SELF_UPGRADE=off`, `GSTACK_CAPY_AUTO_UPGRADE=0`) holds past 24 hours only with a recorded reason (`GSTACK_PIN_REASON=` or `git -C config gstack.pinReason `), else the install overrides it (`PIN_UNREASONED`). Details: `docs/capy-install.md` in the checkout. 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 ` (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. diff --git a/capy/skill/gstack/lane-card.md b/capy/skill/gstack/lane-card.md index 6131f60f7..85ee288e2 100644 --- a/capy/skill/gstack/lane-card.md +++ b/capy/skill/gstack/lane-card.md @@ -4,7 +4,7 @@ For a crew lane (a thread working for a Captain). Read this once `gstack: ok `. Never re-type the assignments as `export $(...)`, and never run `env`, `printenv`, a bare `set` or a bare `export`: they print API keys into the transcript. Hand a child agent the file path, never the values. - Every subagent runs its own `--check` and puts its trailer in its first report; a read-only reviewer uses `--for review-voice`. One machine's install never covers another. diff --git a/changelog.d/fix-behind-trailer-and-rebase-docs.md b/changelog.d/fix-behind-trailer-and-rebase-docs.md new file mode 100644 index 000000000..940499c9e --- /dev/null +++ b/changelog.d/fix-behind-trailer-and-rebase-docs.md @@ -0,0 +1 @@ +- **The Capy install docs now agree on what a `behind=` trailer means, and the unattended rules say a moved base is merged in, never rebased.** The Capy skill said both "run the fix" and "`behind=` alone is informational" for the same trailer, and most lanes took the second reading and kept working on an old gstack. Now `behind=1` or more means run the trailer's fix before the next workflow, everywhere the rule is written. A pin with a recorded reason still holds, and so does an install that was refused. docs/unattended.md no longer names a slot call as a reason to rebase; it points at docs/ship-policy.md, which says to merge the base into a reviewed branch. (GST-2136) diff --git a/docs/capy-install.md b/docs/capy-install.md index 4a7cd6568..9c6e40e5a 100644 --- a/docs/capy-install.md +++ b/docs/capy-install.md @@ -137,8 +137,11 @@ there is refused with the usual `--for ` fix. trailer: `behind= (latest: ); fix: ` (see the trailer grammar below). **Daily (R75).** When the oldest origin commit the checkout lacks is more - than 24 hours old, the trailer adds `stale=h` after `behind=`, and - unlike a bare `behind=` the agent runs the fix before the first workflow. + than 24 hours old, the trailer adds `stale=h` after `behind=`. Either + way the agent runs the fix before the next workflow: `behind=1` or more + always calls for it, and `stale=` adds that a pin with no recorded reason + no longer holds. A `fix:` that starts `unset GSTACK_CAPY_` lifts a pin: + skip it while the pin has a recorded reason. A pin (`GSTACK_CAPY_SELF_UPGRADE=off`, `GSTACK_CAPY_AUTO_UPGRADE=0`, a detached HEAD or another branch) then holds only with a recorded reason: `GSTACK_PIN_REASON=`, `git -C config gstack.pinReason `, diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 74521a121..78d465972 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -1797,10 +1797,12 @@ revision, and the trailer stays `gstack: ok` with the notes above: a gap above the minimum is never a hard fail. The verdict records the installed revision, so a lane that is behind reports it (`behind=` rides every report). -**Fix.** When the lane needs the newer gstack (a `floor=` note, or a fix that -landed upstream), clear the named cause and run the fix: `bin/gstack-upgrade` -after the refusal is resolved, or the Capy skill installer again once the -lease holder finishes. Below the supported minimum the bootstrapper fails +**Fix.** A `behind=` of 1 or more calls for the newer gstack before the next +workflow: clear the named cause, then run `bin/gstack-upgrade` after a refusal +is resolved, or the Capy skill installer again once the lease holder finishes. +While the cause is not yours to clear (another session's lease, an open /ship +or review run), report the trailer and work on the certified revision until it +clears. Below the supported minimum the bootstrapper fails `revision` instead (`REVISION_BELOW_MINIMUM`). @@ -1815,8 +1817,8 @@ with no recorded reason. It checked out the default branch where needed and fast-forwarded. A dirty or diverged checkout is still never moved, and a live workflow lease, eval run or open /ship or review run still defers the upgrade (`UPGRADE_BUSY`). On -`--check` the trailer carries `stale=h` with the install as its fix; unlike a -bare `behind=`, run that fix before the first workflow. +`--check` the trailer carries `stale=h` with the install as its fix; run that +fix before the next workflow, as for any `behind=` of 1 or more. **Fix.** Nothing, when the pin was not deliberate. To keep a pin, record why: `GSTACK_PIN_REASON=` in the session's environment, or diff --git a/docs/unattended.md b/docs/unattended.md index c39f807c2..ced169259 100644 --- a/docs/unattended.md +++ b/docs/unattended.md @@ -1011,18 +1011,22 @@ and `status: "pending"`, named in the handoff, never taken. enabling auto-merge, adding the PR to a merge queue, or `gh pr ready` on a PR the run did not open. - **Force-push: its own branch only, after a rebase the plan named, with a - pinned lease.** The branch is one the run created or was handed in its - prompt, never the base. The rebase is one the plan, the slot call or a - `/modernize` disposition names before it happens, not one the run decides - on its own; `git range-diff .. ..` + pinned lease.** A base that moved is merged in, never rebased: a reviewed + branch, a slot stamp and a gate-ahead lane all take a merge of the base + ([ship-policy.md](ship-policy.md#carrying-a-receipt-across-a-base-merge-or-a-test-only-fix), + [captain.md](../crew/docs/captain.md)). The only rebase is one the plan or + a `/modernize` disposition names before it happens, never one the run + decides on its own, on a branch the run created or was handed in its + prompt, never the base. Rebasing a branch that already holds a receipt + costs a fresh /ship unless the receipt's tree carries; `git range-diff .. ..` runs before the push and its output goes in the handoff. The push is `git push --force-with-lease=: origin `: a bare `--force-with-lease` protects only against commits this clone never fetched, and a plain `--force` is never allowed. A rejected lease means the remote moved: fetch, read what landed, and start the disposition again. `/ship` itself never rebases or force-pushes, and an unattended `/modernize` - stops at its verdict; this route is for a run whose plan or slot call named - the rebase before it started. + stops at its verdict; this route is for a run whose plan named the rebase + before it started. - **Tests: the methods the repository's instructions name, without asking first.** AGENTS.md or CLAUDE.md, the named Setup commands, `package.json` scripts and the pregate configuration define the set; running one of them diff --git a/lib/result-codes-capy.ts b/lib/result-codes-capy.ts index fe279bc6e..2f16da8d8 100644 --- a/lib/result-codes-capy.ts +++ b/lib/result-codes-capy.ts @@ -87,7 +87,7 @@ export const CAPY_RESULT_CODES = { UPGRADE_SKIPPED: { anchor: 'upgrade-skipped', summary: 'the install-mode session was behind origin and its session-start fast-forward was skipped (dirty, diverged or pinned checkout, a live lease, or gstack-upgrade missing); the row is a WARN with the reason and the trailer stays ok with a behind= note, because a gap above the supported minimum is never a hard fail', - fix: 'run the fix the row names (bin/gstack-upgrade after clearing its refusal, or the Capy skill installer) when the lane needs the newer gstack; below the supported minimum the bootstrapper fails revision instead', + fix: 'run the fix the row names (bin/gstack-upgrade after clearing its refusal, or the Capy skill installer) before the next workflow, since any behind= of 1 or more calls for it; below the supported minimum the bootstrapper fails revision instead', }, PLAN_RECORD_MISSING: { anchor: 'plan-record-missing', diff --git a/test/capy-skill-wrapper.test.ts b/test/capy-skill-wrapper.test.ts index ca9c331fa..9a772e0f8 100644 --- a/test/capy-skill-wrapper.test.ts +++ b/test/capy-skill-wrapper.test.ts @@ -786,9 +786,9 @@ describe('capy skill: a checkout more than 24 hours behind origin (R75)', () => return f; }; - // Value: protects=a checkout whose oldest missing commit is over 24h old is flagged stale on --check with the fast-forwarding install as its fix (no longer - // a bare informational behind=), and the install at the workflow boundary fast-forwards it; an hour behind stays plain behind=; fails_when=--check - // leaves a day-old checkout informational, or the boundary install does not move it; why_new=R75; seam=C's commit date; no_claim=GitHub's main + // Value: protects=a checkout whose oldest missing commit is over 24h old is flagged stale on --check with the fast-forwarding install as its fix, + // and the install at the workflow boundary fast-forwards it; an hour behind stays plain behind=; fails_when=--check + // leaves a day-old checkout unflagged, or the boundary install does not move it; why_new=R75; seam=C's commit date; no_claim=GitHub's main test('25 hours behind: --check notes stale=25h with the install as its fix and moves nothing; the install fast-forwards; an hour behind is not stale', () => { const f = behindBy(25); const check = f.run(['--check', '--for', 'autoplan']);