@opengsd/gsd-core 1.7.0-rc.4 → 1.7.0-rc.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +20 -0
- package/agents/gsd-doc-classifier.md +105 -0
- package/agents/gsd-doc-synthesizer.md +61 -0
- package/agents/gsd-ui-checker.md +30 -0
- package/agents/gsd-ui-researcher.md +1 -0
- package/bin/install.js +1568 -622
- package/gsd-core/bin/gsd-tools.cjs +40 -1
- package/gsd-core/bin/lib/api-coverage.cjs +466 -0
- package/gsd-core/bin/lib/audit.cjs +6 -3
- package/gsd-core/bin/lib/capability-loader.cjs +11 -9
- package/gsd-core/bin/lib/capability-registry.cjs +761 -84
- package/gsd-core/bin/lib/capability-validator.cjs +56 -18
- package/gsd-core/bin/lib/capability-writer.cjs +10 -1
- package/gsd-core/bin/lib/check-command-router.cjs +242 -3
- package/gsd-core/bin/lib/commands.cjs +7 -5
- package/gsd-core/bin/lib/config-loader.cjs +1 -0
- package/gsd-core/bin/lib/config.cjs +96 -0
- package/gsd-core/bin/lib/core-utils.cjs +4 -1
- package/gsd-core/bin/lib/host-integration-adapters/cline-sdk-binding.cjs +234 -0
- package/gsd-core/bin/lib/host-integration-adapters/imperative-hook-bus.cjs +145 -0
- package/gsd-core/bin/lib/host-integration.cjs +45 -4
- package/gsd-core/bin/lib/init.cjs +76 -39
- package/gsd-core/bin/lib/install-effort-resolver.cjs +213 -0
- package/gsd-core/bin/lib/install-engine.cjs +228 -18
- package/gsd-core/bin/lib/installer-migration-report.cjs +7 -0
- package/gsd-core/bin/lib/loop-resolver.cjs +68 -17
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +50 -11
- package/gsd-core/bin/lib/mcp-server.cjs +18 -7
- package/gsd-core/bin/lib/milestone.cjs +3 -3
- package/gsd-core/bin/lib/normalize-test-command.cjs +187 -0
- package/gsd-core/bin/lib/phase-id.cjs +132 -3
- package/gsd-core/bin/lib/phase.cjs +78 -16
- package/gsd-core/bin/lib/planning-workspace.cjs +17 -0
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +24 -7
- package/gsd-core/bin/lib/roadmap-command-router.cjs +5 -4
- package/gsd-core/bin/lib/roadmap-parser.cjs +21 -30
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +9 -9
- package/gsd-core/bin/lib/roadmap.cjs +42 -56
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +248 -44
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +2 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +39 -23
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +19 -5
- package/gsd-core/bin/lib/runtime-homes.cjs +30 -0
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +534 -37
- package/gsd-core/bin/lib/runtime-name-policy.cjs +63 -5
- package/gsd-core/bin/lib/security.cjs +6 -36
- package/gsd-core/bin/lib/shell-command-projection.cjs +115 -2
- package/gsd-core/bin/lib/spec-section.cjs +111 -0
- package/gsd-core/bin/lib/stale-bake-guard.cjs +30 -10
- package/gsd-core/bin/lib/state-transition.cjs +1 -1
- package/gsd-core/bin/lib/state.cjs +24 -24
- package/gsd-core/bin/lib/surface.cjs +40 -6
- package/gsd-core/bin/lib/uat.cjs +4 -1
- package/gsd-core/bin/lib/ui-consideration-probe.cjs +249 -0
- package/gsd-core/bin/lib/validate.cjs +15 -6
- package/gsd-core/bin/lib/verify.cjs +33 -37
- package/gsd-core/bin/shared/config-schema.manifest.json +2 -0
- package/gsd-core/bin/shared/model-catalog.json +14 -9
- package/gsd-core/references/api-coverage.md +104 -0
- package/gsd-core/references/model-profiles.md +2 -2
- package/gsd-core/references/planning-config.md +2 -0
- package/gsd-core/references/specless-probe-fallback.md +172 -0
- package/gsd-core/references/ui-consideration-probe.md +73 -0
- package/gsd-core/templates/UI-SPEC.md +25 -0
- package/gsd-core/templates/VALIDATION.md +2 -0
- package/gsd-core/templates/config.json +2 -1
- package/gsd-core/workflows/audit-fix.md +9 -1
- package/gsd-core/workflows/audit-milestone.md +7 -4
- package/gsd-core/workflows/code-review-fix.md +7 -3
- package/gsd-core/workflows/code-review.md +4 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +4 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +8 -4
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +42 -0
- package/gsd-core/workflows/execute-phase.md +1 -25
- package/gsd-core/workflows/plan-phase.md +37 -2
- package/gsd-core/workflows/quick.md +2 -2
- package/gsd-core/workflows/review.md +59 -13
- package/gsd-core/workflows/settings-advanced.md +12 -9
- package/gsd-core/workflows/settings.md +2 -2
- package/gsd-core/workflows/ui-phase.md +146 -1
- package/gsd-core/workflows/validate-phase.md +2 -2
- package/gsd-core/workflows/verify-phase.md +3 -2
- package/gsd-core/workflows/verify-work.md +38 -0
- package/hooks/dist/gsd-cursor-pre-tool.js +76 -0
- package/hooks/dist/gsd-cursor-stop.js +48 -0
- package/hooks/dist/gsd-cursor-subagent-start.js +50 -0
- package/hooks/dist/gsd-cursor-subagent-stop.js +40 -0
- package/hooks/dist/gsd-windsurf-pre-command.js +275 -0
- package/hooks/dist/gsd-windsurf-pre-write.js +132 -0
- package/hooks/dist/managed-hooks-registry.cjs +6 -0
- package/hooks/gsd-cursor-pre-tool.js +76 -0
- package/hooks/gsd-cursor-stop.js +48 -0
- package/hooks/gsd-cursor-subagent-start.js +50 -0
- package/hooks/gsd-cursor-subagent-stop.js +40 -0
- package/hooks/gsd-windsurf-pre-command.js +275 -0
- package/hooks/gsd-windsurf-pre-write.js +132 -0
- package/hooks/managed-hooks-registry.cjs +6 -0
- package/package.json +9 -4
- package/pi/gsd.cjs +354 -0
- package/scripts/build-hooks.js +8 -1
- package/scripts/gen-golden-install-parity-zcode.cjs +11 -1
- package/scripts/gen-registry.cjs +128 -0
- package/scripts/lint-phase-id-drift.cjs +150 -0
- package/scripts/lint-test-file-count.allowlist.json +2 -1
- package/scripts/registry-schema.cjs +565 -0
- package/scripts/run-tests.cjs +21 -1
- package/scripts/validate-registry.cjs +117 -0
- package/vscode/browser.js +197 -0
- package/vscode/extension.js +383 -0
- package/vscode/host-binding.js +113 -0
- package/vscode/package.json +96 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GSD extension for VS Code — ADR-1239 Phase D / #1942, dispatch fixed +
|
|
5
|
+
* extension surface (chat participant, Language Model Tools, #runSubagent
|
|
6
|
+
* wiring) added #2103.
|
|
7
|
+
*
|
|
8
|
+
* This is the DESKTOP (Node) `main` entry — see browser.js for the Web
|
|
9
|
+
* Extension `browser` entry, which is intentionally a SEPARATE, much more
|
|
10
|
+
* minimal file (zero Node APIs; does not require this file or host-binding.js).
|
|
11
|
+
*
|
|
12
|
+
* VS Code is the IDE-profile reference host. This extension binds GSD's command
|
|
13
|
+
* surface to VS Code's Command Palette + Chat participant via the imperative
|
|
14
|
+
* adapter path. Engine entry: in-process CJS require (the extension host runs
|
|
15
|
+
* Node). The engine seams (active model via vscode.lm, engine-owned hook bus,
|
|
16
|
+
* sandboxed-storage stateIO) are composed in activate() via host-binding.js
|
|
17
|
+
* per the #1933 binding.
|
|
18
|
+
*
|
|
19
|
+
* Installation: Marketplace/VSIX extension (see capabilities/vscode/capability.json
|
|
20
|
+
* — installSurface:'none', it is never CLI-installed by bin/install.js).
|
|
21
|
+
*
|
|
22
|
+
* Engine entry: dispatch is SUBPROCESS-REUSE to gsd-tools.cjs (bounded,
|
|
23
|
+
* no-throw — the shared `dispatchGsdCommand` in
|
|
24
|
+
* gsd-core/bin/lib/shell-command-projection.cjs), NOT an in-process
|
|
25
|
+
* command-routing hub. No fully-populated hub factory exists anywhere in
|
|
26
|
+
* gsd-core — every createHub() caller builds a single-family hub for its own
|
|
27
|
+
* narrow purpose — so calling createHub() with no args (the original #1942
|
|
28
|
+
* cut) always answered UnknownCommand. This mirrors the fix already applied
|
|
29
|
+
* to the pi extension (pi/gsd.cjs) and the companion MCP server
|
|
30
|
+
* (gsd-core/bin/lib/mcp-server.cjs), which dispatch through the SAME shared
|
|
31
|
+
* helper (#2102 Stage 2 / #2103).
|
|
32
|
+
*
|
|
33
|
+
* Extension surface (#2103):
|
|
34
|
+
* - Chat participant `@gsd` (contributes.chatParticipants) — dispatches free-form
|
|
35
|
+
* prompts through the same dispatchGsdCommand.
|
|
36
|
+
* - Language Model Tools (contributes.languageModelTools) — a representative
|
|
37
|
+
* set of GSD skills exposed as vscode.lm tools, each dispatching through the
|
|
38
|
+
* same shared helper (UPGRADE 1).
|
|
39
|
+
* - #runSubagent wiring — VS Code 1.105+ lets the primary chat agent invoke
|
|
40
|
+
* registered chat participants / languageModelTools as a nested agent turn
|
|
41
|
+
* via `#runSubagent`, gated by the `chat.subagents.allowInvocationsFromSubagents`
|
|
42
|
+
* setting. There is no separate "subagent contribution" registration API
|
|
43
|
+
* beyond the participant + tools already registered above — VS Code's own
|
|
44
|
+
* chat engine surfaces them. This extension's own contribution is a
|
|
45
|
+
* belt-and-suspenders depth cap (dispatchAsSubagent, GSD_MAX_SUBAGENT_DEPTH)
|
|
46
|
+
* mirroring capabilities/vscode/capability.json's
|
|
47
|
+
* hostIntegration.dispatch.maxDepth:5, independent of whatever VS Code
|
|
48
|
+
* itself enforces natively (UPGRADE 2).
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
const path = require('path');
|
|
52
|
+
const fs = require('fs');
|
|
53
|
+
|
|
54
|
+
// Resolve the GSD engine tree (walk up to find gsd-core/).
|
|
55
|
+
function resolveEngineRoot(startDir) {
|
|
56
|
+
let dir = startDir;
|
|
57
|
+
for (let i = 0; i < 6; i++) {
|
|
58
|
+
if (fs.existsSync(path.join(dir, 'gsd-core'))) return dir;
|
|
59
|
+
const parent = path.dirname(dir);
|
|
60
|
+
if (parent === dir) break;
|
|
61
|
+
dir = parent;
|
|
62
|
+
}
|
|
63
|
+
return path.resolve(startDir, '..');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const ENGINE_ROOT = resolveEngineRoot(__dirname);
|
|
67
|
+
const GSD_CORE = path.join(ENGINE_ROOT, 'gsd-core');
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Pure command handler — dispatches through the SHARED subprocess-shim
|
|
71
|
+
* `dispatchGsdCommand` (gsd-core/bin/lib/shell-command-projection.cjs), the
|
|
72
|
+
* same helper the pi extension (pi/gsd.cjs) and the companion MCP server
|
|
73
|
+
* (gsd-core/bin/lib/mcp-server.cjs) dispatch through.
|
|
74
|
+
*
|
|
75
|
+
* Exported separately from activate() so it is testable WITHOUT a VS Code
|
|
76
|
+
* host. Preserves the original return CONTRACT — a JSON-stringified result
|
|
77
|
+
* object — but now backed by a REAL dispatch instead of an unconfigured
|
|
78
|
+
* `createHub()` that always answered UnknownCommand (#2103 fix).
|
|
79
|
+
*
|
|
80
|
+
* Empty/omitted args default to `--help` (a real, working, ok:true
|
|
81
|
+
* gsd-tools.cjs command) — NOT the `'query'`/`'help'` pairing the original
|
|
82
|
+
* cut used, which is not a valid gsd-tools.cjs command and always produced
|
|
83
|
+
* UnknownCommand (mirrors the same fix already applied to
|
|
84
|
+
* pi/gsd.cjs's parseGsdCommandArgs).
|
|
85
|
+
*
|
|
86
|
+
* @returns {Promise<string>} JSON-stringified dispatch result:
|
|
87
|
+
* `{ok, stdout, stderr, code, timedOut}` on a normal dispatch, or
|
|
88
|
+
* `{ok:false, stdout:'', stderr:'GSD engine unavailable: ...', code:null,
|
|
89
|
+
* timedOut:false}` if the shared helper itself cannot be loaded (e.g.
|
|
90
|
+
* gsd-core/ missing from the tree).
|
|
91
|
+
*/
|
|
92
|
+
async function dispatchGsdCommand(args) {
|
|
93
|
+
const a = (args && typeof args === 'object') ? args : {};
|
|
94
|
+
const family = (typeof a.family === 'string' && a.family) ? a.family : '--help';
|
|
95
|
+
const subcommand = (typeof a.subcommand === 'string' && a.subcommand) ? a.subcommand : undefined;
|
|
96
|
+
const rest = Array.isArray(a.args) ? a.args : [];
|
|
97
|
+
const cwd = a.cwd || process.cwd();
|
|
98
|
+
|
|
99
|
+
let dispatchViaShim;
|
|
100
|
+
try {
|
|
101
|
+
({ dispatchGsdCommand: dispatchViaShim } = require(path.join(GSD_CORE, 'bin', 'lib', 'shell-command-projection.cjs')));
|
|
102
|
+
} catch (e) {
|
|
103
|
+
return JSON.stringify({
|
|
104
|
+
ok: false,
|
|
105
|
+
stdout: '',
|
|
106
|
+
stderr: `GSD engine unavailable: ${e && e.message ? e.message : String(e)}`,
|
|
107
|
+
code: null,
|
|
108
|
+
timedOut: false,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
const result = dispatchViaShim({ family, subcommand, args: rest, cwd });
|
|
112
|
+
return JSON.stringify(result);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Resolves the current workspace's root directory, per-invocation (never
|
|
117
|
+
* cached at activate() time), so GSD commands dispatch against the user's
|
|
118
|
+
* actual project instead of the extension host's own `process.cwd()` (#2103
|
|
119
|
+
* FIX — adversarial review: all three desktop dispatch surfaces previously
|
|
120
|
+
* omitted `cwd` entirely when calling dispatchGsdCommand, silently defaulting
|
|
121
|
+
* to the wrong directory). Falls back to `process.cwd()` only when no
|
|
122
|
+
* workspace folder is open (e.g. an empty window) — mirrors VS Code's own
|
|
123
|
+
* single-root convention of reading `workspaceFolders[0]`.
|
|
124
|
+
* @param {object} vscode
|
|
125
|
+
* @returns {string}
|
|
126
|
+
*/
|
|
127
|
+
function resolveWorkspaceCwd(vscode) {
|
|
128
|
+
const folders = vscode && vscode.workspace && vscode.workspace.workspaceFolders;
|
|
129
|
+
const first = Array.isArray(folders) ? folders[0] : undefined;
|
|
130
|
+
const fsPath = first && first.uri && first.uri.fsPath;
|
|
131
|
+
return (typeof fsPath === 'string' && fsPath) ? fsPath : process.cwd();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Tokenizes a raw chat/free-form prompt string into {family, subcommand, args}.
|
|
136
|
+
* Mirrors pi/gsd.cjs's parseGsdCommandArgs (simple whitespace split — no shell
|
|
137
|
+
* quoting support needed for a chat prompt). Empty input defaults to `--help`
|
|
138
|
+
* (a real, working gsd-tools.cjs command), matching dispatchGsdCommand's own
|
|
139
|
+
* default so the two surfaces (palette vs. chat) never diverge on "no input".
|
|
140
|
+
* @param {string} rawPrompt
|
|
141
|
+
* @returns {{family: string, subcommand: string|undefined, args: string[]}}
|
|
142
|
+
*/
|
|
143
|
+
function parseChatPrompt(rawPrompt) {
|
|
144
|
+
const tokens = String(rawPrompt || '').trim().split(/\s+/).filter(Boolean);
|
|
145
|
+
return {
|
|
146
|
+
family: tokens[0] || '--help',
|
|
147
|
+
subcommand: tokens[1],
|
|
148
|
+
args: tokens.slice(2),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Registers the `@gsd` chat participant (#2103). Its handler dispatches the
|
|
154
|
+
* user's free-form prompt through the SAME dispatchGsdCommand as gsd.invoke —
|
|
155
|
+
* one dispatch path for every command surface (palette / chat / LM tools).
|
|
156
|
+
* Exported separately so it is testable with a mock `vscode.chat`.
|
|
157
|
+
* @param {object} vscode
|
|
158
|
+
* @param {import('vscode').ExtensionContext} context
|
|
159
|
+
* @returns {object|null} the created participant, or null if vscode.chat is absent
|
|
160
|
+
* (older VS Code — fail-soft, never throws).
|
|
161
|
+
*/
|
|
162
|
+
function registerChatParticipant(vscode, context) {
|
|
163
|
+
if (!vscode || !vscode.chat || typeof vscode.chat.createChatParticipant !== 'function') {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
const participant = vscode.chat.createChatParticipant('gsd', async (request, _chatContext, stream, _token) => {
|
|
167
|
+
// #2103 FIX: resolve the user's actual workspace, not the extension host's
|
|
168
|
+
// process.cwd() — computed per-invocation so it always reflects the
|
|
169
|
+
// CURRENT workspace (a chat request has no cwd field of its own; VS Code's
|
|
170
|
+
// ChatContext does not carry one).
|
|
171
|
+
const cwd = resolveWorkspaceCwd(vscode);
|
|
172
|
+
const { family, subcommand, args } = parseChatPrompt(request && request.prompt);
|
|
173
|
+
const result = JSON.parse(await dispatchGsdCommand({ family, subcommand, args, cwd }));
|
|
174
|
+
if (stream && typeof stream.markdown === 'function') {
|
|
175
|
+
stream.markdown(result.ok ? result.stdout : `GSD error: ${result.stderr || result.stdout || 'dispatch failed'}`);
|
|
176
|
+
}
|
|
177
|
+
return { metadata: { command: family } };
|
|
178
|
+
});
|
|
179
|
+
if (context && Array.isArray(context.subscriptions)) context.subscriptions.push(participant);
|
|
180
|
+
return participant;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* #2103 UPGRADE 1 — Language Model Tools.
|
|
185
|
+
*
|
|
186
|
+
* A representative set of GSD skills (see skills/gsd-progress, skills/gsd-workstreams,
|
|
187
|
+
* skills/gsd-plan-phase) exposed as vscode.lm tools, matching the
|
|
188
|
+
* contributes.languageModelTools manifest entries in package.json. Each tool's
|
|
189
|
+
* invoke() dispatches through the SAME shared dispatchGsdCommand as gsd.invoke —
|
|
190
|
+
* the tool name maps to a real, verified family/subcommand pair (verified by
|
|
191
|
+
* direct `gsd-tools.cjs <family> [subcommand] --raw --json-errors` invocation;
|
|
192
|
+
* see the #2103 CORE-stage precedent for `progress json`).
|
|
193
|
+
*
|
|
194
|
+
* Kept to a small, curated set rather than all 71 shipped skills: these three
|
|
195
|
+
* both name a real skill AND map cleanly onto a single, safe, read-only
|
|
196
|
+
* gsd-tools.cjs command (the rest are multi-step agent workflows that do not
|
|
197
|
+
* reduce to one non-interactive CLI call, and stay slash-command-native).
|
|
198
|
+
*/
|
|
199
|
+
// `name` uses underscores (vscode.lm.registerTool's registration name — MUST
|
|
200
|
+
// match contributes.languageModelTools[].name in package.json exactly); the
|
|
201
|
+
// hyphenated `#gsd-progress`-style mention alias is package.json's separate
|
|
202
|
+
// `toolReferenceName` field (VS Code owns that mapping internally).
|
|
203
|
+
const LM_TOOLS = [
|
|
204
|
+
{
|
|
205
|
+
name: 'gsd_progress',
|
|
206
|
+
// skills/gsd-progress — real, verified: `gsd-tools.cjs progress json`.
|
|
207
|
+
resolveCommand: () => ({ family: 'progress', subcommand: 'json', args: [] }),
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
name: 'gsd_workstreams',
|
|
211
|
+
// skills/gsd-workstreams — real, verified: `gsd-tools.cjs workstream list`.
|
|
212
|
+
resolveCommand: () => ({ family: 'workstream', subcommand: 'list', args: [] }),
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: 'gsd_plan_phase',
|
|
216
|
+
// skills/gsd-plan-phase — real, verified: `gsd-tools.cjs phase-plan-index <phase>`.
|
|
217
|
+
// (The full multi-step planning workflow stays slash-command-native; this
|
|
218
|
+
// tool exposes the read-only plan-index lookup the workflow itself queries first.)
|
|
219
|
+
resolveCommand: (input) => ({
|
|
220
|
+
family: 'phase-plan-index',
|
|
221
|
+
subcommand: undefined,
|
|
222
|
+
args: [input && input.phase ? String(input.phase) : ''],
|
|
223
|
+
}),
|
|
224
|
+
},
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Builds a vscode.lm tool implementation for one LM_TOOLS entry.
|
|
229
|
+
* @param {{name:string, resolveCommand:(input:object)=>{family:string,subcommand:string|undefined,args:string[]}}} toolDef
|
|
230
|
+
* @param {object} vscode
|
|
231
|
+
*/
|
|
232
|
+
function createLanguageModelTool(toolDef, vscode) {
|
|
233
|
+
return {
|
|
234
|
+
async invoke(options, _token) {
|
|
235
|
+
const input = (options && options.input) || {};
|
|
236
|
+
// #2103 FIX: resolve the user's actual workspace, not the extension
|
|
237
|
+
// host's process.cwd() — computed per-invocation. LanguageModelToolInvocationOptions
|
|
238
|
+
// has no cwd field of its own.
|
|
239
|
+
const cwd = resolveWorkspaceCwd(vscode);
|
|
240
|
+
const { family, subcommand, args } = toolDef.resolveCommand(input);
|
|
241
|
+
const result = JSON.parse(await dispatchGsdCommand({ family, subcommand, args, cwd }));
|
|
242
|
+
const text = result.ok ? result.stdout : `GSD error: ${result.stderr || result.stdout || 'dispatch failed'}`;
|
|
243
|
+
return new vscode.LanguageModelToolResult([new vscode.LanguageModelTextPart(text)]);
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Registers the LM_TOOLS set via vscode.lm.registerTool (#2103 UPGRADE 1).
|
|
250
|
+
* Exported separately so it is testable with a mock `vscode.lm`.
|
|
251
|
+
* @param {object} vscode
|
|
252
|
+
* @param {import('vscode').ExtensionContext} context
|
|
253
|
+
* @returns {number} count of tools registered (0 if vscode.lm is absent — fail-soft).
|
|
254
|
+
*/
|
|
255
|
+
function registerLanguageModelTools(vscode, context) {
|
|
256
|
+
if (!vscode || !vscode.lm || typeof vscode.lm.registerTool !== 'function') return 0;
|
|
257
|
+
let count = 0;
|
|
258
|
+
for (const toolDef of LM_TOOLS) {
|
|
259
|
+
const disposable = vscode.lm.registerTool(toolDef.name, createLanguageModelTool(toolDef, vscode));
|
|
260
|
+
if (context && Array.isArray(context.subscriptions)) context.subscriptions.push(disposable);
|
|
261
|
+
count++;
|
|
262
|
+
}
|
|
263
|
+
return count;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Mirrors capabilities/vscode/capability.json's hostIntegration.dispatch.maxDepth. */
|
|
267
|
+
const GSD_MAX_SUBAGENT_DEPTH = 5;
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* #2103 UPGRADE 2 — native subagent dispatch (#runSubagent).
|
|
271
|
+
*
|
|
272
|
+
* Dispatches a command as a (possibly nested) subagent turn, enforcing GSD's
|
|
273
|
+
* own maxDepth:5 ceiling (capabilities/vscode/capability.json) independent of
|
|
274
|
+
* whatever VS Code's chat engine enforces natively for `#runSubagent` /
|
|
275
|
+
* `chat.subagents.allowInvocationsFromSubagents` — belt-and-suspenders, never
|
|
276
|
+
* silently trusts the host's own depth accounting.
|
|
277
|
+
* @param {{family?:string, subcommand?:string, args?:string[], cwd?:string, depth?:number}} args
|
|
278
|
+
* @returns {Promise<string>} same JSON-stringified contract as dispatchGsdCommand.
|
|
279
|
+
*/
|
|
280
|
+
async function dispatchAsSubagent(args) {
|
|
281
|
+
const a = (args && typeof args === 'object') ? args : {};
|
|
282
|
+
const depth = Number.isInteger(a.depth) ? a.depth : 0;
|
|
283
|
+
if (depth > GSD_MAX_SUBAGENT_DEPTH) {
|
|
284
|
+
return JSON.stringify({
|
|
285
|
+
ok: false,
|
|
286
|
+
stdout: '',
|
|
287
|
+
stderr: `GSD subagent dispatch refused: depth ${depth} exceeds maxDepth ${GSD_MAX_SUBAGENT_DEPTH}`,
|
|
288
|
+
code: null,
|
|
289
|
+
timedOut: false,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
return dispatchGsdCommand(a);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Detects whether the host VS Code build exposes the `#runSubagent` feature
|
|
297
|
+
* surface (`chat.subagents.allowInvocationsFromSubagents`, VS Code 1.105+).
|
|
298
|
+
* Fail-soft: any missing/older API surface (including an Insiders-gated build
|
|
299
|
+
* where the setting does not exist yet) resolves `available:false` — never
|
|
300
|
+
* throws. There is no separate registration call: VS Code's chat engine
|
|
301
|
+
* surfaces the already-registered chat participant + languageModelTools to
|
|
302
|
+
* `#runSubagent` on its own; this function only reports/confirms availability.
|
|
303
|
+
* @param {object} vscode
|
|
304
|
+
* @returns {{available: boolean, dispatchAsSubagent: typeof dispatchAsSubagent, maxDepth: number}}
|
|
305
|
+
*/
|
|
306
|
+
function registerSubagentDispatch(vscode) {
|
|
307
|
+
let available = false;
|
|
308
|
+
try {
|
|
309
|
+
const cfg = vscode && vscode.workspace && typeof vscode.workspace.getConfiguration === 'function'
|
|
310
|
+
? vscode.workspace.getConfiguration('chat.subagents')
|
|
311
|
+
: null;
|
|
312
|
+
available = !!(cfg && typeof cfg.get === 'function' && cfg.get('allowInvocationsFromSubagents') !== undefined);
|
|
313
|
+
} catch {
|
|
314
|
+
available = false;
|
|
315
|
+
}
|
|
316
|
+
return { available, dispatchAsSubagent, maxDepth: GSD_MAX_SUBAGENT_DEPTH };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* VS Code extension activation. Composes the IDE-profile seams (per the #1933
|
|
321
|
+
* reference binding) + registers the full command surface: palette
|
|
322
|
+
* (gsd.invoke), chat participant (@gsd), Language Model Tools, and the
|
|
323
|
+
* #runSubagent depth-cap wiring.
|
|
324
|
+
* @param {import('vscode').ExtensionContext} context
|
|
325
|
+
*/
|
|
326
|
+
function activate(context) {
|
|
327
|
+
const vscode = require('vscode');
|
|
328
|
+
|
|
329
|
+
// ── Command surface: palette ──────────────────────────────────────────────
|
|
330
|
+
// #2103 FIX: wrap dispatchGsdCommand (rather than registering it directly as
|
|
331
|
+
// the handler) so a palette invocation that omits `cwd` resolves the user's
|
|
332
|
+
// actual workspace instead of silently defaulting to the extension host's
|
|
333
|
+
// own process.cwd(). An explicit `args.cwd` (e.g. from a programmatic
|
|
334
|
+
// vscode.commands.executeCommand('gsd.invoke', {..., cwd}) caller) still
|
|
335
|
+
// takes precedence.
|
|
336
|
+
const gsdCommand = vscode.commands.registerCommand('gsd.invoke', (args) => {
|
|
337
|
+
const a = (args && typeof args === 'object') ? args : {};
|
|
338
|
+
const cwd = a.cwd || resolveWorkspaceCwd(vscode);
|
|
339
|
+
return dispatchGsdCommand({ ...a, cwd });
|
|
340
|
+
});
|
|
341
|
+
context.subscriptions.push(gsdCommand);
|
|
342
|
+
|
|
343
|
+
// ── IDE-profile host binding (#1933 reference binding; desktop/Node only —
|
|
344
|
+
// see host-binding.js's header for why browser.js does NOT use this path).
|
|
345
|
+
// On a real desktop VS Code host this SUCCEEDS (#2103 fix: host-binding.js's
|
|
346
|
+
// guard checks vscode.lm.selectChatModels — the real API — not the
|
|
347
|
+
// nonexistent vscode.lm.sendRequest the pre-#2103 code assumed). Still
|
|
348
|
+
// wrapped fail-open so a genuinely older VS Code build (no vscode.lm at all)
|
|
349
|
+
// degrades to the palette command only, rather than blocking activation.
|
|
350
|
+
try {
|
|
351
|
+
const bindGsdToVscode = require('./host-binding.js');
|
|
352
|
+
const hostStorage = {
|
|
353
|
+
read: (key) => context.globalState.get(key),
|
|
354
|
+
write: (key, value) => context.globalState.update(key, value),
|
|
355
|
+
};
|
|
356
|
+
bindGsdToVscode(vscode, hostStorage);
|
|
357
|
+
} catch {
|
|
358
|
+
// fail-open — see doc comment above.
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// ── Chat participant (@gsd) ───────────────────────────────────────────────
|
|
362
|
+
registerChatParticipant(vscode, context);
|
|
363
|
+
|
|
364
|
+
// ── Language Model Tools (#2103 UPGRADE 1) ────────────────────────────────
|
|
365
|
+
registerLanguageModelTools(vscode, context);
|
|
366
|
+
|
|
367
|
+
// ── #runSubagent wiring (#2103 UPGRADE 2) ─────────────────────────────────
|
|
368
|
+
registerSubagentDispatch(vscode);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
module.exports = {
|
|
372
|
+
activate,
|
|
373
|
+
dispatchGsdCommand,
|
|
374
|
+
resolveEngineRoot,
|
|
375
|
+
resolveWorkspaceCwd,
|
|
376
|
+
parseChatPrompt,
|
|
377
|
+
registerChatParticipant,
|
|
378
|
+
registerLanguageModelTools,
|
|
379
|
+
registerSubagentDispatch,
|
|
380
|
+
dispatchAsSubagent,
|
|
381
|
+
LM_TOOLS,
|
|
382
|
+
GSD_MAX_SUBAGENT_DEPTH,
|
|
383
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* VS Code IDE host binding for GSD (ADR-1239 Phase D / #1933, shipped #2103).
|
|
5
|
+
*
|
|
6
|
+
* VS Code is the IDE-profile reference host. This module composes the Phase-3
|
|
7
|
+
* engine seams for the negotiated `ide` profile (host-integration.cts
|
|
8
|
+
* PROFILE_BASELINES):
|
|
9
|
+
*
|
|
10
|
+
* - modelMode: 'active' → createModelAdapter({modelMode:'active'}, {sendRequest})
|
|
11
|
+
* backed by `vscode.lm` (LanguageModelChat). There is NO
|
|
12
|
+
* `vscode.lm.sendRequest` — the real API is
|
|
13
|
+
* `const [model] = await vscode.lm.selectChatModels(selector?);
|
|
14
|
+
* const response = await model.sendRequest(messages, options?, token?);`
|
|
15
|
+
* (Context7-verified #2103). The injected `sendRequest` wrapper
|
|
16
|
+
* below selects a model on every call and delegates to IT. VS Code
|
|
17
|
+
* rejects system-role messages, so the request mapper uses User
|
|
18
|
+
* role only.
|
|
19
|
+
* - hookBus: 'engine' → createHookBus({bus:'engine'}) — VS Code has NO host event bus,
|
|
20
|
+
* so GSD owns the bus in-process (full subscribe + emit).
|
|
21
|
+
* - stateIO: 'sandboxed-storage' → createStateIO({io:'sandboxed-storage'}, {backend}) bound to a
|
|
22
|
+
* host-supplied storage (no arbitrary FS — web/no-child_process safe
|
|
23
|
+
* AT THE STATE-IO SEAM ITSELF; see the DESKTOP-ONLY note below for
|
|
24
|
+
* why the module as a WHOLE is not safe to require from browser.js).
|
|
25
|
+
* - embeddingMode: 'imperative' → createImperativeAdapter({runtime:'vscode'}) — engine-as-library.
|
|
26
|
+
*
|
|
27
|
+
* Distribution: VS Code is shipped as an EXTENSION (Marketplace), NOT file-projected onto a
|
|
28
|
+
* config dir, so it intentionally has NO runtime descriptor / `--vscode` installer entry — the
|
|
29
|
+
* extension IS the host. This module is the binding `vscode/extension.js` (the Node/desktop
|
|
30
|
+
* `main` entry) runs in activate().
|
|
31
|
+
*
|
|
32
|
+
* DESKTOP-ONLY (#2103 finding — do NOT require this from vscode/browser.js):
|
|
33
|
+
* despite the per-seam design above being conceptually host-agnostic, the CONCRETE engine-lib
|
|
34
|
+
* modules this file requires are NOT web-safe today — each pulls in Node's `fs`/`os`/`path`
|
|
35
|
+
* at module-load time (eagerly, regardless of which code path actually runs):
|
|
36
|
+
* - gsd-core/bin/lib/state-io.cjs → requires 'node:fs' directly.
|
|
37
|
+
* - gsd-core/bin/lib/adapter-imperative.cjs → requires install-engine.cjs (fs/os/path) and
|
|
38
|
+
* capability-loader.cjs (fs/os/path).
|
|
39
|
+
* - gsd-core/bin/lib/model-adapter.cjs → requires model-resolver.cjs → config-loader.cjs
|
|
40
|
+
* (fs/os/path) and configuration.cjs (fs/path).
|
|
41
|
+
* - gsd-core/bin/lib/hook-bus.cjs → no requires; this one alone is web-safe.
|
|
42
|
+
* In a real VS Code Web Extension host (webworker context) `require('node:fs')` does not
|
|
43
|
+
* resolve — activation would throw immediately. This is why `vscode/browser.js` does NOT
|
|
44
|
+
* require this module (or any of the engine-lib adapters) and instead implements its own
|
|
45
|
+
* minimal, genuinely-zero-Node-API composition directly against `vscode.lm`. See browser.js's
|
|
46
|
+
* header comment for the full rationale. Fixing the engine-lib modules to be fs-free is a
|
|
47
|
+
* separate, much larger engine-wide refactor (config-loader/capability-loader/install-engine
|
|
48
|
+
* all read real files from disk) — out of scope for the extension-surface stage; flagged here
|
|
49
|
+
* for visibility rather than silently routed around.
|
|
50
|
+
*
|
|
51
|
+
* Mock-friendly: takes `vscode` (with `vscode.lm`) + `hostStorage` ({read,write}) so it is
|
|
52
|
+
* behaviorally testable without a live VS Code host.
|
|
53
|
+
*
|
|
54
|
+
* @param {{ lm: { selectChatModels: (selector?: unknown) => Promise<Array<{ sendRequest: (messages: unknown, options?: unknown, token?: unknown) => unknown }>> } }} vscode
|
|
55
|
+
* VS Code namespace (vscode.lm.selectChatModels — NOT vscode.lm.sendRequest, which does not exist).
|
|
56
|
+
* @param {{ read: (path: string) => string, write: (path: string, content: string) => void }} hostStorage
|
|
57
|
+
* sandboxed-storage backend (e.g. globalState/workspaceState/secrets).
|
|
58
|
+
* @returns {object} the composed IDE host surface: { runtime, model, hookBus, stateIO, adapter, commands }
|
|
59
|
+
*/
|
|
60
|
+
module.exports = function bindGsdToVscode(vscode, hostStorage) {
|
|
61
|
+
if (!vscode || !vscode.lm || typeof vscode.lm.selectChatModels !== 'function') {
|
|
62
|
+
throw new TypeError('bindGsdToVscode: vscode.lm.selectChatModels is required (active model provider — VS Code exposes no vscode.lm.sendRequest; a model is selected via selectChatModels() and ITS sendRequest is called)');
|
|
63
|
+
}
|
|
64
|
+
if (!hostStorage || typeof hostStorage.read !== 'function' || typeof hostStorage.write !== 'function') {
|
|
65
|
+
throw new TypeError('bindGsdToVscode: hostStorage {read,write} is required (sandboxed-storage backend)');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const { createImperativeAdapter } = require('../gsd-core/bin/lib/adapter-imperative.cjs');
|
|
69
|
+
const { createModelAdapter } = require('../gsd-core/bin/lib/model-adapter.cjs');
|
|
70
|
+
const { createHookBus } = require('../gsd-core/bin/lib/hook-bus.cjs');
|
|
71
|
+
const { createStateIO } = require('../gsd-core/bin/lib/state-io.cjs');
|
|
72
|
+
|
|
73
|
+
// Active model: GSD model calls route through vscode.lm. There is no
|
|
74
|
+
// `vscode.lm.sendRequest` — a model must be selected first
|
|
75
|
+
// (`vscode.lm.selectChatModels()`, async → LanguageModelChat[]), then THAT
|
|
76
|
+
// model's own `.sendRequest(messages, options?, token?)` is called. Selects
|
|
77
|
+
// fresh on every call (no cross-call caching) so a model becoming available/
|
|
78
|
+
// unavailable between calls is always reflected; gsd-core/bin/lib/model-adapter.cjs's
|
|
79
|
+
// ActiveModelAdapter.sendRequest is a plain (non-async) pass-through that
|
|
80
|
+
// returns whatever the injected function returns, so an async injected
|
|
81
|
+
// function composes transparently — no adapter-side change needed (verified
|
|
82
|
+
// by reading model-adapter.cjs: `sendRequest(req) { return sendRequest(req); }`).
|
|
83
|
+
// No system-role messages — VS Code rejects them; a full extension builds
|
|
84
|
+
// LanguageModelChatMessages with User role only.
|
|
85
|
+
const model = createModelAdapter({ modelMode: 'active' }, {
|
|
86
|
+
async sendRequest(req) {
|
|
87
|
+
const models = await vscode.lm.selectChatModels();
|
|
88
|
+
const [chatModel] = models || [];
|
|
89
|
+
if (!chatModel || typeof chatModel.sendRequest !== 'function') {
|
|
90
|
+
throw new Error('bindGsdToVscode: vscode.lm.selectChatModels() returned no usable model (no active model available)');
|
|
91
|
+
}
|
|
92
|
+
return chatModel.sendRequest(req);
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Engine-owned hook bus: VS Code has no host bus, so GSD owns it in-process.
|
|
97
|
+
const hookBus = createHookBus({ bus: 'engine' });
|
|
98
|
+
|
|
99
|
+
// Sandboxed-storage stateIO bound to the host storage backend (no fs / no child_process).
|
|
100
|
+
const stateIO = createStateIO({ io: 'sandboxed-storage' }, { backend: hostStorage });
|
|
101
|
+
|
|
102
|
+
// Imperative adapter: the engine-as-library for the VS Code runtime.
|
|
103
|
+
const adapter = createImperativeAdapter({ runtime: 'vscode' });
|
|
104
|
+
|
|
105
|
+
// Command surface: Command Palette + Chat participant entries bound to the
|
|
106
|
+
// GSD command-routing hub via the imperative adapter (interface point 1).
|
|
107
|
+
const commands = Object.freeze({
|
|
108
|
+
'gsd.invoke': Object.freeze({ description: 'Invoke a GSD command via the embedded engine (palette/chat).' }),
|
|
109
|
+
'gsd.help': Object.freeze({ description: 'List GSD commands available in the IDE host.' }),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return Object.freeze({ runtime: 'vscode', model, hookBus, stateIO, adapter, commands });
|
|
113
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gsd-core-vscode",
|
|
3
|
+
"displayName": "GSD Core",
|
|
4
|
+
"description": "GSD orchestration engine embedded in VS Code (ADR-1239 IDE profile).",
|
|
5
|
+
"version": "1.7.0-rc.6",
|
|
6
|
+
"publisher": "opengsd",
|
|
7
|
+
"engines": {
|
|
8
|
+
"vscode": "^1.105.0"
|
|
9
|
+
},
|
|
10
|
+
"categories": [
|
|
11
|
+
"Other"
|
|
12
|
+
],
|
|
13
|
+
"activationEvents": [
|
|
14
|
+
"onCommand:gsd.invoke"
|
|
15
|
+
],
|
|
16
|
+
"main": "./extension.js",
|
|
17
|
+
"browser": "./browser.js",
|
|
18
|
+
"contributes": {
|
|
19
|
+
"commands": [
|
|
20
|
+
{
|
|
21
|
+
"command": "gsd.invoke",
|
|
22
|
+
"title": "GSD: Invoke Command"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"chatParticipants": [
|
|
26
|
+
{
|
|
27
|
+
"id": "gsd",
|
|
28
|
+
"name": "gsd",
|
|
29
|
+
"fullName": "GSD Core",
|
|
30
|
+
"description": "Invoke GSD orchestration commands from chat.",
|
|
31
|
+
"isSticky": true
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"languageModelTools": [
|
|
35
|
+
{
|
|
36
|
+
"name": "gsd_progress",
|
|
37
|
+
"tags": [
|
|
38
|
+
"gsd",
|
|
39
|
+
"status"
|
|
40
|
+
],
|
|
41
|
+
"toolReferenceName": "gsd-progress",
|
|
42
|
+
"displayName": "GSD Progress",
|
|
43
|
+
"modelDescription": "Reports GSD milestone/phase progress (percent complete, plan and summary counts) for the current project.",
|
|
44
|
+
"userDescription": "Check GSD project progress.",
|
|
45
|
+
"canBeReferencedInPrompt": true,
|
|
46
|
+
"inputSchema": {
|
|
47
|
+
"type": "object",
|
|
48
|
+
"properties": {},
|
|
49
|
+
"additionalProperties": false
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "gsd_workstreams",
|
|
54
|
+
"tags": [
|
|
55
|
+
"gsd",
|
|
56
|
+
"status"
|
|
57
|
+
],
|
|
58
|
+
"toolReferenceName": "gsd-workstreams",
|
|
59
|
+
"displayName": "GSD Workstreams",
|
|
60
|
+
"modelDescription": "Lists the GSD parallel workstreams for the current project (or reports flat/single-workstream mode).",
|
|
61
|
+
"userDescription": "List GSD workstreams.",
|
|
62
|
+
"canBeReferencedInPrompt": true,
|
|
63
|
+
"inputSchema": {
|
|
64
|
+
"type": "object",
|
|
65
|
+
"properties": {},
|
|
66
|
+
"additionalProperties": false
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"name": "gsd_plan_phase",
|
|
71
|
+
"tags": [
|
|
72
|
+
"gsd",
|
|
73
|
+
"plan"
|
|
74
|
+
],
|
|
75
|
+
"toolReferenceName": "gsd-plan-phase",
|
|
76
|
+
"displayName": "GSD Plan Phase",
|
|
77
|
+
"modelDescription": "Looks up the plan index (plans, waves, checkpoints) for a named GSD phase. Read-only — does not create or modify a phase plan; use the /gsd-plan-phase chat workflow for full phase planning.",
|
|
78
|
+
"userDescription": "Look up a GSD phase's plan index.",
|
|
79
|
+
"canBeReferencedInPrompt": true,
|
|
80
|
+
"inputSchema": {
|
|
81
|
+
"type": "object",
|
|
82
|
+
"properties": {
|
|
83
|
+
"phase": {
|
|
84
|
+
"type": "string",
|
|
85
|
+
"description": "The GSD phase name to look up (e.g. \"01-core\")."
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"required": [
|
|
89
|
+
"phase"
|
|
90
|
+
],
|
|
91
|
+
"additionalProperties": false
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
}
|