gsd-pi 2.67.0-dev.1cd1e0f → 2.67.0-dev.2142d3e

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 (74) hide show
  1. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +3 -0
  2. package/dist/resources/extensions/gsd/auto/phases.js +17 -0
  3. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +12 -0
  4. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +11 -435
  5. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +1 -4
  6. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +7 -64
  7. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +88 -8
  8. package/dist/resources/extensions/gsd/commands/handlers/core.js +38 -24
  9. package/dist/resources/extensions/gsd/commands/index.js +8 -1
  10. package/dist/resources/extensions/gsd/guided-flow.js +16 -0
  11. package/dist/resources/extensions/gsd/init-wizard.js +34 -0
  12. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +508 -0
  13. package/dist/resources/extensions/gsd/workflow-logger.js +18 -3
  14. package/dist/resources/extensions/gsd/workflow-mcp.js +190 -0
  15. package/dist/web/standalone/.next/BUILD_ID +1 -1
  16. package/dist/web/standalone/.next/app-path-routes-manifest.json +10 -10
  17. package/dist/web/standalone/.next/build-manifest.json +2 -2
  18. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  19. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  20. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  21. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  22. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  23. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  28. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/index.html +1 -1
  36. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app-paths-manifest.json +10 -10
  43. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  44. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  45. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  46. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  47. package/package.json +1 -1
  48. package/packages/mcp-server/README.md +38 -0
  49. package/packages/mcp-server/src/server.ts +6 -2
  50. package/packages/mcp-server/src/workflow-tools.test.ts +976 -0
  51. package/packages/mcp-server/src/workflow-tools.ts +986 -0
  52. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +3 -0
  53. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +121 -0
  54. package/src/resources/extensions/gsd/auto/phases.ts +25 -0
  55. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +20 -0
  56. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +22 -435
  57. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +1 -5
  58. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +7 -72
  59. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +122 -6
  60. package/src/resources/extensions/gsd/commands/handlers/core.ts +52 -25
  61. package/src/resources/extensions/gsd/commands/index.ts +7 -1
  62. package/src/resources/extensions/gsd/guided-flow.ts +24 -0
  63. package/src/resources/extensions/gsd/init-wizard.ts +34 -0
  64. package/src/resources/extensions/gsd/tests/core-overlay-fallback.test.ts +101 -0
  65. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +66 -0
  66. package/src/resources/extensions/gsd/tests/init-bootstrap-completeness.test.ts +121 -0
  67. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +16 -0
  68. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +301 -0
  69. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +625 -0
  70. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +629 -0
  71. package/src/resources/extensions/gsd/workflow-logger.ts +19 -3
  72. package/src/resources/extensions/gsd/workflow-mcp.ts +233 -0
  73. /package/dist/web/standalone/.next/static/{PHqEommYRR8CRn3i84CGM → xR6qurkuYSvyjBjRyJLxG}/_buildManifest.js +0 -0
  74. /package/dist/web/standalone/.next/static/{PHqEommYRR8CRn3i84CGM → xR6qurkuYSvyjBjRyJLxG}/_ssgManifest.js +0 -0
@@ -8,15 +8,18 @@ import { ensureDbOpen } from "./dynamic-tools.js";
8
8
  import { StringEnum } from "@gsd/pi-ai";
9
9
  import { logError } from "../workflow-logger.js";
10
10
  import { getErrorMessage } from "../error-utils.js";
11
- import { shouldBlockContextArtifactSave } from "./write-gate.js";
12
-
13
- const SUPPORTED_SUMMARY_ARTIFACT_TYPES = ["SUMMARY", "RESEARCH", "CONTEXT", "ASSESSMENT", "CONTEXT-DRAFT"] as const;
14
-
15
- export function isSupportedSummaryArtifactType(
16
- artifactType: string,
17
- ): artifactType is (typeof SUPPORTED_SUMMARY_ARTIFACT_TYPES)[number] {
18
- return (SUPPORTED_SUMMARY_ARTIFACT_TYPES as readonly string[]).includes(artifactType);
19
- }
11
+ import {
12
+ executeCompleteMilestone,
13
+ executePlanMilestone,
14
+ executePlanSlice,
15
+ executeReplanSlice,
16
+ executeReassessRoadmap,
17
+ executeSaveGateResult,
18
+ executeSliceComplete,
19
+ executeSummarySave,
20
+ executeTaskComplete,
21
+ executeValidateMilestone,
22
+ } from "../tools/workflow-tool-executors.js";
20
23
 
