gsd-pi 2.67.0-dev.509bd95 → 2.67.0-dev.5399650

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 (125) hide show
  1. package/dist/resources/extensions/gsd/auto/session.js +6 -0
  2. package/dist/resources/extensions/gsd/auto.js +27 -0
  3. package/dist/resources/extensions/gsd/init-wizard.js +34 -0
  4. package/dist/resources/extensions/gsd/workflow-mcp.js +38 -7
  5. package/dist/web/standalone/.next/BUILD_ID +1 -1
  6. package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
  7. package/dist/web/standalone/.next/build-manifest.json +2 -2
  8. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  9. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  10. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  11. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  13. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  14. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  15. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  16. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  17. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  18. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  19. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  20. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  21. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  22. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  23. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/index.html +1 -1
  26. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app-paths-manifest.json +11 -11
  33. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  34. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  35. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  36. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  37. package/package.json +4 -2
  38. package/packages/mcp-server/dist/cli.d.ts +9 -0
  39. package/packages/mcp-server/dist/cli.d.ts.map +1 -0
  40. package/packages/mcp-server/dist/cli.js +58 -0
  41. package/packages/mcp-server/dist/cli.js.map +1 -0
  42. package/packages/mcp-server/dist/index.d.ts +20 -0
  43. package/packages/mcp-server/dist/index.d.ts.map +1 -0
  44. package/packages/mcp-server/dist/index.js +14 -0
  45. package/packages/mcp-server/dist/index.js.map +1 -0
  46. package/packages/mcp-server/dist/readers/captures.d.ts +25 -0
  47. package/packages/mcp-server/dist/readers/captures.d.ts.map +1 -0
  48. package/packages/mcp-server/dist/readers/captures.js +67 -0
  49. package/packages/mcp-server/dist/readers/captures.js.map +1 -0
  50. package/packages/mcp-server/dist/readers/doctor-lite.d.ts +20 -0
  51. package/packages/mcp-server/dist/readers/doctor-lite.d.ts.map +1 -0
  52. package/packages/mcp-server/dist/readers/doctor-lite.js +173 -0
  53. package/packages/mcp-server/dist/readers/doctor-lite.js.map +1 -0
  54. package/packages/mcp-server/dist/readers/index.d.ts +14 -0
  55. package/packages/mcp-server/dist/readers/index.d.ts.map +1 -0
  56. package/packages/mcp-server/dist/readers/index.js +10 -0
  57. package/packages/mcp-server/dist/readers/index.js.map +1 -0
  58. package/packages/mcp-server/dist/readers/knowledge.d.ts +18 -0
  59. package/packages/mcp-server/dist/readers/knowledge.d.ts.map +1 -0
  60. package/packages/mcp-server/dist/readers/knowledge.js +82 -0
  61. package/packages/mcp-server/dist/readers/knowledge.js.map +1 -0
  62. package/packages/mcp-server/dist/readers/metrics.d.ts +32 -0
  63. package/packages/mcp-server/dist/readers/metrics.d.ts.map +1 -0
  64. package/packages/mcp-server/dist/readers/metrics.js +74 -0
  65. package/packages/mcp-server/dist/readers/metrics.js.map +1 -0
  66. package/packages/mcp-server/dist/readers/paths.d.ts +42 -0
  67. package/packages/mcp-server/dist/readers/paths.d.ts.map +1 -0
  68. package/packages/mcp-server/dist/readers/paths.js +199 -0
  69. package/packages/mcp-server/dist/readers/paths.js.map +1 -0
  70. package/packages/mcp-server/dist/readers/roadmap.d.ts +26 -0
  71. package/packages/mcp-server/dist/readers/roadmap.d.ts.map +1 -0
  72. package/packages/mcp-server/dist/readers/roadmap.js +194 -0
  73. package/packages/mcp-server/dist/readers/roadmap.js.map +1 -0
  74. package/packages/mcp-server/dist/readers/state.d.ts +43 -0
  75. package/packages/mcp-server/dist/readers/state.d.ts.map +1 -0
  76. package/packages/mcp-server/dist/readers/state.js +184 -0
  77. package/packages/mcp-server/dist/readers/state.js.map +1 -0
  78. package/packages/mcp-server/dist/server.d.ts +28 -0
  79. package/packages/mcp-server/dist/server.d.ts.map +1 -0
  80. package/packages/mcp-server/dist/server.js +319 -0
  81. package/packages/mcp-server/dist/server.js.map +1 -0
  82. package/packages/mcp-server/dist/session-manager.d.ts +54 -0
  83. package/packages/mcp-server/dist/session-manager.d.ts.map +1 -0
  84. package/packages/mcp-server/dist/session-manager.js +284 -0
  85. package/packages/mcp-server/dist/session-manager.js.map +1 -0
  86. package/packages/mcp-server/dist/types.d.ts +61 -0
  87. package/packages/mcp-server/dist/types.d.ts.map +1 -0
  88. package/packages/mcp-server/dist/types.js +11 -0
  89. package/packages/mcp-server/dist/types.js.map +1 -0
  90. package/packages/mcp-server/dist/workflow-tools.d.ts +9 -0
  91. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -0
  92. package/packages/mcp-server/dist/workflow-tools.js +526 -0
  93. package/packages/mcp-server/dist/workflow-tools.js.map +1 -0
  94. package/packages/mcp-server/tsconfig.json +1 -1
  95. package/packages/rpc-client/dist/index.d.ts +10 -0
  96. package/packages/rpc-client/dist/index.d.ts.map +1 -0
  97. package/packages/rpc-client/dist/index.js +9 -0
  98. package/packages/rpc-client/dist/index.js.map +1 -0
  99. package/packages/rpc-client/dist/jsonl.d.ts +17 -0
  100. package/packages/rpc-client/dist/jsonl.d.ts.map +1 -0
  101. package/packages/rpc-client/dist/jsonl.js +54 -0
  102. package/packages/rpc-client/dist/jsonl.js.map +1 -0
  103. package/packages/rpc-client/dist/rpc-client.d.ts +259 -0
  104. package/packages/rpc-client/dist/rpc-client.d.ts.map +1 -0
  105. package/packages/rpc-client/dist/rpc-client.js +541 -0
  106. package/packages/rpc-client/dist/rpc-client.js.map +1 -0
  107. package/packages/rpc-client/dist/rpc-client.test.d.ts +2 -0
  108. package/packages/rpc-client/dist/rpc-client.test.d.ts.map +1 -0
  109. package/packages/rpc-client/dist/rpc-client.test.js +477 -0
  110. package/packages/rpc-client/dist/rpc-client.test.js.map +1 -0
  111. package/packages/rpc-client/dist/rpc-types.d.ts +566 -0
  112. package/packages/rpc-client/dist/rpc-types.d.ts.map +1 -0
  113. package/packages/rpc-client/dist/rpc-types.js +12 -0
  114. package/packages/rpc-client/dist/rpc-types.js.map +1 -0
  115. package/scripts/ensure-workspace-builds.cjs +2 -0
  116. package/scripts/link-workspace-packages.cjs +21 -14
  117. package/src/resources/extensions/gsd/auto/session.ts +6 -0
  118. package/src/resources/extensions/gsd/auto.ts +29 -1
  119. package/src/resources/extensions/gsd/init-wizard.ts +34 -0
  120. package/src/resources/extensions/gsd/tests/auto-project-root-env.test.ts +29 -0
  121. package/src/resources/extensions/gsd/tests/init-bootstrap-completeness.test.ts +121 -0
  122. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +39 -1
  123. package/src/resources/extensions/gsd/workflow-mcp.ts +41 -7
  124. /package/dist/web/standalone/.next/static/{mHJZ3Z8yGRzZ32BmQs-I7 → 6_QPFhgX0DQnDhhquheRc}/_buildManifest.js +0 -0
  125. /package/dist/web/standalone/.next/static/{mHJZ3Z8yGRzZ32BmQs-I7 → 6_QPFhgX0DQnDhhquheRc}/_ssgManifest.js +0 -0
