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:
Jakub Zych
2026-10-06 20:02:40 +02:00
parent 12ee75a509
commit 6cfa0c55d2
509 changed files with 1869 additions and 47737 deletions

View File

@@ -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'),
);