21
24
  /**
22
25
  * Register an alias tool that shares the same execute function as its canonical counterpart.
@@ -286,63 +289,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
286
289
  // ─── gsd_summary_save (formerly gsd_save_summary) ──────────────────────
287
290
 
288
291
  const summarySaveExecute = async (_toolCallId: string, params: any, _signal: AbortSignal | undefined, _onUpdate: unknown, _ctx: unknown) => {
289
- const dbAvailable = await ensureDbOpen();
290
- if (!dbAvailable) {
291
- return {
292
- content: [{ type: "text" as const, text: "Error: GSD database is not available. Cannot save artifact." }],
293
- details: { operation: "save_summary", error: "db_unavailable" } as any,
294
- };
295
- }
296
- if (!isSupportedSummaryArtifactType(params.artifact_type)) {
297
- return {
298
- content: [{ type: "text" as const, text: `Error: Invalid artifact_type "${params.artifact_type}". Must be one of: ${SUPPORTED_SUMMARY_ARTIFACT_TYPES.join(", ")}` }],
299
- details: { operation: "save_summary", error: "invalid_artifact_type" } as any,
300
- };
301
- }
302
- const contextGuard = shouldBlockContextArtifactSave(
303
- params.artifact_type,
304
- params.milestone_id ?? null,
305
- params.slice_id ?? null,
306
- );
307
- if (contextGuard.block) {
308
- return {
309
- content: [{ type: "text" as const, text: `Error saving artifact: ${contextGuard.reason ?? "context write blocked"}` }],
310
- details: { operation: "save_summary", error: "context_write_blocked" } as any,
311
- };
312
- }
313
- try {
314
- let relativePath: string;
315
- if (params.task_id && params.slice_id) {
316
- relativePath = `milestones/${params.milestone_id}/slices/${params.slice_id}/tasks/${params.task_id}-${params.artifact_type}.md`;
317
- } else if (params.slice_id) {
318
- relativePath = `milestones/${params.milestone_id}/slices/${params.slice_id}/${params.slice_id}-${params.artifact_type}.md`;
319
- } else {
320
- relativePath = `milestones/${params.milestone_id}/${params.milestone_id}-${params.artifact_type}.md`;
321
- }
322
- const { saveArtifactToDb } = await import("../db-writer.js");
323
- await saveArtifactToDb(
324
- {
325
- path: relativePath,
326
- artifact_type: params.artifact_type,
327
- content: params.content,
328
- milestone_id: params.milestone_id,
329
- slice_id: params.slice_id,
330
- task_id: params.task_id,
331
- },
332
- process.cwd(),
333
- );
334
- return {
335
- content: [{ type: "text" as const, text: `Saved ${params.artifact_type} artifact to ${relativePath}` }],
336
- details: { operation: "save_summary", path: relativePath, artifact_type: params.artifact_type } as any,
337
- };
338
- } catch (err) {
339
- const msg = err instanceof Error ? err.message : String(err);
340
- logError("tool", `gsd_summary_save tool failed: ${msg}`, { tool: "gsd_summary_save", error: String(err) });
341
- return {
342
- content: [{ type: "text" as const, text: `Error saving artifact: ${msg}` }],
343
- details: { operation: "save_summary", error: msg } as any,
344
- };
345
- }
292
+ return executeSummarySave(params, process.cwd());
346
293
  };
347
294
 
348
295
  const summarySaveTool = {
@@ -475,38 +422,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
475
422
  // ─── gsd_plan_milestone (gsd_milestone_plan alias) ─────────────────────
476
423
 
477
424
  const planMilestoneExecute = async (_toolCallId: string, params: any, _signal: AbortSignal | undefined, _onUpdate: unknown, _ctx: unknown) => {
478
- const dbAvailable = await ensureDbOpen();
479
- if (!dbAvailable) {
480
- return {
481
- content: [{ type: "text" as const, text: "Error: GSD database is not available. Cannot plan milestone." }],
482
- details: { operation: "plan_milestone", error: "db_unavailable" } as any,
483
- };
484
- }
485
- try {
486
- const { handlePlanMilestone } = await import("../tools/plan-milestone.js");
487
- const result = await handlePlanMilestone(params, process.cwd());
488
- if ("error" in result) {
489
- return {
490
- content: [{ type: "text" as const, text: `Error planning milestone: ${result.error}` }],
491
- details: { operation: "plan_milestone", error: result.error } as any,
492
- };
493
- }
494
- return {
495
- content: [{ type: "text" as const, text: `Planned milestone ${result.milestoneId}` }],
496
- details: {
497
- operation: "plan_milestone",
498
- milestoneId: result.milestoneId,
499
- roadmapPath: result.roadmapPath,
500
- } as any,
501
- };
502
- } catch (err) {
503
- const msg = err instanceof Error ? err.message : String(err);
504
- logError("tool", `plan_milestone tool failed: ${msg}`, { tool: "gsd_plan_milestone", error: String(err) });
505
- return {
506
- content: [{ type: "text" as const, text: `Error planning milestone: ${msg}` }],
507
- details: { operation: "plan_milestone", error: msg } as any,
508
- };
509
- }
425
+ return executePlanMilestone(params, process.cwd());
510
426
  };
511
427
 
512
428
  const planMilestoneTool = {
@@ -568,40 +484,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
568
484
  // ─── gsd_plan_slice (gsd_slice_plan alias) ─────────────────────────────
569
485
 
570
486
  const planSliceExecute = async (_toolCallId: string, params: any, _signal: AbortSignal | undefined, _onUpdate: unknown, _ctx: unknown) => {
571
- const dbAvailable = await ensureDbOpen();
572
- if (!dbAvailable) {
573
- return {
574
- content: [{ type: "text" as const, text: "Error: GSD database is not available. Cannot plan slice." }],
575
- details: { operation: "plan_slice", error: "db_unavailable" } as any,
576
- };
577
- }
578
- try {
579
- const { handlePlanSlice } = await import("../tools/plan-slice.js");
580
- const result = await handlePlanSlice(params, process.cwd());
581
- if ("error" in result) {
582
- return {
583
- content: [{ type: "text" as const, text: `Error planning slice: ${result.error}` }],
584
- details: { operation: "plan_slice", error: result.error } as any,
585
- };
586
- }
587
- return {
588
- content: [{ type: "text" as const, text: `Planned slice ${result.sliceId} (${result.milestoneId})` }],
589
- details: {
590
- operation: "plan_slice",
591
- milestoneId: result.milestoneId,
592
- sliceId: result.sliceId,
593
- planPath: result.planPath,
594
- taskPlanPaths: result.taskPlanPaths,
595
- } as any,
596
- };
597
- } catch (err) {
598
- const msg = err instanceof Error ? err.message : String(err);
599
- logError("tool", `plan_slice tool failed: ${msg}`, { tool: "gsd_plan_slice", error: String(err) });
600
- return {
601
- content: [{ type: "text" as const, text: `Error planning slice: ${msg}` }],
602
- details: { operation: "plan_slice", error: msg } as any,
603
- };
604
- }
487
+ return executePlanSlice(params, process.cwd());
605
488
  };
606
489
 
607
490
  const planSliceTool = {
@@ -717,46 +600,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
717
600
  // ─── gsd_task_complete (gsd_complete_task alias) ────────────────────────
718
601
 
719
602
  const taskCompleteExecute = async (_toolCallId: string, params: any, _signal: AbortSignal | undefined, _onUpdate: unknown, _ctx: unknown) => {
720
- const dbAvailable = await ensureDbOpen();
721
- if (!dbAvailable) {
722
- return {
723
- content: [{ type: "text" as const, text: "Error: GSD database is not available. Cannot complete task." }],
724
- details: { operation: "complete_task", error: "db_unavailable" } as any,
725
- };
726
- }
727
- try {
728
- // Coerce string items to objects for verificationEvidence (#3541).
729
- const coerced = { ...params };
730
- coerced.verificationEvidence = (params.verificationEvidence ?? []).map((v: any) =>
731
- typeof v === "string" ? { command: v, exitCode: -1, verdict: "unknown (coerced from string)", durationMs: 0 } : v,
732
- );
733
-
734
- const { handleCompleteTask } = await import("../tools/complete-task.js");
735
- const result = await handleCompleteTask(coerced, process.cwd());
736
- if ("error" in result) {
737
- return {
738
- content: [{ type: "text" as const, text: `Error completing task: ${result.error}` }],
739
- details: { operation: "complete_task", error: result.error } as any,
740
- };
741
- }
742
- return {
743
- content: [{ type: "text" as const, text: `Completed task ${result.taskId} (${result.sliceId}/${result.milestoneId})` }],
744
- details: {
745
- operation: "complete_task",
746
- taskId: result.taskId,
747
- sliceId: result.sliceId,
748
- milestoneId: result.milestoneId,
749
- summaryPath: result.summaryPath,
750
- } as any,
751
- };
752
- } catch (err) {
753
- const msg = err instanceof Error ? err.message : String(err);
754
- logError("tool", `complete_task tool failed: ${msg}`, { tool: "gsd_task_complete", error: String(err) });
755
- return {
756
- content: [{ type: "text" as const, text: `Error completing task: ${msg}` }],
757
- details: { operation: "complete_task", error: msg } as any,
758
- };
759
- }
603
+ return executeTaskComplete(params, process.cwd());
760
604
  };
761
605
 
762
606
  const taskCompleteTool = {
@@ -809,86 +653,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
809
653
  // ─── gsd_slice_complete (gsd_complete_slice alias) ─────────────────────
810
654
 
811
655
  const sliceCompleteExecute = async (_toolCallId: string, params: any, _signal: AbortSignal | undefined, _onUpdate: unknown, _ctx: unknown) => {
812
- const dbAvailable = await ensureDbOpen();
813
- if (!dbAvailable) {
814
- return {
815
- content: [{ type: "text" as const, text: "Error: GSD database is not available. Cannot complete slice." }],
816
- details: { operation: "complete_slice", error: "db_unavailable" } as any,
817
- };
818
- }
819
- try {
820
- // Coerce string items to objects for fields where LLMs sometimes pass
821
- // plain strings instead of the expected { key, value } shape (#3541).
822
- // Parses "key — value" or "key - value" format when possible.
823
- const splitPair = (s: string): [string, string] => {
824
- const m = s.match(/^(.+?)\s*(?:—|-)\s+(.+)$/);
825
- return m ? [m[1].trim(), m[2].trim()] : [s.trim(), ""];
826
- };
827
- const coerced = { ...params };
828
- // Coerce simple string-array fields: LLMs sometimes pass a plain string
829
- // instead of a single-element array (#3585).
830
- const wrapArray = (v: any): any[] =>
831
- v == null ? [] : Array.isArray(v) ? v : [v];
832
- coerced.provides = wrapArray(params.provides);
833
- coerced.keyFiles = wrapArray(params.keyFiles);
834
- coerced.keyDecisions = wrapArray(params.keyDecisions);
835
- coerced.patternsEstablished = wrapArray(params.patternsEstablished);
836
- coerced.observabilitySurfaces = wrapArray(params.observabilitySurfaces);
837
- coerced.requirementsSurfaced = wrapArray(params.requirementsSurfaced);
838
- coerced.drillDownPaths = wrapArray(params.drillDownPaths);
839
- coerced.affects = wrapArray(params.affects);
840
- coerced.filesModified = wrapArray(params.filesModified).map((f: any) => {
841
- if (typeof f !== "string") return f;
842
- const [path, description] = splitPair(f);
843
- return { path, description };
844
- });
845
- coerced.requires = wrapArray(params.requires).map((r: any) => {
846
- if (typeof r !== "string") return r;
847
- const [slice, provides] = splitPair(r);
848
- return { slice, provides };
849
- });
850
- coerced.requirementsAdvanced = wrapArray(params.requirementsAdvanced).map((r: any) => {
851
- if (typeof r !== "string") return r;
852
- const [id, how] = splitPair(r);
853
- return { id, how };
854
- });
855
- coerced.requirementsValidated = wrapArray(params.requirementsValidated).map((r: any) => {
856
- if (typeof r !== "string") return r;
857
- const [id, proof] = splitPair(r);
858
- return { id, proof };
859
- });
860
- coerced.requirementsInvalidated = wrapArray(params.requirementsInvalidated).map((r: any) => {
861
- if (typeof r !== "string") return r;
862
- const [id, what] = splitPair(r);
863
- return { id, what };
864
- });
865
-
866
- const { handleCompleteSlice } = await import("../tools/complete-slice.js");
867
- const result = await handleCompleteSlice(coerced, process.cwd());
868
- if ("error" in result) {
869
- return {
870
- content: [{ type: "text" as const, text: `Error completing slice: ${result.error}` }],
871
- details: { operation: "complete_slice", error: result.error } as any,
872
- };
873
- }
874
- return {
875
- content: [{ type: "text" as const, text: `Completed slice ${result.sliceId} (${result.milestoneId})` }],
876
- details: {
877
- operation: "complete_slice",
878
- sliceId: result.sliceId,
879
- milestoneId: result.milestoneId,
880
- summaryPath: result.summaryPath,
881
- uatPath: result.uatPath,
882
- } as any,
883
- };
884
- } catch (err) {
885
- const msg = err instanceof Error ? err.message : String(err);
886
- logError("tool", `complete_slice tool failed: ${msg}`, { tool: "gsd_slice_complete", error: String(err) });
887
- return {
888
- content: [{ type: "text" as const, text: `Error completing slice: ${msg}` }],
889
- details: { operation: "complete_slice", error: msg } as any,
890
- };
891
- }
656
+ return executeSliceComplete(params, process.cwd());
892
657
  };
893
658
 
894
659
  const sliceCompleteTool = {
@@ -1073,42 +838,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
1073
838
  // ─── gsd_complete_milestone ────────────────────────────────────────────
1074
839
 
1075
840
  const milestoneCompleteExecute = async (_toolCallId: string, params: any, _signal: AbortSignal | undefined, _onUpdate: unknown, _ctx: unknown) => {
1076
- const dbAvailable = await ensureDbOpen();
1077
- if (!dbAvailable) {
1078
- return {
1079
- content: [{ type: "text" as const, text: "Error: GSD database is not available. Cannot complete milestone." }],
1080
- details: { operation: "complete_milestone", error: "db_unavailable" } as any,
1081
- };
1082
- }
1083
- try {
1084
- // ── Input sanitization: normalize markdown parameters (#3013) ──────
1085
- const { sanitizeCompleteMilestoneParams } = await import("./sanitize-complete-milestone.js");
1086
- const sanitized = sanitizeCompleteMilestoneParams(params);
1087
-
1088
- const { handleCompleteMilestone } = await import("../tools/complete-milestone.js");
1089
- const result = await handleCompleteMilestone(sanitized, process.cwd());
1090
- if ("error" in result) {
1091
- return {
1092
- content: [{ type: "text" as const, text: `Error completing milestone: ${result.error}` }],
1093
- details: { operation: "complete_milestone", error: result.error } as any,
1094
- };
1095
- }
1096
- return {
1097
- content: [{ type: "text" as const, text: `Completed milestone ${result.milestoneId}. Summary written to ${result.summaryPath}` }],
1098
- details: {
1099
- operation: "complete_milestone",
1100
- milestoneId: result.milestoneId,
1101
- summaryPath: result.summaryPath,
1102
- } as any,
1103
- };
1104
- } catch (err) {
1105
- const msg = err instanceof Error ? err.message : String(err);
1106
- logError("tool", `complete_milestone tool failed: ${msg}`, { tool: "gsd_complete_milestone", error: String(err) });
1107
- return {
1108
- content: [{ type: "text" as const, text: `Error completing milestone: ${msg}` }],
1109
- details: { operation: "complete_milestone", error: msg } as any,
1110
- };
1111
- }
841
+ return executeCompleteMilestone(params, process.cwd());
1112
842
  };
1113
843
 
1114
844
  const milestoneCompleteTool = {
@@ -1150,39 +880,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
1150
880
  // ─── gsd_validate_milestone (gsd_milestone_validate alias) ─────────────
1151
881
 
1152
882
  const milestoneValidateExecute = async (_toolCallId: string, params: any, _signal: AbortSignal | undefined, _onUpdate: unknown, _ctx: unknown) => {
1153
- const dbAvailable = await ensureDbOpen();
1154
- if (!dbAvailable) {
1155
- return {
1156
- content: [{ type: "text" as const, text: "Error: GSD database is not available. Cannot validate milestone." }],
1157
- details: { operation: "validate_milestone", error: "db_unavailable" } as any,
1158
- };
1159
- }
1160
- try {
1161
- const { handleValidateMilestone } = await import("../tools/validate-milestone.js");
1162
- const result = await handleValidateMilestone(params, process.cwd());
1163
- if ("error" in result) {
1164
- return {
1165
- content: [{ type: "text" as const, text: `Error validating milestone: ${result.error}` }],
1166
- details: { operation: "validate_milestone", error: result.error } as any,
1167
- };
1168
- }
1169
- return {
1170
- content: [{ type: "text" as const, text: `Validated milestone ${result.milestoneId} — verdict: ${result.verdict}. Written to ${result.validationPath}` }],
1171
- details: {
1172
- operation: "validate_milestone",
1173
- milestoneId: result.milestoneId,
1174
- verdict: result.verdict,
1175
- validationPath: result.validationPath,
1176
- } as any,
1177
- };
1178
- } catch (err) {
1179
- const msg = err instanceof Error ? err.message : String(err);
1180
- logError("tool", `validate_milestone tool failed: ${msg}`, { tool: "gsd_validate_milestone", error: String(err) });
1181
- return {
1182
- content: [{ type: "text" as const, text: `Error validating milestone: ${msg}` }],
1183
- details: { operation: "validate_milestone", error: msg } as any,
1184
- };
1185
- }
883
+ return executeValidateMilestone(params, process.cwd());
1186
884
  };
1187
885
 
1188
886
  const milestoneValidateTool = {
@@ -1219,40 +917,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
1219
917
  // ─── gsd_replan_slice (gsd_slice_replan alias) ─────────────────────────
1220
918
 
1221
919
  const replanSliceExecute = async (_toolCallId: string, params: any, _signal: AbortSignal | undefined, _onUpdate: unknown, _ctx: unknown) => {
1222
- const dbAvailable = await ensureDbOpen();
1223
- if (!dbAvailable) {
1224
- return {
1225
- content: [{ type: "text" as const, text: "Error: GSD database is not available. Cannot replan slice." }],
1226
- details: { operation: "replan_slice", error: "db_unavailable" } as any,
1227
- };
1228
- }
1229
- try {
1230
- const { handleReplanSlice } = await import("../tools/replan-slice.js");
1231
- const result = await handleReplanSlice(params, process.cwd());
1232
- if ("error" in result) {
1233
- return {
1234
- content: [{ type: "text" as const, text: `Error replanning slice: ${result.error}` }],
1235
- details: { operation: "replan_slice", error: result.error } as any,
1236
- };
1237
- }
1238
- return {
1239
- content: [{ type: "text" as const, text: `Replanned slice ${result.sliceId} (${result.milestoneId})` }],
1240
- details: {
1241
- operation: "replan_slice",
1242
- milestoneId: result.milestoneId,
1243
- sliceId: result.sliceId,
1244
- replanPath: result.replanPath,
1245
- planPath: result.planPath,
1246
- } as any,
1247
- };
1248
- } catch (err) {
1249
- const msg = err instanceof Error ? err.message : String(err);
1250
- logError("tool", `replan_slice tool failed: ${msg}`, { tool: "gsd_replan_slice", error: String(err) });
1251
- return {
1252
- content: [{ type: "text" as const, text: `Error replanning slice: ${msg}` }],
1253
- details: { operation: "replan_slice", error: msg } as any,
1254
- };
1255
- }
920
+ return executeReplanSlice(params, process.cwd());
1256
921
  };
1257
922
 
1258
923
  const replanSliceTool = {
@@ -1299,40 +964,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
1299
964
  // ─── gsd_reassess_roadmap (gsd_roadmap_reassess alias) ─────────────────
1300
965
 
1301
966
  const reassessRoadmapExecute = async (_toolCallId: string, params: any, _signal: AbortSignal | undefined, _onUpdate: unknown, _ctx: unknown) => {
1302
- const dbAvailable = await ensureDbOpen();
1303
- if (!dbAvailable) {
1304
- return {
1305
- content: [{ type: "text" as const, text: "Error: GSD database is not available. Cannot reassess roadmap." }],
1306
- details: { operation: "reassess_roadmap", error: "db_unavailable" } as any,
1307
- };
1308
- }
1309
- try {
1310
- const { handleReassessRoadmap } = await import("../tools/reassess-roadmap.js");
1311
- const result = await handleReassessRoadmap(params, process.cwd());
1312
- if ("error" in result) {
1313
- return {
1314
- content: [{ type: "text" as const, text: `Error reassessing roadmap: ${result.error}` }],
1315
- details: { operation: "reassess_roadmap", error: result.error } as any,
1316
- };
1317
- }
1318
- return {
1319
- content: [{ type: "text" as const, text: `Reassessed roadmap for milestone ${result.milestoneId} after ${result.completedSliceId}` }],
1320
- details: {
1321
- operation: "reassess_roadmap",
1322
- milestoneId: result.milestoneId,
1323
- completedSliceId: result.completedSliceId,
1324
- assessmentPath: result.assessmentPath,
1325
- roadmapPath: result.roadmapPath,
1326
- } as any,
1327
- };
1328
- } catch (err) {
1329
- const msg = err instanceof Error ? err.message : String(err);
1330
- logError("tool", `reassess_roadmap tool failed: ${msg}`, { tool: "gsd_reassess_roadmap", error: String(err) });
1331
- return {
1332
- content: [{ type: "text" as const, text: `Error reassessing roadmap: ${msg}` }],
1333
- details: { operation: "reassess_roadmap", error: msg } as any,
1334
- };
1335
- }
967
+ return executeReassessRoadmap(params, process.cwd());
1336
968
  };
1337
969
 
1338
970
  const reassessRoadmapTool = {
@@ -1387,52 +1019,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
1387
1019
  // ─── gsd_save_gate_result ──────────────────────────────────────────────
1388
1020
 
1389
1021
  const saveGateResultExecute = async (_toolCallId: string, params: any, _signal: AbortSignal | undefined, _onUpdate: unknown, _ctx: unknown) => {
1390
- const dbAvailable = await ensureDbOpen();
1391
- if (!dbAvailable) {
1392
- return {
1393
- content: [{ type: "text" as const, text: "Error: GSD database is not available." }],
1394
- details: { operation: "save_gate_result", error: "db_unavailable" } as any,
1395
- };
1396
- }
1397
- const validGates = ["Q3", "Q4", "Q5", "Q6", "Q7", "Q8"];
1398
- if (!validGates.includes(params.gateId)) {
1399
- return {
1400
- content: [{ type: "text" as const, text: `Error: Invalid gateId "${params.gateId}". Must be one of: ${validGates.join(", ")}` }],
1401
- details: { operation: "save_gate_result", error: "invalid_gate_id" } as any,
1402
- };
1403
- }
1404
- const validVerdicts = ["pass", "flag", "omitted"];
1405
- if (!validVerdicts.includes(params.verdict)) {
1406
- return {
1407
- content: [{ type: "text" as const, text: `Error: Invalid verdict "${params.verdict}". Must be one of: ${validVerdicts.join(", ")}` }],
1408
- details: { operation: "save_gate_result", error: "invalid_verdict" } as any,
1409
- };
1410
- }
1411
- try {
1412
- const { saveGateResult } = await import("../gsd-db.js");
1413
- const { invalidateStateCache } = await import("../state.js");
1414
- saveGateResult({
1415
- milestoneId: params.milestoneId,
1416
- sliceId: params.sliceId,
1417
- gateId: params.gateId,
1418
- taskId: params.taskId ?? "",
1419
- verdict: params.verdict,
1420
- rationale: params.rationale,
1421
- findings: params.findings ?? "",
1422
- });
1423
- invalidateStateCache();
1424
- return {
1425
- content: [{ type: "text" as const, text: `Gate ${params.gateId} result saved: verdict=${params.verdict}` }],
1426
- details: { operation: "save_gate_result", gateId: params.gateId, verdict: params.verdict } as any,
1427
- };
1428
- } catch (err) {
1429
- const msg = err instanceof Error ? err.message : String(err);
1430
- logError("tool", `gsd_save_gate_result failed: ${msg}`, { tool: "gsd_save_gate_result", error: String(err) });
1431
- return {
1432
- content: [{ type: "text" as const, text: `Error saving gate result: ${msg}` }],
1433
- details: { operation: "save_gate_result", error: msg } as any,
1434
- };
1435
- }
1022
+ return executeSaveGateResult(params, process.cwd());
1436
1023
  };
1437
1024
 
1438
1025
  const saveGateResultTool = {
@@ -75,12 +75,9 @@ export function resolveProjectRootDbPath(basePath: string): string {
75
75
  return join(basePath, ".gsd", "gsd.db");
76
76
  }
77
77
 
78
- export async function ensureDbOpen(): Promise<boolean> {
78
+ export async function ensureDbOpen(basePath: string = process.cwd()): Promise<boolean> {
79
79
  try {
80
80
  const db = await import("../gsd-db.js");
81
- if (db.isDbAvailable()) return true;
82
-
83
- const basePath = process.cwd();
84
81
  const dbPath = resolveProjectRootDbPath(basePath);
85
82
  const gsdDir = join(basePath, ".gsd");
86
83
 
@@ -194,4 +191,3 @@ export function registerDynamicTools(pi: ExtensionAPI): void {
194
191
  },
195
192
  } as any);
196
193
  }
197
-
@@ -2,8 +2,8 @@
2
2
 
3
3
  import { Type } from "@sinclair/typebox";
4
4
  import type { ExtensionAPI } from "@gsd/pi-coding-agent";
5
-
6
- import { logWarning } from "../workflow-logger.js";
5
+ import { ensureDbOpen } from "./dynamic-tools.js";
6
+ import { executeMilestoneStatus } from "../tools/workflow-tool-executors.js";
7
7
 
8
8
  export function registerQueryTools(pi: ExtensionAPI): void {
9
9
  pi.registerTool({
@@ -21,79 +21,14 @@ export function registerQueryTools(pi: ExtensionAPI): void {
21
21
  milestoneId: Type.String({ description: "Milestone ID to query (e.g. M001)" }),
22
22
  }),
23
23
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
24
- try {
25
- // Open the DB if not already open — safe for read-only use since
26
- // ensureDbOpen() only creates/migrates when .gsd/ has content (#3644).
27
- const { ensureDbOpen } = await import("./dynamic-tools.js");
28
- const dbAvailable = await ensureDbOpen();
29
- const {
30
- getMilestone,
31
- getSliceStatusSummary,
32
- getSliceTaskCounts,
33
- _getAdapter,
34
- } = await import("../gsd-db.js");
35
-
36
- if (!dbAvailable) {
37
- return {
38
- content: [{ type: "text" as const, text: "Error: GSD database is not available." }],
39
- details: { operation: "milestone_status", error: "db_unavailable" } as any,
40
- };
41
- }
42
-
43
- // Wrap all reads in a single transaction for snapshot consistency.
44
- // SQLite WAL mode guarantees reads within a transaction see a single
45
- // consistent snapshot, preventing torn reads from concurrent writes.
46
- const adapter = _getAdapter()!;
47
- adapter.exec("BEGIN"); // eslint-disable-line -- SQLite exec, not child_process
48
- try {
49
- const milestone = getMilestone(params.milestoneId);
50
- if (!milestone) {
51
- adapter.exec("COMMIT"); // eslint-disable-line
52
- return {
53
- content: [{ type: "text" as const, text: `Milestone ${params.milestoneId} not found in database.` }],
54
- details: { operation: "milestone_status", milestoneId: params.milestoneId, found: false } as any,
55
- };
56
- }
57
-
58
- const sliceStatuses = getSliceStatusSummary(params.milestoneId);
59
-
60
- const slices = sliceStatuses.map((s) => {
61
- const counts = getSliceTaskCounts(params.milestoneId, s.id);
62
- return {
63
- id: s.id,
64
- status: s.status,
65
- taskCounts: counts,
66
- };
67
- });
68
-
69
- adapter.exec("COMMIT"); // eslint-disable-line
70
-
71
- const result = {
72
- milestoneId: milestone.id,
73
- title: milestone.title,
74
- status: milestone.status,
75
- createdAt: milestone.created_at,
76
- completedAt: milestone.completed_at,
77
- sliceCount: slices.length,
78
- slices,
79
- };
80
-
81
- return {
82
- content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
83
- details: { operation: "milestone_status", milestoneId: milestone.id, sliceCount: slices.length } as any,
84
- };
85
- } catch (txErr) {
86
- try { adapter.exec("ROLLBACK"); } catch { /* swallow */ } // eslint-disable-line
87
- throw txErr;
88
- }
89
- } catch (err) {
90
- const msg = err instanceof Error ? err.message : String(err);
91
- logWarning("tool", `gsd_milestone_status tool failed: ${msg}`);
24
+ const dbAvailable = await ensureDbOpen();
25
+ if (!dbAvailable) {
92
26
  return {
93
- content: [{ type: "text" as const, text: `Error querying milestone status: ${msg}` }],
94
- details: { operation: "milestone_status", error: msg } as any,
27
+ content: [{ type: "text", text: "Error: GSD database is not available. Cannot read milestone status." }],
28
+ details: { operation: "milestone_status", error: "db_unavailable" },
95
29
  };
96
30
  }
31
+ return executeMilestoneStatus(params);
97
32
  },
98
33
  });
99
34
  }