@@ -2,7 +2,8 @@
2
2
  /**
3
3
  * link-workspace-packages.cjs
4
4
  *
5
- * Creates node_modules/@gsd/* symlinks pointing to packages/* directories.
5
+ * Creates node_modules/@gsd/* and node_modules/@gsd-build/* symlinks pointing
6
+ * to shipped packages/* directories.
6
7
  *
7
8
  * During development, npm workspaces creates these automatically. But in the
8
9
  * published tarball, workspace packages are shipped under packages/ (via the
@@ -20,27 +21,33 @@ const { resolve, join } = require('path')
20
21
 
21
22
  const root = resolve(__dirname, '..')
22
23
  const packagesDir = join(root, 'packages')
23
- const nodeModulesGsd = join(root, 'node_modules', '@gsd')
24
+ const scopeDirs = {
25
+ '@gsd': join(root, 'node_modules', '@gsd'),
26
+ '@gsd-build': join(root, 'node_modules', '@gsd-build'),
27
+ }
24
28
 
25
- // Map directory names to package names
29
+ // Map directory names to scoped package names
26
30
  const packageMap = {
27
- 'native': 'native',
28
- 'pi-agent-core': 'pi-agent-core',
29
- 'pi-ai': 'pi-ai',
30
- 'pi-coding-agent': 'pi-coding-agent',
31
- 'pi-tui': 'pi-tui',
31
+ 'native': { scope: '@gsd', name: 'native' },
32
+ 'pi-agent-core': { scope: '@gsd', name: 'pi-agent-core' },
33
+ 'pi-ai': { scope: '@gsd', name: 'pi-ai' },
34
+ 'pi-coding-agent': { scope: '@gsd', name: 'pi-coding-agent' },
35
+ 'pi-tui': { scope: '@gsd', name: 'pi-tui' },
36
+ 'rpc-client': { scope: '@gsd-build', name: 'rpc-client' },
32
37
  }
33
38
 
34
- // Ensure @gsd scope directory exists
35
- if (!existsSync(nodeModulesGsd)) {
36
- mkdirSync(nodeModulesGsd, { recursive: true })
39
+ for (const scopeDir of Object.values(scopeDirs)) {
40
+ if (!existsSync(scopeDir)) {
41
+ mkdirSync(scopeDir, { recursive: true })
42
+ }
37
43
  }
38
44
 
39
45
  let linked = 0
40
46
  let copied = 0
41
- for (const [dir, name] of Object.entries(packageMap)) {
47
+ for (const [dir, pkg] of Object.entries(packageMap)) {
42
48
  const source = join(packagesDir, dir)
43
- const target = join(nodeModulesGsd, name)
49
+ const scopeDir = scopeDirs[pkg.scope]
50
+ const target = join(scopeDir, pkg.name)
44
51
 
45
52
  if (!existsSync(source)) continue
46
53
 
@@ -50,7 +57,7 @@ for (const [dir, name] of Object.entries(packageMap)) {
50
57
  const stat = lstatSync(target)
51
58
  if (stat.isSymbolicLink()) {
52
59
  const linkTarget = readlinkSync(target)
53
- if (resolve(join(nodeModulesGsd, linkTarget)) === source || linkTarget === source) {
60
+ if (resolve(join(scopeDir, linkTarget)) === source || linkTarget === source) {
54
61
  continue // Already correct
55
62
  }
56
63
  unlinkSync(target) // Wrong target, relink
@@ -84,6 +84,9 @@ export class AutoSession {
84
84
  // ── Paths ────────────────────────────────────────────────────────────────
85
85
  basePath = "";
86
86
  originalBasePath = "";
87
+ previousProjectRootEnv: string | null = null;
88
+ hadProjectRootEnv = false;
89
+ projectRootEnvCaptured = false;
87
90
  gitService: GitServiceImpl | null = null;
88
91
 
89
92
  // ── Dispatch counters ────────────────────────────────────────────────────
@@ -192,6 +195,9 @@ export class AutoSession {
192
195
  // Paths
193
196
  this.basePath = "";
194
197
  this.originalBasePath = "";
198
+ this.previousProjectRootEnv = null;
199
+ this.hadProjectRootEnv = false;
200
+ this.projectRootEnvCaptured = false;
195
201
  this.gitService = null;
196
202
 
197
203
  // Dispatch
@@ -241,6 +241,29 @@ const s = new AutoSession();
241
241
  /** Throttle STATE.md rebuilds — at most once per 30 seconds */
