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:
@@ -2,15 +2,12 @@
|
||||
/**
|
||||
* Installer Module — date-stamped regression tests.
|
||||
*
|
||||
* Consolidates install-hermes-regressions.test.cjs into a single
|
||||
* regressions file for the installer module cluster.
|
||||
* Regressions file for the installer module cluster.
|
||||
*
|
||||
* Defects covered:
|
||||
* #3664 — --config-dir foreign-agent destination warning (warn-and-proceed)
|
||||
* #3664 Defect #1 — stale skills/msd/msd-<stem>/ dirs on Hermes upgrade
|
||||
* #3664 Defect #2 — --hermes --profile=core falls through to wrong path
|
||||
* #2973 M1–M3 — dev-preferences migration at profile=core for hermes/qwen/claude
|
||||
* #2973 U1–U3 — uninstall preserves dev-preferences via skill migration
|
||||
* #2973 M3 — dev-preferences migration at profile=core for claude
|
||||
* #2973 U2 — uninstall preserves dev-preferences via skill migration
|
||||
*
|
||||
* Closes #3758
|
||||
*/
|
||||
@@ -29,11 +26,6 @@ const { INSTALL_TIMEOUT_MS, FIXTURE_HOOK_TIMEOUT_SECONDS } = require('./helpers/
|
||||
const SCOPED_INSTALL_TIMEOUT_MS = 60_000;
|
||||
|
||||
const { createTempDir, cleanup, mockPartialWriteThenThrow } = require('./helpers.cjs');
|
||||
const {
|
||||
loadSkillsManifest,
|
||||
resolveProfile,
|
||||
} = require('../msd-core/bin/lib/install-profiles.cjs');
|
||||
|
||||
// Load install exports via MSD_TEST_MODE to skip CLI main()
|
||||
const savedTestMode = process.env.MSD_TEST_MODE;
|
||||
process.env.MSD_TEST_MODE = '1';
|
||||
@@ -48,7 +40,6 @@ try {
|
||||
const { install, mergeClaudePermissions, MSD_CLAUDE_ALLOW_PERMISSIONS, MSD_CLAUDE_LEGACY_ALLOW_PERMISSIONS, MSD_CLAUDE_LEGACY_DENY_PERMISSIONS, copyWithPathReplacement } = installExports || {};
|
||||
|
||||
const {
|
||||
installRuntimeArtifacts,
|
||||
uninstallRuntimeArtifacts,
|
||||
} = require('../msd-core/bin/lib/install-engine.cjs');
|
||||
|
||||
@@ -61,9 +52,6 @@ const {
|
||||
const INSTALL_SCRIPT = path.join(__dirname, '..', 'bin', 'install.js');
|
||||
// #3145: class-norm timeout, not a per-suite value — see helpers/timeouts.cjs.
|
||||
const HOOKS_SRC = path.join(__dirname, '..', 'hooks');
|
||||
const REAL_COMMANDS_DIR = path.join(__dirname, '..', 'commands', 'msd');
|
||||
const MANIFEST = loadSkillsManifest(REAL_COMMANDS_DIR);
|
||||
const RESOLVED_CORE = resolveProfile({ modes: ['core'], manifest: MANIFEST });
|
||||
|
||||
/**
|
||||
* Stub managed MSD hook files into targetDir/hooks/ so that
|
||||
@@ -119,171 +107,6 @@ describe('#2429 regression: Codex local skills stay project-scoped', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Defect #1 — Hermes upgrade: bare-stem dirs from #3664 era become stale ──
|
||||
//
|
||||
// #947 REVERSES #3664: the canonical layout is now skills/msd/msd-<stem>/ again.
|
||||
// The migration now removes bare-stem dirs (from #3664: prefix='') and writes
|
||||
// the msd-prefixed layout. Pre-existing msd-prefixed dirs (the "intermediate"
|
||||
// layout from before #3664) are now the CANONICAL dirs and are kept / updated.
|
||||
|
||||
describe('Defect #1 regression (#3664 reversed by #947): bare-stem dirs removed, msd- prefix written', () => {
|
||||
test('installRuntimeArtifacts removes bare-stem skills/msd/<stem>/ dirs and writes msd- prefixed layout', (t) => {
|
||||
const configDir = createTempDir('msd-hermes-reg1-');
|
||||
t.after(() => cleanup(configDir));
|
||||
|
||||
assert.strictEqual(typeof installRuntimeArtifacts, 'function',
|
||||
'installRuntimeArtifacts must be exported from install-engine.cjs');
|
||||
|
||||
// Pre-create #3664-era bare-stem Hermes layout (no msd- prefix, now stale).
|
||||
// Use real MSD command stems (help, quick) that readMsdCommandNames() knows about.
|
||||
const nestedMsdDir = path.join(configDir, 'skills', 'msd');
|
||||
fs.mkdirSync(path.join(nestedMsdDir, 'help'), { recursive: true });
|
||||
fs.writeFileSync(path.join(nestedMsdDir, 'help', 'SKILL.md'), '# legacy bare-stem help\n');
|
||||
fs.mkdirSync(path.join(nestedMsdDir, 'quick'), { recursive: true });
|
||||
fs.writeFileSync(path.join(nestedMsdDir, 'quick', 'SKILL.md'), '# legacy bare-stem quick\n');
|
||||
|
||||
// Sibling non-msd dir inside skills/msd/ must survive
|
||||
const userContentDir = path.join(nestedMsdDir, 'user-content');
|
||||
fs.mkdirSync(userContentDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(userContentDir, 'SKILL.md'), '# user content\n');
|
||||
|
||||
installRuntimeArtifacts('hermes', configDir, 'global', RESOLVED_CORE);
|
||||
|
||||
// Bare-stem dirs from #3664 must be cleaned
|
||||
assert.ok(!fs.existsSync(path.join(nestedMsdDir, 'help')),
|
||||
'skills/msd/help/ (bare-stem from #3664) must be removed (#947)');
|
||||
assert.ok(!fs.existsSync(path.join(nestedMsdDir, 'quick')),
|
||||
'skills/msd/quick/ (bare-stem from #3664) must be removed (#947)');
|
||||
// Canonical msd- prefixed layout must be written
|
||||
assert.ok(fs.existsSync(path.join(nestedMsdDir, 'msd-help', 'SKILL.md')),
|
||||
'skills/msd/msd-help/SKILL.md must exist after install (#947 canonical layout)');
|
||||
// User content preserved
|
||||
assert.ok(fs.existsSync(path.join(userContentDir, 'SKILL.md')),
|
||||
'user-content must be preserved');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Defect #2 — --qwen --profile=core falls through to wrong path ────────────
|
||||
|
||||
describe('Defect #2 regression (Qwen, #3664): --qwen --profile=core writes skills/msd-*/, not commands/msd/', () => {
|
||||
test('spawn --qwen --global --profile=core: skills/msd-*/ written, no commands/msd/', (t) => {
|
||||
const root = createTempDir('msd-qwen-reg2-');
|
||||
t.after(() => cleanup(root));
|
||||
|
||||
const result = runNode(
|
||||
[INSTALL_SCRIPT, '--qwen', '--global', '--config-dir', root, '--profile=core'],
|
||||
{ env: { ...process.env, HOME: root, USERPROFILE: root }, timeoutMs: INSTALL_TIMEOUT_MS },
|
||||
);
|
||||
|
||||
assert.strictEqual(result.exitCode, 0,
|
||||
`installer exited ${result.exitCode}\n${result.stdout}\n${result.stderr}`);
|
||||
|
||||
const qwenSkillsDir = path.join(root, 'skills');
|
||||
assert.ok(fs.existsSync(qwenSkillsDir));
|
||||
|
||||
const skillDirs = fs.readdirSync(qwenSkillsDir, { withFileTypes: true })
|
||||
.filter(e => e.isDirectory() && e.name.startsWith('msd-'));
|
||||
assert.ok(skillDirs.length >= 1, 'at least one msd-* skill dir must exist');
|
||||
assert.ok(
|
||||
skillDirs.some(e => fs.existsSync(path.join(qwenSkillsDir, e.name, 'SKILL.md'))),
|
||||
'at least one skills/msd-*/SKILL.md must exist'
|
||||
);
|
||||
|
||||
const commandsMsd = path.join(root, 'commands', 'msd');
|
||||
if (fs.existsSync(commandsMsd)) {
|
||||
const mdFiles = fs.readdirSync(commandsMsd).filter(f => f.endsWith('.md'));
|
||||
assert.strictEqual(mdFiles.length, 0, `commands/msd/ must not contain .md files (Defect #2). Found: ${mdFiles.join(', ')}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Defect #2 regression (Hermes, #3664): --hermes --profile=core writes skills/msd/, not commands/msd/', () => {
|
||||
test('spawn --hermes --global --profile=core: skills/msd/ written, no commands/msd/', (t) => {
|
||||
const root = createTempDir('msd-hermes-reg2-');
|
||||
t.after(() => cleanup(root));
|
||||
|
||||
const result = runNode(
|
||||
[INSTALL_SCRIPT, '--hermes', '--global', '--config-dir', root, '--profile=core'],
|
||||
{ env: { ...process.env, HOME: root, USERPROFILE: root }, timeoutMs: INSTALL_TIMEOUT_MS },
|
||||
);
|
||||
|
||||
assert.strictEqual(result.exitCode, 0,
|
||||
`installer exited ${result.exitCode}\n${result.stdout}\n${result.stderr}`);
|
||||
|
||||
const hermesSkillsMsd = path.join(root, 'skills', 'msd');
|
||||
assert.ok(fs.existsSync(hermesSkillsMsd));
|
||||
|
||||
const skillDirs = fs.readdirSync(hermesSkillsMsd, { withFileTypes: true })
|
||||
.filter(e => e.isDirectory());
|
||||
assert.ok(skillDirs.length >= 1);
|
||||
assert.ok(
|
||||
skillDirs.some(e => fs.existsSync(path.join(hermesSkillsMsd, e.name, 'SKILL.md'))),
|
||||
);
|
||||
|
||||
const commandsMsd = path.join(root, 'commands', 'msd');
|
||||
if (fs.existsSync(commandsMsd)) {
|
||||
const mdFiles = fs.readdirSync(commandsMsd).filter(f => f.endsWith('.md'));
|
||||
assert.strictEqual(mdFiles.length, 0, `commands/msd/ must not contain .md files (Defect #2). Found: ${mdFiles.join(', ')}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ─── M1 — Hermes minimal-mode migrates dev-preferences (#2973) ───────────────
|
||||
|
||||
describe('M1 (#2973, #947): --hermes --global --profile=core migrates dev-preferences → skills/msd/msd-dev-preferences/SKILL.md', () => {
|
||||
test('dev-preferences migrated to nested Hermes location, legacy source removed', (t) => {
|
||||
const root = createTempDir('msd-hermes-m1-');
|
||||
t.after(() => cleanup(root));
|
||||
|
||||
const legacyDir = path.join(root, 'commands', 'msd');
|
||||
fs.mkdirSync(legacyDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(legacyDir, 'dev-preferences.md'), '# my hermes prefs\n');
|
||||
|
||||
const result = runNode(
|
||||
[INSTALL_SCRIPT, '--hermes', '--global', '--config-dir', root, '--profile=core'],
|
||||
{ env: { ...process.env, HOME: root, USERPROFILE: root }, timeoutMs: INSTALL_TIMEOUT_MS },
|
||||
);
|
||||
|
||||
assert.strictEqual(result.exitCode, 0,
|
||||
`installer exited ${result.exitCode}\n${result.stdout}\n${result.stderr}`);
|
||||
|
||||
// #947: Hermes uses prefix='msd-' so dev-preferences lands at msd-dev-preferences/ (not dev-preferences/)
|
||||
const skillFile = path.join(root, 'skills', 'msd', 'msd-dev-preferences', 'SKILL.md');
|
||||
assert.ok(fs.existsSync(skillFile),
|
||||
'skills/msd/msd-dev-preferences/SKILL.md must exist (M1+#947: msd- prefix, nested)');
|
||||
assert.strictEqual(fs.readFileSync(skillFile, 'utf8'), '# my hermes prefs\n');
|
||||
assert.ok(!fs.existsSync(path.join(legacyDir, 'dev-preferences.md')),
|
||||
'legacy source must be removed');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── M2 — Qwen minimal-mode migrates dev-preferences (#2973) ────────────────
|
||||
|
||||
describe('M2 (#2973): --qwen --global --profile=core migrates dev-preferences → skills/msd-dev-preferences/SKILL.md', () => {
|
||||
test('dev-preferences migrated to flat Qwen location, legacy source removed', (t) => {
|
||||
const root = createTempDir('msd-qwen-m2-');
|
||||
t.after(() => cleanup(root));
|
||||
|
||||
const legacyDir = path.join(root, 'commands', 'msd');
|
||||
fs.mkdirSync(legacyDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(legacyDir, 'dev-preferences.md'), '# my qwen prefs\n');
|
||||
|
||||
const result = runNode(
|
||||
[INSTALL_SCRIPT, '--qwen', '--global', '--config-dir', root, '--profile=core'],
|
||||
{ env: { ...process.env, HOME: root, USERPROFILE: root }, timeoutMs: INSTALL_TIMEOUT_MS },
|
||||
);
|
||||
|
||||
assert.strictEqual(result.exitCode, 0,
|
||||
`installer exited ${result.exitCode}\n${result.stdout}\n${result.stderr}`);
|
||||
|
||||
const skillFile = path.join(root, 'skills', 'msd-dev-preferences', 'SKILL.md');
|
||||
assert.ok(fs.existsSync(skillFile),
|
||||
'skills/msd-dev-preferences/SKILL.md must exist (M2: flat Qwen layout)');
|
||||
assert.strictEqual(fs.readFileSync(skillFile, 'utf8'), '# my qwen prefs\n');
|
||||
assert.ok(!fs.existsSync(path.join(legacyDir, 'dev-preferences.md')));
|
||||
});
|
||||
});
|
||||
|
||||
// ─── M3 — Claude global minimal-mode migrates dev-preferences (#2973) ────────
|
||||
|
||||
describe('M3 (#2973): --claude --global --profile=core migrates dev-preferences → skills/msd-dev-preferences/SKILL.md', () => {
|
||||
@@ -311,32 +134,6 @@ describe('M3 (#2973): --claude --global --profile=core migrates dev-preferences
|
||||
});
|
||||
});
|
||||
|
||||
// ─── U1 — Qwen uninstall preserves dev-preferences via migration (#2973) ─────
|
||||
|
||||
describe('U1 (#2973): uninstallRuntimeArtifacts qwen migrates dev-preferences → skills/msd-dev-preferences/SKILL.md', () => {
|
||||
test('commands/msd/ removed, dev-preferences migrated to skills skill', (t) => {
|
||||
const configDir = createTempDir('msd-qwen-uninstall-u1-');
|
||||
t.after(() => cleanup(configDir));
|
||||
|
||||
assert.strictEqual(typeof uninstallRuntimeArtifacts, 'function',
|
||||
'uninstallRuntimeArtifacts must be exported from install-engine.cjs');
|
||||
|
||||
const legacyDir = path.join(configDir, 'commands', 'msd');
|
||||
fs.mkdirSync(legacyDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(legacyDir, 'dev-preferences.md'), '# my qwen prefs\n');
|
||||
fs.writeFileSync(path.join(legacyDir, 'help.md'), '# help content\n');
|
||||
|
||||
uninstallRuntimeArtifacts('qwen', configDir, 'global');
|
||||
|
||||
assert.ok(!fs.existsSync(path.join(legacyDir, 'help.md')));
|
||||
assert.ok(!fs.existsSync(path.join(legacyDir, 'dev-preferences.md')));
|
||||
|
||||
const skillFile = path.join(configDir, 'skills', 'msd-dev-preferences', 'SKILL.md');
|
||||
assert.ok(fs.existsSync(skillFile), 'skills/msd-dev-preferences/SKILL.md must exist (U1)');
|
||||
assert.strictEqual(fs.readFileSync(skillFile, 'utf8'), '# my qwen prefs\n');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── U2 — Claude-global uninstall preserves dev-preferences (#2973) ──────────
|
||||
|
||||
describe('U2 (#2973): uninstallRuntimeArtifacts claude/global migrates dev-preferences → skills/msd-dev-preferences/SKILL.md', () => {
|
||||
@@ -360,32 +157,6 @@ describe('U2 (#2973): uninstallRuntimeArtifacts claude/global migrates dev-prefe
|
||||
});
|
||||
});
|
||||
|
||||
// ─── U3 — Hermes uninstall migrates dev-preferences to NESTED location (#2973) ─
|
||||
|
||||
describe('U3 (#2973, #947): uninstallRuntimeArtifacts hermes migrates dev-preferences → skills/msd/msd-dev-preferences/SKILL.md', () => {
|
||||
test('commands/msd/ NOT recreated, dev-preferences at nested Hermes location with msd- prefix', (t) => {
|
||||
const configDir = createTempDir('msd-hermes-uninstall-u3-');
|
||||
t.after(() => cleanup(configDir));
|
||||
|
||||
assert.strictEqual(typeof uninstallRuntimeArtifacts, 'function');
|
||||
|
||||
const legacyDir = path.join(configDir, 'commands', 'msd');
|
||||
fs.mkdirSync(legacyDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(legacyDir, 'dev-preferences.md'), '# my hermes prefs\n');
|
||||
|
||||
uninstallRuntimeArtifacts('hermes', configDir, 'global');
|
||||
|
||||
assert.ok(!fs.existsSync(path.join(legacyDir, 'dev-preferences.md')),
|
||||
'commands/msd/dev-preferences.md must not exist after hermes uninstall (U3)');
|
||||
|
||||
// #947: Hermes uses prefix='msd-' so dev-preferences lands at msd-dev-preferences/ (not dev-preferences/)
|
||||
const skillFile = path.join(configDir, 'skills', 'msd', 'msd-dev-preferences', 'SKILL.md');
|
||||
assert.ok(fs.existsSync(skillFile),
|
||||
'skills/msd/msd-dev-preferences/SKILL.md must exist at HERMES nested location (U3+#947)');
|
||||
assert.strictEqual(fs.readFileSync(skillFile, 'utf8'), '# my hermes prefs\n');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── #768 — mergeClaudePermissions: pre-populate permissions.allow/deny ──────
|
||||
|
||||
describe('mergeClaudePermissions (#768): exports and permission constants', () => {
|
||||
@@ -1723,10 +1494,10 @@ describe('#3329 regression: stale managed .sh hook commands are reconciled on in
|
||||
|
||||
const changed = reconcileManagedShellHookCommands(settings, GLOBAL_EXPECTED, {
|
||||
platform: 'win32',
|
||||
runtime: 'qwen',
|
||||
runtime: 'codex',
|
||||
});
|
||||
|
||||
assert.strictEqual(changed, false, 'win32+qwen keeps the bash runner by design');
|
||||
assert.strictEqual(changed, false, 'win32+codex keeps the bash runner by design');
|
||||
assert.strictEqual(settings.hooks.SessionStart[0].hooks[0].command, original);
|
||||
});
|
||||
|
||||
@@ -1909,32 +1680,6 @@ describe('#3664 — config-dir foreign-agent destination warning', () => {
|
||||
assert.ok(!lines.some((l) => l.includes('(#3664)')), `non-agent files are not harness evidence: ${lines.join(' | ')}`);
|
||||
});
|
||||
|
||||
test('warns for the kimi runtime (kimi-agents kind)', (t) => {
|
||||
const { dest } = makeForeignDest(t, 'kimi-foreign', ['junie-guide.md']);
|
||||
const { lines } = capturedLogs(t, () =>
|
||||
installerMod.warnIfForeignAgentDest('kimi', dest, 'global', true),
|
||||
);
|
||||
assert.ok(
|
||||
lines.some((l) => l.includes('(#3664)') && l.includes('kimi')),
|
||||
`kimi's kimi-agents kind must take the gate: ${lines.join(' | ')}`,
|
||||
);
|
||||
});
|
||||
|
||||
test('silent on kimi\'s own msd.md in a shared multi-runtime dir', (t) => {
|
||||
// kimi's root agent is agents/msd.md — a bare stem, no msd- prefix. A
|
||||
// shared --all dir accumulates it alongside msd-*.md; none of it is
|
||||
// foreign, so every runtime's install into that dir must stay silent.
|
||||
const { dest } = makeForeignDest(t, 'shared-kimi', ['msd-executor.md', 'msd.md']);
|
||||
const { lines } = capturedLogs(t, () => {
|
||||
installerMod.warnIfForeignAgentDest('kimi', dest, 'global', true);
|
||||
installerMod.warnIfForeignAgentDest('claude', dest, 'global', true);
|
||||
});
|
||||
assert.ok(
|
||||
!lines.some((l) => l.includes('(#3664)')),
|
||||
`MSD-owned msd.md must not read as a foreign agent: ${lines.join(' | ')}`,
|
||||
);
|
||||
});
|
||||
|
||||
test('degrades silently when the layout cannot resolve', (t) => {
|
||||
const dest = createTempDir('msd-3664-unknown-');
|
||||
t.after(() => cleanup(dest));
|
||||
|
||||
Reference in New Issue
Block a user