mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-06 00:44:07 +01:00
fix: pre-landing review fixes
Red team (/ship Step 9): a Drive copy at a bootstrapper revision below GSTACK_CAPY_MIN_WRAPPER is a WARN with the owner action in a direct (no-provenance) run too; only a pre-bootstrapper copy fails. A --for none session never self-upgrades. Stale fail-behind comment in gstack-capy-session; SKILL.md says ok not-needed is not an install verdict. Tests: UPGRADE_SKIPPED collapse, silent in-place message, direct-run WARN, wrapper-2 --for none handover. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
6a0f9e47bb
commit
fc69d3ff0b
10 files changed
+72
-14
No files matched your search
+5
-2
@@ -5,7 +5,7 @@
|
||||
**The session check stops failing for things that are not failures, and a consuming repository can name the gstack it wants without broadcasting to every thread.** Five Captains running gstack on Capy hit the same install friction this week: eight crew-wide "upgrade to <version>" messages since Oct 16, `gstack: fail not-installed` on coordinator machines that run no workflow, `fail pins` from the image's Node while the tests run on the pinned one, reviewers told to "reinstall" when asked to run a workflow, and an environment dump from a broken `/cso` shell line. Each of these is now a product behaviour instead of a crew rule.
|
||||
|
||||
### Added
|
||||
- **Project floor** (`floor` row in `gstack-doctor --check`, `lib/doctor-components.ts`, `bin/gstack-doctor-check.sh`): a consuming repository declares the oldest gstack it wants its crew on as `min_gstack_version` in its `.gstack/ship-policy.json` (the key `gstack-ship-queue` already validates; a file holding only that key is valid). The doctor reads it from the project the check runs in: `PASS floor <f> declared by <file>`, `SKIP floor (... declares none)`, or `WARN floor — installed <v> is below the floor <f> ...; fix: <install command>` (`PROJECT_FLOOR_UNMET`). The Capy installer adds `floor=<f> (<file>)` and a `fix:` note to its ok trailer. One commit in the project replaces a message to every thread; the fix on Capy is the install command, which fast-forwards a clean checkout to origin in one step.
|
||||
- **Project floor** (`floor` row in `gstack-doctor --check`, `lib/doctor-components.ts`, `bin/gstack-doctor-check.sh`): a consuming repository declares the oldest gstack it wants its crew on as `min_gstack_version` in its `.gstack/ship-policy.json` (the field `gstack-ship-queue` already validates; a file holding only that field is valid). The doctor reads it from the project the check runs in: `PASS floor <f> declared by <file>`, `SKIP floor (... declares none)`, or `WARN floor — installed <v> is below the floor <f> ...; fix: <install command>` (`PROJECT_FLOOR_UNMET`). The Capy installer adds `floor=<f> (<file>)` and a `fix:` note to its ok trailer. One commit in the project replaces a message to every thread; the fix on Capy is the install command, which fast-forwards a clean checkout to origin in one step.
|
||||
- **`--for none`** (`capy/skill/gstack/scripts/install.sh` wrapper 3, `bin/gstack-capy-install`): a machine that runs no gstack workflow (a coordinator, a liaison, a Captain) prints `gstack: ok not-needed for=none (<notes>)` and exits 0, installing, moving and certifying nothing, with or without a checkout. The bootstrapper answers it itself, so the line is honest for any checkout revision. That ok admits no workflow: no install verdict is written, so a workflow started there is refused with the usual `--for <skill>` fix. `GSTACK_CAPY_MIN_WRAPPER` is 3; older Drive copies keep working and print the owner action.
|
||||
- **Review-voice machines upgrade in place** (`bin/gstack-capy-install`): running `--for <skill>` on a machine installed `--for review-voice` says `upgrading the review-voice install in place to --for <skill> (same checkout ...; no second clone)`, applies the project's pins, runs `./setup` and both host profiles, and writes the workflow's verdict over the reviewer's. The admission refusal's fix now says so (`upgrades this checkout in place: ./setup and host profiles, no second clone`) instead of reading like a reinstall.
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
- **A gap above the supported minimum is never a hard fail** (`bin/gstack-capy-install`): an install-mode session whose session-start fast-forward was skipped (dirty, diverged or pinned checkout, a live lease, `gstack-upgrade` missing) prints `WARN behind — ... and the session-start upgrade was skipped: <reason>; fix: <command> (UPGRADE_BUSY|UPGRADE_REFUSED|UPGRADE_SKIPPED)` and keeps its ok trailer with `behind=<n> (latest: <ver>); upgrade skipped: <reason> (<CODE>); fix: <command>` notes, exit 0. It used to be `gstack: fail behind`, exit 1. Below the supported minimum the bootstrapper still fails `revision` (`REVISION_BELOW_MINIMUM`).
|
||||
- **A Node-only pin mismatch is advisory** (`bin/gstack-doctor-check.sh`): when the project's Node pin is unmet by the Node on PATH while every Bun pin is met, `pins` is `WARN pins — ... (node pin only; bun pins pass); fix: ... (PROJECT_NODE_PIN_UNMET)` and the trailer stays ok. gstack runs on Bun and the project's tests choose their Node (nvm, the CI matrix). A Bun pin that is unmet stays `FAIL pins` (`PROJECT_PIN_MISMATCH`).
|
||||
- **WARN rows print their action as given** (`_dc_row`): `owner action: ...` for the Capy skill copy, `fix: ...` for the floor, a skipped upgrade and a Node pin, so a lane knows which WARNs it can clear itself.
|
||||
- **A Drive copy below the wrapper minimum is a WARN in a direct run too** (`bin/gstack-doctor-check.sh`): with no bootstrapper provenance (`bin/gstack-upgrade`'s installer run, a direct `gstack-doctor --check`), every Drive copy at a bootstrapper revision below `GSTACK_CAPY_MIN_WRAPPER` is `WARN wrapper — every Capy skill copy is below the minimum wrapper 3 (it still works): ...` with the owner action and an ok trailer; only a pre-bootstrapper copy (no `GSTACK_CAPY_WRAPPER=` line) still fails. Found by /ship's red team: raising the minimum to 3 would otherwise have failed every direct run on a machine whose Drive copy is the deployed wrapper 2 until the owner recopied the skill.
|
||||
- **A `--for none` session never self-upgrades** (`bin/gstack-capy-session`): an older bootstrapper (wrapper 1 or 2) hands `--for none` to the session in install mode; the session now treats it like `--check` (reports `behind=`, moves nothing) before the installer prints `ok not-needed`. Wrapper 3 answers `--for none` before the session. The session's header comment describes the `WARN behind` outcome (it said `fail behind`).
|
||||
- **/cso** (`cso/SKILL.md.tmpl`): the always-loaded startup block gains two rules. Environment hygiene: prerequisite checks read a variable as set or unset only, and `env`, `printenv`, `set -x`, a bare `set` or `export`, `export $(...)` and `echo "$NAME"` never appear in a tool fence. Tooling errors fail loudly: a non-zero exit with no helper status is retried at most twice (5 s, 30 s) and then ends **not assessed** for whatever the helper never recorded, quoting the command, exit code and stderr tail; never a quiet partial, never another review offered as the missing coverage.
|
||||
- **Capy skill and docs** (`capy/skill/gstack/SKILL.md`, `docs/capy-install.md`): the session-start steps cover `--for none`, `floor=`, `behind=` as informational, the Node WARN and the in-place review-voice upgrade; run directories and scratch on Capy live under `~/.capy/work/<name>`, never `/tmp`, which Capy wipes when a machine sleeps.
|
||||
- **Result codes** (`lib/result-codes-capy.ts`): `PROJECT_FLOOR_UNMET`, `PROJECT_NODE_PIN_UNMET` and `UPGRADE_SKIPPED`, each with a `docs/troubleshooting.md` anchor.
|
||||
@@ -20,7 +22,8 @@
|
||||
### Tests
|
||||
- `test/gstack-doctor.test.ts`: the `floor` row's four outcomes (absent file, null key, met, unmet with the wrapper fix and without it) and the Node-only WARN beside a Bun-plus-Node FAIL; every required-row list gains `floor`.
|
||||
- `test/gstack-capy-install.test.ts`: a skipped session-start upgrade is an ok trailer with the `upgrade skipped` note and a `WARN behind` row (replacing the `fail behind` pin); a `WARN floor` row becomes the `floor=` and `fix:` notes, with one `fix:` when `behind=` is also present; `--for none` with and without a checkout; a review-voice verdict upgraded in place by `--for autoplan` (setup runs, the verdict becomes the workflow's).
|
||||
- `test/capy-skill-wrapper.test.ts`: `--for none` through the bootstrapper (checkout present, install mode, no checkout), wrapper 3 in the handover.
|
||||
- `test/capy-skill-wrapper.test.ts`: `--for none` through the bootstrapper (checkout present, install mode, no checkout), wrapper 3 in the handover; a wrapper-2 handover of `--for none` in install mode leaves HEAD where it was.
|
||||
- `test/gstack-doctor.test.ts`: a direct run with every Drive copy a bootstrapper below the minimum is a WARN with an ok trailer, and a pre-bootstrapper copy beside it keeps the FAIL. `test/gstack-capy-install.test.ts`: an unknown skip code collapses to `UPGRADE_SKIPPED` with its anchor; the in-place upgrade message is silent for a same-profile rerun, `--check` and a second review-voice install.
|
||||
- `test/helpers/carve-guards.ts`: the /cso skeleton pin moves to 18,900 (measured 18,843) for the two startup paragraphs; `test/fixtures/context-budget.json` pins `capy/skill/gstack` and `cso` at their new eager sizes.
|
||||
|
||||
### Gate integrity
|
||||
|
||||
@@ -35,8 +35,10 @@
|
||||
# "; " (the note separator): after a refused self-upgrade it is bin/gstack-upgrade,
|
||||
# which prints its own refusal and fix. A skipped self-upgrade (refused, busy,
|
||||
# or gstack-upgrade missing) also exports GSTACK_CAPY_BEHIND_BLOCKED=<reason; fix: …
|
||||
# (CODE)>, and the installer turns an ok trailer into `gstack: fail behind`: a
|
||||
# session that meant to upgrade and could not is never certified ok.
|
||||
# (CODE)>, and the installer adds a `WARN behind` row with that reason and code:
|
||||
# the trailer stays ok (the installed revision is above the supported minimum),
|
||||
# and the note names the fix. A `--for none` session certifies nothing and moves
|
||||
# nothing, so it never self-upgrades (it behaves like --check).
|
||||
#
|
||||
# Usage: gstack-capy-session [--check] --root <checkout> [gstack-capy-install arguments]
|
||||
# --check certify only (bin/gstack-capy-install --check: the doctor with the
|
||||
@@ -53,6 +55,7 @@ for a in "$@"; do
|
||||
[ "$a" = --check ] && CHECK=1
|
||||
prev="$a"
|
||||
done
|
||||
[ "$SKILL" = none ] && CHECK=1
|
||||
INSTALLER="$ROOT/bin/gstack-capy-install"
|
||||
if [ ! -x "$INSTALLER" ]; then
|
||||
echo "gstack: fail installer — $INSTALLER is missing; fix: bash ${GSTACK_CAPY_WRAPPER_PATH:-<skill>/scripts/install.sh} (re-clones or fast-forwards)"
|
||||
|
||||
@@ -527,7 +527,7 @@ _dc_wrapper_rev() {
|
||||
# ~/.capy/drive/*/skills/gstack/scripts/install.sh, failing only when each is
|
||||
# stale. The workaround runs the checkout's own copy, which certifies.
|
||||
_dc_check_wrapper() {
|
||||
local min copy rev stale="" current="" active="${GSTACK_CAPY_WRAPPER_PATH:-}" workaround owner
|
||||
local min copy rev stale="" current="" pre="" active="${GSTACK_CAPY_WRAPPER_PATH:-}" workaround owner
|
||||
min=$(sed -n 's/^GSTACK_CAPY_MIN_WRAPPER=\([0-9][0-9]*\)[[:space:]]*$/\1/p' "$SRC/capy/support.env" 2>/dev/null | head -1); min="${min:-1}"
|
||||
workaround="bash $SRC/capy/skill/gstack/scripts/install.sh --check${CHECK_FOR:+ --for $CHECK_FOR} (this session)"
|
||||
owner="owner action: copy $SRC/capy/skill/gstack/ over the Drive skill folder, or have Capy re-sync it; this recurs every session until the Drive copy changes"
|
||||
@@ -536,10 +536,12 @@ _dc_check_wrapper() {
|
||||
[ -n "${GSTACK_CAPY_WRAPPER_REV:-}" ] && [ "$copy" = "$active" ] && continue
|
||||
[ -z "${GSTACK_CAPY_WRAPPER_REV:-}" ] && [ -n "$active" ] && [ -f "$active" ] && [ "$copy" != "$active" ] && continue
|
||||
rev=$(_dc_wrapper_rev "$copy")
|
||||
[ "$rev" -eq 0 ] 2>/dev/null && pre=1
|
||||
if [ "$rev" -lt "$min" ] 2>/dev/null; then stale="${stale:+$stale, }$copy (wrapper $rev)"; else current="${current:+$current, }$copy (wrapper $rev)"; fi
|
||||
done
|
||||
if [ -z "${GSTACK_CAPY_WRAPPER_REV:-}" ] && [ -n "$active" ] && [ -f "$active" ] && [ -z "$stale$current" ]; then
|
||||
rev=$(_dc_wrapper_rev "$active")
|
||||
[ "$rev" -eq 0 ] 2>/dev/null && pre=1
|
||||
if [ "$rev" -lt "$min" ] 2>/dev/null; then stale="$active (wrapper $rev)"; else current="$active (wrapper $rev)"; fi
|
||||
fi
|
||||
if [ -n "${GSTACK_CAPY_WRAPPER_REV:-}" ]; then
|
||||
@@ -552,6 +554,10 @@ _dc_check_wrapper() {
|
||||
fi
|
||||
elif [ -n "$current" ]; then
|
||||
_dc_row PASS wrapper "Drive copy $current (minimum $min)${stale:+; stale copies: $stale}"
|
||||
elif [ -n "$stale" ] && [ -z "$pre" ]; then
|
||||
# Every copy is a bootstrapper below the minimum: it still works (a direct
|
||||
# run has no provenance to say so), so the row is the owner action, not a FAIL.
|
||||
_dc_row WARN wrapper "every Capy skill copy is below the minimum wrapper $min (it still works): $stale" "$owner" CAPY_WRAPPER_STALE
|
||||
elif [ -n "$stale" ]; then
|
||||
_dc_row FAIL wrapper "every Capy skill copy is stale (minimum wrapper $min): $stale" "$workaround; owner: ${owner#owner action: }" CAPY_WRAPPER_STALE
|
||||
else
|
||||
|
||||
@@ -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. Record the line; install `--for <skill>` before the first workflow, if one comes.
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -506,8 +506,10 @@ floor is not what keeps machines current). It never exceeds VERSION
|
||||
changes in a way older copies cannot work with; older Drive copies keep
|
||||
working and print an owner action
|
||||
([CAPY_WRAPPER_STALE](troubleshooting.md#capy-wrapper-stale)), and the
|
||||
doctor's `wrapper` row warns (an active copy below the minimum is a WARN,
|
||||
never a FAIL; `bin/gstack-upgrade` never leaves a pending reinstall for it).
|
||||
doctor's `wrapper` row warns (a bootstrapper copy below the minimum is a WARN,
|
||||
active or found in the Drive by a direct run, never a FAIL; only a
|
||||
pre-bootstrapper copy with no `GSTACK_CAPY_WRAPPER=` line fails;
|
||||
`bin/gstack-upgrade` never leaves a pending reinstall for it).
|
||||
Wrapper 2 only added optional exports (the trailer's `latest:` note and the
|
||||
`cloned` line). Wrapper 3 answers `--check --for none` itself (a copy at 1 or 2
|
||||
prints `fail not-installed` for a coordinator machine and still works for every
|
||||
|
||||
+10
-5
@@ -1574,8 +1574,10 @@ every lease with its age and `alive|dead|stale`; a holder is named as
|
||||
When you know they are idle, `GSTACK_UPGRADE_FORCE=1 bin/gstack-upgrade`
|
||||
proceeds. At a Capy session start the same refusal appears as
|
||||
`SELF_UPGRADE: skipped (live gstack sessions hold this installation: ...)`,
|
||||
and the trailer is `gstack: fail behind — … (UPGRADE_BUSY)`: a session that
|
||||
could not upgrade is never certified ok.
|
||||
the doctor adds `WARN behind — … (UPGRADE_BUSY)` and the trailer stays
|
||||
`gstack: ok … (behind=<n> (latest: <v>); upgrade skipped: … (UPGRADE_BUSY); fix: …)`:
|
||||
the installed revision is above the supported minimum, so the session is
|
||||
certified and the note names the fix.
|
||||
|
||||
<a id="upgrade-fetch-failed"></a>
|
||||
### `gstack-upgrade: could not reach origin (...) (UPGRADE_FETCH_FAILED)`
|
||||
@@ -1618,9 +1620,12 @@ action that never fails the run. Without provenance (a direct
|
||||
`gstack-doctor --check`, or a pre-bootstrapper copy with no
|
||||
`GSTACK_CAPY_WRAPPER=` line, which reads as wrapper 0) it reads the Drive
|
||||
copies (`~/.capy/drive/*/skills/gstack/scripts/install.sh`, or only
|
||||
`GSTACK_CAPY_WRAPPER_PATH` when set) and fails only when every copy is stale,
|
||||
listing each with its revision. A stale active wrapper is a `WARN` too: an
|
||||
older copy still works (`capy/support.env`). `bin/gstack-upgrade` runs the
|
||||
`GSTACK_CAPY_WRAPPER_PATH` when set), listing each with its revision: every
|
||||
copy a bootstrapper below the minimum is `WARN wrapper — every Capy skill copy
|
||||
is below the minimum wrapper <m> (it still works): ...` with the owner action,
|
||||
and it fails only when a copy is pre-bootstrapper (wrapper 0), which cannot
|
||||
hand over at all. A stale active wrapper is a `WARN` too: an older copy still
|
||||
works (`capy/support.env`). `bin/gstack-upgrade` runs the
|
||||
installer with the checkout's own copy as the active wrapper and never leaves
|
||||
a pending reinstall when the wrapper row is the only failure.
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
*/
|
||||
|
||||
import { RESULT_CODES } from './result-codes';
|
||||
import { SHIP_POLICY_PATH } from './ship-policy';
|
||||
|
||||
export type ComponentKind = 'core' | 'host' | 'component' | 'optional';
|
||||
|
||||
@@ -100,7 +101,7 @@ export const NOT_NEEDED_PROFILE = 'none';
|
||||
export const NOT_NEEDED_REVISION = 'not-needed';
|
||||
|
||||
/** The project file and key the `floor` row reads (the same key gstack-ship-queue validates). */
|
||||
export const PROJECT_FLOOR_FILE = '.gstack/ship-policy.json';
|
||||
export const PROJECT_FLOOR_FILE = SHIP_POLICY_PATH;
|
||||
export const PROJECT_FLOOR_FIELD = 'min_gstack_version';
|
||||
|
||||
/** The read-only helpers the `helpers` row launches (`<bin> --help` loads each module graph with the launch Bun). */
|
||||
|
||||
@@ -393,6 +393,18 @@ describe('capy skill: the session hands the installer GSTACK_CAPY_BEHIND', () =>
|
||||
}
|
||||
});
|
||||
|
||||
test('an older bootstrapper handing --for none to the session in install mode: the session certifies nothing and moves nothing (no self-upgrade, HEAD unchanged), and reports behind like --check', () => {
|
||||
const f = fixture({ cloneAt: 'B' });
|
||||
write(path.join(f.root, 'bin/gstack-capy-install'), ENV_INSTALLER, 0o755);
|
||||
f.git(['update-index', '--assume-unchanged', 'bin/gstack-capy-install']);
|
||||
f.git(['fetch', '-q', 'origin', 'main']);
|
||||
const r = spawnSync('bash', [path.join(f.root, 'bin/gstack-capy-session'), '--root', f.root, '--for', 'none'], { cwd: f.host.home, encoding: 'utf8', env: { ...f.host.env, GSTACK_CAPY_REMOTE_MIN: '1.0.1', GSTACK_CAPY_WRAPPER_REV: '2', GSTACK_CAPY_WRAPPER_PATH: BOOT }, timeout: 120_000 });
|
||||
expect(r.status, r.stdout + r.stderr).toBe(0);
|
||||
expect(r.stdout).not.toMatch(/^SELF_UPGRADE:/m);
|
||||
expect(r.stdout.trim().split('\n').at(-1)).toBe(`gstack: ok 1.0.1 for=autoplan behind=1 to=1.0.2 fix=bash ${BOOT} --for none`);
|
||||
expect(f.git(['rev-parse', 'HEAD'])).toBe(f.b);
|
||||
});
|
||||
|
||||
test('current, or the fetch failed: nothing is exported, and values inherited from a parent session are dropped', () => {
|
||||
const f = fixture({ cloneAt: 'B' });
|
||||
f.git(['fetch', '-q', 'origin', 'main']);
|
||||
|
||||
@@ -293,6 +293,11 @@ describe('gstack-capy-install', () => {
|
||||
const bare = run(f, [], { ...busy, GSTACK_CAPY_BEHIND_BLOCKED: 'gstack-upgrade exit 9' });
|
||||
expect(bare.status).toBe(0);
|
||||
expect(lastLine(bare.stdout)).toBe(`gstack: ok 1.2.3 for=autoplan (behind=3 (latest: 1.2.6); upgrade skipped: gstack-upgrade exit 9 (UPGRADE_REFUSED); fix: ${f.root}/bin/gstack-upgrade)`);
|
||||
// A code the installer does not know collapses to UPGRADE_SKIPPED, whose anchor exists; the reason and fix are kept.
|
||||
const other = run(f, [], { ...busy, GSTACK_CAPY_BEHIND_BLOCKED: 'origin unreachable; fix: retry when online (UPGRADE_FETCH_FAILED)' });
|
||||
expect(other.status).toBe(0);
|
||||
expect(lastLine(other.stdout)).toBe('gstack: ok 1.2.3 for=autoplan (behind=3 (latest: 1.2.6); upgrade skipped: origin unreachable (UPGRADE_SKIPPED); fix: retry when online)');
|
||||
expect(other.stdout).toMatch(/^WARN behind — .*; fix: retry when online \(UPGRADE_SKIPPED; .*#upgrade-skipped\)$/m);
|
||||
expect(lastLine(run(f, [], { ...busy, STUB_DOCTOR_FAIL: '1' }).stdout)).toBe('gstack: fail codex');
|
||||
});
|
||||
|
||||
@@ -338,6 +343,11 @@ describe('gstack-capy-install', () => {
|
||||
const verdict = full.stdout.match(/GSTACK_INSTALL_VERDICT=(\S+)/)![1]!;
|
||||
expect(JSON.parse(fs.readFileSync(verdict, 'utf8'))).toMatchObject({ for: 'autoplan', ok: true });
|
||||
expect(lastLine(full.stdout)).toBe('gstack: ok 1.2.3 for=autoplan');
|
||||
// The message is for the review-voice -> workflow step only: a rerun of the same profile, a --check and a second review-voice install stay silent.
|
||||
const upgrading = /upgrading the review-voice install in place/;
|
||||
expect(run(f, ['--for', 'autoplan', '--project', project]).stdout).not.toMatch(upgrading);
|
||||
expect(run(f, ['--check', '--for', 'autoplan', '--project', project]).stdout).not.toMatch(upgrading);
|
||||
expect(run(f, ['--for', 'review-voice', '--project', project]).stdout).not.toMatch(upgrading);
|
||||
});
|
||||
|
||||
test('--root clones when the directory is absent and refuses a non-gstack directory', () => {
|
||||
|
||||
@@ -885,6 +885,22 @@ describe('gstack-doctor --check: wrapper and runtime drift', () => {
|
||||
expect(fresh.status, fresh.out).toBe(0);
|
||||
});
|
||||
|
||||
posixPathTest('direct doctor run: a bootstrapper Drive copy below the minimum still works, so it is an owner-action WARN with an ok trailer (only a pre-bootstrapper copy fails)', () => {
|
||||
const f = makeFixture();
|
||||
withSupport(f, Number(currentRev) + 1);
|
||||
const old = drive(f, 'user-garry-tan', current);
|
||||
const r = parse(f, []);
|
||||
expect(r.status, r.out).toBe(0);
|
||||
expect(r.trailer).toMatch(/^gstack: ok /);
|
||||
expect(r.line('wrapper')).toBe(`WARN wrapper — every Capy skill copy is below the minimum wrapper ${Number(currentRev) + 1} (it still works): ${old} (wrapper ${currentRev}); owner action: copy ${f.root}/capy/skill/gstack/ over the Drive skill folder, or have Capy re-sync it; this recurs every session until the Drive copy changes (CAPY_WRAPPER_STALE; https://github.com/garrytan/gstack/blob/main/docs/troubleshooting.md#capy-wrapper-stale)`);
|
||||
const json = JSON.parse(doctor(f, ['--check', '--json']).out);
|
||||
expect(json.ok).toBe(true);
|
||||
expect(json.rows.find((x: { id: string }) => x.id === 'wrapper')).toMatchObject({ status: 'WARN', code: 'CAPY_WRAPPER_STALE' });
|
||||
// A pre-bootstrapper copy beside it keeps the FAIL: that copy cannot hand over at all.
|
||||
drive(f, 'org', PRE_BOOTSTRAPPER);
|
||||
expect(parse(f, []).trailer).toBe('gstack: fail wrapper');
|
||||
});
|
||||
|
||||
posixPathTest('two Drive copies, one current: PASS naming the stale one; GSTACK_CAPY_WRAPPER_PATH alone decides when set without a revision', () => {
|
||||
const f = makeFixture();
|
||||
withSupport(f);
|
||||
|
||||
Reference in new issue
Block a user