242
242
  const STATE_REBUILD_MIN_INTERVAL_MS = 30_000;
243
243
 
244
+ function captureProjectRootEnv(projectRoot: string): void {
245
+ if (!s.projectRootEnvCaptured) {
246
+ s.hadProjectRootEnv = Object.prototype.hasOwnProperty.call(process.env, "GSD_PROJECT_ROOT");
247
+ s.previousProjectRootEnv = process.env.GSD_PROJECT_ROOT ?? null;
248
+ s.projectRootEnvCaptured = true;
249
+ }
250
+ process.env.GSD_PROJECT_ROOT = projectRoot;
251
+ }
252
+
253
+ function restoreProjectRootEnv(): void {
254
+ if (!s.projectRootEnvCaptured) return;
255
+
256
+ if (s.hadProjectRootEnv && s.previousProjectRootEnv !== null) {
257
+ process.env.GSD_PROJECT_ROOT = s.previousProjectRootEnv;
258
+ } else {
259
+ delete process.env.GSD_PROJECT_ROOT;
260
+ }
261
+
262
+ s.previousProjectRootEnv = null;
263
+ s.hadProjectRootEnv = false;
264
+ s.projectRootEnvCaptured = false;
265
+ }
266
+
244
267
  export function shouldUseWorktreeIsolation(): boolean {
245
268
  const prefs = loadEffectiveGSDPreferences()?.preferences?.git;
246
269
  if (prefs?.isolation === "worktree") return true;
@@ -542,6 +565,7 @@ function handleLostSessionLock(
542
565
  s.active = false;
543
566
  s.paused = false;
544
567
  clearUnitTimeout();
568
+ restoreProjectRootEnv();
545
569
  deregisterSigtermHandler();
546
570
  clearCmuxSidebar(loadEffectiveGSDPreferences()?.preferences);
547
571
  const base = lockBase();
@@ -577,6 +601,7 @@ function cleanupAfterLoopExit(ctx: ExtensionContext): void {
577
601
  s.currentUnit = null;
578
602
  s.active = false;
579
603
  clearUnitTimeout();
604
+ restoreProjectRootEnv();
580
605
 
581
606
  // Clear crash lock and release session lock so the next `/gsd next` does
582
607
  // not see a stale lock with the current PID and treat it as a "remote"
@@ -846,6 +871,7 @@ export async function stopAuto(
846
871
  ctx?.ui.setStatus("gsd-auto", undefined);
847
872
  ctx?.ui.setWidget("gsd-progress", undefined);
848
873
  ctx?.ui.setFooter(undefined);
874
+ restoreProjectRootEnv();
849
875
 
850
876
  // Reset all session state in one call
851
877
  s.reset();
@@ -934,6 +960,7 @@ export async function pauseAuto(
934
960
 
935
961
  s.active = false;
936
962
  s.paused = true;
963
+ restoreProjectRootEnv();
937
964
  s.pendingVerificationRetry = null;
938
965
  s.verificationRetryCount.clear();
939
966
  ctx?.ui.setStatus("gsd-auto", "paused");
@@ -1305,6 +1332,7 @@ export async function startAuto(
1305
1332
  );
1306
1333
  logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "progress");
1307
1334
 
1335
+ captureProjectRootEnv(s.originalBasePath || s.basePath);
1308
1336
  await autoLoop(ctx, pi, s, buildLoopDeps());
1309
1337
  cleanupAfterLoopExit(ctx);
1310
1338
  return;
@@ -1329,6 +1357,7 @@ export async function startAuto(
1329
1357
  );
1330
1358
  if (!ready) return;
1331
1359
 
1360
+ captureProjectRootEnv(s.originalBasePath || s.basePath);
1332
1361
  try {
1333
1362
  syncCmuxSidebar(loadEffectiveGSDPreferences()?.preferences, await deriveState(s.basePath));
1334
1363
  } catch (err) {
@@ -1569,4 +1598,3 @@ export {
1569
1598
  buildLoopRemediationSteps,
1570
1599
  } from "./auto-recovery.js";
1571
1600
  export { resolveExpectedArtifactPath } from "./auto-artifact-paths.js";
1572
-
@@ -235,6 +235,20 @@ export async function showProjectInit(
235
235
  // ── Step 9: Bootstrap .gsd/ ────────────────────────────────────────────────
236
236
  bootstrapGsdDirectory(basePath, prefs, signals);
237
237
 
238
+ // Initialize SQLite database so GSD starts in full-capability mode (#3880).
239
+ // Without this, isDbAvailable() returns false and GSD enters degraded
240
+ // markdown-only mode until a tool handler happens to call ensureDbOpen().
241
+ let dbReady = false;
242
+ try {
243
+ const { ensureDbOpen } = await import("./bootstrap/dynamic-tools.js");
244
+ dbReady = await ensureDbOpen(basePath);
245
+ } catch {
246
+ // Swallowed — warning surfaced below
247
+ }
248
+ if (!dbReady) {
249
+ ctx.ui.notify("Warning: database initialization failed — GSD will run in degraded mode until the next /gsd invocation.", "warning");
250
+ }
251
+
238
252
  // Ensure .gitignore
239
253
  ensureGitignore(basePath);
240
254
  untrackRuntimeFiles(basePath);
@@ -250,6 +264,25 @@ export async function showProjectInit(
250
264
  // Non-fatal — codebase map generation failure should never block project init
251
265
  }
252
266
 
267
+ // Write initial STATE.md so it exists before the first /gsd invocation.
268
+ // The explicit /gsd init path (ops.ts) returns without entering showSmartEntry(),
269
+ // which would otherwise generate STATE.md at guided-flow.ts:1358.
270
+ let stateReady = false;
271
+ try {
272
+ const { deriveState } = await import("./state.js");
273
+ const { buildStateMarkdown } = await import("./doctor.js");
274
+ const { saveFile } = await import("./files.js");
275
+ const { resolveGsdRootFile } = await import("./paths.js");
276
+ const state = await deriveState(basePath);
277
+ await saveFile(resolveGsdRootFile(basePath, "STATE"), buildStateMarkdown(state));
278
+ stateReady = true;
279
+ } catch {
280
+ // Swallowed — warning surfaced below
281
+ }
282
+ if (!stateReady) {
283
+ ctx.ui.notify("Warning: initial STATE.md generation failed — it will be created on the next /gsd invocation.", "warning");
284
+ }
285
+
253
286
  ctx.ui.notify("GSD initialized. Starting your first milestone...", "info");
254
287
 
255
288
  return { completed: true, bootstrapped: true };
@@ -433,6 +466,7 @@ function bootstrapGsdDirectory(
433
466
 
434
467
  const gsd = gsdRoot(basePath);
435
468
  mkdirSync(join(gsd, "milestones"), { recursive: true });
469
+ mkdirSync(join(gsd, "runtime"), { recursive: true });
436
470
 
437
471
  // Write PREFERENCES.md from wizard answers
438
472
  const preferencesContent = buildPreferencesFile(prefs);
@@ -0,0 +1,29 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { readFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+
6
+ const sourcePath = join(import.meta.dirname, "..", "auto.ts");
7
+ const source = readFileSync(sourcePath, "utf-8");
8
+
9
+ test("auto-mode captures GSD_PROJECT_ROOT before entering the dispatch loop", () => {
10
+ const captureDeclIdx = source.indexOf("function captureProjectRootEnv(projectRoot: string): void {");
11
+ assert.ok(captureDeclIdx > -1, "auto.ts should define captureProjectRootEnv()");
12
+
13
+ const resumeCallIdx = source.indexOf("captureProjectRootEnv(s.originalBasePath || s.basePath);");
14
+ assert.ok(resumeCallIdx > -1, "auto.ts should capture GSD_PROJECT_ROOT before resume autoLoop");
15
+
16
+ const firstAutoLoopIdx = source.indexOf("await autoLoop(ctx, pi, s, buildLoopDeps());");
17
+ assert.ok(firstAutoLoopIdx > -1, "auto.ts should invoke autoLoop()");
18
+ assert.ok(
19
+ resumeCallIdx < firstAutoLoopIdx,
20
+ "auto.ts must set GSD_PROJECT_ROOT before the first autoLoop() call",
21
+ );
22
+ });
23
+
24
+ test("auto-mode restores GSD_PROJECT_ROOT when execution stops or pauses", () => {
25
+ assert.match(source, /function restoreProjectRootEnv\(\): void \{/);
26
+ assert.match(source, /cleanupAfterLoopExit\(ctx: ExtensionContext\): void \{[\s\S]*restoreProjectRootEnv\(\);/);
27
+ assert.match(source, /export async function pauseAuto\([\s\S]*restoreProjectRootEnv\(\);/);
28
+ assert.match(source, /\} finally \{[\s\S]*restoreProjectRootEnv\(\);[\s\S]*s\.reset\(\);/);
29
+ });
@@ -0,0 +1,121 @@
1
+ /**
2
+ * GSD Init Wizard — Bootstrap completeness regression tests
3
+ *
4
+ * Regression test for #3880 — fresh install never creates gsd.db.
5
+ *
6
+ * The init wizard must create all artifacts needed for full-capability
7
+ * mode: gsd.db (via ensureDbOpen), runtime/ directory, and STATE.md
8
+ * (via deriveState + buildStateMarkdown). Without these, GSD enters
9
+ * degraded markdown-only mode on every fresh install.
10
+ *
11
+ * These are structural tests that verify the init-wizard.ts source
12
+ * contains the required calls in the correct order.
13
+ */
14
+
15
+ import { describe, test } from "node:test";
16
+ import assert from "node:assert/strict";
17
+ import { readFileSync } from "node:fs";
18
+ import { fileURLToPath } from "node:url";
19
+ import { dirname, join } from "node:path";
20
+
21
+ const __filename = fileURLToPath(import.meta.url);
22
+ const __dirname = dirname(__filename);
23
+
24
+ const wizardSrc = readFileSync(
25
+ join(__dirname, "..", "init-wizard.ts"),
26
+ "utf-8",
27
+ );
28
+
29
+ describe("init-wizard bootstrap completeness (#3880)", () => {
30
+ // ── Gap 1: gsd.db must be created during init ─────────────────────────
31
+
32
+ test("bootstrapGsdDirectory is followed by ensureDbOpen", () => {
33
+ const bootstrapIdx = wizardSrc.indexOf("bootstrapGsdDirectory(basePath");
34
+ const ensureDbIdx = wizardSrc.indexOf("ensureDbOpen(basePath)");
35
+ assert.ok(bootstrapIdx > -1, "bootstrapGsdDirectory call should exist");
36
+ assert.ok(ensureDbIdx > -1, "ensureDbOpen(basePath) call should exist");
37
+ assert.ok(
38
+ ensureDbIdx > bootstrapIdx,
39
+ "ensureDbOpen must appear after bootstrapGsdDirectory so .gsd/ exists first",
40
+ );
41
+ });
42
+
43
+ test("ensureDbOpen is imported from dynamic-tools", () => {
44
+ assert.match(
45
+ wizardSrc,
46
+ /import.*dynamic-tools/,
47
+ "init-wizard should import from dynamic-tools for ensureDbOpen",
48
+ );
49
+ });
50
+
51
+ // ── Gap 2: runtime/ directory must be created during init ──────────────
52
+
53
+ test("bootstrapGsdDirectory creates runtime/ directory", () => {
54
+ // Find the bootstrapGsdDirectory function body
55
+ const fnStart = wizardSrc.indexOf("function bootstrapGsdDirectory(");
56
+ assert.ok(fnStart > -1, "bootstrapGsdDirectory function should exist");
57
+
58
+ // Find the next function definition to bound the search
59
+ const fnBody = wizardSrc.slice(fnStart, wizardSrc.indexOf("\nfunction ", fnStart + 1));
60
+
61
+ assert.match(
62
+ fnBody,
63
+ /mkdirSync\(.*"runtime"/,
64
+ 'bootstrapGsdDirectory should create "runtime" directory',
65
+ );
66
+ });
67
+
68
+ // ── Gap 3: STATE.md must be written during init ────────────────────────
69
+
70
+ test("showProjectInit generates STATE.md after bootstrap", () => {
71
+ const bootstrapIdx = wizardSrc.indexOf("bootstrapGsdDirectory(basePath");
72
+ const deriveIdx = wizardSrc.indexOf("deriveState(basePath)");
73
+ const stateIdx = wizardSrc.indexOf("buildStateMarkdown(state)");
74
+ const saveIdx = wizardSrc.indexOf('resolveGsdRootFile(basePath, "STATE")');
75
+
76
+ assert.ok(deriveIdx > -1, "deriveState call should exist in init-wizard");
77
+ assert.ok(stateIdx > -1, "buildStateMarkdown call should exist in init-wizard");
78
+ assert.ok(saveIdx > -1, "resolveGsdRootFile STATE call should exist in init-wizard");
79
+ assert.ok(
80
+ deriveIdx > bootstrapIdx,
81
+ "deriveState must appear after bootstrapGsdDirectory",
82
+ );
83
+ });
84
+
85
+ // ── Ordering: DB must be open before deriveState ───────────────────────
86
+
87
+ test("ensureDbOpen appears before deriveState", () => {
88
+ const ensureDbIdx = wizardSrc.indexOf("ensureDbOpen(basePath)");
89
+ const deriveIdx = wizardSrc.indexOf("deriveState(basePath)");
90
+ assert.ok(ensureDbIdx > -1, "ensureDbOpen should exist");
91
+ assert.ok(deriveIdx > -1, "deriveState should exist");
92
+ assert.ok(
93
+ ensureDbIdx < deriveIdx,
94
+ "ensureDbOpen must appear before deriveState so DB is ready for state derivation",
95
+ );
96
+ });
97
+
98
+ // ── Failure visibility: user must be warned on partial bootstrap ───────
99
+
100
+ test("ensureDbOpen failure surfaces a warning to the user", () => {
101
+ assert.match(
102
+ wizardSrc,
103
+ /if\s*\(\s*!dbReady\s*\)/,
104
+ "init-wizard should check dbReady and warn the user on failure",
105
+ );
106
+ // The warning must reference degraded mode so the user knows what happened
107
+ assert.match(
108
+ wizardSrc,
109
+ /degraded mode/,
110
+ "DB failure warning should mention degraded mode",
111
+ );
112
+ });
113
+
114
+ test("STATE.md failure surfaces a warning to the user", () => {
115
+ assert.match(
116
+ wizardSrc,
117
+ /if\s*\(\s*!stateReady\s*\)/,
118
+ "init-wizard should check stateReady and warn the user on failure",
119
+ );
120
+ });
121
+ });
@@ -1,7 +1,8 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
- import { readFileSync } from "node:fs";
3
+ import { mkdtempSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
4
  import { dirname, join } from "node:path";
5
+ import { tmpdir } from "node:os";
5
6
  import { fileURLToPath } from "node:url";
6
7
 
7
8
  import {
@@ -70,6 +71,43 @@ test("buildWorkflowMcpServers mirrors explicit launch config", () => {
70
71
  });
71
72
  });
72
73
 
74
+ test("detectWorkflowMcpLaunchConfig resolves the bundled server from GSD_PROJECT_ROOT", () => {
75
+ const repoRoot = mkdtempSync(join(tmpdir(), "gsd-workflow-root-"));
76
+ const worktreeRoot = mkdtempSync(join(tmpdir(), "gsd-workflow-worktree-"));
77
+ const cliPath = join(repoRoot, "packages", "mcp-server", "dist", "cli.js");
78
+
79
+ mkdirSync(join(repoRoot, "packages", "mcp-server", "dist"), { recursive: true });
80
+ writeFileSync(cliPath, "#!/usr/bin/env node\n", "utf-8");
81
+
82
+ const launch = detectWorkflowMcpLaunchConfig(worktreeRoot, {
83
+ GSD_PROJECT_ROOT: repoRoot,
84
+ });
85
+
86
+ assert.deepEqual(launch, {
87
+ name: "gsd-workflow",
88
+ command: process.execPath,
89
+ args: [cliPath],
90
+ cwd: repoRoot,
91
+ env: {
92
+ GSD_PERSIST_WRITE_GATE_STATE: "1",
93
+ GSD_WORKFLOW_PROJECT_ROOT: repoRoot,
94
+ },
95
+ });
96
+ });
97
+
98
+ test("detectWorkflowMcpLaunchConfig resolves the bundled server relative to the installed GSD package", () => {
99
+ const launch = detectWorkflowMcpLaunchConfig("/tmp/project", {
100
+ GSD_BIN_PATH: "/tmp/gsd-loader.js",
101
+ });
102
+
103
+ assert.equal(launch?.command, process.execPath);
104
+ assert.equal(launch?.cwd, "/tmp/project");
105
+ assert.equal(launch?.env?.GSD_CLI_PATH, "/tmp/gsd-loader.js");
106
+ assert.equal(launch?.env?.GSD_WORKFLOW_PROJECT_ROOT, "/tmp/project");
107
+ assert.equal(typeof launch?.args?.[0], "string");
108
+ assert.match(launch?.args?.[0] ?? "", /packages[\/\\]mcp-server[\/\\]dist[\/\\]cli\.js$/);
109
+ });
110
+
73
111
  test("usesWorkflowMcpTransport matches local externalCli providers", () => {
74
112
  assert.equal(usesWorkflowMcpTransport("externalCli", "local://claude-code"), true);
75
113
  assert.equal(usesWorkflowMcpTransport("externalCli", "https://api.example.com"), false);
@@ -1,6 +1,7 @@
1
1
  import { execSync } from "node:child_process";
2
2
  import { existsSync } from "node:fs";
3
3
  import { resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
4
5
 
5
6
  export interface WorkflowMcpLaunchConfig {
6
7
  name: string;
@@ -66,6 +67,21 @@ function lookupCommand(command: string, platform: NodeJS.Platform = process.plat
66
67
  }
67
68
  }
68
69
 
70
+ function getBundledWorkflowMcpCliPath(env: NodeJS.ProcessEnv): string | null {
71
+ if (!env.GSD_BIN_PATH?.trim() && !env.GSD_CLI_PATH?.trim()) return null;
72
+
73
+ const candidates = [
74
+ resolve(fileURLToPath(new URL("../../../../packages/mcp-server/dist/cli.js", import.meta.url))),
75
+ resolve(fileURLToPath(new URL("../../../../../packages/mcp-server/dist/cli.js", import.meta.url))),
76
+ ];
77
+
78
+ for (const bundledCli of candidates) {
79
+ if (existsSync(bundledCli)) return bundledCli;
80
+ }
81
+
82
+ return null;
83
+ }
84
+
69
85
  export function detectWorkflowMcpLaunchConfig(
70
86
  projectRoot = process.cwd(),
71
87
  env: NodeJS.ProcessEnv = process.env,
@@ -75,16 +91,19 @@ export function detectWorkflowMcpLaunchConfig(
75
91
  const explicitArgs = parseJsonEnv<unknown>(env, "GSD_WORKFLOW_MCP_ARGS");
76
92
  const explicitEnv = parseJsonEnv<Record<string, string>>(env, "GSD_WORKFLOW_MCP_ENV");
77
93
  const explicitCwd = env.GSD_WORKFLOW_MCP_CWD?.trim();
94
+ const gsdCliPath = env.GSD_CLI_PATH?.trim() || env.GSD_BIN_PATH?.trim();
78
95
  const workflowProjectRoot =
79
96
  explicitEnv?.GSD_WORKFLOW_PROJECT_ROOT?.trim() ||
80
97
  env.GSD_WORKFLOW_PROJECT_ROOT?.trim() ||
98
+ env.GSD_PROJECT_ROOT?.trim() ||
81
99
  explicitCwd ||
82
100
  projectRoot;
101
+ const resolvedWorkflowProjectRoot = resolve(workflowProjectRoot);
83
102
 
84
103
  if (explicitCommand) {
85
104
  const launchEnv = {
86
105
  ...(explicitEnv ?? {}),
87
- ...(env.GSD_CLI_PATH ? { GSD_CLI_PATH: env.GSD_CLI_PATH } : {}),
106
+ ...(gsdCliPath ? { GSD_CLI_PATH: gsdCliPath } : {}),
88
107
  GSD_PERSIST_WRITE_GATE_STATE: "1",
89
108
  GSD_WORKFLOW_PROJECT_ROOT: resolve(workflowProjectRoot),
90
109
  };
@@ -97,17 +116,32 @@ export function detectWorkflowMcpLaunchConfig(
97
116
  };
98
117
  }
99
118
 
100
- const distCli = resolve(projectRoot, "packages", "mcp-server", "dist", "cli.js");
119
+ const distCli = resolve(resolvedWorkflowProjectRoot, "packages", "mcp-server", "dist", "cli.js");
101
120
  if (existsSync(distCli)) {
102
121
  return {
103
122
  name,
104
123
  command: process.execPath,
105
124
  args: [distCli],
106
- cwd: projectRoot,
125
+ cwd: resolvedWorkflowProjectRoot,
126
+ env: {
127
+ ...(gsdCliPath ? { GSD_CLI_PATH: gsdCliPath } : {}),
128
+ GSD_PERSIST_WRITE_GATE_STATE: "1",
129
+ GSD_WORKFLOW_PROJECT_ROOT: resolvedWorkflowProjectRoot,
130
+ },
131
+ };
132
+ }
133
+
134
+ const bundledCli = getBundledWorkflowMcpCliPath(env);
135
+ if (bundledCli) {
136
+ return {
137
+ name,
138
+ command: process.execPath,
139
+ args: [bundledCli],
140
+ cwd: resolvedWorkflowProjectRoot,
107
141
  env: {
108
- ...(env.GSD_CLI_PATH ? { GSD_CLI_PATH: env.GSD_CLI_PATH } : {}),
142
+ ...(gsdCliPath ? { GSD_CLI_PATH: gsdCliPath } : {}),
109
143
  GSD_PERSIST_WRITE_GATE_STATE: "1",
110
- GSD_WORKFLOW_PROJECT_ROOT: resolve(projectRoot),
144
+ GSD_WORKFLOW_PROJECT_ROOT: resolvedWorkflowProjectRoot,
111
145
  },
112
146
  };
113
147
  }
@@ -118,9 +152,9 @@ export function detectWorkflowMcpLaunchConfig(
118
152
  name,
119
153
  command: binPath,
120
154
  env: {
121
- ...(env.GSD_CLI_PATH ? { GSD_CLI_PATH: env.GSD_CLI_PATH } : {}),
155
+ ...(gsdCliPath ? { GSD_CLI_PATH: gsdCliPath } : {}),
122
156
  GSD_PERSIST_WRITE_GATE_STATE: "1",
123
- GSD_WORKFLOW_PROJECT_ROOT: resolve(projectRoot),
157
+ GSD_WORKFLOW_PROJECT_ROOT: resolvedWorkflowProjectRoot,
124
158
  },
125
159
  };
126
160
  }