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:
@@ -490,11 +490,11 @@ describe('installRuntimeArtifacts — F2: fake-adapter install touches no real f
|
||||
);
|
||||
});
|
||||
|
||||
test('fake-adapter install touches no real filesystem (nativePlugin runtime: pi)', (t) => {
|
||||
test('fake-adapter install touches no real filesystem (nativePlugin runtime: opencode)', (t) => {
|
||||
poisonRealFsAgainstDestination(t, ' (nativePlugin)');
|
||||
|
||||
// Resolve the SAME pluginSrc path _installNativePluginIfDeclared
|
||||
// (install-engine.cts) computes for pi's declared nativePlugin, using the
|
||||
// (install-engine.cts) computes for opencode's declared nativePlugin, using the
|
||||
// real (unrouted, package-own-source) findInstallSourceRoot — this read
|
||||
// happens BEFORE the poison mocks above are installed... no: it must
|
||||
// happen before `t.mock.method` calls would matter for IT, but
|
||||
@@ -504,22 +504,22 @@ describe('installRuntimeArtifacts — F2: fake-adapter install touches no real f
|
||||
// resolving this here is safe even after poisoning existsSync et al.
|
||||
const commandsMsdDir = REAL_COMMANDS_DIR;
|
||||
const repoRoot = path.dirname(path.dirname(commandsMsdDir));
|
||||
const nativePlugin = registry.runtimes.pi.runtime.hostBehaviors.nativePlugin;
|
||||
assert.ok(nativePlugin && nativePlugin.source, 'pi must declare hostBehaviors.nativePlugin.source (registry drifted)');
|
||||
const nativePlugin = registry.runtimes.opencode.runtime.hostBehaviors.nativePlugin;
|
||||
assert.ok(nativePlugin && nativePlugin.source, 'opencode must declare hostBehaviors.nativePlugin.source (registry drifted)');
|
||||
const pluginSrc = path.join(repoRoot, nativePlugin.source);
|
||||
|
||||
const configDir = path.join(os.tmpdir(), `msd-f2-pi-nativeplugin-${crypto.randomUUID()}`);
|
||||
const configDir = path.join(os.tmpdir(), `msd-f2-opencode-nativeplugin-${crypto.randomUUID()}`);
|
||||
const fakeFs = createFakeInstallFs([
|
||||
[pluginSrc, { type: 'file', content: '// fake plugin adapter\n' }],
|
||||
]);
|
||||
|
||||
const result = installRuntimeArtifacts(
|
||||
'pi', configDir, 'global', RESOLVED_CORE, undefined, undefined,
|
||||
'opencode', configDir, 'global', RESOLVED_CORE, undefined, undefined,
|
||||
{ fs: fakeFs, packageRoot: REAL_PACKAGE_ROOT },
|
||||
);
|
||||
|
||||
assert.notStrictEqual(result, undefined, 'F2 (nativePlugin): must still return a plan');
|
||||
assert.strictEqual(result.postSteps.nativePlugin, true, 'F2 (nativePlugin): postSteps.nativePlugin must be true for pi');
|
||||
assert.strictEqual(result.postSteps.nativePlugin, true, 'F2 (nativePlugin): postSteps.nativePlugin must be true for opencode');
|
||||
const destPath = path.join(configDir, nativePlugin.dir, nativePlugin.file);
|
||||
assert.strictEqual(
|
||||
fakeFs._store.has(path.normalize(destPath)),
|
||||
@@ -619,69 +619,17 @@ describe('installRuntimeArtifacts — E2: claude local', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('installRuntimeArtifacts — E4: kilo (second family member)', () => {
|
||||
test('kilo family install still returns a plan', (t) => {
|
||||
const configDir = createTempDir('msd-e4-kilo-');
|
||||
t.after(() => cleanup(configDir));
|
||||
sandboxHome(t, configDir);
|
||||
|
||||
const result = installRuntimeArtifacts('kilo', configDir, 'global', RESOLVED_CORE);
|
||||
|
||||
assert.notStrictEqual(
|
||||
result, undefined,
|
||||
'E4: kilo, the SECOND combined-family runtime, must ALSO return a plan — E3 is not a one-runtime special case',
|
||||
);
|
||||
// 'agents' was added here deliberately by #2875 Part 2 Task A
|
||||
// (installAgentsKindStandalone, install-engine.cts:1614-1618): the
|
||||
// combined-family (opencode/kilo) executed plan now also reports the
|
||||
// agents kind it stages via installAgentsKindStandalone, mirroring the
|
||||
// generic layout-driven loop's own top-level shape (install-engine.cts:1627-1637).
|
||||
// This test was written under #2874 (Phase 5), before that kind was
|
||||
// wired in — its expected list was never updated. Kilo's resolved layout
|
||||
// declares an `agents` kind, so a correct plan MUST include it; a
|
||||
// ['commands', 'skills']-only expectation encoded the pre-#2875 shape,
|
||||
// not a real contract.
|
||||
assert.deepStrictEqual(result.kinds.map((k) => k.kind).sort(), ['agents', 'commands', 'skills']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('installRuntimeArtifacts — E5/E7: empty layout + nativePlugin post-step (pi)', () => {
|
||||
test('empty layout returns an empty plan', (t) => {
|
||||
const configDir = createTempDir('msd-e5-pi-');
|
||||
t.after(() => cleanup(configDir));
|
||||
sandboxHome(t, configDir);
|
||||
|
||||
const result = installRuntimeArtifacts('pi', configDir, 'global', RESOLVED_CORE);
|
||||
|
||||
assert.ok(Array.isArray(result.kinds), 'E5: kinds must be an array even when layout.kinds is empty');
|
||||
assert.strictEqual(result.kinds.length, 0, 'E5: pi declares an empty artifactLayout — kinds must be [], never undefined');
|
||||
});
|
||||
|
||||
describe('installRuntimeArtifacts — E7: nativePlugin post-step (opencode)', () => {
|
||||
test('native plugin post-step is recorded', (t) => {
|
||||
const configDir = createTempDir('msd-e7-pi-');
|
||||
const configDir = createTempDir('msd-e7-opencode-');
|
||||
t.after(() => cleanup(configDir));
|
||||
sandboxHome(t, configDir);
|
||||
|
||||
const result = installRuntimeArtifacts('pi', configDir, 'global', RESOLVED_CORE);
|
||||
const result = installRuntimeArtifacts('opencode', configDir, 'global', RESOLVED_CORE);
|
||||
|
||||
assert.strictEqual(
|
||||
result.postSteps.nativePlugin, true,
|
||||
'E7: pi declares hostBehaviors.nativePlugin — postSteps.nativePlugin must record it as a post-step',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('installRuntimeArtifacts — E6: hermes post-step is recorded', () => {
|
||||
test('hermes post-step is recorded', (t) => {
|
||||
const configDir = createTempDir('msd-e6-hermes-');
|
||||
t.after(() => cleanup(configDir));
|
||||
sandboxHome(t, configDir);
|
||||
|
||||
const result = installRuntimeArtifacts('hermes', configDir, 'global', RESOLVED_CORE);
|
||||
|
||||
assert.strictEqual(
|
||||
result.postSteps.hermesBareStemCleanup, true,
|
||||
'E6: hermes must record _removeHermesBareStemDirs having run as a post-step',
|
||||
'E7: opencode declares hostBehaviors.nativePlugin — postSteps.nativePlugin must record it as a post-step',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -846,7 +794,7 @@ function createRealDelegatingFs(overrides = {}) {
|
||||
|
||||
describe('installRuntimeArtifacts — F4: adapter errors propagate, cleanup still runs', () => {
|
||||
test('adapter errors propagate, cleanup still runs', (t) => {
|
||||
const configDir = createTempDir('msd-f4-augment-');
|
||||
const configDir = createTempDir('msd-f4-claude-local-');
|
||||
t.after(() => cleanup(configDir));
|
||||
sandboxHome(t, configDir);
|
||||
|
||||
@@ -866,7 +814,7 @@ describe('installRuntimeArtifacts — F4: adapter errors propagate, cleanup stil
|
||||
});
|
||||
|
||||
assert.throws(
|
||||
() => installRuntimeArtifacts('augment', configDir, 'global', RESOLVED_FULL, TEST_ATTRIBUTION, undefined, { fs: fakeFs }),
|
||||
() => installRuntimeArtifacts('claude', configDir, 'local', RESOLVED_FULL, TEST_ATTRIBUTION, undefined, { fs: fakeFs }),
|
||||
(err) => err.code === 'EACCES',
|
||||
'F4: an EACCES from the injected adapter mid-copy must propagate to the caller unchanged, exactly as a real EACCES would today',
|
||||
);
|
||||
@@ -1063,13 +1011,13 @@ describe('installRuntimeArtifacts — H5: nul byte in dest is rejected', () => {
|
||||
|
||||
describe('installRuntimeArtifacts — I1: successful cleanup is recorded', () => {
|
||||
test('successful cleanup is recorded', (t) => {
|
||||
const configDir = createTempDir('msd-i1-augment-');
|
||||
const configDir = createTempDir('msd-i1-claude-local-');
|
||||
t.after(() => cleanup(configDir));
|
||||
sandboxHome(t, configDir);
|
||||
|
||||
const result = installRuntimeArtifacts('augment', configDir, 'global', RESOLVED_FULL, TEST_ATTRIBUTION);
|
||||
const result = installRuntimeArtifacts('claude', configDir, 'local', RESOLVED_FULL, TEST_ATTRIBUTION);
|
||||
|
||||
assert.ok(result.cleanup.length > 0, 'I1: augment install must produce at least one cleanupDirs entry to prove this row');
|
||||
assert.ok(result.cleanup.length > 0, 'I1: claude local install must produce at least one cleanupDirs entry to prove this row');
|
||||
for (const entry of result.cleanup) {
|
||||
assert.strictEqual(typeof entry.dir, 'string');
|
||||
assert.strictEqual(entry.ok, true, `I1: successful cleanup entries must record ok:true (dir=${entry.dir})`);
|
||||
@@ -1080,7 +1028,7 @@ describe('installRuntimeArtifacts — I1: successful cleanup is recorded', () =>
|
||||
|
||||
describe('installRuntimeArtifacts — I2: failed cleanup is visible, not silent', () => {
|
||||
test('failed cleanup is visible, not silent', (t) => {
|
||||
const configDir = createTempDir('msd-i2-augment-');
|
||||
const configDir = createTempDir('msd-i2-claude-local-');
|
||||
t.after(() => cleanup(configDir));
|
||||
sandboxHome(t, configDir);
|
||||
|
||||
@@ -1094,10 +1042,10 @@ describe('installRuntimeArtifacts — I2: failed cleanup is visible, not silent'
|
||||
},
|
||||
});
|
||||
|
||||
const result = installRuntimeArtifacts('augment', configDir, 'global', RESOLVED_FULL, TEST_ATTRIBUTION, undefined, { fs: fakeFs });
|
||||
const result = installRuntimeArtifacts('claude', configDir, 'local', RESOLVED_FULL, TEST_ATTRIBUTION, undefined, { fs: fakeFs });
|
||||
|
||||
assert.notStrictEqual(result, undefined, 'I2: install must still succeed (never fail) even when cleanup throws');
|
||||
assert.ok(result.cleanup.length > 0, 'I2: augment must have at least one cleanupDirs entry to fail');
|
||||
assert.ok(result.cleanup.length > 0, 'I2: claude local must have at least one cleanupDirs entry to fail');
|
||||
assert.ok(
|
||||
result.cleanup.every((c) => c.ok === false),
|
||||
'I2: a cleanup rmSync throw must be reported as ok:false on the returned plan, never silently dropped',
|
||||
@@ -1138,15 +1086,15 @@ describe('installRuntimeArtifacts — I4: stage failure before any item cleans u
|
||||
|
||||
describe('installRuntimeArtifacts — I5: rewrite failure mid-plan cleans up and throws', () => {
|
||||
test('rewrite failure cleans up and throws', (t) => {
|
||||
const configDir = createTempDir('msd-i5-augment-');
|
||||
const configDir = createTempDir('msd-i5-claude-local-');
|
||||
t.after(() => cleanup(configDir));
|
||||
sandboxHome(t, configDir);
|
||||
|
||||
let capturedCleanupDir;
|
||||
const fakeFs = createRealDelegatingFs({
|
||||
mkdirSync: (p, opts) => {
|
||||
if (String(p).includes('msd-profile-runtime-skills-')) {
|
||||
throw new Error('I5: simulated skills-stage failure AFTER commands already rewrote+registered a cleanup dir');
|
||||
if (String(p).includes('msd-profile-runtime-agents-')) {
|
||||
throw new Error('I5: simulated agents-stage failure AFTER commands already rewrote+registered a cleanup dir');
|
||||
}
|
||||
fs.mkdirSync(p, opts);
|
||||
return undefined;
|
||||
@@ -1160,8 +1108,8 @@ describe('installRuntimeArtifacts — I5: rewrite failure mid-plan cleans up and
|
||||
});
|
||||
|
||||
assert.throws(
|
||||
() => installRuntimeArtifacts('augment', configDir, 'global', RESOLVED_FULL, TEST_ATTRIBUTION, undefined, { fs: fakeFs }),
|
||||
/I5: simulated skills-stage failure/,
|
||||
() => installRuntimeArtifacts('claude', configDir, 'local', RESOLVED_FULL, TEST_ATTRIBUTION, undefined, { fs: fakeFs }),
|
||||
/I5: simulated agents-stage failure/,
|
||||
'I5: a failure in a LATER kind\'s stage step must still propagate as a thrown error',
|
||||
);
|
||||
assert.ok(capturedCleanupDir, 'I5 test precondition: the commands kind\'s rewrite dir must have been observed before the skills-stage failure');
|
||||
@@ -1191,14 +1139,14 @@ function walkFilesRecursively(root) {
|
||||
|
||||
describe('installRuntimeArtifacts — K3: real install before/after, full recursive diff', () => {
|
||||
test('writes are byte-identical', (t) => {
|
||||
for (const runtime of ['claude', 'qwen']) {
|
||||
for (const runtime of ['claude', 'codex', 'cursor', 'zcode', 'antigravity']) {
|
||||
const dirA = createTempDir(`msd-k3-${runtime}-a-`);
|
||||
const dirB = createTempDir(`msd-k3-${runtime}-b-`);
|
||||
// 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 msd-k3-qwen-* home
|
||||
// shard (observed on the windows matrix: a leaked msd-k3-codex-* 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;
|
||||
|
||||
Reference in New Issue
Block a user