gsd-pi 2.75.0-dev.2203010a0 → 2.75.0-dev.96d4bb599

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 (109) hide show
  1. package/dist/onboarding.d.ts +5 -1
  2. package/dist/onboarding.js +5 -3
  3. package/dist/resources/extensions/gsd/auto-model-selection.js +1 -1
  4. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +23 -19
  5. package/dist/resources/extensions/gsd/bootstrap/memory-tools.js +128 -0
  6. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +2 -0
  7. package/dist/resources/extensions/gsd/bootstrap/system-context.js +17 -4
  8. package/dist/resources/extensions/gsd/commands/handlers/onboarding.js +52 -68
  9. package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
  10. package/dist/resources/extensions/gsd/commands-memory.js +462 -0
  11. package/dist/resources/extensions/gsd/gsd-db.js +237 -4
  12. package/dist/resources/extensions/gsd/memory-embeddings.js +219 -0
  13. package/dist/resources/extensions/gsd/memory-extractor.js +78 -27
  14. package/dist/resources/extensions/gsd/memory-ingest.js +218 -0
  15. package/dist/resources/extensions/gsd/memory-relations.js +189 -0
  16. package/dist/resources/extensions/gsd/memory-source-store.js +113 -0
  17. package/dist/resources/extensions/gsd/memory-store.js +299 -6
  18. package/dist/resources/extensions/gsd/model-router.js +9 -5
  19. package/dist/resources/extensions/gsd/notification-overlay.js +7 -22
  20. package/dist/resources/extensions/gsd/tools/memory-tools.js +306 -0
  21. package/dist/resources/extensions/gsd/tools/skip-slice.js +78 -0
  22. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  23. package/dist/web/standalone/.next/BUILD_ID +1 -1
  24. package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
  25. package/dist/web/standalone/.next/build-manifest.json +2 -2
  26. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  27. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/index.html +1 -1
  44. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
  51. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  52. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  53. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  54. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  55. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  56. package/package.json +1 -1
  57. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  58. package/packages/mcp-server/dist/server.js +12 -10
  59. package/packages/mcp-server/dist/server.js.map +1 -1
  60. package/packages/mcp-server/dist/session-manager.d.ts.map +1 -1
  61. package/packages/mcp-server/dist/session-manager.js +8 -1
  62. package/packages/mcp-server/dist/session-manager.js.map +1 -1
  63. package/packages/mcp-server/dist/workflow-tools.d.ts +1 -0
  64. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  65. package/packages/mcp-server/dist/workflow-tools.js +113 -14
  66. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  67. package/packages/mcp-server/src/mcp-server.test.ts +40 -4
  68. package/packages/mcp-server/src/server.ts +12 -10
  69. package/packages/mcp-server/src/session-manager.ts +10 -3
  70. package/packages/mcp-server/src/workflow-tools.test.ts +91 -1
  71. package/packages/mcp-server/src/workflow-tools.ts +128 -18
  72. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  73. package/src/resources/extensions/gsd/auto-model-selection.ts +1 -1
  74. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +24 -20
  75. package/src/resources/extensions/gsd/bootstrap/memory-tools.ts +158 -0
  76. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +2 -0
  77. package/src/resources/extensions/gsd/bootstrap/system-context.ts +20 -4
  78. package/src/resources/extensions/gsd/commands/handlers/onboarding.ts +65 -98
  79. package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
  80. package/src/resources/extensions/gsd/commands-memory.ts +551 -0
  81. package/src/resources/extensions/gsd/gsd-db.ts +273 -4
  82. package/src/resources/extensions/gsd/memory-embeddings.ts +235 -0
  83. package/src/resources/extensions/gsd/memory-extractor.ts +100 -34
  84. package/src/resources/extensions/gsd/memory-ingest.ts +286 -0
  85. package/src/resources/extensions/gsd/memory-relations.ts +240 -0
  86. package/src/resources/extensions/gsd/memory-source-store.ts +138 -0
  87. package/src/resources/extensions/gsd/memory-store.ts +351 -7
  88. package/src/resources/extensions/gsd/model-router.ts +10 -5
  89. package/src/resources/extensions/gsd/notification-overlay.ts +9 -19
  90. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +12 -0
  91. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +2 -2
  92. package/src/resources/extensions/gsd/tests/complete-task.test.ts +2 -2
  93. package/src/resources/extensions/gsd/tests/escalation.test.ts +2 -2
  94. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
  95. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  96. package/src/resources/extensions/gsd/tests/memory-embeddings.test.ts +213 -0
  97. package/src/resources/extensions/gsd/tests/memory-ingest.test.ts +153 -0
  98. package/src/resources/extensions/gsd/tests/memory-maintenance.test.ts +107 -0
  99. package/src/resources/extensions/gsd/tests/memory-relations.test.ts +175 -0
  100. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  101. package/src/resources/extensions/gsd/tests/memory-tools.test.ts +295 -0
  102. package/src/resources/extensions/gsd/tests/model-router.test.ts +50 -0
  103. package/src/resources/extensions/gsd/tests/notification-overlay.test.ts +56 -37
  104. package/src/resources/extensions/gsd/tests/skip-slice-cascades-tasks.test.ts +125 -0
  105. package/src/resources/extensions/gsd/tools/memory-tools.ts +380 -0
  106. package/src/resources/extensions/gsd/tools/skip-slice.ts +133 -0
  107. package/src/resources/extensions/gsd/workflow-logger.ts +3 -1
  108. /package/dist/web/standalone/.next/static/{8FZqxNe9FxQDmsbRzR8tA → o61X3klsB6C0UE0X1x3PA}/_buildManifest.js +0 -0
  109. /package/dist/web/standalone/.next/static/{8FZqxNe9FxQDmsbRzR8tA → o61X3klsB6C0UE0X1x3PA}/_ssgManifest.js +0 -0
