refactor: drop 12 runtimes, keep Claude, Codex, OpenCode, Cursor, ZCode, Antigravity
Removes kilo, kimi, kimi-code, copilot, windsurf, augment, trae, qwen, hermes, cline, codebuddy and pi end to end: capability descriptors, installer branches and converters (bin/install.js 14.9k -> 11.2k lines), TypeScript converters, hook surfaces and runtime homes, review lanes qwen/kimi-code, the two pi migrations, Kimi payload normalization in the hook guards, dead hostBehaviors vocabulary, launcher home probes, fixtures, runtime-specific tests and the prose that presented them as supported. Installer output for the six kept runtimes is byte-identical to before the prune. The Kimi tool-vocabulary tests in workflow-guard, read-guard and read-injection-scanner are left in place pending a decision.
This commit is contained in:
@@ -436,7 +436,7 @@ describe('runtime-launcher-parity (#373)', () => {
|
||||
// Guards the guard: a truncated/renamed/unreadable snippet would make
|
||||
// `extracted` empty, and an empty `uncovered` below would pass vacuously.
|
||||
assert.ok(
|
||||
extracted.length >= 15,
|
||||
extracted.length >= 8,
|
||||
`expected the snippet to yield many distinct \${VAR:-default} arms, got ${extracted.length}`,
|
||||
);
|
||||
const uncovered = extracted.filter((name) => !covered.has(name));
|
||||
@@ -1591,7 +1591,7 @@ describe('bug-211: launcher ~/.claude home fallback', () => {
|
||||
* runtime homes before emitting the hard error.
|
||||
*
|
||||
* The last-resort $HOME/.claude/msd-core branch is Claude Code-specific.
|
||||
* Every non-Claude runtime (Hermes, Cursor, Codex, Copilot, Windsurf, …)
|
||||
* Every non-Claude runtime (Cursor, Codex, OpenCode, Antigravity, …)
|
||||
* installs msd-core into a *different* directory that the shim never tried,
|
||||
* causing a false-positive fatal ERROR on all non-Claude runtimes when
|
||||
* RUNTIME_DIR is not set and msd_run is not on PATH.
|
||||
@@ -1599,12 +1599,12 @@ describe('bug-211: launcher ~/.claude home fallback', () => {
|
||||
* Asserts:
|
||||
* (A) Snippet contains all expected non-Claude runtime home probes (structural).
|
||||
* (B0) buildIsolatedPath() co-location invariant (see below).
|
||||
* (B) HERMES_HOME behavioral: when RUNTIME_DIR misses and msd_run is NOT on
|
||||
* PATH, the stub at ${HERMES_HOME}/msd-core/bin/msd-tools.cjs is invoked.
|
||||
* (B) CODEX_HOME behavioral: when RUNTIME_DIR misses and msd_run is NOT on
|
||||
* PATH, the stub at ${CODEX_HOME}/msd-core/bin/msd-tools.cjs is invoked.
|
||||
* It plants a leaked msd_run on PATH first (#4205), on every platform,
|
||||
* which is what makes it fail on a clean machine as well as a leaking one.
|
||||
* (C) Default Hermes path behavioral: stub at $HOME/.hermes/msd-core/bin/
|
||||
* msd-tools.cjs is invoked when HERMES_HOME is not set.
|
||||
* (C) Default Codex path behavioral: stub at $HOME/.codex/msd-core/bin/
|
||||
* msd-tools.cjs is invoked when CODEX_HOME is not set.
|
||||
* (D) Resolution order: non-Claude homes are probed BEFORE the hard error,
|
||||
* and AFTER the $HOME/.claude branch.
|
||||
* (E) Propagation: all workflow .md files using msd_run contain each probe
|
||||
@@ -1633,21 +1633,12 @@ const SNIPPET_FILE = path.join(WORKFLOWS_DIR, '_runtime-launcher.snippet.sh');
|
||||
// in the snippet (the env-var-with-default expansion that probes that runtime's
|
||||
// msd-core install location). Mirrors src/runtime-homes.cts getGlobalConfigDir().
|
||||
const EXPECTED_RUNTIME_PROBES = {
|
||||
hermes: '.hermes}/msd-core/bin/',
|
||||
cursor: '.cursor}/msd-core/bin/',
|
||||
codex: '.codex}/msd-core/bin/',
|
||||
gemini: '.gemini}/msd-core/bin/',
|
||||
copilot: '.copilot}/msd-core/bin/',
|
||||
windsurf: '.codeium/windsurf}/msd-core/bin/',
|
||||
augment: '.augment}/msd-core/bin/',
|
||||
trae: '.trae}/msd-core/bin/',
|
||||
qwen: '.qwen}/msd-core/bin/',
|
||||
codebuddy: '.codebuddy}/msd-core/bin/',
|
||||
cline: '.cline}/msd-core/bin/',
|
||||
grok: '.agents}/msd-core/bin/',
|
||||
antigravity: '.gemini/antigravity}/msd-core/bin/',
|
||||
opencode: 'opencode}/msd-core/bin/',
|
||||
kilo: 'kilo}/msd-core/bin/',
|
||||
};
|
||||
|
||||
|
||||
@@ -1669,7 +1660,7 @@ describe('bug-891: non-Claude runtime home fallback arms', () => {
|
||||
[],
|
||||
`_runtime-launcher.snippet.sh is missing fallback probes for non-Claude runtimes:\n` +
|
||||
missing.join('\n') +
|
||||
`\n\nAdd elif arms for each runtime home (e.g. "\${HERMES_HOME:-$HOME/.hermes}/msd-core/bin/...")` +
|
||||
`\n\nAdd elif arms for each runtime home (e.g. "\${CODEX_HOME:-$HOME/.codex}/msd-core/bin/...")` +
|
||||
` before the hard-error else. Current snippet:\n${snippetContent.trim()}`,
|
||||
);
|
||||
});
|
||||
@@ -1803,10 +1794,10 @@ describe('bug-891: non-Claude runtime home fallback arms', () => {
|
||||
);
|
||||
|
||||
|
||||
// ── (B) Behavioral: HERMES_HOME stub is resolved, even past a leaked PATH ──
|
||||
// ── (B) Behavioral: CODEX_HOME stub is resolved, even past a leaked PATH ──
|
||||
//
|
||||
// #4205 regression, and the reason this asserts the sentinel rather than just
|
||||
// the stub: on a CLEAN machine the bare HERMES_HOME assertion passes whether
|
||||
// the stub: on a CLEAN machine the bare CODEX_HOME assertion passes whether
|
||||
// buildIsolatedPath() filters msd_run or msd-tools, so it only catches the bug
|
||||
// on a machine that already has the leak. Planting the sentinel makes it fail
|
||||
// on any machine, and on either platform: the sentinel is planted in the one
|
||||
@@ -1814,12 +1805,12 @@ describe('bug-891: non-Claude runtime home fallback arms', () => {
|
||||
// on POSIX, `msd_run.exe` alone on Windows (see below for why alone). Note
|
||||
// that only the POSIX sentinel can print SENTINEL_INVOKED; on Windows the
|
||||
// basename assertion is what carries the guarantee (#4344).
|
||||
test('(B) buildIsolatedPath strips a leaked PATH msd_run; the ${HERMES_HOME} stub wins', (t) => {
|
||||
test('(B) buildIsolatedPath strips a leaked PATH msd_run; the ${CODEX_HOME} stub wins', (t) => {
|
||||
const fakeHome = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-891-home-b-'));
|
||||
const fakeHermesHome = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-891-hermes-'));
|
||||
const fakeCodexHome = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-891-codex-'));
|
||||
const fakeRuntime = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-891-rt-'));
|
||||
t.after(() => cleanup(fakeHome));
|
||||
t.after(() => cleanup(fakeHermesHome));
|
||||
t.after(() => cleanup(fakeCodexHome));
|
||||
t.after(() => cleanup(fakeRuntime));
|
||||
|
||||
const sentinelBinDir = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-4205-sentinel-'));
|
||||
@@ -1855,33 +1846,33 @@ describe('bug-891: non-Claude runtime home fallback arms', () => {
|
||||
`Expected buildIsolatedPath to strip the leaked ${sentinelBinDir}, got:\n${isolatedPath}`,
|
||||
);
|
||||
|
||||
const hermesBinDir = path.join(fakeHermesHome, 'msd-core', 'bin');
|
||||
fs.mkdirSync(hermesBinDir, { recursive: true });
|
||||
const codexBinDir = path.join(fakeCodexHome, 'msd-core', 'bin');
|
||||
fs.mkdirSync(codexBinDir, { recursive: true });
|
||||
|
||||
const stubPath = path.join(hermesBinDir, 'msd-tools.cjs');
|
||||
const stubPath = path.join(codexBinDir, 'msd-tools.cjs');
|
||||
fs.writeFileSync(
|
||||
stubPath,
|
||||
'#!/usr/bin/env node\nconsole.log("HERMES_HOME_STUB:" + process.argv.slice(2).join(","));\n',
|
||||
'#!/usr/bin/env node\nconsole.log("CODEX_HOME_STUB:" + process.argv.slice(2).join(","));\n',
|
||||
);
|
||||
fs.chmodSync(stubPath, 0o755);
|
||||
|
||||
const snippet = fs.readFileSync(SNIPPET_FILE, 'utf8');
|
||||
// HOME points at an isolated temp dir with no .claude install, so the
|
||||
// $HOME/.claude arm misses and the HERMES_HOME arm is the one under test.
|
||||
// $HOME/.claude arm misses and the CODEX_HOME arm is the one under test.
|
||||
const scriptContent =
|
||||
`unset MSD_TOOLS\n` +
|
||||
`export HOME=${JSON.stringify(fakeHome)}\n` +
|
||||
`export RUNTIME_DIR=${JSON.stringify(fakeRuntime)}\n` +
|
||||
`export HERMES_HOME=${JSON.stringify(fakeHermesHome)}\n` +
|
||||
`export CODEX_HOME=${JSON.stringify(fakeCodexHome)}\n` +
|
||||
snippet +
|
||||
`\nprintf "MSD_TOOLS=%s\\n" "$MSD_TOOLS"\n` +
|
||||
`msd_run ping test\n`;
|
||||
|
||||
const scriptPath = path.join(fakeRuntime, 'test-hermes-home.sh');
|
||||
const scriptPath = path.join(fakeRuntime, 'test-codex-home.sh');
|
||||
fs.writeFileSync(scriptPath, scriptContent);
|
||||
|
||||
const stdout = runBashFile(scriptPath, {
|
||||
env: { PATH: isolatedPath, HOME: fakeHome, HERMES_HOME: fakeHermesHome },
|
||||
env: { PATH: isolatedPath, HOME: fakeHome, CODEX_HOME: fakeCodexHome },
|
||||
});
|
||||
|
||||
const normStdout = stdout.replace(/\\/g, '/');
|
||||
@@ -1900,49 +1891,49 @@ describe('bug-891: non-Claude runtime home fallback arms', () => {
|
||||
!normStdout.includes(path.basename(sentinelBinDir)),
|
||||
`Expected MSD_TOOLS to resolve outside the leaked ${sentinelBinDir}, got:\n${stdout.trim()}`,
|
||||
);
|
||||
// Assert the hermes dir itself: every arm of the resolver ends in
|
||||
// Assert the codex dir itself: every arm of the resolver ends in
|
||||
// msd-core/bin/, so that substring alone cannot tell them apart.
|
||||
assert.ok(
|
||||
normStdout.includes(fakeHermesHome.replace(/\\/g, '/')),
|
||||
`Expected MSD_TOOLS to resolve into ${fakeHermesHome}, got:\n${stdout.trim()}`,
|
||||
normStdout.includes(fakeCodexHome.replace(/\\/g, '/')),
|
||||
`Expected MSD_TOOLS to resolve into ${fakeCodexHome}, got:\n${stdout.trim()}`,
|
||||
);
|
||||
assert.ok(
|
||||
normStdout.includes('HERMES_HOME_STUB:ping,test'),
|
||||
`Expected stub output "HERMES_HOME_STUB:ping,test", got:\n${stdout.trim()}`,
|
||||
normStdout.includes('CODEX_HOME_STUB:ping,test'),
|
||||
`Expected stub output "CODEX_HOME_STUB:ping,test", got:\n${stdout.trim()}`,
|
||||
);
|
||||
});
|
||||
|
||||
// ── (C) Behavioral: default .hermes path used when HERMES_HOME not set ────
|
||||
test('(C) msd_run resolves $HOME/.hermes/msd-core/bin/ stub when HERMES_HOME is unset', () => {
|
||||
// ── (C) Behavioral: default .codex path used when CODEX_HOME not set ────
|
||||
test('(C) msd_run resolves $HOME/.codex/msd-core/bin/ stub when CODEX_HOME is unset', () => {
|
||||
const fakeHome = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-891-home-'));
|
||||
const fakeRuntime = fs.mkdtempSync(path.join(os.tmpdir(), 'msd-891-rt2-'));
|
||||
const { isolatedPath, nodeBinDir } = buildIsolatedPath();
|
||||
try {
|
||||
const hermesBinDir = path.join(fakeHome, '.hermes', 'msd-core', 'bin');
|
||||
fs.mkdirSync(hermesBinDir, { recursive: true });
|
||||
const codexBinDir = path.join(fakeHome, '.codex', 'msd-core', 'bin');
|
||||
fs.mkdirSync(codexBinDir, { recursive: true });
|
||||
|
||||
const stubPath = path.join(hermesBinDir, 'msd-tools.cjs');
|
||||
const stubPath = path.join(codexBinDir, 'msd-tools.cjs');
|
||||
fs.writeFileSync(
|
||||
stubPath,
|
||||
'#!/usr/bin/env node\nconsole.log("HERMES_DEFAULT_STUB:" + process.argv.slice(2).join(","));\n',
|
||||
'#!/usr/bin/env node\nconsole.log("CODEX_DEFAULT_STUB:" + process.argv.slice(2).join(","));\n',
|
||||
);
|
||||
fs.chmodSync(stubPath, 0o755);
|
||||
|
||||
const snippet = fs.readFileSync(SNIPPET_FILE, 'utf8');
|
||||
const scriptContent =
|
||||
`unset MSD_TOOLS HERMES_HOME\n` +
|
||||
`unset MSD_TOOLS CODEX_HOME\n` +
|
||||
`export RUNTIME_DIR=${JSON.stringify(fakeRuntime)}\n` +
|
||||
`export HOME=${JSON.stringify(fakeHome)}\n` +
|
||||
snippet +
|
||||
`\nprintf "MSD_TOOLS=%s\\n" "$MSD_TOOLS"\n` +
|
||||
`msd_run status\n`;
|
||||
|
||||
const scriptPath = path.join(fakeRuntime, 'test-hermes-default.sh');
|
||||
const scriptPath = path.join(fakeRuntime, 'test-codex-default.sh');
|
||||
fs.writeFileSync(scriptPath, scriptContent);
|
||||
|
||||
const stdout = runBashFile(scriptPath, {
|
||||
// CLAUDE_CONFIG_DIR resolves before the HERMES_HOME arm under test
|
||||
// (#4205 class, env vector); script-level `unset HERMES_HOME` above
|
||||
// CLAUDE_CONFIG_DIR resolves before the CODEX_HOME arm under test
|
||||
// (#4205 class, env vector); script-level `unset CODEX_HOME` above
|
||||
// already handles that var, and snippetEnv()'s derived TEST_ENV_BASE
|
||||
// clears CLAUDE_CONFIG_DIR before bash ever sees it.
|
||||
env: { PATH: isolatedPath, HOME: fakeHome },
|
||||
@@ -1950,12 +1941,12 @@ describe('bug-891: non-Claude runtime home fallback arms', () => {
|
||||
|
||||
const normStdout = stdout.replace(/\\/g, '/');
|
||||
assert.ok(
|
||||
normStdout.includes('.hermes/msd-core/bin/'),
|
||||
`Expected MSD_TOOLS to resolve into .hermes/msd-core/bin/, got:\n${stdout.trim()}`,
|
||||
normStdout.includes('.codex/msd-core/bin/'),
|
||||
`Expected MSD_TOOLS to resolve into .codex/msd-core/bin/, got:\n${stdout.trim()}`,
|
||||
);
|
||||
assert.ok(
|
||||
stdout.includes('HERMES_DEFAULT_STUB:status'),
|
||||
`Expected stub output "HERMES_DEFAULT_STUB:status", got:\n${stdout.trim()}`,
|
||||
stdout.includes('CODEX_DEFAULT_STUB:status'),
|
||||
`Expected stub output "CODEX_DEFAULT_STUB:status", got:\n${stdout.trim()}`,
|
||||
);
|
||||
} finally {
|
||||
cleanup(fakeHome);
|
||||
@@ -1964,8 +1955,8 @@ describe('bug-891: non-Claude runtime home fallback arms', () => {
|
||||
}
|
||||
});
|
||||
|
||||
// ── (D) Resolution order: claude < hermes < hard-error ───────────────────
|
||||
test('(D) resolution order: .claude probe comes before hermes probe, hermes before hard error', () => {
|
||||
// ── (D) Resolution order: claude < codex < hard-error ───────────────────
|
||||
test('(D) resolution order: .claude probe comes before codex probe, codex before hard error', () => {
|
||||
const snippetContent = fs.readFileSync(SNIPPET_FILE, 'utf8');
|
||||
// #4834 folded the config-home probes into the _msd_homes() helper defined
|
||||
// ahead of the if-chain, so raw first-occurrence positions no longer
|
||||
@@ -1979,18 +1970,18 @@ describe('bug-891: non-Claude runtime home fallback arms', () => {
|
||||
|
||||
// The helper's claude arm reads `${CLAUDE_CONFIG_DIR:-$HOME/.claude}/...` —
|
||||
// the closing brace sits between `.claude` and the slash, so probe for the
|
||||
// arm text as it is actually spelled (same style as the hermes probe below).
|
||||
// arm text as it is actually spelled (same style as the codex probe below).
|
||||
const claudePos = homesBody.indexOf('$HOME/.claude}/msd-core/bin/');
|
||||
const hermesPos = homesBody.indexOf('.hermes}/msd-core/bin/');
|
||||
const codexPos = homesBody.indexOf('.codex}/msd-core/bin/');
|
||||
const errorPos = snippetContent.indexOf('exit 1');
|
||||
|
||||
assert.ok(claudePos !== -1, 'Snippet must contain the claude config-home arm');
|
||||
assert.ok(hermesPos !== -1, 'Snippet must contain .hermes}/msd-core/bin/ arm');
|
||||
assert.ok(codexPos !== -1, 'Snippet must contain .codex}/msd-core/bin/ arm');
|
||||
assert.ok(errorPos !== -1, 'Snippet must contain exit 1 hard-error');
|
||||
|
||||
assert.ok(
|
||||
claudePos < hermesPos,
|
||||
`Expected .claude probe (at ${claudePos}) before .hermes probe (at ${hermesPos}) among the config-home probes`,
|
||||
claudePos < codexPos,
|
||||
`Expected .claude probe (at ${claudePos}) before .codex probe (at ${codexPos}) among the config-home probes`,
|
||||
);
|
||||
assert.ok(
|
||||
helperEnd < errorPos,
|
||||
@@ -1998,9 +1989,9 @@ describe('bug-891: non-Claude runtime home fallback arms', () => {
|
||||
);
|
||||
});
|
||||
|
||||
// ── (E) Propagation: workflow .md files using msd_run contain hermes probe ─
|
||||
test('(E) all workflow .md files using msd_run contain the hermes runtime home probe', () => {
|
||||
const HERMES_PROBE = '.hermes}/msd-core/bin/';
|
||||
// ── (E) Propagation: workflow .md files using msd_run contain codex probe ─
|
||||
test('(E) all workflow .md files using msd_run contain the codex runtime home probe', () => {
|
||||
const CODEX_PROBE = '.codex}/msd-core/bin/';
|
||||
const files = collectWorkflowFiles();
|
||||
assert.ok(files.length > 0, 'expected at least one workflow .md file');
|
||||
|
||||
@@ -2013,7 +2004,7 @@ describe('bug-891: non-Claude runtime home fallback arms', () => {
|
||||
const fileHasMsdRun = allBlockLines.some((l) => /\bmsd_run\b/.test(l));
|
||||
if (!fileHasMsdRun) continue;
|
||||
const allContent = allBlockLines.join('\n');
|
||||
if (!allContent.includes(HERMES_PROBE)) {
|
||||
if (!allContent.includes(CODEX_PROBE)) {
|
||||
missing.push(path.relative(WORKFLOWS_DIR, f));
|
||||
}
|
||||
}
|
||||
@@ -2021,7 +2012,7 @@ describe('bug-891: non-Claude runtime home fallback arms', () => {
|
||||
assert.deepStrictEqual(
|
||||
missing,
|
||||
[],
|
||||
`These workflow files use msd_run but are missing the hermes runtime home probe ("${HERMES_PROBE}"). ` +
|
||||
`These workflow files use msd_run but are missing the codex runtime home probe ("${CODEX_PROBE}"). ` +
|
||||
`Run \`node scripts/sync-runtime-launcher.cjs\` to propagate:\n` +
|
||||
missing.join('\n'),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user