diff --git a/.changeset/503-antigravity-agent-local-detection.md b/.changeset/503-antigravity-agent-local-detection.md new file mode 100644 index 000000000..3d723b039 --- /dev/null +++ b/.changeset/503-antigravity-agent-local-detection.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 512 +--- +**`/gsd:update` now detects local Antigravity (`.agent`) installs** — local Antigravity installs live in `./.agent/`, but the update detection cascade in `update.md` only knew the global `.gemini/antigravity{,-ide,-cli}` layout, so a local install was misclassified as `claude` and refreshed Claude artifacts instead. `.agent` → antigravity is now mapped across all four runtime-dir surfaces (path classifier, `RUNTIME_DIRS`, local-scope discovery loop, and the post-update cache-clear loop). (#503) diff --git a/get-shit-done/workflows/update.md b/get-shit-done/workflows/update.md index 3246f4f6f..22ab36fdb 100644 --- a/get-shit-done/workflows/update.md +++ b/get-shit-done/workflows/update.md @@ -17,6 +17,7 @@ First, derive `PREFERRED_CONFIG_DIR` and `PREFERRED_RUNTIME` from the invoking p - Path contains `/.gemini/antigravity-ide/` -> `antigravity` - Path contains `/.gemini/antigravity-cli/` -> `antigravity` - Path contains `/.gemini/antigravity/` -> `antigravity` +- Path contains `/.agent/` -> `antigravity` (local Antigravity install dir; see bin/install.js getDirName('antigravity')) - Path contains `/.gemini/` -> `gemini` - Path contains `/.config/kilo/` or `/.kilo/`, or `PREFERRED_CONFIG_DIR` contains `kilo.json` / `kilo.jsonc` -> `kilo` - Path contains `/.config/opencode/` or `/.opencode/`, or `PREFERRED_CONFIG_DIR` contains `opencode.json` / `opencode.jsonc` -> `opencode` @@ -39,7 +40,7 @@ expand_home() { # Using an array instead of a space-separated string ensures correct # iteration in both bash and zsh (zsh does not word-split unquoted # variables by default). Fixes #1173. -RUNTIME_DIRS=( "claude:.claude" "opencode:.config/opencode" "opencode:.opencode" "antigravity:.gemini/antigravity-ide" "antigravity:.gemini/antigravity-cli" "antigravity:.gemini/antigravity" "gemini:.gemini" "kilo:.config/kilo" "kilo:.kilo" "codex:.codex" ) +RUNTIME_DIRS=( "claude:.claude" "opencode:.config/opencode" "opencode:.opencode" "antigravity:.gemini/antigravity-ide" "antigravity:.gemini/antigravity-cli" "antigravity:.gemini/antigravity" "antigravity:.agent" "gemini:.gemini" "kilo:.config/kilo" "kilo:.kilo" "codex:.codex" ) ENV_RUNTIME_DIRS=() # PREFERRED_CONFIG_DIR / PREFERRED_RUNTIME should be set from execution_context @@ -100,7 +101,7 @@ if [ -n "$PREFERRED_CONFIG_DIR" ] && { [ -f "$PREFERRED_CONFIG_DIR/get-shit-done printf '%s' "$p" } normalized_preferred="$(normalize_path "$PREFERRED_CONFIG_DIR")" - for dir in .claude .config/opencode .opencode .gemini/antigravity-ide .gemini/antigravity-cli .gemini/antigravity .gemini .config/kilo .kilo .codex; do + for dir in .claude .config/opencode .opencode .gemini/antigravity-ide .gemini/antigravity-cli .gemini/antigravity .agent .gemini .config/kilo .kilo .codex; do resolved_local="$(cd "./$dir" 2>/dev/null && pwd)" normalized_local="$(normalize_path "$resolved_local")" if [ -n "$normalized_local" ] && [ "$normalized_local" = "$normalized_preferred" ]; then @@ -606,7 +607,7 @@ for dir in "${CACHE_DIRS[@]}"; do fi done -for dir in .claude .config/opencode .opencode .gemini/antigravity-ide .gemini/antigravity-cli .gemini/antigravity .gemini .config/kilo .kilo .codex; do +for dir in .claude .config/opencode .opencode .gemini/antigravity-ide .gemini/antigravity-cli .gemini/antigravity .agent .gemini .config/kilo .kilo .codex; do rm -f "./$dir/cache/gsd-update-check.json" rm -f "$HOME/$dir/cache/gsd-update-check.json" done diff --git a/tests/bug-503-update-agent-antigravity-detection.test.cjs b/tests/bug-503-update-agent-antigravity-detection.test.cjs new file mode 100644 index 000000000..d503c1d30 --- /dev/null +++ b/tests/bug-503-update-agent-antigravity-detection.test.cjs @@ -0,0 +1,75 @@ +// allow-test-rule: source-text-is-the-product +// update.md is a workflow file whose text IS the contract the runtime loads +// and executes (the embedded bash detection cascade). Asserting on its text +// tests the deployed behavior. Per CONTRIBUTING.md exception matrix. + +/** + * Bug #503: /gsd:update misclassifies local Antigravity (.agent) installs as claude + * + * The installer places a LOCAL Antigravity install in ./.agent/ + * (bin/install.js: getDirName('antigravity') === '.agent'). But the + * /gsd:update detection cascade in get-shit-done/workflows/update.md only + * knew the GLOBAL Antigravity layout (.gemini/antigravity{,-ide,-cli}), so a + * local .agent install fell through to the `Otherwise -> claude` default and + * the update refreshed Claude artifacts instead of the Antigravity install. + * + * The cascade has three coupled detection surfaces; .agent must be mapped to + * antigravity in all three: + * 1. the execution_context path classifier, + * 2. the RUNTIME_DIRS candidate array, + * 3. the LOCAL-scope discovery `for dir in ...` loop. + */ + +const { describe, test } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('fs'); +const path = require('path'); + +const UPDATE_MD = fs.readFileSync( + path.join(__dirname, '..', 'get-shit-done', 'workflows', 'update.md'), + 'utf-8' +); + +describe('/gsd:update detects local Antigravity (.agent) installs (#503)', () => { + test('execution_context classifier maps a /.agent/ path to antigravity', () => { + // A rule line of the form: Path contains `/.agent/` -> `antigravity` + const hasAgentClassifierRule = + /\/\.agent\/[^\n]*->[^\n]*antigravity/.test(UPDATE_MD); + assert.ok( + hasAgentClassifierRule, + 'update.md classifier must map a `/.agent/` path to the `antigravity` runtime' + ); + }); + + test('RUNTIME_DIRS candidate array includes antigravity:.agent', () => { + const runtimeDirsLine = UPDATE_MD + .split('\n') + .find((l) => l.includes('RUNTIME_DIRS=(')); + assert.ok(runtimeDirsLine, 'RUNTIME_DIRS array must exist in update.md'); + assert.ok( + runtimeDirsLine.includes('antigravity:.agent'), + `RUNTIME_DIRS must contain "antigravity:.agent", got: ${runtimeDirsLine}` + ); + }); + + test('every runtime-dir `for dir in` loop includes .agent', () => { + // Both the LOCAL-scope discovery loop AND the post-update cache-clear loop + // enumerate the runtime config dirs as a literal `.claude ... .codex` list. + // The same root cause (.agent missing from the runtime-dir list) breaks + // detection in the first and leaves a stale update indicator in the second, + // so ALL such loops must include .agent. + const runtimeDirLoops = UPDATE_MD + .split('\n') + .filter((l) => /for dir in .*\.claude.*\.codex/.test(l)); + assert.ok( + runtimeDirLoops.length >= 2, + `expected at least 2 runtime-dir loops in update.md, found ${runtimeDirLoops.length}` + ); + for (const loop of runtimeDirLoops) { + assert.ok( + /(^|\s)\.agent(\s|$)/.test(loop), + `every runtime-dir loop must include .agent, got: ${loop.trim()}` + ); + } + }); +});