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
This commit is contained in:
Tom Boucher
2026-05-24 14:17:03 -04:00
committed by GitHub
parent aaba233f83
commit 5b3646d6c8
11 changed files with 257 additions and 38 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<string, string | undefined>} [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,
};

View File

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

View File

@@ -308,10 +308,18 @@ const RUNTIME_ENV_VARS = [
describe('getRuntimeConfigDir', () => {
const saved: Record<string, string | undefined> = {};
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', () => {

View File

@@ -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 `<configDir>/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':

View File

@@ -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', () => {

View File

@@ -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',
);
});
});

View File

@@ -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)', () => {