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:
@@ -3,16 +3,12 @@
|
||||
/**
|
||||
* #3023 — shared hook bundle directory-name resolution.
|
||||
*
|
||||
* Coverage gaps closed here (tests/install-minimal-hooks.test.cjs already
|
||||
* covers the installed-tree rows — pi local+global: no `hooks/`, bundle at
|
||||
* `msd-hooks/`, manifest keys — and is not duplicated):
|
||||
* Coverage here:
|
||||
*
|
||||
* GROUP A bin/install.js `resolveSharedHooksDirName(runtime)` — the
|
||||
* descriptor-driven sanitizer that rejects anything that is not a
|
||||
* plain, non-empty, separator-free, non-dot, non-absolute,
|
||||
* NUL-free single path segment.
|
||||
* GROUP B pi/msd.cjs `_internals.resolveSharedHooksDir(engineRoot)` — the
|
||||
* adapter-side probe over `SHARED_HOOKS_DIR_CANDIDATES`.
|
||||
* GROUP C the two latent bundle-directory-NAME dependencies:
|
||||
* hooks/msd-check-update-worker.js (stale-hook scan) and
|
||||
* hooks/msd-read-injection-scanner.js (own-bundle exclusion).
|
||||
@@ -55,8 +51,6 @@ const PROPERTY_DRIVER_TIMEOUT_MS = 60000;
|
||||
// Requiring the installer (not as main) never runs the CLI — matches the
|
||||
// existing tests/claude-imperative-reference.test.cjs convention.
|
||||
const installMod = require('../bin/install.js');
|
||||
const piExtension = require('../pi/msd.cjs');
|
||||
const { resolveSharedHooksDir, SHARED_HOOKS_DIR_CANDIDATES } = piExtension._internals;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GROUP A.1 — real registry, real runtimes (in-process, no stubbing needed)
|
||||
@@ -64,7 +58,7 @@ const { resolveSharedHooksDir, SHARED_HOOKS_DIR_CANDIDATES } = piExtension._inte
|
||||
|
||||
describe('GROUP A.1: resolveSharedHooksDirName — real registry, real runtimes', () => {
|
||||
test('absent sharedHooksDirName field resolves to the default "hooks"', () => {
|
||||
for (const runtime of ['claude', 'kimi', 'cursor', 'opencode']) {
|
||||
for (const runtime of ['claude', 'codex', 'cursor', 'opencode']) {
|
||||
assert.equal(
|
||||
installMod.resolveSharedHooksDirName(runtime),
|
||||
'hooks',
|
||||
@@ -73,10 +67,6 @@ describe('GROUP A.1: resolveSharedHooksDirName — real registry, real runtimes'
|
||||
}
|
||||
});
|
||||
|
||||
test('the real pi descriptor resolves to "msd-hooks" (#3023)', () => {
|
||||
assert.equal(installMod.resolveSharedHooksDirName('pi'), 'msd-hooks');
|
||||
});
|
||||
|
||||
test('an unknown runtime id, and an empty-string runtime id, both degrade to the default and never throw', () => {
|
||||
assert.doesNotThrow(() => installMod.resolveSharedHooksDirName('__nonexistent_runtime__'));
|
||||
assert.equal(installMod.resolveSharedHooksDirName('__nonexistent_runtime__'), 'hooks');
|
||||
@@ -358,160 +348,6 @@ describe('GROUP A.2: resolveSharedHooksDirName — malformed/hostile values + pr
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GROUP B — pi/msd.cjs _internals.resolveSharedHooksDir(engineRoot)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('GROUP B: pi adapter resolveSharedHooksDir (pi/msd.cjs)', () => {
|
||||
// A real staged bundle always has at least one hook file in it; these tests
|
||||
// populate every "should qualify" candidate with a placeholder file so they
|
||||
// exercise the same non-empty invariant defect 2's fix enforces, rather than
|
||||
// relying on an literally-empty directory that no real install ever produces.
|
||||
function populate(dirPath) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
fs.writeFileSync(path.join(dirPath, 'msd-placeholder-hook.js'), '// placeholder\n');
|
||||
}
|
||||
|
||||
test('candidate order is exactly ["msd-hooks", "hooks"] (a reordering that silently prefers the stale dir must fail loudly)', () => {
|
||||
assert.deepEqual(SHARED_HOOKS_DIR_CANDIDATES, ['msd-hooks', 'hooks']);
|
||||
});
|
||||
|
||||
test('only msd-hooks/ exists (populated) -> returns that path', (t) => {
|
||||
const root = createTempDir('fix-3023-adapter-');
|
||||
t.after(() => cleanup(root));
|
||||
populate(path.join(root, 'msd-hooks'));
|
||||
assert.equal(resolveSharedHooksDir(root), path.join(root, 'msd-hooks'));
|
||||
});
|
||||
|
||||
test('only hooks/ exists (dev-tree back-compat, populated) -> returns that path', (t) => {
|
||||
const root = createTempDir('fix-3023-adapter-');
|
||||
t.after(() => cleanup(root));
|
||||
populate(path.join(root, 'hooks'));
|
||||
assert.equal(resolveSharedHooksDir(root), path.join(root, 'hooks'));
|
||||
});
|
||||
|
||||
test('both exist and both populated (half-upgraded tree) -> msd-hooks wins deterministically', (t) => {
|
||||
const root = createTempDir('fix-3023-adapter-');
|
||||
t.after(() => cleanup(root));
|
||||
populate(path.join(root, 'msd-hooks'));
|
||||
populate(path.join(root, 'hooks'));
|
||||
assert.equal(resolveSharedHooksDir(root), path.join(root, 'msd-hooks'));
|
||||
});
|
||||
|
||||
test('neither exists -> null, does not throw', (t) => {
|
||||
const root = createTempDir('fix-3023-adapter-');
|
||||
t.after(() => cleanup(root));
|
||||
assert.doesNotThrow(() => resolveSharedHooksDir(root));
|
||||
assert.equal(resolveSharedHooksDir(root), null);
|
||||
});
|
||||
|
||||
test('msd-hooks exists as a FILE, not a directory -> skipped; falls through to a populated hooks/ when present', (t) => {
|
||||
const root = createTempDir('fix-3023-adapter-');
|
||||
t.after(() => cleanup(root));
|
||||
fs.writeFileSync(path.join(root, 'msd-hooks'), 'not a directory');
|
||||
populate(path.join(root, 'hooks'));
|
||||
assert.equal(resolveSharedHooksDir(root), path.join(root, 'hooks'));
|
||||
});
|
||||
|
||||
test('msd-hooks exists as a FILE and hooks/ is absent -> null', (t) => {
|
||||
const root = createTempDir('fix-3023-adapter-');
|
||||
t.after(() => cleanup(root));
|
||||
fs.writeFileSync(path.join(root, 'msd-hooks'), 'not a directory');
|
||||
assert.equal(resolveSharedHooksDir(root), null);
|
||||
});
|
||||
|
||||
// ── Defect 2 (adversarial review): empty-bundle qualification ────────────
|
||||
// An install interrupted between mkdirSync(msd-hooks) and the file copy
|
||||
// leaves a directory that EXISTS but is EMPTY. Since msd-hooks is probed
|
||||
// FIRST, an empty msd-hooks/ must lose to a fully-staged legacy hooks/ —
|
||||
// otherwise every hook silently no-ops (runHook's fs.existsSync guard
|
||||
// degrades per-file, producing no error at all).
|
||||
|
||||
test('regression: msd-hooks/ exists but is EMPTY, hooks/ is populated -> resolves hooks/ (fails before the fix)', (t) => {
|
||||
const root = createTempDir('fix-3023-adapter-');
|
||||
t.after(() => cleanup(root));
|
||||
fs.mkdirSync(path.join(root, 'msd-hooks'), { recursive: true }); // empty — no files written
|
||||
populate(path.join(root, 'hooks'));
|
||||
assert.equal(
|
||||
resolveSharedHooksDir(root),
|
||||
path.join(root, 'hooks'),
|
||||
'an empty msd-hooks/ must not win over a fully-staged legacy hooks/',
|
||||
);
|
||||
});
|
||||
|
||||
test('msd-hooks/ populated, hooks/ populated -> resolves msd-hooks/', (t) => {
|
||||
const root = createTempDir('fix-3023-adapter-');
|
||||
t.after(() => cleanup(root));
|
||||
populate(path.join(root, 'msd-hooks'));
|
||||
populate(path.join(root, 'hooks'));
|
||||
assert.equal(resolveSharedHooksDir(root), path.join(root, 'msd-hooks'));
|
||||
});
|
||||
|
||||
test('both msd-hooks/ and hooks/ exist but are BOTH empty -> null', (t) => {
|
||||
const root = createTempDir('fix-3023-adapter-');
|
||||
t.after(() => cleanup(root));
|
||||
fs.mkdirSync(path.join(root, 'msd-hooks'), { recursive: true });
|
||||
fs.mkdirSync(path.join(root, 'hooks'), { recursive: true });
|
||||
assert.equal(resolveSharedHooksDir(root), null);
|
||||
});
|
||||
|
||||
test('msd-hooks/ is empty, hooks/ does not exist -> null', (t) => {
|
||||
const root = createTempDir('fix-3023-adapter-');
|
||||
t.after(() => cleanup(root));
|
||||
fs.mkdirSync(path.join(root, 'msd-hooks'), { recursive: true });
|
||||
assert.equal(resolveSharedHooksDir(root), null);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GROUP D — parity assertion: installer descriptor vs pi's own candidate list
|
||||
// ---------------------------------------------------------------------------
|
||||
//
|
||||
// bin/install.js derives the shared-hooks directory NAME for a runtime from
|
||||
// the capability registry (resolveSharedHooksDirName). pi/msd.cjs cannot read
|
||||
// that registry at runtime — it must resolve correctly in a dev checkout AND
|
||||
// in a half-upgraded tree, where the registry's CURRENT answer would be the
|
||||
// wrong one to probe for (see the SHARED_HOOKS_DIR_CANDIDATES doc comment in
|
||||
// pi/msd.cjs) — so it keeps its own hardcoded probe list instead. That is two
|
||||
// independent sources of truth for the same name: exactly the "Generative Fix
|
||||
// Divergence" anti-pattern (CLAUDE.md -> KNOWN DEFECTS: "When sharing
|
||||
// constants/arrays/parsers between parallel surfaces, add a parity assertion
|
||||
// test that fails if they diverge."). If a future descriptor rename is not
|
||||
// mirrored into pi/msd.cjs, this is the guard that fails loudly instead of
|
||||
// every pi hook going quiet with no error.
|
||||
describe('GROUP D: parity — installer descriptor vs pi/msd.cjs SHARED_HOOKS_DIR_CANDIDATES', () => {
|
||||
const REMEDY = 'if the descriptor changes, update SHARED_HOOKS_DIR_CANDIDATES in pi/msd.cjs to match';
|
||||
|
||||
test('the installer-resolved pi descriptor name is a member of the adapter probe list', () => {
|
||||
const descriptorName = installMod.resolveSharedHooksDirName('pi');
|
||||
assert.ok(
|
||||
SHARED_HOOKS_DIR_CANDIDATES.includes(descriptorName),
|
||||
`pi's capability descriptor resolves to "${descriptorName}", which is NOT in pi/msd.cjs's ` +
|
||||
`SHARED_HOOKS_DIR_CANDIDATES (${JSON.stringify(SHARED_HOOKS_DIR_CANDIDATES)}) — ${REMEDY}.`,
|
||||
);
|
||||
});
|
||||
|
||||
test('the installer-resolved pi descriptor name is the FIRST candidate (must outrank the legacy dir)', () => {
|
||||
const descriptorName = installMod.resolveSharedHooksDirName('pi');
|
||||
assert.equal(
|
||||
SHARED_HOOKS_DIR_CANDIDATES[0],
|
||||
descriptorName,
|
||||
`pi/msd.cjs's SHARED_HOOKS_DIR_CANDIDATES probes "${SHARED_HOOKS_DIR_CANDIDATES[0]}" first, but the ` +
|
||||
`installer's current descriptor for pi resolves to "${descriptorName}" — a half-upgraded tree (both dirs ` +
|
||||
`present) would bind to the stale bundle first. ${REMEDY}, with the descriptor's current name listed first.`,
|
||||
);
|
||||
});
|
||||
|
||||
test('the back-compat default ("hooks") is present in the adapter probe list', () => {
|
||||
assert.ok(
|
||||
SHARED_HOOKS_DIR_CANDIDATES.includes(installMod.SHARED_HOOKS_DIR_DEFAULT),
|
||||
`pi/msd.cjs's SHARED_HOOKS_DIR_CANDIDATES (${JSON.stringify(SHARED_HOOKS_DIR_CANDIDATES)}) no longer ` +
|
||||
`contains the installer's back-compat default ("${installMod.SHARED_HOOKS_DIR_DEFAULT}") — dropping it ` +
|
||||
`breaks the dev-checkout/back-compat path (a checkout with only a legacy hooks/ dir would resolve to null). ${REMEDY}.`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GROUP C — bundle-directory-NAME-agnostic hook scripts
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user