* 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:
@@ -1165,12 +1165,29 @@ describe('installRuntimeArtifacts — K3: real install before/after, full recurs
|
||||
for (const runtime of ['claude', 'qwen']) {
|
||||
const dirA = createTempDir(`gsd-k3-${runtime}-a-`);
|
||||
const dirB = createTempDir(`gsd-k3-${runtime}-b-`);
|
||||
t.after(() => { cleanup(dirA); cleanup(dirB); });
|
||||
// Two SEQUENTIAL sandboxes for one test: sandboxHome()'s per-call
|
||||
// t.after hooks each save the env as they found it, so the second call
|
||||
// saves the FIRST sandbox as its "original" — hook ordering then leaves
|
||||
// HOME pointing at dirA after the test, leaking into later tests in the
|
||||
// shard (observed on the windows matrix: a leaked gsd-k3-qwen-* home
|
||||
// made the L2 property's antigravity/global run refuse via the
|
||||
// #3712 real-home guard). Manage the env directly with ONE restore.
|
||||
const savedHome = process.env.HOME;
|
||||
const savedUserProfile = process.env.USERPROFILE;
|
||||
const savedMarker = process.env.GSD_TEST_HOME_SANDBOX;
|
||||
t.after(() => {
|
||||
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 (savedMarker === undefined) delete process.env.GSD_TEST_HOME_SANDBOX; else process.env.GSD_TEST_HOME_SANDBOX = savedMarker;
|
||||
cleanup(dirA); cleanup(dirB);
|
||||
});
|
||||
|
||||
sandboxHome(t, dirA);
|
||||
installRuntimeArtifacts(runtime, dirA, 'global', RESOLVED_FULL);
|
||||
sandboxHome(t, dirB);
|
||||
installRuntimeArtifacts(runtime, dirB, 'global', RESOLVED_FULL);
|
||||
for (const dir of [dirA, dirB]) {
|
||||
process.env.HOME = dir;
|
||||
process.env.USERPROFILE = dir;
|
||||
process.env.GSD_TEST_HOME_SANDBOX = dir;
|
||||
installRuntimeArtifacts(runtime, dir, 'global', RESOLVED_FULL);
|
||||
}
|
||||
|
||||
const filesA = walkFilesRecursively(dirA);
|
||||
const filesB = walkFilesRecursively(dirB);
|
||||
@@ -1260,17 +1277,36 @@ describe('installRuntimeArtifacts — L2: plan is deterministic (property)', ()
|
||||
};
|
||||
}
|
||||
|
||||
test('plan is deterministic', () => {
|
||||
test('plan is deterministic', (t) => {
|
||||
const runtimes = Object.keys(registry.runtimes);
|
||||
const RUNTIME_ARB = fc.constantFrom(...runtimes);
|
||||
const SCOPE_ARB = fc.constantFrom('global', 'local');
|
||||
let hits = 0;
|
||||
// #3738: the per-run HOME sandbox must EXIST on disk — the #3712 guard's
|
||||
// sandbox exemption fails closed when it cannot stat the effective home
|
||||
// (identify() -> 'absent'), which is exactly what a never-created configDir
|
||||
// gives it on the windows matrix where tmpdir sits under the real home.
|
||||
const createdL2Dirs = [];
|
||||
t.after(() => { for (const d of createdL2Dirs) cleanup(d); });
|
||||
|
||||
fc.assert(
|
||||
fc.property(RUNTIME_ARB, SCOPE_ARB, (runtime, scope) => {
|
||||
// configDir is never created for real — both calls run against fresh,
|
||||
// independent fake adapters, so no real fs cleanup is needed here.
|
||||
const configDir = path.join(os.tmpdir(), `gsd-l2-${runtime}-${crypto.randomUUID()}`);
|
||||
fs.mkdirSync(configDir, { recursive: true });
|
||||
createdL2Dirs.push(configDir);
|
||||
// #3738: a home-override runtime (antigravity → <HOME>/.gemini/config)
|
||||
// resolves its dest from os.homedir(), NOT configDir — sandbox HOME to
|
||||
// configDir for the duration of both calls (mirroring L1 above) so the
|
||||
// plan never escapes into an ambient or leaked home and the #3712
|
||||
// real-home guard stays satisfied on hosts where tmpdir sits under the
|
||||
// real home (windows).
|
||||
const savedL2Home = process.env.HOME;
|
||||
const savedL2UserProfile = process.env.USERPROFILE;
|
||||
process.env.HOME = configDir;
|
||||
process.env.USERPROFILE = configDir;
|
||||
try {
|
||||
const planA = installRuntimeArtifacts(runtime, configDir, scope, RESOLVED_CORE, undefined, undefined, { fs: createFakeInstallFs() });
|
||||
const planB = installRuntimeArtifacts(runtime, configDir, scope, RESOLVED_CORE, undefined, undefined, { fs: createFakeInstallFs() });
|
||||
hits++;
|
||||
@@ -1281,6 +1317,10 @@ describe('installRuntimeArtifacts — L2: plan is deterministic (property)', ()
|
||||
`L2 (${runtime}/${scope}): two installs against fresh fake adapters with the same inputs must ` +
|
||||
'yield structurally identical plans (temp-dir names normalized — see normalizePlanForIdempotence)',
|
||||
);
|
||||
} finally {
|
||||
if (savedL2Home === undefined) delete process.env.HOME; else process.env.HOME = savedL2Home;
|
||||
if (savedL2UserProfile === undefined) delete process.env.USERPROFILE; else process.env.USERPROFILE = savedL2UserProfile;
|
||||
}
|
||||
}),
|
||||
{ numRuns: 30, seed: 2874, verbose: true },
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user