fix(#503): detect local Antigravity (.agent) installs in /gsd:update (#512)

* fix(#503): detect local Antigravity (.agent) installs in /gsd:update

The installer places local Antigravity installs in ./.agent/
(bin/install.js getDirName('antigravity') === '.agent'), but the
/gsd:update detection cascade in update.md only knew the global
Antigravity layout (.gemini/antigravity{,-ide,-cli}). A local .agent
install fell through to the `Otherwise -> claude` default, so the
update refreshed Claude artifacts instead of the Antigravity install.

Add `.agent` -> antigravity to all four runtime-dir surfaces in
update.md: the execution_context path classifier, the RUNTIME_DIRS
candidate array, the local-scope discovery loop, and the post-update
cache-clear loop (the last otherwise left a stale update indicator on
local Antigravity installs).

Note: the issue also cited update-context.cjs, which does not exist on
`next` (it is introduced by the still-open PR #499). The same .agent
fix should be carried into update-context.cjs when #499 lands.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(#503): add changeset for Antigravity .agent detection fix

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-05-30 12:00:55 -04:00
committed by GitHub
parent 3b4f293825
commit b0c5d86aba
3 changed files with 84 additions and 3 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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()}`
);
}
});
});