* test(#4667): add failing-first coverage for the codex @-include rewrite Behavioral end-to-end: a real in-process install(true,'codex') into a temp CODEX_HOME must leave zero @~/.claude includes in GSD-owned .md artifacts, rewrite the issue's own example include to @~/.codex/, keep the deliberate _GSD_RUNTIME_ROOT .claude fallback chains byte-identical, and stay idempotent across a reinstall (no doubled prefix). All four are RED until the installer grows the manifest-scoped rewrite pass. * fix(#4667): rewrite codex @ includes to the codex install root Codex-installed agents and commands kept @~/.claude/gsd-core/... (and @/Users/trekkie/.claude/gsd-core/...) include references pointing into the Claude install: silent wrong-copy reads on dual-runtime machines at divergent versions, missing files on codex-only ones. Several emitters bypass the per-runtime converters, so the per-emitter fixes since #570 rotted. Adds a manifest-scoped rewrite pass in install() beside the leak scanner: for codex, every manifest-tracked .md/.toml artifact has the @-include forms rewritten to the codex root. The pass matches the exact include literal only — the _GSD_RUNTIME_ROOT/$PREFERRED_CONFIG_DIR fallback chains, prose .claude mentions, and CHANGELOG.md are untouched — and runs before the scanner, which remains the verification backstop for anything a future emitter introduces. * test(#4667): cover the HOME-anchored include form and sync the pass comment Adds behavioral coverage for the second rewrite literal (@$HOME/.claude/gsd-core/ -> @$HOME/.codex/gsd-core/) via the plan-review-convergence command, and corrects the pass's comment: the agent .tomls are generated after it and prefix themselves, so the .toml branch of the rewrite is inert by design. * test(#4667): baseline the offline upgrade against the deployed tree (sanctioned) * chore(#4667): backfill changeset PR number (4858) * test(#4667): sandbox the install home in the include-rewrite tests (#3712 guard) * test(#4667): install via subprocess with an isolated env in the include-rewrite tests --------- Co-authored-by: sim <sim@local>
This commit is contained in:
@@ -8293,3 +8293,128 @@ describe('#3738: antigravity global artifacts install under ~/.gemini/config', (
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ── #4667 — codex installs must not keep @~/.claude includes ──────────────────
|
||||
// The @-include form points Codex at the CLAUDE install: silent wrong-copy on
|
||||
// dual-runtime machines, nothing at all on codex-only ones (#570 cause 2
|
||||
// residue). Every target ships in the codex install, so the installer rewrites
|
||||
// the `@~/.claude/gsd-core/` include form in manifest-tracked artifacts;
|
||||
// the `_GSD_RUNTIME_ROOT` fallback chains and prose `.claude` mentions are a
|
||||
// must-NOT-rewrite group (issue evidence table) and stay byte-identical.
|
||||
|
||||
const { test: __test4667, describe: __describe4667, before: __before4667, after: __after4667 } = require('node:test');
|
||||
const assert4667 = require('node:assert/strict');
|
||||
const fs4667 = require('node:fs');
|
||||
const { spawnSync: __spawnSync4667 } = require('node:child_process');
|
||||
const { createTempDir: __createTempDir4667, cleanup: __cleanup4667, isolatedNpmEnv: __isolatedNpmEnv4667 } = require('./helpers.cjs');
|
||||
|
||||
describe('install() global codex — @~/.claude include rewrite (#4667)', () => {
|
||||
const path4667 = path;
|
||||
let tmpCodexHome;
|
||||
let configDir;
|
||||
|
||||
// Install through the real CLI as a SUBPROCESS with a fully controlled env —
|
||||
// the same shape test G uses. In-process installs share the test process's
|
||||
// real HOME and the installer's own exit paths; the subprocess isolates both
|
||||
// (#3712's real-home guard + GSD_TEST_MODE/npm isolation in one place).
|
||||
function codexInstall() {
|
||||
const result = __spawnSync4667(process.execPath, [
|
||||
path.join(__dirname, '..', 'bin', 'install.js'),
|
||||
'--codex',
|
||||
'--global',
|
||||
'--config-dir',
|
||||
configDir,
|
||||
], {
|
||||
cwd: tmpCodexHome,
|
||||
env: {
|
||||
...process.env,
|
||||
...__isolatedNpmEnv4667(),
|
||||
HOME: tmpCodexHome,
|
||||
USERPROFILE: tmpCodexHome,
|
||||
GSD_TEST_MODE: '',
|
||||
NO_UPDATE_NOTIFIER: '1',
|
||||
npm_config_update_notifier: 'false',
|
||||
},
|
||||
encoding: 'utf8',
|
||||
timeout: INSTALL_TIMEOUT_MS,
|
||||
});
|
||||
assert.equal(result.status, 0, `codex install failed:\n${result.stdout}\n${result.stderr}`);
|
||||
}
|
||||
|
||||
function installedMdFiles() {
|
||||
// Mirror the installer's own leak-scanner scope: manifest-tracked .md
|
||||
// artifacts under the gsd-core payload PLUS the codex skills staging root
|
||||
// ($HOME/.agents/skills — the "skills" kind declares a global home
|
||||
// override), CHANGELOG excluded.
|
||||
const roots = [
|
||||
path4667.join(configDir, 'gsd-core'),
|
||||
path4667.join(tmpCodexHome, '.agents', 'skills'),
|
||||
];
|
||||
const out = [];
|
||||
const walk = (dir) => {
|
||||
if (!fs4667.existsSync(dir)) return;
|
||||
for (const entry of fs4667.readdirSync(dir, { withFileTypes: true })) {
|
||||
const p = path4667.join(dir, entry.name);
|
||||
if (entry.isDirectory()) walk(p);
|
||||
else if (entry.name.endsWith('.md') && entry.name !== 'CHANGELOG.md') out.push(p);
|
||||
}
|
||||
};
|
||||
for (const root of roots) walk(root);
|
||||
return out;
|
||||
}
|
||||
|
||||
before(() => {
|
||||
tmpCodexHome = __createTempDir4667('gsd-codex-4667-');
|
||||
configDir = path4667.join(tmpCodexHome, 'codex-config');
|
||||
fs4667.mkdirSync(configDir, { recursive: true });
|
||||
codexInstall();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
__cleanup4667(tmpCodexHome);
|
||||
});
|
||||
|
||||
test('codex install leaves zero @~/.claude includes in GSD-owned .md artifacts (#4667)', () => {
|
||||
const leaks = installedMdFiles().filter((file) => fs4667.readFileSync(file, 'utf8').includes('@~/.claude/'));
|
||||
assert4667.equal(
|
||||
leaks.length, 0,
|
||||
`files still carrying @~/.claude includes:\n${leaks.join('\n')}`
|
||||
);
|
||||
});
|
||||
|
||||
test('codex install rewrites agent @ includes to the codex root (#4667)', () => {
|
||||
const agentFile = path4667.join(configDir, 'gsd-core', 'agents', 'gsd-advisor-researcher.md');
|
||||
assert4667.ok(fs.existsSync(agentFile), 'the advisor-researcher agent must be installed');
|
||||
const content = fs4667.readFileSync(agentFile, 'utf8');
|
||||
assert4667.ok(
|
||||
content.includes('@~/.codex/gsd-core/references/untrusted-input-boundary.md'),
|
||||
'the include must point at the codex install'
|
||||
);
|
||||
assert4667.ok(!content.includes('@~/.claude/'), 'no @~/.claude include may survive');
|
||||
});
|
||||
|
||||
test('codex install rewrites $HOME-anchored @ includes too (#4667)', () => {
|
||||
const cmdFile = path4667.join(configDir, 'gsd-core', 'commands', 'gsd', 'plan-review-convergence.md');
|
||||
assert4667.ok(fs4667.existsSync(cmdFile), 'the plan-review-convergence command must be installed');
|
||||
const content = fs4667.readFileSync(cmdFile, 'utf8');
|
||||
assert4667.ok(
|
||||
content.includes('@$HOME/.codex/gsd-core/workflows/plan-review-convergence.md'),
|
||||
'the $HOME-anchored include must point at the codex install'
|
||||
);
|
||||
assert4667.ok(!content.includes('@$HOME/.claude/'), 'no @$HOME/.claude include may survive');
|
||||
});
|
||||
|
||||
test('codex install keeps the _GSD_RUNTIME_ROOT .claude fallbacks (#4667)', () => {
|
||||
const workflowsDir = path4667.join(configDir, 'gsd-core', 'workflows');
|
||||
let fallbacks = 0;
|
||||
for (const file of fs4667.readdirSync(workflowsDir)) {
|
||||
if (!file.endsWith('.md')) continue;
|
||||
const content = fs4667.readFileSync(path4667.join(workflowsDir, file), 'utf8');
|
||||
fallbacks += (content.match(/_GSD_RUNTIME_ROOT\}\/\.claude\//g) || []).length;
|
||||
}
|
||||
assert4667.ok(
|
||||
fallbacks > 0,
|
||||
'the _GSD_RUNTIME_ROOT .claude fallback chains must survive the rewrite (must-NOT-rewrite group)'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user