refactor: hard-fork GSD -> MSD (Make Software Done)
Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD across contents and paths, upstream package/repo coordinates -> @golem15/msd-core and golem15com/msd-core. Deep links into upstream history, sibling upstream packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is. Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line, package/plugin identity, regenerated lockfile, install-tree fixtures, derived registries and benchmark baseline; migration checksum baseline re-locked (MSD keeps its own install state, so no install had applied the old sums); sort-order and regex-escaped expectations in tests adjusted.
This commit is contained in:
@@ -2,14 +2,14 @@
|
||||
|
||||
/**
|
||||
* opencode-plugin-adapter.test.cjs — unit + integration coverage for the
|
||||
* OpenCode native plugin adapter (.opencode/plugins/gsd-core.js, issue #1914).
|
||||
* OpenCode native plugin adapter (.opencode/plugins/msd-core.js, issue #1914).
|
||||
*
|
||||
* The adapter bridges OpenCode's plugin event bus onto GSD's existing hook
|
||||
* The adapter bridges OpenCode's plugin event bus onto MSD's existing hook
|
||||
* scripts by spawning them as subprocesses. These tests exercise it WITHOUT a
|
||||
* live OpenCode runtime by:
|
||||
* 1. Unit-testing the pure translation helpers exposed on `_internals`.
|
||||
* 2. Building a temp "install" layout (hooks/ with deterministic STUB hooks +
|
||||
* gsd-core/ + plugins/gsd-core.js) and driving the plugin's returned
|
||||
* msd-core/ + plugins/msd-core.js) and driving the plugin's returned
|
||||
* handlers directly, asserting the real spawn bridge maps block/advisory/
|
||||
* allow correctly and that REPO_ROOT resolves to the payload dir.
|
||||
*
|
||||
@@ -28,7 +28,7 @@ const os = require('os');
|
||||
const { cleanup } = require('./helpers.cjs');
|
||||
const { INSTALL_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
|
||||
|
||||
const ADAPTER_SRC = path.join(__dirname, '..', '.opencode', 'plugins', 'gsd-core.js');
|
||||
const ADAPTER_SRC = path.join(__dirname, '..', '.opencode', 'plugins', 'msd-core.js');
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Pure-helper unit tests (no filesystem / no spawn)
|
||||
@@ -59,7 +59,7 @@ function loaderExtract(mod) {
|
||||
test('export survives the loader loop as raw CommonJS (require)', () => {
|
||||
const mod = require(ADAPTER_SRC);
|
||||
// `id` must be readable for identity/dedup...
|
||||
assert.equal(mod.id, 'gsd-core');
|
||||
assert.equal(mod.id, 'msd-core');
|
||||
// ...but NON-ENUMERABLE so it never lands in Object.values (would throw).
|
||||
assert.ok(!Object.keys(mod).includes('id'), 'id must be non-enumerable');
|
||||
const servers = loaderExtract(mod); // must not throw
|
||||
@@ -143,7 +143,7 @@ test('handleHookResult: block decision throws with the hook reason', () => {
|
||||
test('handleHookResult: exit code 2 is a hard block even without JSON', () => {
|
||||
assert.throws(
|
||||
() => _internals.handleHookResult({ stdout: '', exitCode: 2 }),
|
||||
/Blocked by GSD hook/,
|
||||
/Blocked by MSD hook/,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -155,7 +155,7 @@ test('handleHookResult: advisory sets metadata + does not throw', () => {
|
||||
output,
|
||||
),
|
||||
);
|
||||
assert.deepEqual(output.metadata._gsdAdvisory, ['heads up']);
|
||||
assert.deepEqual(output.metadata._msdAdvisory, ['heads up']);
|
||||
});
|
||||
|
||||
test('handleHookResult: multiple advisories accumulate (no clobber)', () => {
|
||||
@@ -170,7 +170,7 @@ test('handleHookResult: multiple advisories accumulate (no clobber)', () => {
|
||||
advise('prompt-guard note');
|
||||
advise('read-guard note');
|
||||
advise('workflow-guard note');
|
||||
assert.deepEqual(output.metadata._gsdAdvisory, [
|
||||
assert.deepEqual(output.metadata._msdAdvisory, [
|
||||
'prompt-guard note',
|
||||
'read-guard note',
|
||||
'workflow-guard note',
|
||||
@@ -187,17 +187,17 @@ test('handleHookResult: silent allow is a no-op', () => {
|
||||
// Integration: drive the plugin against a temp install layout with STUB hooks
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Build a self-contained payload dir: <root>/hooks/<stub>.js, <root>/gsd-core/,
|
||||
// and <root>/plugins/gsd-core.js (a copy of the adapter). Returns the loaded
|
||||
// Build a self-contained payload dir: <root>/hooks/<stub>.js, <root>/msd-core/,
|
||||
// and <root>/plugins/msd-core.js (a copy of the adapter). Returns the loaded
|
||||
// plugin module for that layout. Each stub hook echoes a fixed JSON verdict.
|
||||
function buildInstalledLayout(t, stubHooks) {
|
||||
// realpath so `root` matches Node's realpath-resolved __dirname inside the
|
||||
// copied plugin (macOS /var → /private/var symlink would otherwise diverge).
|
||||
const root = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-oc-plugin-')));
|
||||
const root = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'msd-oc-plugin-')));
|
||||
t.after(() => cleanup(root));
|
||||
|
||||
fs.mkdirSync(path.join(root, 'hooks'), { recursive: true });
|
||||
fs.mkdirSync(path.join(root, 'gsd-core', 'workflows'), { recursive: true });
|
||||
fs.mkdirSync(path.join(root, 'msd-core', 'workflows'), { recursive: true });
|
||||
fs.mkdirSync(path.join(root, 'plugins'), { recursive: true });
|
||||
|
||||
for (const [name, jsBody] of Object.entries(stubHooks)) {
|
||||
@@ -205,8 +205,8 @@ function buildInstalledLayout(t, stubHooks) {
|
||||
}
|
||||
|
||||
// Copy the real adapter into the payload's plugins/ dir so REPO_ROOT resolves
|
||||
// to `root` via the walk-up probe (root has both hooks/ and gsd-core/).
|
||||
const dest = path.join(root, 'plugins', 'gsd-core.js');
|
||||
// to `root` via the walk-up probe (root has both hooks/ and msd-core/).
|
||||
const dest = path.join(root, 'plugins', 'msd-core.js');
|
||||
fs.copyFileSync(ADAPTER_SRC, dest);
|
||||
// Fresh module instance (bypass require cache — each layout is distinct).
|
||||
delete require.cache[require.resolve(dest)];
|
||||
@@ -229,13 +229,13 @@ if(process.stdin.isTTY){process.stdout.write(${JSON.stringify(verdictJson)});pro
|
||||
test('REPO_ROOT resolves to the payload dir in an installed layout', (t) => {
|
||||
const { root, mod } = buildInstalledLayout(t, {});
|
||||
assert.equal(mod.server._internals.REPO_ROOT, fs.realpathSync(root));
|
||||
// No source commands/gsd/ present → treated as installed (not package) tree.
|
||||
// No source commands/msd/ present → treated as installed (not package) tree.
|
||||
assert.equal(mod.server._internals.IS_PACKAGE_TREE, false);
|
||||
});
|
||||
|
||||
test('tool.execute.before: a blocking hook aborts the tool call (throws)', async (t) => {
|
||||
const { mod } = buildInstalledLayout(t, {
|
||||
'gsd-prompt-guard.js': stubHook(JSON.stringify({ decision: 'block', reason: 'injection detected' })),
|
||||
'msd-prompt-guard.js': stubHook(JSON.stringify({ decision: 'block', reason: 'injection detected' })),
|
||||
});
|
||||
const handlers = await mod.server({ directory: process.cwd() });
|
||||
await assert.rejects(
|
||||
@@ -249,10 +249,10 @@ test('tool.execute.before: a blocking hook aborts the tool call (throws)', async
|
||||
|
||||
test('tool.execute.before: a silent hook allows the tool call (no throw)', async (t) => {
|
||||
const { mod } = buildInstalledLayout(t, {
|
||||
'gsd-prompt-guard.js': stubHook(''),
|
||||
'gsd-read-guard.js': stubHook(''),
|
||||
'gsd-worktree-path-guard.js': stubHook(''),
|
||||
'gsd-workflow-guard.js': stubHook(''),
|
||||
'msd-prompt-guard.js': stubHook(''),
|
||||
'msd-read-guard.js': stubHook(''),
|
||||
'msd-worktree-path-guard.js': stubHook(''),
|
||||
'msd-workflow-guard.js': stubHook(''),
|
||||
});
|
||||
const handlers = await mod.server({ directory: process.cwd() });
|
||||
await assert.doesNotReject(() =>
|
||||
@@ -265,8 +265,8 @@ test('tool.execute.before: a silent hook allows the tool call (no throw)', async
|
||||
|
||||
test('tool.execute.before: the secret read guard blocks a Bash read of .env (#4221)', async (t) => {
|
||||
const { mod } = buildInstalledLayout(t, {
|
||||
'gsd-workflow-guard.js': stubHook(''),
|
||||
'gsd-secret-read-guard.js': stubHook(JSON.stringify({ decision: 'block', code: 'secret-read', reason: 'secret read denied' }), 2),
|
||||
'msd-workflow-guard.js': stubHook(''),
|
||||
'msd-secret-read-guard.js': stubHook(JSON.stringify({ decision: 'block', code: 'secret-read', reason: 'secret read denied' }), 2),
|
||||
});
|
||||
const handlers = await mod.server({ directory: process.cwd() });
|
||||
await assert.rejects(
|
||||
@@ -277,7 +277,7 @@ test('tool.execute.before: the secret read guard blocks a Bash read of .env (#42
|
||||
|
||||
test('tool.execute.before: the secret read guard blocks a grep with a secret path (#4221)', async (t) => {
|
||||
const { mod } = buildInstalledLayout(t, {
|
||||
'gsd-secret-read-guard.js': stubHook(JSON.stringify({ decision: 'block', code: 'secret-read', reason: 'secret grep denied' }), 2),
|
||||
'msd-secret-read-guard.js': stubHook(JSON.stringify({ decision: 'block', code: 'secret-read', reason: 'secret grep denied' }), 2),
|
||||
});
|
||||
const handlers = await mod.server({ directory: process.cwd() });
|
||||
await assert.rejects(
|
||||
@@ -288,12 +288,12 @@ test('tool.execute.before: the secret read guard blocks a grep with a secret pat
|
||||
|
||||
test('tool.execute.before: the secret read guard is dispatched for read, not for write (#4221)', async (t) => {
|
||||
const { mod } = buildInstalledLayout(t, {
|
||||
'gsd-prompt-guard.js': stubHook(''),
|
||||
'gsd-read-guard.js': stubHook(''),
|
||||
'gsd-worktree-path-guard.js': stubHook(''),
|
||||
'gsd-workflow-guard.js': stubHook(''),
|
||||
'gsd-write-guard.js': stubHook(''),
|
||||
'gsd-secret-read-guard.js': stubHook(JSON.stringify({ decision: 'block', code: 'secret-read', reason: 'secret read denied' }), 2),
|
||||
'msd-prompt-guard.js': stubHook(''),
|
||||
'msd-read-guard.js': stubHook(''),
|
||||
'msd-worktree-path-guard.js': stubHook(''),
|
||||
'msd-workflow-guard.js': stubHook(''),
|
||||
'msd-write-guard.js': stubHook(''),
|
||||
'msd-secret-read-guard.js': stubHook(JSON.stringify({ decision: 'block', code: 'secret-read', reason: 'secret read denied' }), 2),
|
||||
});
|
||||
const handlers = await mod.server({ directory: process.cwd() });
|
||||
await assert.rejects(
|
||||
@@ -305,28 +305,28 @@ test('tool.execute.before: the secret read guard is dispatched for read, not for
|
||||
);
|
||||
});
|
||||
|
||||
test('tool.execute.after: Read content rewriting maps ~/.claude/gsd-core paths', async (t) => {
|
||||
test('tool.execute.after: Read content rewriting maps ~/.claude/msd-core paths', async (t) => {
|
||||
const { root, mod } = buildInstalledLayout(t, {
|
||||
'gsd-read-injection-scanner.js': stubHook(''),
|
||||
'msd-read-injection-scanner.js': stubHook(''),
|
||||
});
|
||||
const handlers = await mod.server({ directory: process.cwd() });
|
||||
// A file under the payload's gsd-core/workflows is a GSD-managed file, so its
|
||||
// Read output is rewritten (canonical ~/.claude/gsd-core/ → real payload path).
|
||||
const managed = path.join(root, 'gsd-core', 'workflows', 'x.md');
|
||||
const output = { output: 'see ~/.claude/gsd-core/references/foo.md for details' };
|
||||
// A file under the payload's msd-core/workflows is a MSD-managed file, so its
|
||||
// Read output is rewritten (canonical ~/.claude/msd-core/ → real payload path).
|
||||
const managed = path.join(root, 'msd-core', 'workflows', 'x.md');
|
||||
const output = { output: 'see ~/.claude/msd-core/references/foo.md for details' };
|
||||
await handlers['tool.execute.after']({ tool: 'read', args: { filePath: managed } }, output);
|
||||
// The adapter rewrites `~/.claude/gsd-core/` → `${GSD_CORE}/`, where GSD_CORE
|
||||
// is `path.join(root, 'gsd-core')` (OS-native separators). Assert with a plain
|
||||
// The adapter rewrites `~/.claude/msd-core/` → `${MSD_CORE}/`, where MSD_CORE
|
||||
// is `path.join(root, 'msd-core')` (OS-native separators). Assert with a plain
|
||||
// string include, NOT a RegExp built from a path — on Windows the backslashes
|
||||
// in the path would be interpreted as regex escapes and never match.
|
||||
const expected = path.join(root, 'gsd-core') + '/references/foo.md';
|
||||
const expected = path.join(root, 'msd-core') + '/references/foo.md';
|
||||
assert.ok(
|
||||
output.output.includes(expected),
|
||||
`expected rewritten path "${expected}" in output: ${output.output}`,
|
||||
);
|
||||
assert.ok(
|
||||
!output.output.includes('~/.claude/gsd-core/'),
|
||||
'canonical ~/.claude/gsd-core/ prefix must be rewritten away',
|
||||
!output.output.includes('~/.claude/msd-core/'),
|
||||
'canonical ~/.claude/msd-core/ prefix must be rewritten away',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -383,8 +383,8 @@ test('session.idle event is handled (no-op sentinel) without throwing', async (t
|
||||
await assert.doesNotReject(() => handlers.event({ event: { type: 'session.idle' } }));
|
||||
});
|
||||
|
||||
test('experimental.session.compacting injects the GSD state breadcrumb', async (t) => {
|
||||
const { mod } = buildInstalledLayout(t, { 'gsd-context-monitor.js': stubHook('') });
|
||||
test('experimental.session.compacting injects the MSD state breadcrumb', async (t) => {
|
||||
const { mod } = buildInstalledLayout(t, { 'msd-context-monitor.js': stubHook('') });
|
||||
const handlers = await mod.server({ directory: process.cwd() });
|
||||
// Compaction fires only with an active session; session.created sets it.
|
||||
await handlers.event({
|
||||
@@ -392,12 +392,12 @@ test('experimental.session.compacting injects the GSD state breadcrumb', async (
|
||||
});
|
||||
const output = {};
|
||||
await handlers['experimental.session.compacting']({}, output);
|
||||
assert.ok(Array.isArray(output.context) && output.context.length > 0, 'compaction injects a GSD breadcrumb');
|
||||
assert.ok(output.context.some((c) => /GSD/.test(c)), 'breadcrumb is GSD-tagged');
|
||||
assert.ok(Array.isArray(output.context) && output.context.length > 0, 'compaction injects a MSD breadcrumb');
|
||||
assert.ok(output.context.some((c) => /MSD/.test(c)), 'breadcrumb is MSD-tagged');
|
||||
});
|
||||
|
||||
test('plugin implements the full declared opencode extension-event surface (Claude parity — #1943)', async (t) => {
|
||||
const { extensionEventSurfaceFor } = require('../gsd-core/bin/lib/host-integration.cjs');
|
||||
const { extensionEventSurfaceFor } = require('../msd-core/bin/lib/host-integration.cjs');
|
||||
const surface = extensionEventSurfaceFor('opencode');
|
||||
assert.ok(surface, 'opencode is a consumed extensionEvents dialect (non-null surface)');
|
||||
// The engine — not the host bus — owns workflow-phase sequencing on this host.
|
||||
@@ -432,13 +432,13 @@ test('plugin implements the full declared opencode extension-event surface (Clau
|
||||
test('installer copies plugin as .js, records it in the manifest, and removes it on uninstall', (t) => {
|
||||
const { runNode } = require('./helpers/process-seam.cjs');
|
||||
const installer = path.join(__dirname, '..', 'bin', 'install.js');
|
||||
const cfg = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-oc-install-')));
|
||||
const cfg = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'msd-oc-install-')));
|
||||
t.after(() => cleanup(cfg));
|
||||
|
||||
const run = (args) => {
|
||||
const result = runNode([installer, '--opencode', '--global', '--config-dir', cfg, ...args], {
|
||||
timeoutMs: INSTALL_TIMEOUT_MS,
|
||||
// #3156: sandbox HOME — the installer writes <home>/.gsd/defaults.json via
|
||||
// #3156: sandbox HOME — the installer writes <home>/.msd/defaults.json via
|
||||
// os.homedir() directly, which no env scrub can reach. See installSpawnEnv.
|
||||
env: require('./helpers.cjs').installSpawnEnv(),
|
||||
});
|
||||
@@ -450,18 +450,18 @@ test('installer copies plugin as .js, records it in the manifest, and removes it
|
||||
const install = run([]);
|
||||
assert.equal(install.status, 0, `install failed: ${install.stderr}`);
|
||||
|
||||
const pluginPath = path.join(cfg, 'plugins', 'gsd-core.js');
|
||||
assert.ok(fs.existsSync(pluginPath), 'plugin must land at plugins/gsd-core.js (matches OpenCode {plugin,plugins}/*.{ts,js} glob)');
|
||||
assert.ok(!fs.existsSync(path.join(cfg, 'plugins', 'gsd-core.cjs')), 'must NOT ship a .cjs (never auto-discovered)');
|
||||
const pluginPath = path.join(cfg, 'plugins', 'msd-core.js');
|
||||
assert.ok(fs.existsSync(pluginPath), 'plugin must land at plugins/msd-core.js (matches OpenCode {plugin,plugins}/*.{ts,js} glob)');
|
||||
assert.ok(!fs.existsSync(path.join(cfg, 'plugins', 'msd-core.cjs')), 'must NOT ship a .cjs (never auto-discovered)');
|
||||
|
||||
// Manifest records the plugin for drift/uninstall accounting.
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(cfg, 'gsd-file-manifest.json'), 'utf8'));
|
||||
assert.ok(manifest.files['plugins/gsd-core.js'], 'manifest must track plugins/gsd-core.js');
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(cfg, 'msd-file-manifest.json'), 'utf8'));
|
||||
assert.ok(manifest.files['plugins/msd-core.js'], 'manifest must track plugins/msd-core.js');
|
||||
|
||||
// The installed plugin loads and resolves REPO_ROOT to the config dir.
|
||||
delete require.cache[require.resolve(pluginPath)];
|
||||
const installed = require(pluginPath);
|
||||
assert.equal(installed.id, 'gsd-core');
|
||||
assert.equal(installed.id, 'msd-core');
|
||||
assert.equal(installed.server._internals.REPO_ROOT, cfg);
|
||||
assert.equal(installed.server._internals.IS_PACKAGE_TREE, false);
|
||||
|
||||
@@ -502,7 +502,7 @@ async function buildLayoutWithSpawnTrace(t, { stubHooks, planningConfig }) {
|
||||
const { mod } = buildInstalledLayout(t, stubHooks || {});
|
||||
|
||||
// Project dir is the cwd the plugin reads config from (currentCwd).
|
||||
const projectDir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-oc-proj-')));
|
||||
const projectDir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'msd-oc-proj-')));
|
||||
t.after(() => cleanup(projectDir));
|
||||
if (planningConfig !== null) {
|
||||
fs.mkdirSync(path.join(projectDir, '.planning'), { recursive: true });
|
||||
@@ -526,13 +526,13 @@ async function buildLayoutWithSpawnTrace(t, { stubHooks, planningConfig }) {
|
||||
function spawnTargetedMonitor(spawns) {
|
||||
return spawns.some((a) => {
|
||||
const argv = a[1];
|
||||
return Array.isArray(argv) && argv.some((s) => String(s).includes('gsd-context-monitor.js'));
|
||||
return Array.isArray(argv) && argv.some((s) => String(s).includes('msd-context-monitor.js'));
|
||||
});
|
||||
}
|
||||
|
||||
test('#2697: context-monitor subprocess is skipped when context_warnings is disabled', async (t) => {
|
||||
const { handlers, spawns } = await buildLayoutWithSpawnTrace(t, {
|
||||
stubHooks: { 'gsd-context-monitor.js': stubHook('') },
|
||||
stubHooks: { 'msd-context-monitor.js': stubHook('') },
|
||||
planningConfig: { hooks: { context_warnings: false } },
|
||||
});
|
||||
// Bash is a non-Read tool → reaches the context-monitor dispatch.
|
||||
@@ -545,7 +545,7 @@ test('#2697: context-monitor subprocess is skipped when context_warnings is disa
|
||||
|
||||
test('#2697: context-monitor subprocess still runs when config is absent (default enabled)', async (t) => {
|
||||
const { handlers, spawns } = await buildLayoutWithSpawnTrace(t, {
|
||||
stubHooks: { 'gsd-context-monitor.js': stubHook('') },
|
||||
stubHooks: { 'msd-context-monitor.js': stubHook('') },
|
||||
planningConfig: null,
|
||||
});
|
||||
await handlers['tool.execute.after']({ tool: 'bash', args: { command: 'echo hi' } }, {});
|
||||
@@ -557,7 +557,7 @@ test('#2697: context-monitor subprocess still runs when config is absent (defaul
|
||||
|
||||
test('#2697: context-monitor subprocess runs when context_warnings explicitly true', async (t) => {
|
||||
const { handlers, spawns } = await buildLayoutWithSpawnTrace(t, {
|
||||
stubHooks: { 'gsd-context-monitor.js': stubHook('') },
|
||||
stubHooks: { 'msd-context-monitor.js': stubHook('') },
|
||||
planningConfig: { hooks: { context_warnings: true } },
|
||||
});
|
||||
await handlers['tool.execute.after']({ tool: 'bash', args: { command: 'echo hi' } }, {});
|
||||
@@ -575,8 +575,8 @@ test('#2697: context-monitor subprocess runs when config.json is unparseable (de
|
||||
cp.spawnSync = (...args) => { spawns.push(args); return { stdout: '', status: 0, signal: null }; };
|
||||
t.after(() => { cp.spawnSync = realSpawnSync; });
|
||||
|
||||
const { mod } = buildInstalledLayout(t, { 'gsd-context-monitor.js': stubHook('') });
|
||||
const projectDir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-oc-proj-')));
|
||||
const { mod } = buildInstalledLayout(t, { 'msd-context-monitor.js': stubHook('') });
|
||||
const projectDir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'msd-oc-proj-')));
|
||||
t.after(() => cleanup(projectDir));
|
||||
fs.mkdirSync(path.join(projectDir, '.planning'), { recursive: true });
|
||||
// Malformed JSON → the in-process check must treat it as "enabled" (Postel: conservative).
|
||||
@@ -596,7 +596,7 @@ test('#2697: context-monitor subprocess runs when config.json is unparseable (de
|
||||
|
||||
test('#2697: context-monitor skipped for Write when context_warnings disabled (not Bash-specific)', async (t) => {
|
||||
const { handlers, spawns } = await buildLayoutWithSpawnTrace(t, {
|
||||
stubHooks: { 'gsd-context-monitor.js': stubHook('') },
|
||||
stubHooks: { 'msd-context-monitor.js': stubHook('') },
|
||||
planningConfig: { hooks: { context_warnings: false } },
|
||||
});
|
||||
// Write reaches context-monitor dispatch (non-Read); the guard hooks run before it
|
||||
|
||||
Reference in New Issue
Block a user