@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.
Files changed (113) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.opencode/plugins/gsd-core.js +20 -0
  4. package/agents/gsd-doc-classifier.md +105 -0
  5. package/agents/gsd-doc-synthesizer.md +61 -0
  6. package/agents/gsd-ui-checker.md +30 -0
  7. package/agents/gsd-ui-researcher.md +1 -0
  8. package/bin/install.js +1568 -622
  9. package/gsd-core/bin/gsd-tools.cjs +40 -1
  10. package/gsd-core/bin/lib/api-coverage.cjs +466 -0
  11. package/gsd-core/bin/lib/audit.cjs +6 -3
  12. package/gsd-core/bin/lib/capability-loader.cjs +11 -9
  13. package/gsd-core/bin/lib/capability-registry.cjs +761 -84
  14. package/gsd-core/bin/lib/capability-validator.cjs +56 -18
  15. package/gsd-core/bin/lib/capability-writer.cjs +10 -1
  16. package/gsd-core/bin/lib/check-command-router.cjs +242 -3
  17. package/gsd-core/bin/lib/commands.cjs +7 -5
  18. package/gsd-core/bin/lib/config-loader.cjs +1 -0
  19. package/gsd-core/bin/lib/config.cjs +96 -0
  20. package/gsd-core/bin/lib/core-utils.cjs +4 -1
  21. package/gsd-core/bin/lib/host-integration-adapters/cline-sdk-binding.cjs +234 -0
  22. package/gsd-core/bin/lib/host-integration-adapters/imperative-hook-bus.cjs +145 -0
  23. package/gsd-core/bin/lib/host-integration.cjs +45 -4
  24. package/gsd-core/bin/lib/init.cjs +76 -39
  25. package/gsd-core/bin/lib/install-effort-resolver.cjs +213 -0
  26. package/gsd-core/bin/lib/install-engine.cjs +228 -18
  27. package/gsd-core/bin/lib/installer-migration-report.cjs +7 -0
  28. package/gsd-core/bin/lib/loop-resolver.cjs +68 -17
  29. package/gsd-core/bin/lib/markdown-sectionizer.cjs +50 -11
  30. package/gsd-core/bin/lib/mcp-server.cjs +18 -7
  31. package/gsd-core/bin/lib/milestone.cjs +3 -3
  32. package/gsd-core/bin/lib/normalize-test-command.cjs +187 -0
  33. package/gsd-core/bin/lib/phase-id.cjs +132 -3
  34. package/gsd-core/bin/lib/phase.cjs +78 -16
  35. package/gsd-core/bin/lib/planning-workspace.cjs +17 -0
  36. package/gsd-core/bin/lib/review-reviewer-selection.cjs +24 -7
  37. package/gsd-core/bin/lib/roadmap-command-router.cjs +5 -4
  38. package/gsd-core/bin/lib/roadmap-parser.cjs +21 -30
  39. package/gsd-core/bin/lib/roadmap-upgrade.cjs +9 -9
  40. package/gsd-core/bin/lib/roadmap.cjs +42 -56
  41. package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +248 -44
  42. package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +2 -2
  43. package/gsd-core/bin/lib/runtime-artifact-layout.cjs +39 -23
  44. package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +19 -5
  45. package/gsd-core/bin/lib/runtime-homes.cjs +30 -0
  46. package/gsd-core/bin/lib/runtime-hooks-surface.cjs +534 -37
  47. package/gsd-core/bin/lib/runtime-name-policy.cjs +63 -5
  48. package/gsd-core/bin/lib/security.cjs +6 -36
  49. package/gsd-core/bin/lib/shell-command-projection.cjs +115 -2
  50. package/gsd-core/bin/lib/spec-section.cjs +111 -0
  51. package/gsd-core/bin/lib/stale-bake-guard.cjs +30 -10
  52. package/gsd-core/bin/lib/state-transition.cjs +1 -1
  53. package/gsd-core/bin/lib/state.cjs +24 -24
  54. package/gsd-core/bin/lib/surface.cjs +40 -6
  55. package/gsd-core/bin/lib/uat.cjs +4 -1
  56. package/gsd-core/bin/lib/ui-consideration-probe.cjs +249 -0
  57. package/gsd-core/bin/lib/validate.cjs +15 -6
  58. package/gsd-core/bin/lib/verify.cjs +33 -37
  59. package/gsd-core/bin/shared/config-schema.manifest.json +2 -0
  60. package/gsd-core/bin/shared/model-catalog.json +14 -9
  61. package/gsd-core/references/api-coverage.md +104 -0
  62. package/gsd-core/references/model-profiles.md +2 -2
  63. package/gsd-core/references/planning-config.md +2 -0
  64. package/gsd-core/references/specless-probe-fallback.md +172 -0
  65. package/gsd-core/references/ui-consideration-probe.md +73 -0
  66. package/gsd-core/templates/UI-SPEC.md +25 -0
  67. package/gsd-core/templates/VALIDATION.md +2 -0
  68. package/gsd-core/templates/config.json +2 -1
  69. package/gsd-core/workflows/audit-fix.md +9 -1
  70. package/gsd-core/workflows/audit-milestone.md +7 -4
  71. package/gsd-core/workflows/code-review-fix.md +7 -3
  72. package/gsd-core/workflows/code-review.md +4 -1
  73. package/gsd-core/workflows/discuss-phase-assumptions.md +4 -1
  74. package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +8 -4
  75. package/gsd-core/workflows/execute-phase/steps/regression-gate.md +42 -0
  76. package/gsd-core/workflows/execute-phase.md +1 -25
  77. package/gsd-core/workflows/plan-phase.md +37 -2
  78. package/gsd-core/workflows/quick.md +2 -2
  79. package/gsd-core/workflows/review.md +59 -13
  80. package/gsd-core/workflows/settings-advanced.md +12 -9
  81. package/gsd-core/workflows/settings.md +2 -2
  82. package/gsd-core/workflows/ui-phase.md +146 -1
  83. package/gsd-core/workflows/validate-phase.md +2 -2
  84. package/gsd-core/workflows/verify-phase.md +3 -2
  85. package/gsd-core/workflows/verify-work.md +38 -0
  86. package/hooks/dist/gsd-cursor-pre-tool.js +76 -0
  87. package/hooks/dist/gsd-cursor-stop.js +48 -0
  88. package/hooks/dist/gsd-cursor-subagent-start.js +50 -0
  89. package/hooks/dist/gsd-cursor-subagent-stop.js +40 -0
  90. package/hooks/dist/gsd-windsurf-pre-command.js +275 -0
  91. package/hooks/dist/gsd-windsurf-pre-write.js +132 -0
  92. package/hooks/dist/managed-hooks-registry.cjs +6 -0
  93. package/hooks/gsd-cursor-pre-tool.js +76 -0
  94. package/hooks/gsd-cursor-stop.js +48 -0
  95. package/hooks/gsd-cursor-subagent-start.js +50 -0
  96. package/hooks/gsd-cursor-subagent-stop.js +40 -0
  97. package/hooks/gsd-windsurf-pre-command.js +275 -0
  98. package/hooks/gsd-windsurf-pre-write.js +132 -0
  99. package/hooks/managed-hooks-registry.cjs +6 -0
  100. package/package.json +9 -4
  101. package/pi/gsd.cjs +354 -0
  102. package/scripts/build-hooks.js +8 -1
  103. package/scripts/gen-golden-install-parity-zcode.cjs +11 -1
  104. package/scripts/gen-registry.cjs +128 -0
  105. package/scripts/lint-phase-id-drift.cjs +150 -0
  106. package/scripts/lint-test-file-count.allowlist.json +2 -1
  107. package/scripts/registry-schema.cjs +565 -0
  108. package/scripts/run-tests.cjs +21 -1
  109. package/scripts/validate-registry.cjs +117 -0
  110. package/vscode/browser.js +197 -0
  111. package/vscode/extension.js +383 -0
  112. package/vscode/host-binding.js +113 -0
  113. 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
+ }