* test(#3738): antigravity global skills/agents must resolve under ~/.gemini/config Regression tests (RED first): --skills-root and gsd-tools query surfaces, install-plan dest dirs, and converter skills-path rewrite. * fix(#3738): antigravity global skills/agents install to ~/.gemini/config Antigravity's machine-local discovery scans ~/.gemini/config/{skills,agents}; the configHome (~/.gemini/antigravity) is deprecated for artifacts. Declare the ADR-1239 skills/agents 'home' override on the antigravity global layout — the same mechanism codex uses (.agents) — and divert ~/.claude/skills/ references in converted global content to ~/.gemini/config/skills/. configHome, settings, probe/migration semantics, and the local .agents layout are unchanged. * fix(#3738): retire deprecated configHome artifacts via installer migration 010 Next install converges an existing antigravity install: manifest-managed skills/gsd-*/ and agents/gsd-*.md under the configHome (a location AGY does not scan) are removed — modified files backed up first, unmanifested and non-gsd entries preserved — and now-empty containers retired. Global scope only; the local .agents surface is live. Docs + inventory updated. * fix(#3738): converter sync in bin/install.js, harness emit-root coverage, migration baseline - bin/install.js converter gains the same ~/.claude/skills → ~/.gemini/config/ rewrite as src (ADR-1508 dual copy must stay in sync). - Parity-manifest walk covers home-override emit roots (extraEmitRootsFor) so antigravity's emitted skills/agents stay differential-visible at their new install root; install-tree fixture regen confirms an unchanged key set. - skills-from-commands rule declares the antigravity converter as a runtime-scoped transform; one ack fragment covers the identity-classed workflow whose antigravity copy embeds the old skills path. - Migration 010 checksum baseline + home-override set doc updated; existing tests updated to the #3738 contract (global dest, golden parity via layout dest, integration expectations). * fix(#3738): tolerate an absent extra emit root on baseline-side measurement The base tree's installer predates the home override, so <HOME>/.gemini/config does not exist there; walk() threw ENOENT and the in-job baseline build failed. An absent extra root is the legitimate pre-override shape — skip it. * fix(#3738): review findings — manifest agents root, bare skills-path rewrite, guard comment - writeManifest resolves the agents-kind home override (_kindDestDirSafe), so the manifest records agents at their actual install root and drift detection keeps working (isolated review finding 1, major). - Converter bare forms ~/.claude/skills and $HOME/.claude/skills (no trailing slash) divert to ~/.gemini/config/skills instead of falling through to the retired configHome path (finding 2). - real-home-guard comment updated: antigravity's global agents kind is the first agents-kind home override (finding 3, doc-only). - Regression tests for both behavioral findings. * chore(#3738): changeset fragment (pr number backfilled after PR creation) * chore(#3738): backfill changeset PR number (3921) * fix(#3738): sandbox HOME in tests that install antigravity global artifacts antigravity is the first home-override runtime in the golden-parity and skills-wrapper suites (codex is not in their runtime lists), so those tests never needed HOME sandboxing — the real-home guard now (correctly) refuses their un-sandboxed global installs on CI, where HOME is the passwd home. * fix(#3738): stop the K3 sequential-sandbox env leak; sandbox L2's home-override plans K3's two back-to-back sandboxHome calls leave HOME pointing at the first sandbox once the after-hooks restore (each call saves the env as it found it, so the second saves the first's sandbox as 'original'). On the windows matrix that leaked gsd-k3-qwen-* home into the L2 property, whose antigravity/global run then (correctly) refused via the #3712 real-home guard — antigravity is the runtime that made L2's plan escape into os.homedir(). K3 now manages the env with a single restore; L2 sandboxes HOME per run, mirroring L1. * fix(#3738): L2 property's HOME sandbox must exist on disk The #3712 guard's sandbox exemption fails closed when identify(effectiveHome) is 'absent' — L2 never created its configDir, so on the windows matrix (tmpdir under the real home) the antigravity/global run refused even with HOME sandboxed. Create the per-run sandbox dir and clean it up. --------- Co-authored-by: sim <sim@local>
This commit is contained in:
@@ -1001,6 +1001,10 @@ describe('skills wrapper threads install scope into converter isGlobal (regressi
|
||||
const globalDir = createTempDir(`gsd-ial-g-${runtime}-`);
|
||||
const localDir = createTempDir(`gsd-ial-l-${runtime}-`);
|
||||
t.after(() => { cleanup(globalDir); cleanup(localDir); });
|
||||
// #3738: antigravity's global skills kind resolves its `home` override
|
||||
// from os.homedir(); sandbox HOME (with the #3712 marker) so the global
|
||||
// install writes inside globalDir instead of the runner's real home.
|
||||
sandboxHome(t, globalDir);
|
||||
|
||||
installRuntimeArtifacts(runtime, globalDir, 'global', RESOLVED_CORE);
|
||||
installRuntimeArtifacts(runtime, localDir, 'local', RESOLVED_CORE);
|
||||
@@ -1009,8 +1013,10 @@ describe('skills wrapper threads install scope into converter isGlobal (regressi
|
||||
const lSkills = resolveRuntimeArtifactLayout(runtime, localDir, 'local').kinds.find(k => k.kind === 'skills');
|
||||
assert.ok(gSkills && lSkills, `${runtime}: must resolve a skills kind for both scopes`);
|
||||
|
||||
const gCombined = readAllSkillMd(path.join(globalDir, gSkills.destSubpath));
|
||||
const lCombined = readAllSkillMd(path.join(localDir, lSkills.destSubpath));
|
||||
// #3738: the global skills tree may live under the kind `home` override
|
||||
// (antigravity → ~/.gemini/config), so honor it like the installer does.
|
||||
const gCombined = readAllSkillMd(path.join(gSkills.home ?? globalDir, gSkills.destSubpath));
|
||||
const lCombined = readAllSkillMd(path.join(lSkills.home ?? localDir, lSkills.destSubpath));
|
||||
|
||||
// Precondition (non-vacuity guard): some core skill carries a ~/.claude
|
||||
// reference, so the GLOBAL install surfaces the global home marker. If this
|
||||
@@ -1108,6 +1114,58 @@ describe('convertClaudeToAntigravityContent bare path replacement (#2418)', () =
|
||||
const count = (result.match(/~\/.gemini\/antigravity\//g) || []).length;
|
||||
assert.strictEqual(count, 1, `Expected exactly 1 replacement, got ${count} in: ${result}`);
|
||||
});
|
||||
|
||||
// #3738: global skills install under ~/.gemini/config/skills (the dir AGY
|
||||
// scans), while gsd-core runtime references stay under configHome. A skills
|
||||
// path must therefore rewrite to the config root, not ~/.gemini/antigravity.
|
||||
test('replaces ~/.claude/skills/ with ~/.gemini/config/skills (#3738)', () => {
|
||||
const input = 'Skill dirs live at `~/.claude/skills/gsd-*/`.';
|
||||
const result = convertClaudeToAntigravityContent(input, true);
|
||||
assert.ok(
|
||||
result.includes('~/.gemini/config/skills/gsd-*/'),
|
||||
`Expected ~/.gemini/config/skills rewrite, got: ${result}`
|
||||
);
|
||||
assert.ok(
|
||||
!result.includes('~/.gemini/antigravity/skills'),
|
||||
`Skills must not point at the deprecated dir, got: ${result}`
|
||||
);
|
||||
});
|
||||
|
||||
test('replaces $HOME/.claude/skills/ with $HOME/.gemini/config/skills (#3738)', () => {
|
||||
const input = 'ls $HOME/.claude/skills/';
|
||||
const result = convertClaudeToAntigravityContent(input, true);
|
||||
assert.ok(
|
||||
result.includes('$HOME/.gemini/config/skills/'),
|
||||
`Expected $HOME/.gemini/config/skills rewrite, got: ${result}`
|
||||
);
|
||||
assert.ok(!result.includes('$HOME/.claude/'), `Expected full replacement, got: ${result}`);
|
||||
});
|
||||
|
||||
test('replaces bare ~/.claude/skills (no trailing slash) with ~/.gemini/config/skills (#3738)', () => {
|
||||
const input = 'ls ~/.claude/skills';
|
||||
const result = convertClaudeToAntigravityContent(input, true);
|
||||
assert.ok(
|
||||
result.includes('~/.gemini/config/skills'),
|
||||
`bare skills form must divert to the config root, got: ${result}`
|
||||
);
|
||||
assert.ok(
|
||||
!result.includes('~/.gemini/antigravity/skills'),
|
||||
`bare skills form must not fall through to the retired configHome path, got: ${result}`
|
||||
);
|
||||
});
|
||||
|
||||
test('keeps gsd-core references under ~/.gemini/antigravity when a skills ref is present (#3738)', () => {
|
||||
const input = 'Read ~/.claude/gsd-core/workflows/x.md then list ~/.claude/skills/.';
|
||||
const result = convertClaudeToAntigravityContent(input, true);
|
||||
assert.ok(
|
||||
result.includes('~/.gemini/antigravity/gsd-core/workflows/x.md'),
|
||||
`gsd-core ref must stay under configHome, got: ${result}`
|
||||
);
|
||||
assert.ok(
|
||||
result.includes('~/.gemini/config/skills/'),
|
||||
`skills ref must move to the config root, got: ${result}`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('local install', () => {
|
||||
@@ -7606,3 +7664,54 @@ describe('#3719: real global Claude install — agents/*.md @-refs must resolve
|
||||
assert.deepStrictEqual(failures, [], `local install must not leak @$HOME/ or @~/.claude/:\n${failures.join('\n')}`);
|
||||
});
|
||||
});
|
||||
|
||||
// ── #3738: antigravity global artifacts install under ~/.gemini/config ────────
|
||||
describe('#3738: antigravity global artifacts install under ~/.gemini/config', () => {
|
||||
test('global skills and agents dest dirs resolve under <home>/.gemini/config, not configHome', (t) => {
|
||||
const configDir = createTempDir('gsd-3738-antigravity-');
|
||||
t.after(() => cleanup(configDir));
|
||||
sandboxHome(t, configDir);
|
||||
|
||||
const layout = resolveRuntimeArtifactLayout('antigravity', configDir, 'global');
|
||||
const skillsKind = layout.kinds.find(k => k.kind === 'skills');
|
||||
const agentsKind = layout.kinds.find(k => k.kind === 'agents');
|
||||
assert.ok(skillsKind, 'antigravity must have a skills kind');
|
||||
assert.ok(agentsKind, 'antigravity must have an agents kind');
|
||||
const expectedHome = path.join(configDir, '.gemini', 'config');
|
||||
assert.strictEqual(skillsKind.home, expectedHome, 'skills home override must be ~/.gemini/config');
|
||||
assert.strictEqual(agentsKind.home, expectedHome, 'agents home override must be ~/.gemini/config');
|
||||
|
||||
installRuntimeArtifacts('antigravity', configDir, 'global', RESOLVED_CORE);
|
||||
|
||||
assert.ok(
|
||||
fs.existsSync(path.join(expectedHome, 'skills', 'gsd-help', 'SKILL.md')),
|
||||
'a gsd-* skill must exist under ~/.gemini/config/skills'
|
||||
);
|
||||
const agentsDir = path.join(expectedHome, 'agents');
|
||||
assert.ok(fs.existsSync(agentsDir), '~/.gemini/config/agents must exist');
|
||||
assert.ok(
|
||||
fs.readdirSync(agentsDir).some(n => n.startsWith('gsd-')),
|
||||
'at least one gsd-* agent must exist under ~/.gemini/config/agents'
|
||||
);
|
||||
assert.ok(
|
||||
!fs.existsSync(path.join(configDir, 'skills')),
|
||||
'no skills dir may be created under the configHome (~/.gemini/antigravity)'
|
||||
);
|
||||
assert.ok(
|
||||
!fs.existsSync(path.join(configDir, 'agents')),
|
||||
'no agents dir may be created under the configHome (~/.gemini/antigravity)'
|
||||
);
|
||||
});
|
||||
|
||||
test('local (workspace) layout is unchanged: .agents/skills and .agents/agents', (t) => {
|
||||
const configDir = createTempDir('gsd-3738-antigravity-local-');
|
||||
t.after(() => cleanup(configDir));
|
||||
sandboxHome(t, configDir);
|
||||
|
||||
const layout = resolveRuntimeArtifactLayout('antigravity', configDir, 'local');
|
||||
for (const kind of layout.kinds) {
|
||||
assert.strictEqual(kind.home, undefined, `local ${kind.kind} must not carry a home override`);
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user