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

@@ -59,14 +59,14 @@ test('finishInstall rejects an invalid configured entrypoint before Done output'
process.env.USERPROFILE = root;
const restoreConfigLocationEnv = helpers.scrubConfigLocationEnv();
try {
assert.throws(() => finishInstall(null, null, null, false, 'cline', false, root, {
configuredEntrypoints: [{ runtime: 'cline', configPath: path.join(root, 'config'), scriptPath: path.join(root, 'missing.js') }],
assert.throws(() => finishInstall(null, null, null, false, 'cursor', false, root, {
configuredEntrypoints: [{ runtime: 'cursor', configPath: path.join(root, 'config'), scriptPath: path.join(root, 'missing.js') }],
}), /Configured entrypoint validation failed/);
// #4249 review, Major: the message must name the actual consequence for
// the failing runtime, not just that something failed — Cline has no
// the failing runtime, not just that something failed — Cursor has no
// revert path, so its entry must be flagged "NOT reverted".
assert.throws(() => finishInstall(null, null, null, false, 'cline', false, root, {
configuredEntrypoints: [{ runtime: 'cline', configPath: path.join(root, 'config'), scriptPath: path.join(root, 'missing.js') }],
assert.throws(() => finishInstall(null, null, null, false, 'cursor', false, root, {
configuredEntrypoints: [{ runtime: 'cursor', configPath: path.join(root, 'config'), scriptPath: path.join(root, 'missing.js') }],
}), /NOT reverted/);
} finally {
console.log = originalLog;
@@ -357,35 +357,6 @@ test('runtime config writers expose the exact configured entrypoints they emit',
);
assert.ok(cursor.configuredEntrypoints.every(entry => entry.configPath === cursor.hooksJsonPath));
const windsurfRoot = path.join(root, 'windsurf');
const windsurf = hooksSurface.writeWindsurfHooksJson(windsurfRoot, sourceRoot);
assert.deepEqual(
windsurf.configuredEntrypoints.map(entry => path.basename(entry.scriptPath)).sort(),
['msd-windsurf-pre-command.js', 'msd-windsurf-pre-write.js'],
);
const kimiRoot = path.join(root, 'kimi');
fs.cpSync(path.join(sourceRoot, 'hooks'), path.join(kimiRoot, 'hooks'), { recursive: true });
const kimiConfig = path.join(root, 'kimi-config.toml');
const kimi = hooksSurface.writeKimiHooksToml(kimiConfig, kimiRoot, {
hookOpts: { runtime: 'kimi' },
});
assert.equal(kimi.configuredEntrypoints.length, kimi.entryCount);
assert.ok(kimi.configuredEntrypoints.every(entry => entry.configPath === kimiConfig));
const clineRoot = path.join(root, 'cline');
const cline = hooksSurface.writeClineArtifacts(clineRoot, false);
assert.deepEqual(
cline.configuredEntrypoints.map(entry => path.basename(entry.scriptPath)),
['PreToolUse'],
);
// #4249 (CodeRabbit): Cline's `#!/usr/bin/env node` hook is a hybrid —
// self-executable (needs its own execute bit checked) AND PATH-dependent
// on `node` (needs an interpreter candidate resolved), unlike MSD's other
// JS hooks which bake an absolute node path specifically to avoid this.
assert.equal(cline.configuredEntrypoints[0].selfExecutable, true);
assert.deepEqual(cline.configuredEntrypoints[0].interpreterCandidates, ['node']);
const portable = [];
assert.ok(hooksSurface.buildHookCommand(root, 'msd-context-monitor.js', {
runtime: 'claude',
@@ -431,6 +402,22 @@ test('a minimal Codex rollback restores skills from the alternate skills home (#
});
});
// Drive a REAL aggregate failure through installAllRuntimes: delegate to the real
// validator, then append an invalid entry for the named runtime. Every real
// entrypoint is a path the installer just wrote, so there is no PATH trick left
// that fails one runtime's entry on its own.
function forceEntrypointFailureFor(runtime) {
const real = hooksSurface.validateConfiguredEntrypoints;
hooksSurface.validateConfiguredEntrypoints = (entries, deps) => {
const result = real(entries, deps);
const invalid = [...(result.invalid || []), {
runtime, configPath: '/nonexistent/config', role: 'script', path: '/nonexistent/hook.js', reason: 'missing',
}];
return { ok: false, invalid };
};
return () => { hooksSurface.validateConfiguredEntrypoints = real; };
}
test('an aggregate entrypoint validation failure rolls the Codex install back (#4249)', (t) => {
withSandboxedHome(t, 'configured-entrypoint-aggregate-', () => {
const first = install(true, 'codex');
@@ -441,21 +428,17 @@ test('an aggregate entrypoint validation failure rolls the Codex install back (#
const priorBytes = '# bytes only this test wrote\n';
fs.writeFileSync(configPath, priorBytes);
// Cline's `#!/usr/bin/env node` hook records interpreterCandidates: ['node'],
// resolved off PATH. Emptying PATH makes exactly that entry fail, which is
// the only way to drive a REAL aggregate failure through installAllRuntimes:
// every other entrypoint is a path the installer just wrote. Codex installs
// first, so the reversed rollback loop reaches its restoreCodexSnapshot.
const savedPath = process.env.PATH;
process.env.PATH = '';
// Codex installs first, so the reversed rollback loop reaches its
// restoreCodexSnapshot.
const restore = forceEntrypointFailureFor('cursor');
try {
assert.throws(
() => installAllRuntimes(['codex', 'cline'], true, false),
() => installAllRuntimes(['codex', 'cursor'], true, false),
/Configured entrypoint validation failed/,
'an unresolvable interpreter must fail the aggregate gate',
'an invalid entrypoint must fail the aggregate gate',
);
} finally {
process.env.PATH = savedPath;
restore();
}
assert.equal(
@@ -466,38 +449,32 @@ test('an aggregate entrypoint validation failure rolls the Codex install back (#
});
});
test('an aggregate entrypoint validation failure leaves Cline\'s own config file on disk, unreverted (#4249 review, Major)', (t) => {
test('an aggregate entrypoint validation failure leaves Cursor\'s own config file on disk, unreverted (#4249 review, Major)', (t) => {
withSandboxedHome(t, 'configured-entrypoint-unreverted-', () => {
// Cline is one of the four runtimes (Cursor/Windsurf/Kimi/Cline) that
// write their config file inside install(), ahead of the validation
// gate, with no snapshot/restore path — see the rollback-matrix
// paragraph in the update-msd how-to. Unlike the Codex companion test above,
// this asserts the POSITIVE case that same paragraph discloses in
// prose but no prior test proved: the file the failing runtime itself
// just wrote is still there afterward, broken and unreverted.
const clineFirst = install(true, 'cline');
const clineHookPath = path.join(clineFirst.configDir, '.clinerules', 'hooks', 'PreToolUse');
assert.equal(fs.existsSync(clineHookPath), true, 'precondition: Cline hook must exist before the failing install');
// Cursor writes its config file inside install(), ahead of the validation
// gate, with no snapshot/restore path — see the rollback-matrix paragraph
// in the update-msd how-to. This asserts the POSITIVE case that paragraph
// discloses in prose: the file the failing runtime itself just wrote is
// still there afterward, broken and unreverted.
const cursorFirst = install(true, 'cursor');
const cursorHooksJson = path.join(cursorFirst.configDir, 'hooks.json');
assert.equal(fs.existsSync(cursorHooksJson), true, 'precondition: Cursor hooks.json must exist before the failing install');
// Same technique as the Codex rollback test: an emptied PATH makes
// Cline's own `#!/usr/bin/env node` hook fail interpreter resolution,
// driving a REAL aggregate failure through installAllRuntimes.
const savedPath = process.env.PATH;
process.env.PATH = '';
const restore = forceEntrypointFailureFor('cursor');
try {
assert.throws(
() => installAllRuntimes(['codex', 'cline'], true, false),
/Configured entrypoint validation failed/,
'an unresolvable interpreter must fail the aggregate gate',
() => installAllRuntimes(['codex', 'cursor'], true, false),
/Configured entrypoint validation failed[\s\S]*cursor[\s\S]*NOT reverted/,
'an invalid entrypoint must fail the aggregate gate and flag Cursor as NOT reverted',
);
} finally {
process.env.PATH = savedPath;
restore();
}
assert.equal(
fs.existsSync(clineHookPath),
fs.existsSync(cursorHooksJson),
true,
'Cline has no snapshot/restore path — its already-written hook file must remain on disk, not be deleted',
'Cursor has no snapshot/restore path — its already-written hooks.json must remain on disk, not be deleted',
);
});
});
@@ -511,20 +488,18 @@ test('a NON-entrypoint finalize failure leaves an already-successful Codex insta
const priorBytes = '# bytes only this test wrote\n';
fs.writeFileSync(configPath, priorBytes);
// Kilo is the smallest real non-Codex runtime whose finishInstall still
// writes: plan.finishPermissionWriter === 'kilo' makes it call
// configureKiloPermissions unconditionally (that writer, unlike OpenCode's,
// is NOT MSD_TEST_MODE-gated), ending in an unguarded
// fs.writeFileSync(<configDir>/kilo.json). Cline can't stand in here — its
// plan is writesSharedSettings:false + finishPermissionWriter:null, so its
// finishInstall performs no write at all and has no non-entrypoint failure
// path to force. EACCES on the Kilo write is injected by monkeypatching
// Antigravity is the smallest real non-Codex runtime whose finishInstall
// still writes: plan.finishPermissionWriter === 'antigravity' makes it call
// configureAntigravityMcpConfig unconditionally (that writer, unlike
// OpenCode's, is NOT MSD_TEST_MODE-gated), ending in an unguarded
// fs.writeFileSync(<configDir>/mcp_config.json). EACCES on that write is
// injected by monkeypatching
// node:fs and restoring it in a finally — never chmod 0o000, which root
// bypasses under Docker/CI and would give this test zero real coverage.
const realWriteFileSync = fs.writeFileSync;
fs.writeFileSync = function poisonedWriteFileSync(target, ...args) {
const targetStr = typeof target === 'string' ? target : String(target);
if (/[\\/]kilo\.jsonc?$/.test(targetStr)) {
if (/[\\/]mcp_config\.json$/.test(targetStr)) {
const injected = new Error(`EACCES: permission denied, open '${targetStr}'`);
injected.code = 'EACCES';
throw injected;
@@ -533,16 +508,16 @@ test('a NON-entrypoint finalize failure leaves an already-successful Codex insta
};
try {
assert.throws(
() => installAllRuntimes(['codex', 'kilo'], true, false),
() => installAllRuntimes(['codex', 'antigravity'], true, false),
/EACCES: permission denied/,
'a failed Kilo permission write must abort the aggregate install',
'a failed Antigravity MCP config write must abort the aggregate install',
);
} finally {
fs.writeFileSync = realWriteFileSync;
}
// Codex installs first and finishInstall prints its "Done!" summary before
// Kilo's throws, so the reversed rollback loop does reach Codex's entry —
// Antigravity's throws, so the reversed rollback loop does reach Codex's entry —
// but with a non-entrypoint error it must take the installer-migrations-only
// closure, not restoreCodexSnapshot. A configured-entrypoint validation
// failure is the only trigger docs/how-to/update-msd.md documents for