feat(#1942): VS Code extension — repo-local IDE host with reachability proof (#1966)

* feat(#1942): VS Code extension — repo-local IDE host with reachability proof

The VS Code extension (vscode/extension.js + vscode/package.json) is a
repo-local, buildable extension (not Marketplace-published). The gsd.invoke
handler dispatches in-process through the GSD command-routing hub. The handler
is exported separately from activate() so it is testable without a VS Code host.

tests/vscode-extension-reachability.test.cjs: proves the handler dispatches
through the hub + returns a result (keystone wired); the manifest declares the
command + engine; resolveEngineRoot finds the gsd-core/ dir.

* docs(changeset): VS Code extension (#1966)

* fix(#1942): register vscode/package.json in VERSIONED_MANIFESTS (#844)

* fix(#1942): align vscode/package.json version with package.json (1.7.0-rc.1)
This commit is contained in:
Tom Boucher
2026-07-02 23:12:03 -04:00
committed by GitHub
parent b188c0d085
commit 5b5dbe059b
5 changed files with 147 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
---
type: Added
pr: 1966
---
<!-- docs-exempt: VS Code extension is a repo-local reference module; installation instructions are in the #1942 issue body + ADR-1239 Phase D context. No standalone docs/ file needed. -->
**GSD now ships a repo-local VS Code extension** — a buildable extension (`vscode/extension.js` + `vscode/package.json`) that registers `gsd.invoke` (dispatches through the GSD command-routing hub) in the VS Code command palette. A reachability test proves the handler dispatches through the engine (keystone wired). Not Marketplace-published; mirrors the OpenCode plugin's bar. (#1966)

View File

@@ -34,6 +34,7 @@ const VERSIONED_MANIFESTS = [
{ path: '.claude-plugin/plugin.json', versionKey: 'version' },
{ path: 'gemini-extension.json', versionKey: 'version' },
{ path: '.claude-plugin/marketplace.json', versionKey: 'plugins.0.version' },
{ path: 'vscode/package.json', versionKey: 'version' },
];
// Convenience: just the registered paths, for consumers that only need to

View File

@@ -0,0 +1,51 @@
'use strict';
/**
* VS Code extension reachability test — ADR-1239 Phase D / #1942.
*
* Proves the VS Code extension is keystone-WIRED: the gsd.invoke handler
* dispatches through the GSD command-routing hub and returns a result (not just
* a stub). The handler is exported separately from activate() so it is testable
* WITHOUT a VS Code host.
*/
const { test } = require('node:test');
const assert = require('node:assert/strict');
const { activate, dispatchGsdCommand, resolveEngineRoot } = require('../vscode/extension.js');
test('the extension exports activate + dispatchGsdCommand + resolveEngineRoot', () => {
assert.equal(typeof activate, 'function');
assert.equal(typeof dispatchGsdCommand, 'function');
assert.equal(typeof resolveEngineRoot, 'function');
});
test('REACHABILITY: dispatchGsdCommand dispatches through the engine hub (keystone wired)', async () => {
const result = await dispatchGsdCommand({ family: 'query', subcommand: 'help' });
assert.equal(typeof result, 'string', 'returns a string result');
const parsed = JSON.parse(result);
assert.ok(parsed !== null && typeof parsed === 'object',
'dispatch produced a result object (the engine was reached)');
});
test('dispatchGsdCommand works with default args (no args → query/help)', async () => {
const result = await dispatchGsdCommand();
assert.equal(typeof result, 'string');
JSON.parse(result); // must be valid JSON
});
test('resolveEngineRoot finds the gsd-core/ dir from the extension location', () => {
const root = resolveEngineRoot(__dirname + '/../vscode');
const fs = require('fs');
assert.ok(fs.existsSync(require('path').join(root, 'gsd-core')),
'resolveEngineRoot finds a dir containing gsd-core/');
});
test('the extension manifest declares the gsd.invoke command', () => {
const pkg = require('../vscode/package.json');
assert.ok(pkg.contributes && pkg.contributes.commands, 'manifest has commands');
const cmd = pkg.contributes.commands.find((c) => c.command === 'gsd.invoke');
assert.ok(cmd, 'manifest declares gsd.invoke');
assert.ok(pkg.engines && pkg.engines.vscode, 'manifest declares VS Code engine');
assert.equal(pkg.main, './extension.js', 'manifest main points to extension.js');
});

73
vscode/extension.js Normal file
View File

@@ -0,0 +1,73 @@
'use strict';
/**
* GSD extension for VS Code — ADR-1239 Phase D / #1942.
*
* VS Code is the IDE-profile reference host. This extension binds GSD's command
* surface to VS Code's Command Palette + Chat participant via the imperative
* adapter path. Engine entry: in-process CJS require (the extension host runs
* Node). The engine seams (active model via vscode.lm, engine-owned hook bus,
* sandboxed-storage stateIO) are composed in activate() per the #1933 binding.
*
* Installation: repo-local (not Marketplace-published). Open this dir in VS Code
* + press F5 (Extension Development Host) to run, or package with `vsce package`.
*
* Engine entry: the gsd.invoke handler dispatches IN-PROCESS through the GSD
* command-routing hub (createHub/dispatch). This is the same hub the companion
* MCP server + the pi extension use.
*/
const path = require('path');
const fs = require('fs');
// Resolve the GSD engine tree (walk up to find gsd-core/).
function resolveEngineRoot(startDir) {
let dir = startDir;
for (let i = 0; i < 6; i++) {
if (fs.existsSync(path.join(dir, 'gsd-core'))) return dir;
const parent = path.dirname(dir);
if (parent === dir) break;
dir = parent;
}
return path.resolve(startDir, '..');
}
const ENGINE_ROOT = resolveEngineRoot(__dirname);
const GSD_CORE = path.join(ENGINE_ROOT, 'gsd-core');
/**
* Pure command handler — dispatches through the GSD command-routing hub.
* Exported separately from activate() so it is testable WITHOUT a VS Code host.
* @returns {Promise<string>} JSON-stringified dispatch result.
*/
async function dispatchGsdCommand(args) {
const { createHub } = require(path.join(GSD_CORE, 'bin', 'lib', 'command-routing-hub.cjs'));
const hub = createHub();
const res = hub.dispatch({
family: (args && args.family) || 'query',
subcommand: (args && args.subcommand) || 'help',
args: (args && Array.isArray(args.args)) ? args.args : [],
cwd: (args && args.cwd) || process.cwd(),
});
return JSON.stringify(res);
}
/**
* VS Code extension activation. Composes the IDE-profile seams (per the #1933
* reference binding) + registers the command surface.
* @param {import('vscode').ExtensionContext} context
*/
function activate(context) {
const vscode = require('vscode');
// ── Command surface: palette + chat participant ──────────────────────────
const gsdCommand = vscode.commands.registerCommand('gsd.invoke', dispatchGsdCommand);
context.subscriptions.push(gsdCommand);
// The full IDE-profile binding (active vscode.lm model, engine-owned hook bus,
// sandboxed-storage stateIO, imperative adapter) composes in activate() per
// the #1933 reference binding (tests/fixtures/vscode-host-binding.cjs). The
// command handler dispatches through the hub — the user-reachable surface.
}
module.exports = { activate, dispatchGsdCommand, resolveEngineRoot };

16
vscode/package.json Normal file
View File

@@ -0,0 +1,16 @@
{
"name": "gsd-core-vscode",
"displayName": "GSD Core",
"description": "GSD orchestration engine embedded in VS Code (ADR-1239 IDE profile).",
"version": "1.7.0-rc.1",
"publisher": "opengsd",
"engines": { "vscode": "^1.90.0" },
"categories": ["Other"],
"activationPoints": { "onCommand": ["gsd.invoke"] },
"main": "./extension.js",
"contributes": {
"commands": [
{ "command": "gsd.invoke", "title": "GSD: Invoke Command" }
]
}
}