From 93d73bf755da71a16a942f47e626fcbe8cbfdb07 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Sun, 1 Nov 2026 00:41:10 -0700 Subject: [PATCH] v1.92.18.0 fix(install): --project resolves to its git toplevel and refuses a repo slug (GST-743) (#3783) * test(install): a --project repo slug is a usage error, not a project-less verdict (GST-743, failing first) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(install): --project refuses a value that is not a directory (GST-743) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pre-landing review fixes (GST-743: key --project by its git toplevel, refuse a non-directory before cloning, ignore CDPATH) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pre-landing review fixes (GST-743: --project resolves to its git toplevel for pins and the key; an unenterable directory is refused) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * changelog: GST-743 fragment names the subpackage-pin and admission-key behavior Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: capy-install names how --project resolves (GST-743) 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> --- CHANGELOG.md | 4 ++++ VERSION | 2 +- agents-digest/gstack-AGENTS.md | 2 +- bin/gstack-capy-install | 3 ++- docs/capy-install.md | 7 ++++++- package.json | 2 +- test/gstack-capy-install.test.ts | 24 ++++++++++++++++++++++++ 7 files changed, 39 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c2134832..e8bf19dcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [1.92.18.0] - 2026-11-01 + +- `install.sh --project` (through `gstack-capy-install`) resolves the directory it names to its git toplevel, which is where pregate admission looks for the install verdict. `--project /` now writes, and reads pins from, the same place as `--project `, so a Bun pin declared only in a subpackage no longer applies. A value that isn't a directory, such as a repo slug or a directory this shell can't enter, is a usage error that asks for the checkout's path, and it stops before anything is cloned or installed. Before this change, a slug or a subdirectory keyed the verdict somewhere admission never looks, so a later pregate admission check read a stale verdict (GST-743). + ## [1.92.17.0] - 2026-11-01 - `gstack-review-dispatch pass` takes the readers' diff base from the merge base with `origin/`, the same base the review plan scores and the targeted reader checks. Before, a local `` branch that lagged `origin/` gave every brief the stale merge base, so reviewers read upstream commits the branch only inherited (GST-716). diff --git a/VERSION b/VERSION index 5d570f356..96029e617 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.92.17.0 +1.92.18.0 diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md index 4803fadf7..b5fdacc28 100644 --- a/agents-digest/gstack-AGENTS.md +++ b/agents-digest/gstack-AGENTS.md @@ -1,4 +1,4 @@ -# gstack digest v1.92.17.0 — regenerate/re-copy after upgrading gstack +# gstack digest v1.92.18.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, diff --git a/bin/gstack-capy-install b/bin/gstack-capy-install index 4afc9a153..9bad871a7 100755 --- a/bin/gstack-capy-install +++ b/bin/gstack-capy-install @@ -70,6 +70,7 @@ while [ $# -gt 0 ]; do done [ -n "$FOR" ] && [ -n "$REQUIRE" ] && { echo "gstack-capy-install: --for and --require are exclusive" >&2; exit 2; } [ -z "$FOR" ] && [ -z "$REQUIRE" ] && REQUIRE="claude,codex,patch" +[ -z "$PROJECT" ] || { _given=$PROJECT; PROJECT=$(CDPATH= cd -- "$_given" 2>/dev/null && { git rev-parse --show-toplevel 2>/dev/null || pwd -P; }) || { echo "gstack-capy-install: --project takes a directory path; '$_given' is not a directory (pass the project checkout's path, not a repo slug)" >&2; exit 2; }; } say() { echo "gstack-capy-install: $*"; } stop() { # CODE MESSAGE FIX — print and exit 3 before ./setup @@ -148,7 +149,7 @@ STORE=$(gstack_bun_store "$GSTACK_STATE_ROOT") done INSTALL_VERSION=$(gstack_bun_install_version) || stop BUN_INSTALL_VERSION_INVALID "GSTACK_BUN_INSTALL_VERSION=$INSTALL_VERSION is not a verified Bun at or above gstack's supported minimum $GSTACK_BUN_TESTED (verified archives: $GSTACK_BUN_VERIFIED)" "unset GSTACK_BUN_INSTALL_VERSION, or set it to one of: $GSTACK_BUN_VERIFIED" [ -n "$PROJECT" ] || { PROJECT=$(cd "$ORIG_PWD" && git rev-parse --show-toplevel 2>/dev/null || true); [ "$(cd "${PROJECT:-/nonexistent}" 2>/dev/null && pwd -P)" = "$ROOT_REAL" ] && PROJECT=""; } -PROJECT_REAL=""; [ -n "$PROJECT" ] && PROJECT_REAL=$(cd "$PROJECT" 2>/dev/null && pwd -P) +PROJECT_REAL=""; [ -n "$PROJECT" ] && PROJECT_REAL=$(cd "$PROJECT" && pwd -P) && [ "$PROJECT_REAL" = "$ROOT_REAL" ] && PROJECT_REAL="" VERDICT=$(gstack_install_verdict_path "$GSTACK_STATE_ROOT" "$ROOT_REAL" "$PROJECT_REAL") REC_BUN="" REC_VER="" REC_RULE="" REC_BUN_INSTALL="" REC_FOR="" if [ -f "$VERDICT" ]; then diff --git a/docs/capy-install.md b/docs/capy-install.md index 22a3481b0..1c1a1f0b3 100644 --- a/docs/capy-install.md +++ b/docs/capy-install.md @@ -241,7 +241,12 @@ keeps the lease. `bin/gstack-lease list` shows `alive|dead|stale`, and clone too, through `GSTACK_CAPY_CLONED`). 2. Preflight: `git`, `curl`, `node`, `jq`, then Bun. The project's pins (`--project `, default the git toplevel of the current directory) are - read with the doctor's own parser (`bin/gstack-runtime-pins.ts`: engines, + read (a `--project` inside a repository resolves to that repository's git + toplevel, so `--project /` means ``; outside a repository + it resolves to the directory's real path, and a value that is not a + directory, such as a repo slug, exits 2 with a usage error before anything + runs; the pins, the doctor and the install verdict all use the resolved + directory) with the doctor's own parser (`bin/gstack-runtime-pins.ts`: engines, packageManager, `.tool-versions`, `.bun-version` and CI `bun-version:` lanes for this platform). CI exact pins for this platform are alternatives: a repository may test several Bun versions (gstack's own CI pins 1.4.2 and diff --git a/package.json b/package.json index 3188b9f75..5aa2064c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.92.17", + "version": "1.92.18", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module", diff --git a/test/gstack-capy-install.test.ts b/test/gstack-capy-install.test.ts index 491b1ae20..b24c8fff7 100644 --- a/test/gstack-capy-install.test.ts +++ b/test/gstack-capy-install.test.ts @@ -156,6 +156,30 @@ describe('gstack-capy-install', () => { expect(run(f, ['--revision']).status).toBe(2); }); + // Value: protects=--project names the project the verdict and launch file are keyed to; + // fails_when=a repo slug (owner/name) that is no directory silently keys the verdict to no project, so a later check reads a stale one; + // why_new=GST-743, HARN-12 lost three pregate runs to it; seam=bin/gstack-capy-install --project; no_claim=resolving slugs + test('--project that is not a directory (a repo slug) is a usage error: exit 2, no setup, no verdict', () => { + const f = fixture(); + const r = run(f, ['--for', 'ship', '--project', 'garrytan/harness-evals']); + expect([r.status, r.stderr.trim()]).toEqual([2, "gstack-capy-install: --project takes a directory path; 'garrytan/harness-evals' is not a directory (pass the project checkout's path, not a repo slug)"]); + expect(f.calls().filter(l => l.startsWith('setup') || l.startsWith('doctor '))).toEqual([]); + expect(fs.existsSync(path.join(f.home, '.gstack-state/install-verdicts'))).toBe(false); + }); + + // Value: protects=the installer keys the verdict by the project's git toplevel, the key pregate admission reads (lib/install-verdict.ts installProject); + // fails_when=--project / writes a verdict at the subdirectory's key, which admission never reads; seam=bin/gstack-capy-install --project + test('--project names a subdirectory: the verdict is keyed by its git toplevel, the same file --project writes', () => { + const f = fixture(); + const project = path.join(f.base, 'proj'); + fs.mkdirSync(path.join(project, 'packages/x'), { recursive: true }); + sh('git init -q', project); + const verdictOf = (r: { stdout: string; status: number | null; stderr: string }) => { expect(r.status, r.stdout + r.stderr).toBe(0); return r.stdout.match(/GSTACK_INSTALL_VERDICT=(\S+)/)![1]!; }; + const sub = verdictOf(run(f, ['--for', 'autoplan', '--project', path.join(project, 'packages/x')])); + expect(sub).toBe(verdictOf(run(f, ['--for', 'autoplan', '--project', project]))); + expect(JSON.parse(fs.readFileSync(sub, 'utf8')).project).toBe(fs.realpathSync(project)); + }); + test('preflight: a missing tool stops with exit 3 and INSTALL_PREFLIGHT_FAILED before ./setup runs', () => { const f = fixture({ jq: false }); const r = run(f);