@@ -32,26 +32,47 @@ function resolveExternalStateRoot(allowedRoot) {
32
32
  return null;
33
33
  }
34
34
  }
35
- function validateProjectDir(projectDir, env = process.env) {
35
+ export function validateProjectDir(projectDir, env = process.env) {
36
36
  if (!isAbsolute(projectDir)) {
37
37
  throw new Error(`projectDir must be an absolute path. Received: ${projectDir}`);
38
38
  }
39
- const resolvedProjectDir = resolve(projectDir);
39
+ const lexicallyResolved = resolve(projectDir);
40
+ // Resolve symlinks on the candidate before the containment check so that a
41
+ // symlink inside the allowed root pointing outside of it cannot bypass the
42
+ // guard. Falls back to the lexical path if the candidate does not exist yet
43
+ // (legitimate for a brand-new worktree dir about to be created).
44
+ const resolvedProjectDir = safeRealpath(lexicallyResolved);
40
45
  const allowedRoot = getAllowedProjectRoot(env);
41
46
  if (!allowedRoot)
42
47
  return resolvedProjectDir;
43
- if (isWithinRoot(resolvedProjectDir, allowedRoot))
48
+ const resolvedAllowedRoot = safeRealpath(allowedRoot);
49
+ if (isWithinRoot(resolvedProjectDir, resolvedAllowedRoot))
44
50
  return resolvedProjectDir;
45
51
  // External state layout: `<allowedRoot>/.gsd` may be a symlink into
46
52
  // `~/.gsd/projects/<hash>/`, and auto-worktrees live under
47
53
  // `~/.gsd/projects/<hash>/worktrees/<MID>/`. Accept candidates that are
48
54
  // under the realpath of `<allowedRoot>/.gsd` — they belong to this project
49
55
  // even though their absolute path is outside allowedRoot (#issue-a44).
50
- const externalRoot = resolveExternalStateRoot(allowedRoot);
56
+ const externalRoot = resolveExternalStateRoot(resolvedAllowedRoot);
51
57
  if (externalRoot && isWithinRoot(resolvedProjectDir, externalRoot)) {
52
58
  return resolvedProjectDir;
53
59
  }
54
- throw new Error(`projectDir must stay within the configured workflow project root. Received: ${resolvedProjectDir}; allowed root: ${allowedRoot}`);
60
+ throw new Error(`projectDir must stay within the configured workflow project root. Received: ${resolvedProjectDir}; allowed root: ${resolvedAllowedRoot}`);
61
+ }
62
+ function safeRealpath(path) {
63
+ try {
64
+ return realpathSync(path);
65
+ }
66
+ catch (err) {
67
+ // Only fall back for non-existent paths — a legitimate case when a worktree
68
+ // directory hasn't been created yet. Permission errors (EACCES), not-a-
69
+ // directory (ENOTDIR), etc. must propagate so we do not silently degrade
70
+ // to a lexical-only containment check that a restricted symlink could
71
+ // bypass.
72
+ if (err?.code === "ENOENT")
73
+ return path;
74
+ throw err;
75
+ }
55
76
  }
56
77
  function parseToolArgs(schema, args) {
57
78
  return schema.parse(args);
@@ -276,17 +297,55 @@ export const WORKFLOW_TOOL_NAMES = [
276
297
  "gsd_milestone_status",
277
298
  "gsd_journal_query",
278
299
  ];
300
+ const DEFAULT_WORKFLOW_OP_TIMEOUT_MS = 5 * 60 * 1000;
301
+ function getWorkflowOpTimeoutMs(env = process.env) {
302
+ const raw = env.GSD_MCP_WORKFLOW_TIMEOUT_MS?.trim();
303
+ if (!raw)
304
+ return DEFAULT_WORKFLOW_OP_TIMEOUT_MS;
305
+ const parsed = Number.parseInt(raw, 10);
306
+ if (!Number.isFinite(parsed) || parsed < 0)
307
+ return DEFAULT_WORKFLOW_OP_TIMEOUT_MS;
308
+ return parsed; // 0 disables the timeout
309
+ }
279
310
  async function runSerializedWorkflowOperation(fn) {
280
311
  // The shared DB adapter and workflow log base path are process-global, so
281
312
  // workflow MCP mutations must not overlap within a single server process.
313
+ // A per-operation deadline prevents a single stuck call from wedging every
314
+ // subsequent write for the lifetime of the process.
315
+ //
316
+ // Known limitation: on timeout we surface an error and release the queue,
317
+ // but Promise.race cannot cancel the underlying `fn()` — it may continue
318
+ // running in the background and overlap with the next admitted operation.
319
+ // Proper cancellation requires threading an AbortSignal through every
320
+ // workflow executor (`workflow-tool-executors.ts` and friends), which is
321
+ // a larger change. The current trade-off: risk a theoretical overlap after
322
+ // a 5-minute wall-clock timeout vs permanently wedging the server. The
323
+ // overlap window is bounded by how long the zombie `fn()` keeps running;
324
+ // in practice DB writes complete quickly even when the caller gave up.
282
325
  const prior = workflowExecutionQueue;
283
326
  let release;
284
327
  workflowExecutionQueue = new Promise((resolve) => {
285
328
  release = resolve;
286
329
  });
287
330
  await prior;
331
+ const timeoutMs = getWorkflowOpTimeoutMs();
288
332
  try {
289
- return await fn();
333
+ if (timeoutMs === 0) {
334
+ return await fn();
335
+ }
336
+ let timer;
337
+ const timeoutPromise = new Promise((_, reject) => {
338
+ timer = setTimeout(() => {
339
+ reject(new Error(`Workflow operation exceeded ${timeoutMs}ms deadline (GSD_MCP_WORKFLOW_TIMEOUT_MS)`));
340
+ }, timeoutMs);
341
+ });
342
+ try {
343
+ return await Promise.race([fn(), timeoutPromise]);
344
+ }
345
+ finally {
346
+ if (timer)
347
+ clearTimeout(timer);
348
+ }
290
349
  }
291
350
  finally {
292
351
  release();
@@ -398,10 +457,14 @@ const planMilestoneParams = {
398
457
  depends: z.array(z.string()),
399
458
  demo: z.string(),
400
459
  goal: z.string(),
401
- successCriteria: z.string(),
402
- proofLevel: z.string(),
403
- integrationClosure: z.string(),
404
- observabilityImpact: z.string(),
460
+ // ADR-011: heavy planning fields are optional for sketch slices; required for full slices.
461
+ successCriteria: z.string().optional(),
462
+ proofLevel: z.string().optional(),
463
+ integrationClosure: z.string().optional(),
464
+ observabilityImpact: z.string().optional(),
465
+ // ADR-011 sketch-then-refine fields.
466
+ isSketch: z.boolean().optional().describe("ADR-011: true marks this slice as a sketch awaiting refine-slice expansion"),
467
+ sketchScope: z.string().optional().describe("ADR-011: 2-3 sentence scope boundary, required when isSketch=true"),
405
468
  })).describe("Planned slices for the milestone"),
406
469
  status: z.string().optional().describe("Milestone status"),
407
470
  dependsOn: z.array(z.string()).optional().describe("Milestone dependencies"),
@@ -501,7 +564,7 @@ const saveGateResultParams = {
501
564
  projectDir: projectDirParam,
502
565
  milestoneId: z.string().describe("Milestone ID (e.g. M001)"),
503
566
  sliceId: z.string().describe("Slice ID (e.g. S01)"),
504
- gateId: z.enum(["Q3", "Q4", "Q5", "Q6", "Q7", "Q8", "MV01", "MV02", "MV03", "MV04"]).describe("Gate ID"),
567
+ gateId: z.string().describe("Gate ID (e.g. Q3, Q4, Q5, Q6, Q7, Q8, MV01, MV02, MV03, MV04). Accepts any string for forward-compatibility with new gates."),
505
568
  taskId: z.string().optional().describe("Task ID for task-scoped gates"),
506
569
  verdict: z.enum(["pass", "flag", "omitted"]).describe("Gate verdict"),
507
570
  rationale: z.string().describe("One-sentence justification"),
@@ -654,6 +717,18 @@ const taskCompleteParams = {
654
717
  keyFiles: z.array(z.string()).optional().describe("List of key files created or modified"),
655
718
  keyDecisions: z.array(z.string()).optional().describe("List of key decisions made during this task"),
656
719
  blockerDiscovered: z.boolean().optional().describe("Whether a plan-invalidating blocker was discovered"),
720
+ // ADR-011 Phase 2: mid-execution escalation — agent asks the user to resolve an ambiguity.
721
+ escalation: z.object({
722
+ question: z.string().describe("The question the user needs to answer — one clear sentence."),
723
+ options: z.array(z.object({
724
+ id: z.string().describe("Short id (e.g. 'A', 'B') used by /gsd escalate resolve."),
725
+ label: z.string().describe("One-line label."),
726
+ tradeoffs: z.string().describe("1-2 sentences on the tradeoffs of this option."),
727
+ })).min(2).max(4).describe("2-4 options the user can choose between."),
728
+ recommendation: z.string().describe("Option id the executor recommends."),
729
+ recommendationRationale: z.string().describe("Why the recommendation — 1-2 sentences."),
730
+ continueWithDefault: z.boolean().describe("When true, loop continues (artifact logged for later review). When false, auto-mode pauses until the user resolves via /gsd escalate resolve."),
731
+ }).optional().describe("ADR-011 Phase 2: optional escalation payload. Only honored when phases.mid_execution_escalation is true."),
657
732
  verificationEvidence: z.array(z.union([
658
733
  z.object({
659
734
  command: z.string(),
@@ -753,7 +828,18 @@ export function registerWorkflowTools(server) {
753
828
  return reserved;
754
829
  }
755
830
  const allIds = [...new Set([...findMilestoneIds(projectDir), ...getReservedMilestoneIds()])];
756
- const nextId = nextMilestoneId(allIds);
831
+ const prefsMod = await importLocalModule("../../../src/resources/extensions/gsd/preferences.js").catch(() => null);
832
+ // Graceful degradation: a corrupt preferences file should not crash
833
+ // milestone-id generation. Fall back to non-unique IDs if anything
834
+ // throws here — matches the pre-fix behavior for missing prefs.
835
+ let uniqueEnabled = false;
836
+ try {
837
+ uniqueEnabled = !!prefsMod?.loadEffectiveGSDPreferences?.()?.preferences?.unique_milestone_ids;
838
+ }
839
+ catch {
840
+ uniqueEnabled = false;
841
+ }
842
+ const nextId = nextMilestoneId(allIds, uniqueEnabled);
757
843
  await ensureMilestoneDbRow(nextId);
758
844
  return nextId;
759
845
  });
@@ -770,7 +856,18 @@ export function registerWorkflowTools(server) {
770
856
  return reserved;
771
857
  }
772
858
  const allIds = [...new Set([...findMilestoneIds(projectDir), ...getReservedMilestoneIds()])];
773
- const nextId = nextMilestoneId(allIds);
859
+ const prefsMod = await importLocalModule("../../../src/resources/extensions/gsd/preferences.js").catch(() => null);
860
+ // Graceful degradation: a corrupt preferences file should not crash
861
+ // milestone-id generation. Fall back to non-unique IDs if anything
862
+ // throws here — matches the pre-fix behavior for missing prefs.
863
+ let uniqueEnabled = false;
864
+ try {
865
+ uniqueEnabled = !!prefsMod?.loadEffectiveGSDPreferences?.()?.preferences?.unique_milestone_ids;
866
+ }
867
+ catch {
868
+ uniqueEnabled = false;
869
+ }
870
+ const nextId = nextMilestoneId(allIds, uniqueEnabled);
774
871
  await ensureMilestoneDbRow(nextId);
775
872
  return nextId;
776
873
  });
@@ -910,8 +1007,10 @@ export function registerWorkflowTools(server) {
910
1007
  return handleTaskComplete(projectDir, taskArgs);
911
1008
  });
912
1009
  server.tool("gsd_milestone_status", "Read the current status of a milestone and all its slices from the GSD database.", milestoneStatusParams, async (args) => {
1010
+ // gsd_milestone_status is a read-only query. In-process (query-tools.ts)
1011
+ // does not apply the write-gate; MCP must match to avoid blocking reads
1012
+ // during pending-gate or queue-mode states.
913
1013
  const { projectDir, milestoneId } = parseWorkflowArgs(milestoneStatusSchema, args);
914
- await enforceWorkflowWriteGate("gsd_milestone_status", projectDir, milestoneId);
915
1014
  const { executeMilestoneStatus } = await getWorkflowToolExecutors();
916
1015
  return runSerializedWorkflowOperation(() => executeMilestoneStatus({ milestoneId }, projectDir));
917
1016
  });