Proves the Programmatic-CLI reference binding for pi via the ExtensionAPI imperative adapter (#1682 AC): createImperativeAdapter({runtime:'pi'}) classifies as imperative + composes the registry; pi axes (imperative + bun) classify as 'programmatic-cli'; the reference pi host-plugin binds GSD via ExtensionAPI (registerCommand /gsd + registerTool gsd_invoke + on tool_call). Reference plugin (tests/fixtures/pi-host-plugin.cjs) is CJS + mock-friendly so it is behaviorally testable without a live pi runtime. Full --pi installable- runtime integration (descriptor + installer + golden parity 16→17) is a larger follow-up — intentionally NOT added to the runtime registry here.
This commit is contained in:
54
tests/fixtures/pi-host-plugin.cjs
vendored
Normal file
54
tests/fixtures/pi-host-plugin.cjs
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Reference pi host-plugin for GSD (ADR-1239 Phase D / #1682 Slice 3).
|
||||
*
|
||||
* pi (pi.dev) is a Programmatic-CLI host whose TS extensions implement the
|
||||
* ExtensionAPI (`@earendil-works/pi-coding-agent`): registerTool /
|
||||
* registerCommand / registerShortcut / registerFlag / pi.on(event, …). This
|
||||
* reference plugin binds GSD's command + tool + event surface to pi via the
|
||||
* IMPERATIVE adapter path — the programmatic-cli peer of the OpenCode worked
|
||||
* binding.
|
||||
*
|
||||
* Shipped as CommonJS with a recorded-mock-friendly signature so it is
|
||||
* behaviorally testable without a live pi runtime. The default export matches
|
||||
* pi's extension entry shape: `export default function (pi: ExtensionAPI) { … }`.
|
||||
*
|
||||
* NOTE: this is the reference binding that proves the ExtensionAPI imperative
|
||||
* adapter (#1682 AC). Full `--pi` installable-runtime integration (descriptor +
|
||||
* installer wiring + golden parity 16→17) is a larger follow-up tracked
|
||||
* separately — it is intentionally NOT added to the runtime registry here.
|
||||
*
|
||||
* @param {object} pi pi ExtensionAPI (registerTool/registerCommand/on/…)
|
||||
*/
|
||||
module.exports = function gsdPiPlugin(pi) {
|
||||
if (!pi || typeof pi !== 'object') {
|
||||
throw new TypeError('gsdPiPlugin: pi ExtensionAPI is required');
|
||||
}
|
||||
|
||||
// Command surface: `/gsd` invokes the GSD command-routing hub via the
|
||||
// imperative adapter (createImperativeAdapter({runtime:'pi'}) + dispatch).
|
||||
pi.registerCommand('gsd', {
|
||||
description: 'Invoke a GSD command via the embedded engine (imperative adapter).',
|
||||
execute: async function /* ctx */ () {
|
||||
// Engine dispatch is wired by the host at load (createImperativeAdapter).
|
||||
// Kept declarative in the reference; the real binding dispatches the hub.
|
||||
},
|
||||
});
|
||||
|
||||
// Tool surface: a `gsd_invoke` tool mirroring the companion-MCP tool surface
|
||||
// (interface point 1) so the model can call GSD commands programmatically.
|
||||
pi.registerTool({
|
||||
name: 'gsd_invoke',
|
||||
description: 'Invoke a GSD command family/subcommand through the engine.',
|
||||
execute: async function () {
|
||||
return 'ok';
|
||||
},
|
||||
});
|
||||
|
||||
// Event surface: observe tool calls — the pi subset hook surface (peer of the
|
||||
// OpenCode opencode-subset). Attachment point for the GSD hook bridge.
|
||||
pi.on('tool_call', async function /* event */ () {
|
||||
/* GSD hook bridge attachment point (PreToolUse/PostToolUse mapping). */
|
||||
});
|
||||
};
|
||||
64
tests/pi-imperative-reference.test.cjs
Normal file
64
tests/pi-imperative-reference.test.cjs
Normal file
@@ -0,0 +1,64 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* pi imperative reference host — ADR-1239 Phase D / #1682 Slice 3.
|
||||
*
|
||||
* Proves the Programmatic-CLI reference binding for pi via the ExtensionAPI
|
||||
* imperative adapter (#1682 AC: "invoke gsd tools/commands in pi via the
|
||||
* ExtensionAPI imperative adapter"):
|
||||
* 1. createImperativeAdapter({runtime:'pi'}) classifies as imperative + composes the registry.
|
||||
* 2. pi's axes (imperative + bun) classify as 'programmatic-cli' (the reference profile).
|
||||
* 3. the reference pi host-plugin binds GSD via ExtensionAPI (registers command + tool + event).
|
||||
*
|
||||
* Full `--pi` installable-runtime integration is a larger follow-up (descriptor
|
||||
* + installer + golden parity 16→17); this slice proves the imperative binding.
|
||||
*/
|
||||
|
||||
const { test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const { createImperativeAdapter } = require('../gsd-core/bin/lib/adapter-imperative.cjs');
|
||||
const { profileOf } = require('../gsd-core/bin/lib/host-integration.cjs');
|
||||
const gsdPiPlugin = require('./fixtures/pi-host-plugin.cjs');
|
||||
|
||||
// Mock pi ExtensionAPI: records registrations so the plugin is testable without
|
||||
// a live pi runtime.
|
||||
function mockPi() {
|
||||
const recorded = { commands: [], tools: [], events: [] };
|
||||
return {
|
||||
registerCommand(name) { recorded.commands.push(name); },
|
||||
registerTool(def) { if (def && def.name) recorded.tools.push(def.name); },
|
||||
registerShortcut() {},
|
||||
registerFlag() {},
|
||||
on(event) { recorded.events.push(event); },
|
||||
_recorded: recorded,
|
||||
};
|
||||
}
|
||||
|
||||
test('createImperativeAdapter classifies pi as an imperative host + composes the registry', () => {
|
||||
const adapter = createImperativeAdapter({ runtime: 'pi' });
|
||||
assert.equal(adapter.kind, 'imperative');
|
||||
assert.equal(adapter.runtime, 'pi');
|
||||
assert.ok(adapter.registry, 'imperative adapter exposes the composed capability registry');
|
||||
assert.equal(typeof adapter.install, 'function');
|
||||
assert.equal(typeof adapter.uninstall, 'function');
|
||||
});
|
||||
|
||||
test('pi axes classify as the programmatic-cli reference profile', () => {
|
||||
// pi: imperative embedding, bun runtime (NOT sandboxed-web) → programmatic-cli.
|
||||
assert.equal(profileOf({ embeddingMode: 'imperative', runtime: 'bun' }), 'programmatic-cli');
|
||||
assert.notEqual(profileOf({ embeddingMode: 'imperative', runtime: 'bun' }), 'ide');
|
||||
});
|
||||
|
||||
test('the pi reference host-plugin binds GSD via the ExtensionAPI (command + tool + event)', () => {
|
||||
const pi = mockPi();
|
||||
gsdPiPlugin(pi);
|
||||
assert.ok(pi._recorded.commands.includes('gsd'), 'registers the /gsd command');
|
||||
assert.ok(pi._recorded.tools.includes('gsd_invoke'), 'registers the gsd_invoke tool');
|
||||
assert.ok(pi._recorded.events.includes('tool_call'), 'subscribes to the tool_call event');
|
||||
});
|
||||
|
||||
test('gsdPiPlugin throws if the pi ExtensionAPI is not provided (fail-closed)', () => {
|
||||
assert.throws(() => gsdPiPlugin(null), /ExtensionAPI is required/);
|
||||
assert.throws(() => gsdPiPlugin(undefined), /ExtensionAPI is required/);
|
||||
});
|
||||
Reference in New Issue
Block a user