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:
@@ -1,15 +1,11 @@
|
||||
// docs-guard-exempt: kilo.ai/docs/... is an external URL citation in a comment, not a repo path.
|
||||
'use strict';
|
||||
|
||||
const { describe, test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const path = require('node:path');
|
||||
const fs = require('node:fs');
|
||||
|
||||
const ROOT = path.join(__dirname, '..');
|
||||
const {
|
||||
canonicalizeRuntimeName,
|
||||
resolveRuntimeNameFromCandidates,
|
||||
getProjectInstructionFile,
|
||||
RETIRED_RUNTIME_IDS,
|
||||
isRetiredRuntimeId,
|
||||
@@ -18,68 +14,6 @@ const {
|
||||
} = require(path.join(ROOT, 'msd-core', 'bin', 'lib', 'runtime-name-policy.cjs'));
|
||||
const { getGlobalConfigDir } = require(path.join(ROOT, 'msd-core', 'bin', 'lib', 'runtime-homes.cjs'));
|
||||
|
||||
describe('runtime-name-policy canonical runtime ids', () => {
|
||||
test('canonicalizes Kimi without adding extra aliases', () => {
|
||||
assert.strictEqual(canonicalizeRuntimeName('kimi'), 'kimi');
|
||||
assert.strictEqual(canonicalizeRuntimeName(' KIMI '), 'kimi');
|
||||
assert.strictEqual(resolveRuntimeNameFromCandidates('', null, 'kimi'), 'kimi');
|
||||
assert.strictEqual(canonicalizeRuntimeName('kimi-cli'), null);
|
||||
});
|
||||
|
||||
test('canonicalizes devin-desktop to windsurf (#792)', () => {
|
||||
assert.strictEqual(canonicalizeRuntimeName('devin-desktop'), 'windsurf');
|
||||
assert.strictEqual(canonicalizeRuntimeName('DEVIN-DESKTOP'), 'windsurf');
|
||||
assert.strictEqual(resolveRuntimeNameFromCandidates('devin-desktop'), 'windsurf');
|
||||
});
|
||||
});
|
||||
|
||||
describe('runtime-name-policy windsurf alias parity — manifest vs FALLBACK_ALIASES (#792)', () => {
|
||||
// DEFECT.GENERATIVE-FIX: manifest and FALLBACK_ALIASES are manually mirrored;
|
||||
// this test fails if they diverge for the windsurf key.
|
||||
const manifestPath = path.join(ROOT, 'msd-core', 'bin', 'shared', 'runtime-aliases.manifest.json');
|
||||
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
||||
|
||||
test('manifest windsurf array includes devin-desktop', () => {
|
||||
assert.ok(
|
||||
Array.isArray(manifest.windsurf) && manifest.windsurf.includes('devin-desktop'),
|
||||
`runtime-aliases.manifest.json windsurf array must include 'devin-desktop'; got: ${JSON.stringify(manifest.windsurf)}`,
|
||||
);
|
||||
});
|
||||
|
||||
test('FALLBACK_ALIASES windsurf includes devin-desktop (via canonicalization round-trip)', () => {
|
||||
// The built module merges manifest over FALLBACK_ALIASES; if the manifest is present
|
||||
// this verifies the combined set. The manifest test above separately guards the manifest.
|
||||
// Here we verify the live canonicalizer sees devin-desktop -> windsurf.
|
||||
assert.strictEqual(
|
||||
canonicalizeRuntimeName('devin-desktop'),
|
||||
'windsurf',
|
||||
'devin-desktop must resolve to windsurf via alias lookup',
|
||||
);
|
||||
});
|
||||
|
||||
test('manifest and FALLBACK_ALIASES windsurf alias sets are identical', () => {
|
||||
// Read FALLBACK_ALIASES from source to detect manual drift before a build.
|
||||
const srcPath = path.join(ROOT, 'src', 'runtime-name-policy.cts');
|
||||
// allow-test-rule: source-text-is-the-product (#3464)
|
||||
// FALLBACK_ALIASES source text IS the product contract for runtimes that can't load the manifest at runtime; verifying
|
||||
// both surfaces contain the same windsurf aliases catches manual-mirror drift.
|
||||
const src = fs.readFileSync(srcPath, 'utf8');
|
||||
// eslint-disable-next-line local/no-unbounded-quantifier -- parses this repo's own bounded src/runtime-name-policy.cts source, not adversarial input
|
||||
const match = src.match(/windsurf:\s*\[([^\]]+)\]/);
|
||||
assert.ok(match, 'FALLBACK_ALIASES windsurf row must exist in src/runtime-name-policy.cts');
|
||||
const srcAliases = match[1]
|
||||
.split(',')
|
||||
.map(s => s.trim().replace(/^['"]|['"]$/g, ''))
|
||||
.filter(Boolean);
|
||||
const manifestAliases = [...manifest.windsurf].sort();
|
||||
assert.deepStrictEqual(
|
||||
[...srcAliases].sort(),
|
||||
manifestAliases,
|
||||
`FALLBACK_ALIASES windsurf=${JSON.stringify(srcAliases.sort())} must match manifest windsurf=${JSON.stringify(manifestAliases)}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('runtime-name-policy getProjectInstructionFile (#1529)', () => {
|
||||
test('claude maps to .claude/CLAUDE.md (kept-as-is boundary case)', () => {
|
||||
assert.strictEqual(getProjectInstructionFile('claude'), '.claude/CLAUDE.md');
|
||||
@@ -93,18 +27,6 @@ describe('runtime-name-policy getProjectInstructionFile (#1529)', () => {
|
||||
assert.strictEqual(getProjectInstructionFile('opencode'), 'AGENTS.md');
|
||||
});
|
||||
|
||||
test('kilo maps to AGENTS.md', () => {
|
||||
assert.strictEqual(getProjectInstructionFile('kilo'), 'AGENTS.md');
|
||||
});
|
||||
|
||||
test('kimi maps to AGENTS.md', () => {
|
||||
assert.strictEqual(getProjectInstructionFile('kimi'), 'AGENTS.md');
|
||||
});
|
||||
|
||||
test('copilot maps to .github/copilot-instructions.md (GitHub docs read path)', () => {
|
||||
assert.strictEqual(getProjectInstructionFile('copilot'), '.github/copilot-instructions.md');
|
||||
});
|
||||
|
||||
test('a retired runtime id is REFUSED, not fallen back (#1928 / #4709 AC#1)', () => {
|
||||
// This test previously asserted the fallback — its own title said "falls
|
||||
// back to AGENTS.md" — which is precisely the defect #4709 AC#1 names: a
|
||||
@@ -136,126 +58,10 @@ describe('runtime-name-policy getProjectInstructionFile (#1529)', () => {
|
||||
// pinned the fallback; inverted rather than deleted so the record of the
|
||||
// old behaviour stays attached to the test that pinned it.
|
||||
assert.throws(() => getProjectInstructionFile('gem' + 'ini-cli'), /retired by #1928/);
|
||||
// github-copilot is an alias for copilot.
|
||||
assert.strictEqual(getProjectInstructionFile('github-copilot'), '.github/copilot-instructions.md');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────
|
||||
// Folded from tests/bug-783-kilo-global-skills-base.test.cjs — consolidation epic #1969 (B3 #1972)
|
||||
// ────────────────────────────────────────────────────────────────────────
|
||||
{
|
||||
const { describe: __foldDescribe } = require('node:test');
|
||||
__foldDescribe("folded:bug-783-kilo-global-skills-base (consolidation epic #1969 B3 #1972)", () => {
|
||||
'use strict';
|
||||
// Regression guard for bug #783.
|
||||
//
|
||||
// getGlobalSkillsBase('kilo') was returning ~/.config/kilo/skills (the XDG
|
||||
// config dir) instead of ~/.kilo/skills — where Kilo Code actually discovers
|
||||
// global skills per its docs:
|
||||
// https://kilo.ai/docs/customize/skills
|
||||
// "Global skills are located in the `.kilo` directory within your Home
|
||||
// directory: ~/.kilo/skills/"
|
||||
//
|
||||
// The fix adds a special case in getGlobalSkillsBase() that resolves kilo's
|
||||
// skills dir from HOME (not from the XDG config dir). The config dir at
|
||||
// ~/.config/kilo is still CORRECT for commands (command/) and must stay
|
||||
// unchanged — this test verifies both roles are separate.
|
||||
|
||||
const { describe, test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const path = require('node:path');
|
||||
const os = require('node:os');
|
||||
|
||||
const ROOT = path.join(__dirname, '..');
|
||||
const {
|
||||
getGlobalConfigDir,
|
||||
getGlobalSkillsBase,
|
||||
} = require(path.join(ROOT, 'msd-core', 'bin', 'lib', 'runtime-homes.cjs'));
|
||||
|
||||
// Helper: temporarily override env vars for a test, restoring them afterwards.
|
||||
function withEnv(overrides, fn) {
|
||||
const saved = {};
|
||||
for (const [key, value] of Object.entries(overrides)) {
|
||||
saved[key] = process.env[key];
|
||||
if (value === undefined) delete process.env[key];
|
||||
else process.env[key] = value;
|
||||
}
|
||||
try {
|
||||
return fn();
|
||||
} finally {
|
||||
for (const [key] of Object.entries(overrides)) {
|
||||
if (saved[key] === undefined) delete process.env[key];
|
||||
else process.env[key] = saved[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear all kilo-relevant env vars so tests are hermetic.
|
||||
const kiloEnvClears = {
|
||||
KILO_CONFIG_DIR: undefined,
|
||||
XDG_CONFIG_HOME: undefined,
|
||||
};
|
||||
|
||||
describe('bug #783: kilo global skills dir is ~/.kilo/skills, not ~/.config/kilo/skills', () => {
|
||||
test('getGlobalSkillsBase("kilo") resolves to ~/.kilo/skills', () => {
|
||||
withEnv(kiloEnvClears, () => {
|
||||
assert.strictEqual(
|
||||
getGlobalSkillsBase('kilo'),
|
||||
path.join(os.homedir(), '.kilo', 'skills'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('getGlobalConfigDir("kilo") still resolves to ~/.config/kilo (config dir unchanged)', () => {
|
||||
withEnv(kiloEnvClears, () => {
|
||||
assert.strictEqual(
|
||||
getGlobalConfigDir('kilo'),
|
||||
path.join(os.homedir(), '.config', 'kilo'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('kilo skills dir and config dir are decoupled (not equal, not nested)', () => {
|
||||
withEnv(kiloEnvClears, () => {
|
||||
const skillsBase = getGlobalSkillsBase('kilo');
|
||||
const configDir = getGlobalConfigDir('kilo');
|
||||
|
||||
assert.notStrictEqual(skillsBase, configDir, 'skills dir must differ from config dir');
|
||||
assert.ok(
|
||||
!skillsBase.startsWith(configDir + path.sep),
|
||||
`skills dir (${skillsBase}) must not be nested under config dir (${configDir})`,
|
||||
);
|
||||
assert.ok(
|
||||
!configDir.startsWith(skillsBase + path.sep),
|
||||
`config dir (${configDir}) must not be nested under skills dir (${skillsBase})`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('getGlobalSkillsBase("kilo") is NOT affected by KILO_CONFIG_DIR override', () => {
|
||||
// Skills always live in ~/.kilo/skills regardless of XDG/config-dir overrides.
|
||||
withEnv({ KILO_CONFIG_DIR: '/tmp/custom-kilo-config', XDG_CONFIG_HOME: undefined }, () => {
|
||||
assert.strictEqual(
|
||||
getGlobalSkillsBase('kilo'),
|
||||
path.join(os.homedir(), '.kilo', 'skills'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('getGlobalSkillsBase("kilo") is NOT affected by XDG_CONFIG_HOME override', () => {
|
||||
withEnv({ KILO_CONFIG_DIR: undefined, XDG_CONFIG_HOME: '/tmp/custom-xdg' }, () => {
|
||||
assert.strictEqual(
|
||||
getGlobalSkillsBase('kilo'),
|
||||
path.join(os.homedir(), '.kilo', 'skills'),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────
|
||||
// Folded from tests/fix-4709-retired-runtime-ids.test.cjs — AC#1 of epic #4709.
|
||||
//
|
||||
@@ -412,11 +218,10 @@ describe('#4709 AC#1 — no canonical runtime regressed', () => {
|
||||
test('claude and codex keep their exact instruction files', () => {
|
||||
assert.strictEqual(getProjectInstructionFile('claude'), '.claude/CLAUDE.md');
|
||||
assert.strictEqual(getProjectInstructionFile('codex'), 'AGENTS.md');
|
||||
assert.strictEqual(getProjectInstructionFile('copilot'), '.github/copilot-instructions.md');
|
||||
});
|
||||
|
||||
test('a spread of canonical ids resolve on all four accessors without throwing', () => {
|
||||
for (const id of ['claude', 'codex', 'opencode', 'antigravity', 'copilot', 'cursor', 'kimi', 'pi']) {
|
||||
for (const id of ['claude', 'codex', 'opencode', 'antigravity', 'cursor', 'zcode']) {
|
||||
for (const { label, call } of ACCESSORS) {
|
||||
assert.doesNotThrow(() => call(id), `${label} must not throw for canonical id ${id}`);
|
||||
}
|
||||
@@ -430,7 +235,6 @@ describe('#4709 AC#1 — no canonical runtime regressed', () => {
|
||||
assert.strictEqual(getGlobalConfigHomeFragment('claude'), "'.claude'");
|
||||
assert.strictEqual(getGlobalConfigHomeFragment('codex'), "'.codex'");
|
||||
assert.strictEqual(getGlobalConfigHomeFragment('opencode'), "'.config', 'opencode'");
|
||||
assert.strictEqual(getGlobalConfigHomeFragment('pi'), "'.pi', 'agent'");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user