Files
msd-core/tests/pi-extension-reachability.test.cjs
Jakub Zych a9a7a328e6 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.
2026-10-06 01:47:40 +02:00

174 lines
8.0 KiB
JavaScript

'use strict';
/**
* pi extension reachability test — ADR-1239 Phase D / #1944, upgraded #2102.
*
* Proves the pi extension is keystone-WIRED: the registered /msd command's
* `handler(args, ctx)` (pi's REAL ExtensionAPI shape — NOT the `execute(ctx)`
* shape the original #1944 cut used) dispatches through msd-tools.cjs
* (subprocess-reuse — dispatchMsdCommand) and renders real output through
* `ctx.ui.notify` (#3456 — Pi discards command-handler return values), not
* just a registration on a mock. This is the "user can invoke X" proof.
*
* Dispatch is exercised with a real read-only family/subcommand
* (progress/json) against a real temp project, matching the sibling
* tests/vscode-extension-reachability.test.cjs pattern — no fake dispatcher
* injected, because the whole point of "reachability" is that the real
* engine is reached.
*/
const { test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const msdPiExtension = require('../pi/msd.cjs');
const { _internals } = require('../pi/msd.cjs');
const { createTempDir, cleanup } = require('./helpers.cjs');
function mockPi() {
const recorded = { commands: {}, tools: {}, events: {} };
return {
registerCommand(name, def) { recorded.commands[name] = def; },
registerTool(def) { if (def && def.name) recorded.tools[def.name] = def; },
on(event, handler) { (recorded.events[event] = recorded.events[event] || []).push(handler); },
_recorded: recorded,
};
}
test('the pi extension registers /msd (with getArgumentCompletions + handler) + msd_invoke + the event surface via ExtensionAPI', () => {
const pi = mockPi();
msdPiExtension(pi);
const msdCommand = pi._recorded.commands['msd'];
assert.ok(msdCommand, 'registers /msd command');
assert.equal(typeof msdCommand.handler, 'function', '/msd registers a handler(args, ctx) — pi\'s REAL ExtensionAPI shape, not execute(ctx)');
assert.equal(typeof msdCommand.getArgumentCompletions, 'function', '/msd registers getArgumentCompletions');
assert.ok(pi._recorded.tools['msd_invoke'], 'registers msd_invoke tool');
assert.equal(typeof pi._recorded.tools['msd_invoke'].execute, 'function');
assert.ok(pi._recorded.events['tool_call'], 'subscribes to tool_call');
assert.ok(pi._recorded.events['before_provider_request'], 'subscribes to before_provider_request');
});
test('REACHABILITY: parseMsdCommandArgs tokenizes a raw args string into {family, subcommand, args}', () => {
const parsed = _internals.parseMsdCommandArgs('phase add --name test');
assert.deepEqual(parsed, { family: 'phase', subcommand: 'add', args: ['--name', 'test'] });
});
test('REACHABILITY: empty args dispatch a working default (msd-tools.cjs --help), not the broken "query help"', () => {
const parsed = _internals.parseMsdCommandArgs('');
assert.equal(parsed.family, '--help');
assert.equal(parsed.subcommand, undefined);
});
// #3456: Pi's command dispatcher does `await command.handler(args, ctx);
// return true;` — it DISCARDS the handler's return value in any shape. The
// only command-output mechanism Pi consumes is `ctx.ui.notify(message, type)`
// (earendil-works/pi docs: extensions.md#piregistercommandname-options). The
// #3097 tests asserted on the discarded return object, so they passed while
// `/msd` stayed mute in real Pi. These tests invoke the handler exactly as
// Pi's dispatcher does (await + discard) and assert the notify side effect.
function makePiCtx(dir) {
const notifications = [];
return {
ctx: {
cwd: dir,
ui: {
notify(message, type) { notifications.push({ message, type }); },
},
},
notifications,
};
}
test('REACHABILITY: the /msd handler dispatches a real family through msd-tools.cjs and renders output via ctx.ui.notify (keystone wired)', async () => {
const pi = mockPi();
msdPiExtension(pi);
const dir = createTempDir();
try {
// #3217 (ADR-3180 §7.6 rule 4): a free-form ROADMAP.md (no version
// token) is COMPLETE scope for windowing (§7.1) — without this, a
// bare temp dir has no ROADMAP.md at all (UNREADABLE) and `percent`
// is withheld (null), breaking this reachability proxy.
fs.mkdirSync(path.join(dir, '.planning'), { recursive: true });
fs.writeFileSync(path.join(dir, '.planning', 'ROADMAP.md'), '# Roadmap\n');
const { ctx, notifications } = makePiCtx(dir);
// Invoke EXACTLY as Pi's dispatcher does: await, then discard the return.
const returned = await pi._recorded.commands['msd'].handler('progress json', ctx);
assert.equal(notifications.length, 1, `/msd must push exactly one ui.notify; got ${notifications.length}`);
assert.equal(notifications[0].type, 'info', 'successful dispatch notifies with type "info"');
const parsed = JSON.parse(notifications[0].message);
assert.equal(typeof parsed.percent, 'number', '/msd dispatch reached msd-tools.cjs for real (the engine was reached)');
assert.equal(returned, undefined, 'the handler return value is the boundary Pi discards — the output must travel via ctx.ui.notify, not a return (#3456)');
} finally {
cleanup(dir);
}
});
test('REACHABILITY: an unknown family renders a clear MSD error via ctx.ui.notify, not a throw', async () => {
const pi = mockPi();
msdPiExtension(pi);
const dir = createTempDir();
try {
const { ctx, notifications } = makePiCtx(dir);
const returned = await pi._recorded.commands['msd'].handler('no-such-family-8675309', ctx);
assert.equal(notifications.length, 1, `failed dispatch must push exactly one ui.notify; got ${notifications.length}`);
assert.equal(notifications[0].type, 'error', 'failed dispatch notifies with type "error"');
assert.match(notifications[0].message, /MSD error:/);
assert.match(notifications[0].message, /no-such-family-8675309|Unknown command/);
assert.equal(returned, undefined, 'the error text must travel via ctx.ui.notify, not the discarded return (#3456)');
} finally {
cleanup(dir);
}
});
test('REACHABILITY: the /msd handler tolerates a ctx without ui (older Pi host) without throwing', async () => {
const pi = mockPi();
msdPiExtension(pi);
const dir = createTempDir();
try {
fs.mkdirSync(path.join(dir, '.planning'), { recursive: true });
fs.writeFileSync(path.join(dir, '.planning', 'ROADMAP.md'), '# Roadmap\n');
// No assert on output — a host without ctx.ui has no renderable channel;
// this only proves the handler does not crash such a host.
await pi._recorded.commands['msd'].handler('progress json', { cwd: dir });
} finally {
cleanup(dir);
}
});
test('REACHABILITY: the msd_invoke tool dispatches through the engine and returns real content', async () => {
const pi = mockPi();
msdPiExtension(pi);
const dir = createTempDir();
try {
// #3217 (ADR-3180 §7.6 rule 4): see the /msd handler reachability test
// above — a bare temp dir has no ROADMAP.md (UNREADABLE), withholding
// `percent`.
fs.mkdirSync(path.join(dir, '.planning'), { recursive: true });
fs.writeFileSync(path.join(dir, '.planning', 'ROADMAP.md'), '# Roadmap\n');
const result = await pi._recorded.tools['msd_invoke'].execute(
'call-1',
{ family: 'progress', subcommand: 'json' },
null,
null,
{ cwd: dir },
);
assert.ok(result && Array.isArray(result.content), 'msd_invoke returns {content:[...]}');
const parsed = JSON.parse(result.content[0].text);
assert.equal(typeof parsed.percent, 'number', 'msd_invoke dispatch reached msd-tools.cjs for real');
} finally {
cleanup(dir);
}
});
test('msd_invoke rejects a missing "family" without dispatching', async () => {
const pi = mockPi();
msdPiExtension(pi);
const result = await pi._recorded.tools['msd_invoke'].execute('call-2', {}, null, null, {});
assert.match(result.content[0].text, /requires a non-empty string "family"/);
});
test('msdPiExtension throws without pi ExtensionAPI (fail-closed)', () => {
assert.throws(() => msdPiExtension(null), /ExtensionAPI is required/);
});