* fix(#1441): antigravity resolver prefers GSD-owned dir over first-existing resolveConfigHomeFromDescriptor's dot-home-nested probe returned the first bare-existing candidate, so a CLI user (~/.gemini/antigravity-cli) who also had the IDE's ~/.gemini/antigravity dir was silently shadowed to the legacy dir (probed first). Regression from #217 — pre-#217 returned ~/.gemini/antigravity unconditionally. Add an optional probeMarker (gsd-core/VERSION) to the dot-home-nested descriptor: a two-pass probe prefers the candidate GSD installed into, then bare existence, then probe[0]. Behavior is byte-identical when probeMarker is absent (windsurf etc. unaffected). Adds detectAntigravityDirAmbiguity() for installer/operator guidance on already-misinstalled users (auto-relocation ruled out per ADR-0008's single-configDir migration bound). Regression tests fail before / pass after: coexistence + marker-priority cases, end-to-end through the registry descriptor. Claude-Session: https://claude.ai/code/session_01JU2WB23JLE3QTysPXuFJjB * chore(#1441): add changeset for antigravity resolver fix Claude-Session: https://claude.ai/code/session_01JU2WB23JLE3QTysPXuFJjB
This commit is contained in:
@@ -76,6 +76,20 @@ interface DotHomeNestedDescriptor {
|
||||
parent: string;
|
||||
env: string[];
|
||||
probe?: string[];
|
||||
/**
|
||||
* Optional sub-path that qualifies which probe candidate GSD actually owns
|
||||
* (e.g. `gsd-core/VERSION`). The same field name and check used by the
|
||||
* generic-agents-root descriptor — unified vocabulary per ADR-1016. The
|
||||
* resolution *strength* differs per kind: generic-agents-root treats it as a
|
||||
* hard filter (a candidate only qualifies if `<candidate>/<probeExists>`
|
||||
* exists), whereas dot-home-nested treats it as a *preference* — probing runs
|
||||
* in two passes: first the candidate whose `<candidate>/<probeExists>` exists
|
||||
* wins (the dir GSD installed into), then a bare-existence pass, then
|
||||
* `probe[0]`. Without it, behaviour is the legacy first-bare-existing-wins
|
||||
* probe, so other dot-home-nested runtimes (e.g. windsurf, which has no probe)
|
||||
* are unaffected. See ADR-1016 and #213/#217 (antigravity split).
|
||||
*/
|
||||
probeExists?: string;
|
||||
skillsHome?: ConfigHomeDescriptor;
|
||||
}
|
||||
|
||||
@@ -163,7 +177,20 @@ export function resolveConfigHomeFromDescriptor(
|
||||
}
|
||||
const base = path.join(home, configHome.parent);
|
||||
if (configHome.probe && configHome.probe.length > 0) {
|
||||
// probe each candidate under base; return first that exists
|
||||
// Pass 1 (marker-priority): when probeExists is declared, prefer the
|
||||
// candidate GSD actually owns (its `<candidate>/<probeExists>` exists).
|
||||
// This disambiguates an active-but-shadowing sibling dir (e.g. the
|
||||
// Antigravity-IDE `~/.gemini/antigravity` dir) from the dir GSD was
|
||||
// installed into, instead of blindly taking the first dir that exists.
|
||||
if (configHome.probeExists) {
|
||||
for (const candidate of configHome.probe) {
|
||||
const resolved = path.join(base, candidate);
|
||||
if (existsSyncFn(path.join(resolved, configHome.probeExists))) {
|
||||
return resolved;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Pass 2 (legacy bare-existence): first candidate dir that exists.
|
||||
for (const candidate of configHome.probe) {
|
||||
const resolved = path.join(base, candidate);
|
||||
if (existsSyncFn(resolved)) return resolved;
|
||||
@@ -239,11 +266,72 @@ export function resolveAntigravityGlobalDir(opts: ResolveAntigravityOpts = {}):
|
||||
parent: '.gemini',
|
||||
env: ['ANTIGRAVITY_CONFIG_DIR'],
|
||||
probe: ['antigravity', 'antigravity-ide', 'antigravity-cli'],
|
||||
// Prefer the candidate GSD installed into (carries gsd-core/VERSION) over
|
||||
// a bare-existing sibling. Without this, a CLI user (antigravity-cli) who
|
||||
// also has the IDE's ~/.gemini/antigravity dir is shadowed to the legacy
|
||||
// dir because it is probed first. See #213/#217. The posix-slash literal
|
||||
// matches capabilities/antigravity/capability.json; both normalize via
|
||||
// path.join at the check site, so Windows backslash handling is covered.
|
||||
probeExists: 'gsd-core/VERSION',
|
||||
},
|
||||
{ env, home, existsSync: existsSyncFn },
|
||||
);
|
||||
}
|
||||
|
||||
export interface AntigravityAmbiguity {
|
||||
/** True when more than one ~/.gemini/antigravity{,-ide,-cli} dir is present. */
|
||||
ambiguous: boolean;
|
||||
/** The dir GSD currently resolves to (where install/update will write). */
|
||||
resolved: string;
|
||||
/** All probe candidate dirs that exist on disk (absolute paths). */
|
||||
presentDirs: string[];
|
||||
/**
|
||||
* Candidate dirs that carry the GSD marker (gsd-core/VERSION). When this has
|
||||
* exactly one entry, resolution is unambiguous. Zero or >1 entries (or a
|
||||
* marker in a dir other than the one a CLI/IDE user expects) is the #213/#217
|
||||
* misinstall surface: a prior install may have landed in the wrong sibling dir.
|
||||
*/
|
||||
gsdMarkedDirs: string[];
|
||||
/** ANTIGRAVITY_CONFIG_DIR is the operator escape hatch; true when already set. */
|
||||
envOverridden: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect whether the Antigravity config-dir resolution is ambiguous — i.e. more
|
||||
* than one of ~/.gemini/{antigravity,antigravity-ide,antigravity-cli} exists, so
|
||||
* a user upgrading from a pre-#217 install may have had GSD written into the
|
||||
* wrong sibling dir (the legacy/IDE dir shadowing an active CLI dir).
|
||||
*
|
||||
* This is a pure, side-effect-free probe intended for the installer and
|
||||
* /gsd-update to surface operator guidance (set ANTIGRAVITY_CONFIG_DIR or move
|
||||
* gsd-core/ into the intended dir). The migration framework cannot relocate an
|
||||
* install across sibling config dirs (it is bounded to a single configDir and
|
||||
* has no cross-dir move primitive — see installer-migrations 004), so existing
|
||||
* misinstalls are corrected by re-detection + operator guidance, not an
|
||||
* automatic move.
|
||||
*/
|
||||
export function detectAntigravityDirAmbiguity(
|
||||
opts: ResolveAntigravityOpts = {},
|
||||
): AntigravityAmbiguity {
|
||||
const env: Record<string, string | undefined> = opts.env ?? process.env;
|
||||
const home = opts.home ?? os.homedir();
|
||||
const existsSyncFn = opts.existsSync ?? fs.existsSync;
|
||||
const marker = path.join('gsd-core', 'VERSION');
|
||||
const base = path.join(home, '.gemini');
|
||||
const candidates = ['antigravity', 'antigravity-ide', 'antigravity-cli'].map((c) =>
|
||||
path.join(base, c),
|
||||
);
|
||||
const presentDirs = candidates.filter((dir) => existsSyncFn(dir));
|
||||
const gsdMarkedDirs = candidates.filter((dir) => existsSyncFn(path.join(dir, marker)));
|
||||
return {
|
||||
ambiguous: presentDirs.length > 1,
|
||||
resolved: resolveAntigravityGlobalDir({ env, home, existsSync: existsSyncFn }),
|
||||
presentDirs,
|
||||
gsdMarkedDirs,
|
||||
envOverridden: Boolean(env['ANTIGRAVITY_CONFIG_DIR']),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve Kimi's generic user root using Kimi CLI's documented first-existing
|
||||
* generic skills directory policy:
|
||||
|
||||
Reference in New Issue
Block a user