From 5b3646d6c89d39d8ffeba4dd1de29b14061193b5 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Sun, 24 May 2026 14:17:03 -0400 Subject: [PATCH] fix(#213): support antigravity 2.x config directory split (#217) * fix(#213): support antigravity 2.x config directory split * fix(#213): harden antigravity tests for windows parity --- .changeset/213-antigravity-2-runtime-dirs.md | 5 ++ bin/install.js | 19 +++-- docs/ARCHITECTURE.md | 4 +- docs/USER-GUIDE.md | 2 +- get-shit-done/bin/lib/runtime-homes.cjs | 43 +++++++++- get-shit-done/workflows/update.md | 8 +- sdk/src/query/helpers.test.ts | 17 ++++ sdk/src/query/helpers.ts | 20 ++++- ...6-global-skills-base-runtime-path.test.cjs | 24 ++++++ ...ity-update-runtime-classification.test.cjs | 74 ++++++++++++----- tests/install.test.cjs | 79 ++++++++++++++++++- 11 files changed, 257 insertions(+), 38 deletions(-) create mode 100644 .changeset/213-antigravity-2-runtime-dirs.md diff --git a/.changeset/213-antigravity-2-runtime-dirs.md b/.changeset/213-antigravity-2-runtime-dirs.md new file mode 100644 index 000000000..6aab91b4d --- /dev/null +++ b/.changeset/213-antigravity-2-runtime-dirs.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 213 +--- +Add Antigravity 2.x config-dir compatibility by auto-detecting `~/.gemini/antigravity-ide` and `~/.gemini/antigravity-cli` (with legacy `~/.gemini/antigravity` fallback), then routing installer, SDK runtime config resolution, and `/gsd-update` runtime classification through the same detection model. diff --git a/bin/install.js b/bin/install.js index ba0b68a4f..6e55320f7 100755 --- a/bin/install.js +++ b/bin/install.js @@ -28,6 +28,9 @@ const { transformContentToHyphen, readCmdNames: readGsdCommandNames, } = require(path.join(__dirname, '..', 'scripts', 'fix-slash-commands.cjs')); +const { + resolveAntigravityGlobalDir, +} = require('../get-shit-done/bin/lib/runtime-homes.cjs'); /** * Runtimes that register hyphen-form `name:` per #2808 AND copy agent bodies @@ -325,6 +328,14 @@ function getConfigDirFromHome(runtime, isGlobal) { if (runtime === 'codex') return "'.codex'"; if (runtime === 'antigravity') { if (!isGlobal) return "'.agent'"; + const antigravityDir = resolveAntigravityGlobalDir(); + const rel = path.relative(os.homedir(), antigravityDir); + const segments = rel.split(path.sep).filter(Boolean); + if (segments.length > 0 && !rel.startsWith('..') && !path.isAbsolute(rel)) { + return segments.map((seg) => `'${seg}'`).join(', '); + } + // If resolution points outside HOME (e.g. via env override), keep the + // stable legacy template so generated path.join() calls remain valid. return "'.gemini', 'antigravity'"; } if (runtime === 'cursor') return "'.cursor'"; @@ -444,14 +455,12 @@ function getGlobalDir(runtime, explicitDir = null) { } if (runtime === 'antigravity') { - // Antigravity: --config-dir > ANTIGRAVITY_CONFIG_DIR > ~/.gemini/antigravity + // Antigravity: --config-dir > ANTIGRAVITY_CONFIG_DIR > auto-detected + // ~/.gemini/{antigravity,antigravity-ide,antigravity-cli} if (explicitDir) { return expandTilde(explicitDir); } - if (process.env.ANTIGRAVITY_CONFIG_DIR) { - return expandTilde(process.env.ANTIGRAVITY_CONFIG_DIR); - } - return path.join(os.homedir(), '.gemini', 'antigravity'); + return resolveAntigravityGlobalDir(); } if (runtime === 'cursor') { diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index a8c103942..73e9c951b 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -510,7 +510,7 @@ Equivalent paths for other runtimes: - **Gemini CLI:** `~/.gemini/` global or `./.gemini/` local - **Codex:** `~/.codex/` global or `./.codex/` local - **Copilot:** `~/.copilot/` global or `./.github/` local -- **Antigravity:** `~/.gemini/antigravity/` global or `./.agent/` local +- **Antigravity:** auto-detected global root (`~/.gemini/antigravity/`, `~/.gemini/antigravity-ide/`, or `~/.gemini/antigravity-cli/`) or `./.agent/` local - **Cursor:** `~/.cursor/` global or `./.cursor/` local - **Windsurf:** `~/.codeium/windsurf/` global or `./.windsurf/` local - **Augment Code:** `~/.augment/` global or `./.augment/` local @@ -743,7 +743,7 @@ The migration-specific ownership and source snapshots live in | Gemini CLI | `~/.gemini` | `./.gemini` | `commands/gsd/*.toml` | `agents/gsd-*.md` | `settings.json` feature flag, hooks, and statusline | | Codex | `~/.codex` | `./.codex` | `skills/gsd-*/SKILL.md` | `agents/` source markdown plus per-agent TOML | `config.toml` `[agents.gsd-*]`, `[features].hooks` (canonical; legacy alias `codex_hooks` is recognized and migrated forward on reinstall, #3566), and hook tables | | GitHub Copilot | `~/.copilot` | `./.github` | `skills/gsd-*/SKILL.md` and `copilot-instructions.md` | `.agent.md` files | No GSD hooks or statusline | -| Antigravity | `~/.gemini/antigravity` | `./.agent` | `skills/gsd-*/SKILL.md` | `agents/gsd-*.md` | Gemini-style `settings.json` hook entries when installed by GSD | +| Antigravity | auto-detected: `~/.gemini/antigravity`, `~/.gemini/antigravity-ide`, or `~/.gemini/antigravity-cli` | `./.agent` | `skills/gsd-*/SKILL.md` | `agents/gsd-*.md` | Gemini-style `settings.json` hook entries when installed by GSD | | Cursor | `~/.cursor` | `./.cursor` | `skills/gsd-*/SKILL.md` | `agents/gsd-*.md` | Rule references under `rules/`; no GSD hooks | | Windsurf | `~/.codeium/windsurf` | `./.windsurf` | `skills/gsd-*/SKILL.md` | `agents/gsd-*.md` | Rule references under `rules/`; no GSD hooks | | Augment Code | `~/.augment` | `./.augment` | `skills/gsd-*/SKILL.md` | `agents/gsd-*.md` | No GSD hooks or statusline | diff --git a/docs/USER-GUIDE.md b/docs/USER-GUIDE.md index c8da1b2fc..af7606319 100644 --- a/docs/USER-GUIDE.md +++ b/docs/USER-GUIDE.md @@ -1354,7 +1354,7 @@ Select the corresponding stable runtime in the installer prompt. Skills land in | Copilot | `~/.copilot` | `COPILOT_CONFIG_DIR` | | Cursor | `~/.cursor` | `CURSOR_CONFIG_DIR` | | Windsurf | `~/.codeium/windsurf` | `WINDSURF_CONFIG_DIR` | -| Antigravity | `~/.gemini/antigravity` | `ANTIGRAVITY_CONFIG_DIR` | +| Antigravity | auto-detected: `~/.gemini/antigravity` (legacy), `~/.gemini/antigravity-ide`, or `~/.gemini/antigravity-cli` | `ANTIGRAVITY_CONFIG_DIR` | | Augment | `~/.augment` | `AUGMENT_CONFIG_DIR` | | Trae | `~/.trae` | `TRAE_CONFIG_DIR` | | Qwen Code | `~/.qwen` | `QWEN_CONFIG_DIR` | diff --git a/get-shit-done/bin/lib/runtime-homes.cjs b/get-shit-done/bin/lib/runtime-homes.cjs index 4909a4ce5..d5749eb66 100644 --- a/get-shit-done/bin/lib/runtime-homes.cjs +++ b/get-shit-done/bin/lib/runtime-homes.cjs @@ -22,6 +22,7 @@ const os = require('os'); const path = require('path'); +const fs = require('fs'); /** * Expand a leading ~ to os.homedir(). @@ -34,6 +35,43 @@ function expandTilde(p) { return p; } +/** + * Resolve Antigravity global config dir across 1.x and 2.x layouts. + * + * Order: + * 1) ANTIGRAVITY_CONFIG_DIR override + * 2) Existing legacy/new runtime directories under ~/.gemini/ + * - antigravity (1.x + documented baseline) + * - antigravity-ide (2.x IDE split) + * - antigravity-cli (2.x CLI split) + * 3) Legacy default (~/.gemini/antigravity) for backward compatibility + * + * @param {object} [opts] + * @param {Record} [opts.env] + * @param {string} [opts.home] + * @param {(p: string) => boolean} [opts.existsSync] + * @returns {string} + */ +function resolveAntigravityGlobalDir(opts = {}) { + const env = opts.env || process.env; + const home = opts.home || os.homedir(); + const existsSyncFn = opts.existsSync || fs.existsSync; + + if (env.ANTIGRAVITY_CONFIG_DIR) return expandTilde(env.ANTIGRAVITY_CONFIG_DIR); + + const base = path.join(home, '.gemini'); + const candidates = [ + path.join(base, 'antigravity'), + path.join(base, 'antigravity-ide'), + path.join(base, 'antigravity-cli'), + ]; + for (const candidate of candidates) { + if (existsSyncFn(candidate)) return candidate; + } + + return path.join(base, 'antigravity'); +} + /** * Return the global config base directory for the given runtime. * Respects the same env-var overrides as bin/install.js getGlobalDir(). @@ -75,9 +113,7 @@ function getGlobalConfigDir(runtime) { // ── Antigravity ────────────────────────────────────────────────────────── case 'antigravity': - return env.ANTIGRAVITY_CONFIG_DIR - ? expandTilde(env.ANTIGRAVITY_CONFIG_DIR) - : path.join(home, '.gemini', 'antigravity'); + return resolveAntigravityGlobalDir({ env, home }); // ── Windsurf ───────────────────────────────────────────────────────────── case 'windsurf': @@ -182,4 +218,5 @@ module.exports = { getGlobalSkillsBase, getGlobalSkillDir, getGlobalSkillDisplayPath, + resolveAntigravityGlobalDir, }; diff --git a/get-shit-done/workflows/update.md b/get-shit-done/workflows/update.md index 7e150935a..16a5044f4 100644 --- a/get-shit-done/workflows/update.md +++ b/get-shit-done/workflows/update.md @@ -14,6 +14,8 @@ Detect whether GSD is installed locally or globally by checking both locations a First, derive `PREFERRED_CONFIG_DIR` and `PREFERRED_RUNTIME` from the invoking prompt's `execution_context` path: - If the path contains `/get-shit-done/workflows/update.md`, strip that suffix and store the remainder as `PREFERRED_CONFIG_DIR` - Path contains `/.codex/` -> `codex` +- Path contains `/.gemini/antigravity-ide/` -> `antigravity` +- Path contains `/.gemini/antigravity-cli/` -> `antigravity` - Path contains `/.gemini/antigravity/` -> `antigravity` - Path contains `/.gemini/` -> `gemini` - Path contains `/.config/kilo/` or `/.kilo/`, or `PREFERRED_CONFIG_DIR` contains `kilo.json` / `kilo.jsonc` -> `kilo` @@ -37,7 +39,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" "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" "gemini:.gemini" "kilo:.config/kilo" "kilo:.kilo" "codex:.codex" ) ENV_RUNTIME_DIRS=() # PREFERRED_CONFIG_DIR / PREFERRED_RUNTIME should be set from execution_context @@ -98,7 +100,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 .gemini .config/kilo .kilo .codex; do + for dir in .claude .config/opencode .opencode .gemini/antigravity-ide .gemini/antigravity-cli .gemini/antigravity .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 @@ -607,7 +609,7 @@ for dir in "${CACHE_DIRS[@]}"; do fi done -for dir in .claude .config/opencode .opencode .gemini/antigravity .gemini .config/kilo .kilo .codex; do +for dir in .claude .config/opencode .opencode .gemini/antigravity-ide .gemini/antigravity-cli .gemini/antigravity .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/sdk/src/query/helpers.test.ts b/sdk/src/query/helpers.test.ts index 20a47403c..92810f82d 100644 --- a/sdk/src/query/helpers.test.ts +++ b/sdk/src/query/helpers.test.ts @@ -308,10 +308,18 @@ const RUNTIME_ENV_VARS = [ describe('getRuntimeConfigDir', () => { const saved: Record = {}; + let savedHome: string | undefined; + let savedUserProfile: string | undefined; beforeEach(() => { + savedHome = process.env.HOME; + savedUserProfile = process.env.USERPROFILE; for (const k of RUNTIME_ENV_VARS) { saved[k] = process.env[k]; delete process.env[k]; } }); afterEach(() => { + if (savedHome === undefined) delete process.env.HOME; + else process.env.HOME = savedHome; + if (savedUserProfile === undefined) delete process.env.USERPROFILE; + else process.env.USERPROFILE = savedUserProfile; for (const k of RUNTIME_ENV_VARS) { if (saved[k] === undefined) delete process.env[k]; else process.env[k] = saved[k]; @@ -380,6 +388,15 @@ describe('getRuntimeConfigDir', () => { process.env.XDG_CONFIG_HOME = '/xdg'; expect(getRuntimeConfigDir('kilo')).toBe(join('/xdg', 'kilo')); }); + + it('antigravity detects 2.x IDE dir when present and legacy dir is absent', async () => { + const home = await mkdtemp(join(tmpdir(), 'gsd-antigravity-sdk-')); + await mkdir(join(home, '.gemini', 'antigravity-ide'), { recursive: true }); + process.env.HOME = home; + process.env.USERPROFILE = home; + expect(getRuntimeConfigDir('antigravity')).toBe(join(home, '.gemini', 'antigravity-ide')); + await rm(home, { recursive: true, force: true }); + }); }); describe('detectRuntime', () => { diff --git a/sdk/src/query/helpers.ts b/sdk/src/query/helpers.ts index 42809f126..2adea8cd9 100644 --- a/sdk/src/query/helpers.ts +++ b/sdk/src/query/helpers.ts @@ -35,6 +35,24 @@ function expandTilde(p: string): string { return p.startsWith('~/') || p === '~' ? join(homedir(), p.slice(1)) : p; } +function resolveAntigravityConfigDir(): string { + if (process.env.ANTIGRAVITY_CONFIG_DIR) { + return expandTilde(process.env.ANTIGRAVITY_CONFIG_DIR); + } + + const home = homedir(); + const base = join(home, '.gemini'); + const candidates = [ + join(base, 'antigravity'), + join(base, 'antigravity-ide'), + join(base, 'antigravity-cli'), + ]; + for (const candidate of candidates) { + if (existsSync(candidate)) return candidate; + } + return join(base, 'antigravity'); +} + /** * Resolve the per-runtime config directory, mirroring * `bin/install.js:getGlobalDir()`. Agents live at `/agents`. @@ -62,7 +80,7 @@ export function getRuntimeConfigDir(runtime: Runtime): string { case 'copilot': return process.env.COPILOT_CONFIG_DIR ? expandTilde(process.env.COPILOT_CONFIG_DIR) : join(homedir(), '.copilot'); case 'antigravity': - return process.env.ANTIGRAVITY_CONFIG_DIR ? expandTilde(process.env.ANTIGRAVITY_CONFIG_DIR) : join(homedir(), '.gemini', 'antigravity'); + return resolveAntigravityConfigDir(); case 'cursor': return process.env.CURSOR_CONFIG_DIR ? expandTilde(process.env.CURSOR_CONFIG_DIR) : join(homedir(), '.cursor'); case 'windsurf': diff --git a/tests/bug-3126-global-skills-base-runtime-path.test.cjs b/tests/bug-3126-global-skills-base-runtime-path.test.cjs index 9a219b739..8a79db77e 100644 --- a/tests/bug-3126-global-skills-base-runtime-path.test.cjs +++ b/tests/bug-3126-global-skills-base-runtime-path.test.cjs @@ -20,6 +20,7 @@ const { describe, test } = require('node:test'); const assert = require('node:assert/strict'); const path = require('node:path'); const os = require('node:os'); +const { cleanup } = require('./helpers.cjs'); const ROOT = path.join(__dirname, '..'); const { @@ -117,6 +118,29 @@ describe('bug #3126: runtime-homes env-var overrides', () => { }); }); }); + + test('antigravity detects 2.x IDE dir when legacy dir is absent', () => { + const home = require('node:fs').mkdtempSync(path.join(os.tmpdir(), 'gsd-antigravity-home-')); + try { + require('node:fs').mkdirSync(path.join(home, '.gemini', 'antigravity-ide'), { recursive: true }); + const savedHome = process.env.HOME; + const savedUserProfile = process.env.USERPROFILE; + process.env.HOME = home; + process.env.USERPROFILE = home; + withEnv('ANTIGRAVITY_CONFIG_DIR', undefined, () => { + assert.strictEqual( + getGlobalConfigDir('antigravity'), + path.join(home, '.gemini', 'antigravity-ide'), + ); + }); + if (savedHome === undefined) delete process.env.HOME; + else process.env.HOME = savedHome; + if (savedUserProfile === undefined) delete process.env.USERPROFILE; + else process.env.USERPROFILE = savedUserProfile; + } finally { + cleanup(home); + } + }); }); describe('bug #3126: runtime-homes getGlobalSkillsBase', () => { diff --git a/tests/bug-3608-antigravity-update-runtime-classification.test.cjs b/tests/bug-3608-antigravity-update-runtime-classification.test.cjs index d3d9e9c12..901f2b18e 100644 --- a/tests/bug-3608-antigravity-update-runtime-classification.test.cjs +++ b/tests/bug-3608-antigravity-update-runtime-classification.test.cjs @@ -71,17 +71,23 @@ describe('bug #3608: update.md models Antigravity as a first-class runtime', () ); }); - test('RUNTIME_DIRS antigravity entry points at .gemini/antigravity', () => { + test('RUNTIME_DIRS includes antigravity entries for 2.x + legacy dirs', () => { const entries = parseBashArray(content, 'RUNTIME_DIRS'); assert.ok(entries); - const ant = entries.find((e) => e.startsWith('antigravity:')); - assert.ok(ant, 'antigravity entry missing'); - assert.strictEqual( - ant, - 'antigravity:.gemini/antigravity', - `antigravity entry must be exactly "antigravity:.gemini/antigravity" to match the installer ` + - `(bin/install.js line ~404: ~/.gemini/antigravity)`, + const antEntries = entries.filter((e) => e.startsWith('antigravity:')); + assert.ok(antEntries.length >= 1, 'antigravity entry missing'); + assert.ok( + antEntries.includes('antigravity:.gemini/antigravity-ide'), + 'RUNTIME_DIRS must include antigravity:.gemini/antigravity-ide', + ); + assert.ok( + antEntries.includes('antigravity:.gemini/antigravity-cli'), + 'RUNTIME_DIRS must include antigravity:.gemini/antigravity-cli', + ); + assert.ok( + antEntries.includes('antigravity:.gemini/antigravity'), + 'RUNTIME_DIRS must include legacy antigravity:.gemini/antigravity fallback', ); }); @@ -125,7 +131,7 @@ describe('bug #3608: update.md models Antigravity as a first-class runtime', () ); }); - test('local-scope scan dir list includes .gemini/antigravity before .gemini', () => { + test('local-scope scan dir list includes 2.x antigravity dirs before .gemini', () => { // Lines like: for dir in .claude .config/opencode .opencode .gemini ... // The first iteration of the local scan ranges over a hardcoded list. const forLoops = [...content.matchAll(/for dir in ([^;]+); do/g)]; @@ -133,38 +139,66 @@ describe('bug #3608: update.md models Antigravity as a first-class runtime', () for (const m of forLoops) { const tokens = m[1].trim().split(/\s+/); - const antIdx = tokens.indexOf('.gemini/antigravity'); + const antLegacyIdx = tokens.indexOf('.gemini/antigravity'); + const antIdeIdx = tokens.indexOf('.gemini/antigravity-ide'); + const antCliIdx = tokens.indexOf('.gemini/antigravity-cli'); const gemIdx = tokens.indexOf('.gemini'); if (gemIdx === -1) continue; // scan loop without .gemini — irrelevant assert.notStrictEqual( - antIdx, + antIdeIdx, -1, - `scan loop "for dir in ${m[1].trim()}" mentions .gemini but not .gemini/antigravity — ` + - `the more-specific Antigravity dir must be present`, + `scan loop "for dir in ${m[1].trim()}" mentions .gemini but not .gemini/antigravity-ide — ` + + `the more-specific Antigravity 2.x dir must be present`, ); assert.ok( - antIdx < gemIdx, - `scan loop "for dir in ${m[1].trim()}": .gemini/antigravity (idx ${antIdx}) must precede .gemini (idx ${gemIdx})`, + antIdeIdx < gemIdx, + `scan loop "for dir in ${m[1].trim()}": .gemini/antigravity-ide (idx ${antIdeIdx}) must precede .gemini (idx ${gemIdx})`, + ); + assert.notStrictEqual( + antCliIdx, + -1, + `scan loop "for dir in ${m[1].trim()}" must include .gemini/antigravity-cli for Antigravity 2.x CLI installs`, + ); + assert.ok( + antCliIdx < gemIdx, + `scan loop "for dir in ${m[1].trim()}": .gemini/antigravity-cli (idx ${antCliIdx}) must precede .gemini (idx ${gemIdx})`, + ); + assert.notStrictEqual( + antLegacyIdx, + -1, + `scan loop "for dir in ${m[1].trim()}" must retain .gemini/antigravity legacy fallback`, ); } }); - test('path-to-runtime classification documents /.gemini/antigravity/ before /.gemini/', () => { + test('path-to-runtime classification documents antigravity 2.x and legacy paths before /.gemini/', () => { // The markdown bullet list near the top of get_installed_version step. // We assert the antigravity bullet exists and appears before the gemini bullet // in the file (textual order = evaluation order in the prose contract). - const antBullet = content.search(/Path contains `\/\.gemini\/antigravity\/?` -> `antigravity`/); + const antIdeBullet = content.search(/Path contains `\/\.gemini\/antigravity-ide\/?` -> `antigravity`/); + const antCliBullet = content.search(/Path contains `\/\.gemini\/antigravity-cli\/?` -> `antigravity`/); + const antLegacyBullet = content.search(/Path contains `\/\.gemini\/antigravity\/?` -> `antigravity`/); const gemBullet = content.search(/Path contains `\/\.gemini\/` -> `gemini`/); assert.notStrictEqual( - antBullet, + antIdeBullet, + -1, + 'classification bullet for /.gemini/antigravity-ide/ -> antigravity is missing', + ); + assert.notStrictEqual( + antCliBullet, + -1, + 'classification bullet for /.gemini/antigravity-cli/ -> antigravity is missing', + ); + assert.notStrictEqual( + antLegacyBullet, -1, 'classification bullet for /.gemini/antigravity/ -> antigravity is missing', ); assert.notStrictEqual(gemBullet, -1, 'classification bullet for /.gemini/ -> gemini is missing'); assert.ok( - antBullet < gemBullet, - 'antigravity bullet must precede gemini bullet in path-classification list', + antIdeBullet < gemBullet && antCliBullet < gemBullet && antLegacyBullet < gemBullet, + 'all antigravity bullets must precede gemini bullet in path-classification list', ); }); }); diff --git a/tests/install.test.cjs b/tests/install.test.cjs index 2548a13c7..1af638783 100644 --- a/tests/install.test.cjs +++ b/tests/install.test.cjs @@ -95,6 +95,62 @@ describe('getGlobalDir — all runtimes default paths', () => { } }); +describe('getGlobalDir/getConfigDirFromHome — antigravity 2.x layout detection', () => { + const saved = {}; + beforeEach(() => { + saved.HOME = process.env.HOME; + saved.USERPROFILE = process.env.USERPROFILE; + saved.ANTIGRAVITY_CONFIG_DIR = process.env.ANTIGRAVITY_CONFIG_DIR; + delete process.env.ANTIGRAVITY_CONFIG_DIR; + }); + afterEach(() => { + if (saved.HOME !== undefined) process.env.HOME = saved.HOME; + else delete process.env.HOME; + if (saved.USERPROFILE !== undefined) process.env.USERPROFILE = saved.USERPROFILE; + else delete process.env.USERPROFILE; + if (saved.ANTIGRAVITY_CONFIG_DIR !== undefined) process.env.ANTIGRAVITY_CONFIG_DIR = saved.ANTIGRAVITY_CONFIG_DIR; + else delete process.env.ANTIGRAVITY_CONFIG_DIR; + }); + + test('uses ~/.gemini/antigravity-ide when legacy dir is absent and ide dir exists', () => { + const home = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-antigravity-ide-')); + try { + fs.mkdirSync(path.join(home, '.gemini', 'antigravity-ide'), { recursive: true }); + process.env.HOME = home; + process.env.USERPROFILE = home; + assert.strictEqual( + getGlobalDir('antigravity'), + path.join(home, '.gemini', 'antigravity-ide'), + ); + assert.strictEqual( + getConfigDirFromHome('antigravity', true), + "'.gemini', 'antigravity-ide'", + ); + } finally { + cleanup(home); + } + }); + + test('uses ~/.gemini/antigravity-cli when only cli dir exists', () => { + const home = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-antigravity-cli-')); + try { + fs.mkdirSync(path.join(home, '.gemini', 'antigravity-cli'), { recursive: true }); + process.env.HOME = home; + process.env.USERPROFILE = home; + assert.strictEqual( + getGlobalDir('antigravity'), + path.join(home, '.gemini', 'antigravity-cli'), + ); + assert.strictEqual( + getConfigDirFromHome('antigravity', true), + "'.gemini', 'antigravity-cli'", + ); + } finally { + cleanup(home); + } + }); +}); + describe('getGlobalDir — explicit configDir overrides env for all runtimes', () => { test('explicit dir overrides any env var for hermes', () => { const savedHome = process.env.HERMES_HOME; @@ -195,9 +251,26 @@ describe('getConfigDirFromHome — spot-checks', () => { assert.strictEqual(getConfigDirFromHome('trae', true), "'.trae'"); }); - test('antigravity returns .agent (local) and .gemini, antigravity (global)', () => { - assert.strictEqual(getConfigDirFromHome('antigravity', false), "'.agent'"); - assert.strictEqual(getConfigDirFromHome('antigravity', true), "'.gemini', 'antigravity'"); + test('antigravity returns .agent (local) and legacy fallback global path when no 2.x dirs exist', () => { + const home = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-antigravity-empty-')); + const savedHome = process.env.HOME; + const savedUserProfile = process.env.USERPROFILE; + const savedAntigravityConfig = process.env.ANTIGRAVITY_CONFIG_DIR; + delete process.env.ANTIGRAVITY_CONFIG_DIR; + process.env.HOME = home; + process.env.USERPROFILE = home; + try { + assert.strictEqual(getConfigDirFromHome('antigravity', false), "'.agent'"); + assert.strictEqual(getConfigDirFromHome('antigravity', true), "'.gemini', 'antigravity'"); + } finally { + if (savedHome === undefined) delete process.env.HOME; + else process.env.HOME = savedHome; + if (savedUserProfile === undefined) delete process.env.USERPROFILE; + else process.env.USERPROFILE = savedUserProfile; + if (savedAntigravityConfig === undefined) delete process.env.ANTIGRAVITY_CONFIG_DIR; + else process.env.ANTIGRAVITY_CONFIG_DIR = savedAntigravityConfig; + cleanup(home); + } }); test('kilo returns .kilo (local) and .config, kilo (global)', () => {