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

@@ -35,15 +35,6 @@ const {
FIXTURE_HOOK_TIMEOUT_SECONDS,
} = require('./helpers/timeouts.cjs');
/**
* Bounds executing a single already-staged hook script directly (not the
* installer itself, just the emitted script under a real node process).
* 30000ms digits coincide with the shared BUILD_TIMEOUT_MS, but this is a
* different operation class (running staged output vs. bundling it), so it
* is kept as its own local constant rather than aliased onto that norm.
*/
const INSTALLED_HOOK_EXEC_TIMEOUT_MS = 30000;
const { createTempDir, cleanup } = require('./helpers.cjs');
const {
@@ -394,24 +385,6 @@ describe('install: --minimal honoured for every runtime, on-disk matches manifes
}
});
describe('install: Cline --minimal (rules-based, no skills/ dir)', () => {
for (const scope of ['global', 'local']) {
test(`cline --${scope} --minimal: mode=minimal, zero agents, .clinerules present`, () => {
const { manifest, configDir, root } = runMinimalInstall({
runtime: 'cline', scope, extraArgs: ['--minimal'],
});
try {
assert.ok(manifest, 'cline must produce manifest');
assert.strictEqual(manifest.mode, 'minimal');
assert.strictEqual(manifestAgentCount(manifest), 0);
assert.ok(fs.existsSync(path.join(configDir, '.clinerules')));
} finally {
cleanup(root);
}
});
}
});
// ─── Section 11: --minimal manifest mode + downgrade ─────────────────────────
// Shared across "manifest records mode" and "install-minimal-backcompat": both
@@ -691,7 +664,7 @@ describe('#1755: .sh hooks are copied and executable after install', () => {
// hook Kilo's plugin spawns.) These tests assert the split: ZCode gets no
// hooks; Kilo/OpenCode/pi (and Claude) do.
describe('#1821/#2305: ZCode receives no dead hook files; Kilo/OpenCode/Claude keep their hooks', () => {
describe('#1821/#2305: ZCode receives no dead hook files; OpenCode/Claude keep their hooks', () => {
function msdHookFilesUnder(configDir, hooksDirName) {
const hooksDir = path.join(configDir, hooksDirName);
if (!fs.existsSync(hooksDir)) return [];
@@ -730,8 +703,7 @@ describe('#1821/#2305: ZCode receives no dead hook files; Kilo/OpenCode/Claude k
// ZCode declares hooksSurface:'none' with no plugin surface, so its staged
// hooks are genuinely dead weight (#1821) — this is the case the fix
// removes. (Kilo was originally in this loop; #2305 moved it to the
// OpenCode group below — its native plugin spawns the staged guard hooks.)
// removes.
for (const runtime of ['zcode']) {
test(`${runtime} --global install creates no msd-*.js/.sh hook files or hooks/lib`, () => {
const { hookFiles, hooksLibExists } = installAndCollect(runtime);
@@ -740,26 +712,6 @@ describe('#1821/#2305: ZCode receives no dead hook files; Kilo/OpenCode/Claude k
});
}
// #2305: Kilo's native plugin (plugins/msd-core.js, byte-identical to
// OpenCode's) spawns the staged PreToolUse guard hooks as subprocesses, so
// Kilo must receive the shared hooks bundle as a sibling of msd-core/ —
// the shape the plugin's resolveRepoRoot walk requires. #1821 excluded
// Kilo on the false premise that it had no plugin surface; with the skip
// flag set, every guard silently no-opped on every Kilo install.
test('kilo --global install stages the guard hooks its plugin spawns, hooks/lib, and the plugin', () => {
const { hookFiles, hooksLibExists, gitCmdExists, pluginExists } = installAndCollect('kilo');
const basenames = hookFiles.map((f) => path.basename(f));
for (const expected of ['msd-prompt-guard.js', 'msd-read-guard.js', 'msd-worktree-path-guard.js']) {
assert.ok(
basenames.includes(expected),
`kilo install must copy ${expected} (spawned by the plugin's runHook), found: ${basenames.join(', ')}`,
);
}
assert.ok(hooksLibExists, 'kilo install must create hooks/lib/');
assert.ok(gitCmdExists, 'kilo install must copy hooks/lib/git-cmd.js (required by the shared hooks)');
assert.ok(pluginExists, 'kilo install must install plugins/msd-core.js (the hook-spawning plugin)');
});
// Regression guard for #1914: OpenCode's plugin adapter spawns the staged
// hooks, so excluding OpenCode from the hook copy would break it. OpenCode
// must KEEP its hooks and receive the plugin.
@@ -773,48 +725,6 @@ describe('#1821/#2305: ZCode receives no dead hook files; Kilo/OpenCode/Claude k
assert.ok(pluginExists, 'opencode install must install plugins/msd-core.js (#1914 hook bridge)');
});
// pi ALSO declares hooksSurface:'none', but — like OpenCode — it is NOT a
// dead-weight case: pi's native extension (pi/msd.cjs → extensions/msd.js)
// spawns the staged msd-hooks/*.js scripts as bounded subprocesses (session_start
// → msd-ensure-canonical-path.js, before_agent_start → msd-workflow-guard.js,
// session_before_compact → msd-context-monitor.js — #2102 Stage 2), and its
// /msd command handler tokenizes raw args via the shared msd-hooks/lib/git-cmd.js
// tokenizer. hostBehaviors.skipSharedHooksInstall is therefore NOT set for
// pi (unlike Kilo/ZCode/Cursor/Cline/Trae/Copilot/Windsurf/Kimi) — pi is in
// the OpenCode group, not the Kilo/ZCode group. #3023: pi's bundle is staged
// under `msd-hooks/` (hostBehaviors.sharedHooksDirName), not the default
// `hooks/` every other runtime in this describe block uses.
test('pi --global install still copies msd-hooks/ (spawned by the native extension) + msd-hooks/lib/git-cmd.js + the extension itself', () => {
// #2470: derive the extension filename from pi's own descriptor rather than
// hardcoding it, and assert it satisfies pi's isExtensionFile() discovery
// filter (.ts/.js only) — a dest pi cannot discover installs "successfully"
// while /msd never registers, which is exactly how this shipped broken.
const piNativePlugin = JSON.parse(
fs.readFileSync(path.join(__dirname, '..', 'capabilities', 'pi', 'capability.json'), 'utf8'),
).runtime.hostBehaviors.nativePlugin;
assert.ok(
piNativePlugin.file.endsWith('.ts') || piNativePlugin.file.endsWith('.js'),
`pi's installed extension "${piNativePlugin.file}" must end in .ts or .js — pi silently ` +
'skips any other suffix during extensions/ auto-discovery (#2470)',
);
const { hookFiles, hooksLibExists, gitCmdExists, pluginExists } = installAndCollect('pi', {
pluginRelPath: path.join(piNativePlugin.dir, piNativePlugin.file),
});
const basenames = hookFiles.map((f) => path.basename(f));
for (const expected of ['msd-ensure-canonical-path.js', 'msd-workflow-guard.js', 'msd-context-monitor.js']) {
assert.ok(
basenames.includes(expected),
`pi install must copy ${expected} (spawned by pi/msd.cjs's event bridges), found: ${basenames.join(', ')}`,
);
}
assert.ok(hooksLibExists, 'pi install must create msd-hooks/lib/');
assert.ok(gitCmdExists, 'pi install must copy msd-hooks/lib/git-cmd.js (the /msd command tokenizer)');
assert.ok(
pluginExists,
`pi install must install ${piNativePlugin.dir}/${piNativePlugin.file} (the native-extension hook bridge)`,
);
});
// Positive control: guards against over-exclusion breaking runtimes that
// legitimately need hooks (hooksSurface !== 'none').
test('claude --global install still copies msd-*.js hooks', () => {
@@ -1909,68 +1819,6 @@ describe('enh-1077 phase 5f-2: claude hookEvents dialect → PostToolUse/PreTool
});
});
// ── augment ──
describe('augment install uses PostToolUse/PreToolUse (claude dialect)', () => {
let tmpDir;
let previousCwd;
let settings;
beforeEach(() => {
tmpDir = createTempDir('msd-1077-augment-');
previousCwd = process.cwd();
process.chdir(tmpDir);
const augDir = path.join(tmpDir, '.augment');
fs.mkdirSync(augDir, { recursive: true });
const result = install(false, 'augment');
settings = result && result.settings;
});
afterEach(() => {
process.chdir(previousCwd);
cleanup(tmpDir);
});
test('registry confirms augment hookEvents is "claude"', () => {
const he = registryHookEvents('augment');
if (he !== undefined) {
assert.strictEqual(he, 'claude',
'Registry descriptor for augment must declare hookEvents="claude"');
}
});
test('augment install returns a settings object', () => {
assert.ok(settings !== null && typeof settings === 'object',
'augment install must return a non-null settings object');
});
test('augment install registers at least one hook under PostToolUse', () => {
assert.ok(hasHooksFor(settings, 'PostToolUse'),
`Expected PostToolUse hooks on augment; got hooks keys: ${JSON.stringify(Object.keys((settings && settings.hooks) || {}))}`);
});
test('augment install does NOT register context-monitor under AfterTool', () => {
const cmds = hooksForEvent(settings, 'AfterTool');
const hasMonitor = cmds.some(c => c && c.includes('msd-context-monitor'));
assert.strictEqual(hasMonitor, false,
`augment must NOT use AfterTool for context-monitor; got: ${JSON.stringify(cmds)}`);
});
test('augment install registers at least one pre-tool hook (prompt-guard) under PreToolUse', () => {
const cmds = hooksForEvent(settings, 'PreToolUse');
const hasPromptGuard = cmds.some(c => c && c.includes('msd-prompt-guard'));
assert.ok(hasPromptGuard,
`Expected prompt-guard hook under PreToolUse on augment; PreToolUse commands: ${JSON.stringify(cmds)}; hooks keys: ${JSON.stringify(Object.keys((settings && settings.hooks) || {}))}`);
});
test('augment install does NOT register prompt-guard under BeforeTool (wrong pre-tool dialect)', () => {
const cmds = hooksForEvent(settings, 'BeforeTool');
const hasPromptGuard = cmds.some(c => c && c.includes('msd-prompt-guard'));
assert.strictEqual(hasPromptGuard, false,
`augment must NOT use BeforeTool for prompt-guard; got BeforeTool commands: ${JSON.stringify(cmds)}`);
});
});
});
// ─── Suite 3: Registry-parity invariant ──────────────────────────────────────
@@ -2087,7 +1935,7 @@ const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { install, uninstall, validateHookFields } = require('../bin/install.js');
const { install } = require('../bin/install.js');
const { createTempDir, cleanup } = require('./helpers.cjs');
// ─── Helpers ─────────────────────────────────────────────────────────────────
@@ -2128,94 +1976,6 @@ function stubHooksIntoTarget(targetDir) {
}
}
/**
* Persist in-memory settings to disk, simulating what finishInstall() does
* (finishInstall is not exported). Required for tests that call install()
* twice and need the second call to read the first call's hook registrations.
*/
function persistSettings(settingsPath, settings) {
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
fs.writeFileSync(settingsPath, JSON.stringify(validateHookFields(settings), null, 2) + '\n', 'utf8');
}
// ─── Suite 1: Qwen — new events are registered ───────────────────────────────
describe('enh-788: Qwen install registers 3 new hook events', () => {
let tmpDir;
let previousCwd;
let settings;
beforeEach(() => {
tmpDir = createTempDir('msd-788-qwen-');
previousCwd = process.cwd();
process.chdir(tmpDir);
const targetDir = path.join(tmpDir, '.qwen');
fs.mkdirSync(targetDir, { recursive: true });
// Pre-populate hook files so installer registration guards (fs.existsSync)
// pass and hooks are actually registered in settings.json.
stubHooksIntoTarget(targetDir);
const result = install(false, 'qwen');
settings = result.settings;
});
afterEach(() => {
process.chdir(previousCwd);
cleanup(tmpDir);
});
test('install returns a settings object (not null)', () => {
assert.ok(settings !== null && typeof settings === 'object',
'Qwen install must return a non-null settings object');
});
test('SubagentStop event is registered with at least one hook', () => {
const cmds = hooksForEvent(settings, 'SubagentStop');
assert.ok(cmds.length > 0,
`Expected SubagentStop hooks; got hooks: ${JSON.stringify(settings && settings.hooks)}`);
});
test('Stop event is registered with at least one hook', () => {
const cmds = hooksForEvent(settings, 'Stop');
assert.ok(cmds.length > 0,
`Expected Stop hooks; got hooks: ${JSON.stringify(settings && settings.hooks)}`);
});
test('PreCompact event is registered with at least one hook', () => {
const cmds = hooksForEvent(settings, 'PreCompact');
assert.ok(cmds.length > 0,
`Expected PreCompact hooks; got hooks: ${JSON.stringify(settings && settings.hooks)}`);
});
test('UserPromptSubmit is NOT registered (handler not yet implemented for that payload shape)', () => {
// msd-prompt-guard exits unless tool_name is Write|Edit — it is a no-op
// for UserPromptSubmit payloads. Registration is deferred until a
// dedicated hook can process the user-prompt payload shape.
const cmds = hooksForEvent(settings, 'UserPromptSubmit');
assert.strictEqual(cmds.length, 0,
`UserPromptSubmit should NOT be registered yet; got: ${JSON.stringify(cmds)}`);
});
test('SubagentStop / Stop / PreCompact all use msd-context-monitor', () => {
for (const event of ['SubagentStop', 'Stop', 'PreCompact']) {
const cmds = hooksForEvent(settings, event);
assert.ok(
cmds.some(c => c.includes('msd-context-monitor')),
`Event ${event} should use msd-context-monitor; got commands: ${JSON.stringify(cmds)}`
);
}
});
test('FileChanged is NOT registered for Qwen (Claude-only event)', () => {
// msd-config-reload / FileChanged is a Claude Code-only registration.
// Qwen does not support the FileChanged hook event at all.
const cmds = hooksForEvent(settings, 'FileChanged');
assert.strictEqual(cmds.length, 0,
`FileChanged should NOT be registered for Qwen; got: ${JSON.stringify(cmds)}`);
});
});
// ─── Suite 2: Claude install DOES get the context events (since #770) ───────
// Note: Prior to #770, these were Qwen-only events. #770 extended them to
// Claude Code. This suite is updated to match the new expected behavior.
@@ -2258,83 +2018,6 @@ describe('enh-788 (updated by #770): Claude install registers context lifecycle
`Claude should have PreCompact since #770; got: ${JSON.stringify(cmds)}`);
});
});
// ─── Suite 3: Idempotency — persisted reinstall does not duplicate hooks ──────
describe('enh-788: Qwen install is idempotent across persisted reinstalls', () => {
let tmpDir;
let previousCwd;
beforeEach(() => {
tmpDir = createTempDir('msd-788-idem-');
previousCwd = process.cwd();
process.chdir(tmpDir);
const targetDir = path.join(tmpDir, '.qwen');
fs.mkdirSync(targetDir, { recursive: true });
stubHooksIntoTarget(targetDir);
});
afterEach(() => {
process.chdir(previousCwd);
cleanup(tmpDir);
});
test('re-running after persisted first install does not duplicate hook entries', () => {
// First install: get settings and persist to disk (simulating finishInstall)
const result1 = install(false, 'qwen');
persistSettings(result1.settingsPath, result1.settings);
// Second install: reads the persisted settings.json — dedup guards apply
process.chdir(tmpDir);
const result2 = install(false, 'qwen');
const s2 = result2.settings;
for (const event of ['SubagentStop', 'Stop', 'PreCompact']) {
const cmds = hooksForEvent(s2, event);
assert.strictEqual(cmds.length, 1,
`Event ${event} should have exactly 1 hook command after idempotent reinstall; got ${cmds.length}: ${JSON.stringify(cmds)}`);
}
});
});
// ─── Suite 4: Uninstall removes the new event registrations ──────────────────
describe('enh-788: Qwen uninstall removes new hook event entries', () => {
let tmpDir;
let previousCwd;
beforeEach(() => {
tmpDir = createTempDir('msd-788-uninstall-');
previousCwd = process.cwd();
process.chdir(tmpDir);
const targetDir = path.join(tmpDir, '.qwen');
fs.mkdirSync(targetDir, { recursive: true });
stubHooksIntoTarget(targetDir);
// Install and persist to disk so uninstall has a settings.json to clean
const result = install(false, 'qwen');
persistSettings(result.settingsPath, result.settings);
});
afterEach(() => {
process.chdir(previousCwd);
cleanup(tmpDir);
});
test('settings.json hook entries are removed on uninstall', () => {
uninstall(false, 'qwen');
const settingsPath = path.join(tmpDir, '.qwen', 'settings.json');
if (!fs.existsSync(settingsPath)) return; // file removed entirely is fine
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
for (const event of ['SubagentStop', 'Stop', 'PreCompact']) {
const cmds = hooksForEvent(settings, event);
assert.strictEqual(cmds.length, 0,
`After uninstall, ${event} should have 0 hooks; got: ${JSON.stringify(cmds)}`);
}
});
});
});
}
@@ -3202,139 +2885,6 @@ describe('#4087 regression: Codex install stages the hook helpers its hooks requ
});
});
// ─── #3023: pi must not stage its shared-hooks bundle in pi's reserved hooks/ ──
//
// pi (pi.dev) renamed its `hooks/` directory to `extensions/` and now prints a
// deprecation warning on every startup when a `hooks/` directory exists in its
// agent dir. MSD's installer stages the shared hook bundle at
// `<destRootDir>/hooks` for every runtime that does not set
// hostBehaviors.skipSharedHooksInstall — which since #2102 Stage 2 includes pi.
// The bundle must live under a name pi does not reserve.
//
// The expected directory name is asserted as a LITERAL on purpose: importing the
// production constant would make the assertion re-derive the very value under
// test, and it could then never catch that value changing.
describe('#4087 review: Windsurf install stages the hook helpers its hooks require', () => {
// Same defect class as the Codex rows above, one runtime over. Windsurf sets
// skipSharedHooksInstall, so it never reaches installSharedHooksBundle, and
// writeWindsurfHooksJson staged the two Cascade guards without the hooks/lib
// helpers both require at module load. Measured before the fix against a real
// `--windsurf --global` install: installer exit 0, hooks/ holding only the two
// scripts, and each one exiting 1 with "Cannot find module './lib/hook-exit.js'".
//
// tests/windsurf-hooks-bridge.test.cjs runs these guards from the SOURCE tree,
// where hooks/lib/ is a sibling and require() trivially resolves — the same
// "assert existence, never execute the installed copy" blind spot that let
// #4087 ship. These rows execute the INSTALLED copy.
const { before: __gtmBefore, after: __gtmAfter } = require('node:test');
let __savedMsdTestMode;
__gtmBefore(() => { __savedMsdTestMode = process.env.MSD_TEST_MODE; delete process.env.MSD_TEST_MODE; });
__gtmAfter(() => { if (__savedMsdTestMode === undefined) delete process.env.MSD_TEST_MODE; else process.env.MSD_TEST_MODE = __savedMsdTestMode; });
let tmpDir;
beforeEach(() => { tmpDir = createTempDir('msd-install-4087-windsurf-'); });
afterEach(() => { cleanup(tmpDir); });
function installWindsurf(configDir) {
// HOME/USERPROFILE sandboxed for the CHILD, for the same reason as installCodex.
throwIfFailed(
runNode([INSTALL_SCRIPT, '--windsurf', '--global', '--yes', '--config-dir', configDir], {
timeoutMs: INSTALL_TIMEOUT_MS,
env: { ...process.env, HOME: configDir, USERPROFILE: configDir },
}),
`node ${INSTALL_SCRIPT} --windsurf --global --config-dir ${configDir}`,
);
return path.join(configDir, 'hooks');
}
test('both installed Windsurf guards LOAD AND RUN, and their transitive helpers are staged', () => {
const hooksDir = installWindsurf(tmpDir);
for (const script of ['msd-windsurf-pre-write.js', 'msd-windsurf-pre-command.js']) {
const hook = path.join(hooksDir, script);
assert.ok(fs.existsSync(hook), `precondition: ${script} must be staged`);
const result = runNode([hook], { timeoutMs: INSTALLED_HOOK_EXEC_TIMEOUT_MS, input: '{}', env: { ...process.env } });
assert.strictEqual(result.outcome, 'exited',
`${script} must run to completion, not time out or be killed. outcome=${result.outcome}`);
assert.strictEqual(result.exitCode, 0,
`the installed ${script} must load and exit 0 — a MODULE_NOT_FOUND at load fires on every `
+ `pre_write_code / pre_run_command event and is invisible to the installer's exit code. stderr: ${result.stderr}`);
assert.doesNotMatch(String(result.stderr || ''), /MODULE_NOT_FOUND|Cannot find module/,
`no missing-module error may reach stderr for ${script}`);
}
const libDir = path.join(hooksDir, 'lib');
assert.ok(fs.existsSync(libDir), 'hooks/lib/ must be staged for Windsurf');
// Direct requires of the two guards, plus what hook-exit.js itself requires
// (cli-exit.js → exit-code-registry.js). The exact set is also pinned by
// tests/fixtures/install-tree/windsurf.json via the golden-install-tree test;
// this row states the reason each file must be present.
for (const helper of ['hook-exit.js', 'git-probe.js', 'cli-exit.js', 'exit-code-registry.js']) {
assert.ok(fs.existsSync(path.join(libDir, helper)),
`${helper} is on the require path of a staged Windsurf guard and must be staged`);
}
});
});
describe('#3023 pi shared-hooks bundle avoids the host-reserved hooks/ directory', () => {
const PI_RESERVED_DIR = 'hooks';
const PI_BUNDLE_DIR = 'msd-hooks';
for (const scope of ['local', 'global']) {
test(`pi ${scope} install: no host-reserved hooks/ dir, bundle staged under ${PI_BUNDLE_DIR}/, manifested`, (t) => {
const { manifest, configDir, root } = runMinimalInstall({ runtime: 'pi', scope });
t.after(() => cleanup(root));
const reserved = path.join(configDir, PI_RESERVED_DIR);
assert.equal(
fs.existsSync(reserved),
false,
`pi reserves <configDir>/${PI_RESERVED_DIR} as its deprecated extension location; ` +
`MSD must not create it (found ${reserved})`
);
const bundle = path.join(configDir, PI_BUNDLE_DIR);
assert.equal(
fs.existsSync(bundle) && fs.statSync(bundle).isDirectory(),
true,
`pi's shared hook bundle must be staged at ${bundle}`
);
// The adapter's live require target (pi/msd.cjs parseMsdCommandArgs).
const gitCmd = path.join(bundle, 'lib', 'git-cmd.js');
assert.equal(
fs.existsSync(gitCmd) && fs.statSync(gitCmd).isFile(),
true,
`pi adapter requires ${gitCmd}; the hooks/lib helpers must move with the bundle`
);
// #2544 CommonJS marker follows the bundle, and is NOT dropped at the
// shared config root (user-owned territory).
assert.equal(
fs.existsSync(path.join(bundle, 'package.json')),
true,
'the CommonJS marker must live inside the bundle directory'
);
assert.ok(manifest && manifest.files, 'pi install must write a file manifest');
const keys = Object.keys(manifest.files);
const stale = keys.filter((k) => k.startsWith(`${PI_RESERVED_DIR}/`));
assert.deepEqual(
stale,
[],
'no manifest key may reference the host-reserved hooks/ directory'
);
const staged = keys.filter((k) => k.startsWith(`${PI_BUNDLE_DIR}/`));
assert.ok(
staged.length > 0,
`manifest must track the staged bundle under ${PI_BUNDLE_DIR}/ so uninstall can remove it`
);
});
}
});
// ─── #3981: blocking PreToolUse guards must not fail open on a host stall ────
describe('bug #3981: blocking-guard timeout budget + migration', () => {