gsd-pi 2.70.0-dev.55a1c68 → 2.70.0-dev.8f4d92b

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 (82) hide show
  1. package/dist/loader.js +4 -0
  2. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +150 -2
  3. package/dist/resources/extensions/gsd/auto-prompts.js +7 -3
  4. package/dist/resources/extensions/gsd/auto.js +12 -8
  5. package/dist/resources/extensions/gsd/commands-handlers.js +22 -8
  6. package/dist/resources/extensions/gsd/doctor-engine-checks.js +12 -0
  7. package/dist/resources/extensions/gsd/doctor-format.js +2 -0
  8. package/dist/resources/extensions/gsd/guided-flow.js +19 -9
  9. package/dist/resources/extensions/gsd/pre-execution-checks.js +2 -2
  10. package/dist/resources/extensions/gsd/workflow-mcp.js +11 -0
  11. package/dist/update-check.d.ts +1 -0
  12. package/dist/update-check.js +30 -27
  13. package/dist/update-cmd.js +3 -11
  14. package/dist/web/standalone/.next/BUILD_ID +1 -1
  15. package/dist/web/standalone/.next/app-path-routes-manifest.json +11 -11
  16. package/dist/web/standalone/.next/build-manifest.json +2 -2
  17. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  18. package/dist/web/standalone/.next/required-server-files.json +1 -1
  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 +11 -11
  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/dist/web/standalone/server.js +1 -1
  48. package/package.json +1 -1
  49. package/packages/mcp-server/dist/workflow-tools.d.ts +2 -0
  50. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  51. package/packages/mcp-server/dist/workflow-tools.js +35 -3
  52. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  53. package/packages/mcp-server/src/import-candidates.test.ts +48 -0
  54. package/packages/mcp-server/src/workflow-tools.ts +34 -1
  55. package/packages/pi-agent-core/dist/agent.d.ts +8 -0
  56. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  57. package/packages/pi-agent-core/dist/agent.js +3 -0
  58. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  59. package/packages/pi-agent-core/src/agent.test.ts +82 -0
  60. package/packages/pi-agent-core/src/agent.ts +12 -0
  61. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  62. package/packages/pi-coding-agent/dist/core/sdk.js +10 -0
  63. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  64. package/packages/pi-coding-agent/src/core/sdk.ts +8 -0
  65. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +227 -2
  66. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +172 -0
  67. package/src/resources/extensions/gsd/auto-prompts.ts +7 -3
  68. package/src/resources/extensions/gsd/auto.ts +12 -8
  69. package/src/resources/extensions/gsd/commands-handlers.ts +22 -7
  70. package/src/resources/extensions/gsd/doctor-engine-checks.ts +14 -0
  71. package/src/resources/extensions/gsd/doctor-format.ts +1 -0
  72. package/src/resources/extensions/gsd/doctor-types.ts +1 -0
  73. package/src/resources/extensions/gsd/guided-flow.ts +23 -8
  74. package/src/resources/extensions/gsd/pre-execution-checks.ts +2 -2
  75. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +43 -0
  76. package/src/resources/extensions/gsd/tests/pre-exec-backtick-strip.test.ts +48 -1
  77. package/src/resources/extensions/gsd/tests/resource-loader-import-path.test.ts +8 -7
  78. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +87 -1
  79. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +25 -0
  80. package/src/resources/extensions/gsd/workflow-mcp.ts +15 -0
  81. /package/dist/web/standalone/.next/static/{0CnmwCBOy-QNRFzdWLB7Q → j_Ae_qOKzzIlA6oFOxVx4}/_buildManifest.js +0 -0
  82. /package/dist/web/standalone/.next/static/{0CnmwCBOy-QNRFzdWLB7Q → j_Ae_qOKzzIlA6oFOxVx4}/_ssgManifest.js +0 -0
@@ -48,6 +48,7 @@ import { DISCUSS_TOOLS_ALLOWLIST } from "./constants.js";
48
48
  import {
49
49
  getWorkflowTransportSupportError,
50
50
  getRequiredWorkflowToolsForGuidedUnit,
51
+ supportsStructuredQuestions,
51
52
  } from "./workflow-mcp.js";
52
53
  import {
53
54
  runPreparation,
@@ -367,6 +368,20 @@ async function dispatchWorkflow(
367
368
  }
368
369
  }
369
370
 
371
+ function getStructuredQuestionsAvailability(
372
+ pi: ExtensionAPI,
373
+ ctx: ExtensionContext | undefined,
374
+ ): "true" | "false" {
375
+ if (!ctx) return "false";
376
+
377
+ const provider = ctx.model?.provider;
378
+ const authMode = provider ? ctx.modelRegistry.getProviderAuthMode(provider) : undefined;
379
+ return supportsStructuredQuestions(pi.getActiveTools(), {
380
+ authMode,
381
+ baseUrl: ctx.model?.baseUrl,
382
+ }) ? "true" : "false";
383
+ }
384
+
370
385
  /**
371
386
  * Resolve a model ID string to a model object from available models.
372
387
  * Handles "provider/model" and bare ID formats.
@@ -739,7 +754,7 @@ export async function showDiscuss(
739
754
 
740
755
  if (choice === "discuss_draft") {
741
756
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
742
- const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
757
+ const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
743
758
  const basePrompt = loadPrompt("guided-discuss-milestone", {
744
759
  milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
745
760
  commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
@@ -752,7 +767,7 @@ export async function showDiscuss(
752
767
  await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
753
768
  } else if (choice === "discuss_fresh") {
754
769
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
755
- const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
770
+ const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
756
771
  pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId: mid, step: false, createdAt: Date.now() });
757
772
  await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
758
773
  milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
@@ -910,7 +925,7 @@ export async function showDiscuss(
910
925
  if (confirm !== "rediscuss") continue;
911
926
  }
912
927
 
913
- const sqAvail = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
928
+ const sqAvail = getStructuredQuestionsAvailability(pi, ctx);
914
929
  const prompt = await buildDiscussSlicePrompt(mid, chosen.id, chosen.title, basePath, { rediscuss: isRediscuss, structuredQuestionsAvailable: sqAvail });
915
930
  await dispatchWorkflow(pi, prompt, "gsd-discuss", ctx, "discuss-slice");
916
931
 
@@ -1020,7 +1035,7 @@ async function dispatchDiscussForMilestone(
1020
1035
  ].join("\n")
1021
1036
  : "";
1022
1037
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
1023
- const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
1038
+ const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
1024
1039
  const basePrompt = loadPrompt("guided-discuss-milestone", {
1025
1040
  milestoneId: mid,
1026
1041
  milestoneTitle,
@@ -1461,7 +1476,7 @@ export async function showSmartEntry(
1461
1476
 
1462
1477
  if (choice === "discuss_draft") {
1463
1478
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
1464
- const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
1479
+ const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
1465
1480
  const basePrompt = loadPrompt("guided-discuss-milestone", {
1466
1481
  milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
1467
1482
  commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
@@ -1474,7 +1489,7 @@ export async function showSmartEntry(
1474
1489
  await dispatchWorkflow(pi, seed, "gsd-discuss", ctx, "discuss-milestone");
1475
1490
  } else if (choice === "discuss_fresh") {
1476
1491
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
1477
- const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
1492
+ const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
1478
1493
  pendingAutoStartMap.set(basePath, { ctx, pi, basePath, milestoneId, step: stepMode, createdAt: Date.now() });
1479
1494
  await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
1480
1495
  milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
@@ -1572,7 +1587,7 @@ export async function showSmartEntry(
1572
1587
  }), "gsd-run", ctx, "plan-milestone");
1573
1588
  } else if (choice === "discuss") {
1574
1589
  const discussMilestoneTemplates = inlineTemplate("context", "Context");
1575
- const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
1590
+ const structuredQuestionsAvailable = getStructuredQuestionsAvailability(pi, ctx);
1576
1591
  await dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
1577
1592
  milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
1578
1593
  commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
@@ -1712,7 +1727,7 @@ export async function showSmartEntry(
1712
1727
  }),
1713
1728
  }), "gsd-run", ctx, "plan-slice");
1714
1729
  } else if (choice === "discuss") {
1715
- const sqAvail = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
1730
+ const sqAvail = getStructuredQuestionsAvailability(pi, ctx);
1716
1731
  await dispatchWorkflow(pi, await buildDiscussSlicePrompt(milestoneId, sliceId, sliceTitle, basePath, { rediscuss: hasContext, structuredQuestionsAvailable: sqAvail }), "gsd-run", ctx, "discuss-slice");
1717
1732
  } else if (choice === "research") {
1718
1733
  const researchTemplates = inlineTemplate("research", "Research");
@@ -263,9 +263,9 @@ function extractPathFromAnnotation(raw: string): string {
263
263
  const trimmed = raw.trim();
264
264
  if (!trimmed) return trimmed;
265
265
 
266
- const backtickMatch = trimmed.match(/^`([^`]+)`(?:\s+[—–-]\s+.*)?$/);
266
+ const backtickMatch = trimmed.match(/^(`+)([^`]+)\1(?:(?:\s+[—–-]\s+.+)|(?:\s+\([^()]+\)))?$/);
267
267
  if (backtickMatch) {
268
- return backtickMatch[1].trim();
268
+ return backtickMatch[2].trim();
269
269
  }
270
270
 
271
271
  const annotatedMatch = trimmed.match(/^(.+?)\s+[—–-]\s+.+$/);
@@ -0,0 +1,43 @@
1
+ import { afterEach, test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { closeDatabase } from "../gsd-db.ts";
4
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { tmpdir } from "node:os";
7
+ import { filterDoctorIssues } from "../doctor-format.ts";
8
+ import { checkEngineHealth } from "../doctor-engine-checks.ts";
9
+
10
+ afterEach(() => {
11
+ closeDatabase();
12
+ });
13
+
14
+ test("filterDoctorIssues keeps project and environment issues in scoped reports", () => {
15
+ const issues = [
16
+ { severity: "error", code: "env_dependencies", scope: "project", unitId: "environment", message: "node_modules missing", fixable: false },
17
+ { severity: "warning", code: "db_unavailable", scope: "project", unitId: "project", message: "DB unavailable", fixable: false },
18
+ { severity: "warning", code: "state_file_missing", scope: "slice", unitId: "M016/S01", message: "slice warning", fixable: false },
19
+ ] as const;
20
+
21
+ const filtered = filterDoctorIssues([...issues], { scope: "M016", includeWarnings: true });
22
+ assert.deepEqual(
23
+ filtered.map((issue) => issue.unitId),
24
+ ["environment", "project", "M016/S01"],
25
+ );
26
+ });
27
+
28
+ test("checkEngineHealth reports db_unavailable when gsd.db exists but the DB is closed", async (t) => {
29
+ const base = mkdtempSync(join(tmpdir(), "gsd-doctor-db-unavailable-"));
30
+ t.after(() => rmSync(base, { recursive: true, force: true }));
31
+
32
+ const gsdDir = join(base, ".gsd");
33
+ mkdirSync(gsdDir, { recursive: true });
34
+ writeFileSync(join(gsdDir, "gsd.db"), "");
35
+
36
+ const issues: any[] = [];
37
+ await checkEngineHealth(base, issues, []);
38
+
39
+ const dbIssue = issues.find((issue) => issue.code === "db_unavailable");
40
+ assert.ok(dbIssue, "doctor should surface degraded DB mode when a DB file exists");
41
+ assert.equal(dbIssue.unitId, "project");
42
+ assert.equal(dbIssue.file, ".gsd/gsd.db");
43
+ });
@@ -12,7 +12,7 @@
12
12
  import { describe, it } from 'node:test'
13
13
  import assert from 'node:assert/strict'
14
14
  import { normalizeFilePath, checkFilePathConsistency } from '../pre-execution-checks.ts'
15
- import { readFileSync } from 'node:fs'
15
+ import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
16
16
  import { resolve } from 'node:path'
17
17
 
18
18
  const src = readFileSync(
@@ -25,6 +25,11 @@ describe('normalizeFilePath backtick stripping (#3649)', () => {
25
25
  assert.equal(normalizeFilePath('`src/foo.ts`'), 'src/foo.ts')
26
26
  })
27
27
 
28
+ it('strips doubled backticks and trailing notes from file paths', () => {
29
+ assert.equal(normalizeFilePath('``src/foo.ts`` - current state'), 'src/foo.ts')
30
+ assert.equal(normalizeFilePath('``src/foo.ts`` (current state)'), 'src/foo.ts')
31
+ })
32
+
28
33
  it('strips backticks even when mixed with other normalization', () => {
29
34
  assert.equal(normalizeFilePath('`./src//bar.ts`'), 'src/bar.ts')
30
35
  })
@@ -66,3 +71,45 @@ describe('checkFilePathConsistency checks task.inputs not task.files (#3626)', (
66
71
  )
67
72
  })
68
73
  })
74
+
75
+ describe('checkFilePathConsistency handles doubled-backtick annotations (#3892)', () => {
76
+ it('accepts existing files when task.inputs include doubled-backtick notes', () => {
77
+ const task = {
78
+ milestone_id: 'M001',
79
+ slice_id: 'S01',
80
+ id: 'T01',
81
+ title: 'Test Task',
82
+ status: 'pending',
83
+ one_liner: '',
84
+ narrative: '',
85
+ verification_result: '',
86
+ duration: '',
87
+ completed_at: null,
88
+ blocker_discovered: false,
89
+ deviations: '',
90
+ known_issues: '',
91
+ key_files: [],
92
+ key_decisions: [],
93
+ full_summary_md: '',
94
+ description: '',
95
+ estimate: '',
96
+ files: [],
97
+ verify: '',
98
+ inputs: ['``src/foo.ts`` (current state)'],
99
+ expected_output: [],
100
+ observability_impact: '',
101
+ full_plan_md: '',
102
+ sequence: 0,
103
+ }
104
+
105
+ const tmp = resolve(process.cwd(), '.tmp-pre-exec-3892')
106
+ try {
107
+ mkdirSync(resolve(tmp, 'src'), { recursive: true })
108
+ writeFileSync(resolve(tmp, 'src', 'foo.ts'), '// ok')
109
+ const results = checkFilePathConsistency([task as any], tmp)
110
+ assert.deepEqual(results, [])
111
+ } finally {
112
+ rmSync(tmp, { recursive: true, force: true })
113
+ }
114
+ })
115
+ })
@@ -22,16 +22,17 @@ describe("resource-loader import path", () => {
22
22
  );
23
23
  });
24
24
 
25
- test("uses createRequire to resolve resource-loader from package root", () => {
26
- // The fix uses createRequire to find gsd-pi/package.json, then imports
27
- // dist/resource-loader.js from there — works in both source and deployed.
25
+ test("uses GSD_PKG_ROOT to resolve resource-loader from package root", () => {
26
+ // The fix uses GSD_PKG_ROOT (set by loader.ts) to construct an absolute
27
+ // file URL to dist/resource-loader.js — works in both source and deployed,
28
+ // and on Windows where raw paths fail with ERR_UNSUPPORTED_ESM_URL_SCHEME.
28
29
  assert.ok(
29
- autoSrc.includes('createRequire(import.meta.url)'),
30
- "auto.ts should use createRequire to resolve resource-loader",
30
+ autoSrc.includes('process.env.GSD_PKG_ROOT'),
31
+ "auto.ts should use GSD_PKG_ROOT to resolve resource-loader",
31
32
  );
32
33
  assert.ok(
33
- autoSrc.includes('resolve("gsd-pi/package.json")'),
34
- "auto.ts should resolve gsd-pi package root via package.json",
34
+ autoSrc.includes('pathToFileURL'),
35
+ "auto.ts should convert path to file URL for cross-platform import()",
35
36
  );
36
37
  });
37
38
  });
@@ -9,10 +9,11 @@ import { deriveState, isValidationTerminal } from "../state.ts";
9
9
  import { resolveExpectedArtifactPath, diagnoseExpectedArtifact } from "../auto-artifact-paths.ts";
10
10
  import { verifyExpectedArtifact, buildLoopRemediationSteps } from "../auto-recovery.ts";
11
11
  import { resolveDispatch, type DispatchContext } from "../auto-dispatch.ts";
12
- import { buildValidateMilestonePrompt } from "../auto-prompts.ts";
12
+ import { buildCompleteMilestonePrompt, buildValidateMilestonePrompt } from "../auto-prompts.ts";
13
13
  import type { GSDState } from "../types.ts";
14
14
  import { clearPathCache } from "../paths.ts";
15
15
  import { clearParseCache } from "../files.ts";
16
+ import { closeDatabase, insertMilestone, insertSlice, openDatabase } from "../gsd-db.ts";
16
17
 
17
18
  // ─── Helpers ──────────────────────────────────────────────────────────────
18
19
 
@@ -25,9 +26,15 @@ function makeTmpBase(): string {
25
26
  function cleanup(base: string): void {
26
27
  clearPathCache();
27
28
  clearParseCache();
29
+ closeDatabase();
28
30
  try { rmSync(base, { recursive: true, force: true }); } catch { /* */ }
29
31
  }
30
32
 
33
+ function openTestDb(base: string): void {
34
+ const dbPath = join(base, ".gsd", "gsd.db");
35
+ assert.equal(openDatabase(dbPath), true, "test DB should open");
36
+ }
37
+
31
38
  function writeRoadmap(base: string, mid: string, content: string): void {
32
39
  const dir = join(base, ".gsd", "milestones", mid);
33
40
  mkdirSync(dir, { recursive: true });
@@ -218,6 +225,85 @@ test("buildValidateMilestonePrompt inlines ASSESSMENT evidence instead of UAT sp
218
225
  }
219
226
  });
220
227
 
228
+ test("buildCompleteMilestonePrompt skips skipped slices from DB-backed summary inlining", async () => {
229
+ const base = makeTmpBase();
230
+ try {
231
+ writeRoadmap(base, "M001", `# M001: Test Milestone
232
+
233
+ ## Vision
234
+ Test
235
+
236
+ ## Success Criteria
237
+ - It works
238
+
239
+ ## Slices
240
+
241
+ - [x] **S01: First slice** \`risk:low\` \`depends:[]\`
242
+ > Done
243
+ - [ ] **S02: Skipped slice** \`risk:low\` \`depends:[]\`
244
+ > Intentionally skipped
245
+
246
+ ## Boundary Map
247
+
248
+ | From | To | Produces | Consumes |
249
+ |------|-----|----------|----------|
250
+ | S01 | terminal | output | nothing |
251
+ `);
252
+ openTestDb(base);
253
+ insertMilestone({ id: "M001", title: "Test Milestone", status: "active" });
254
+ insertSlice({ id: "S01", milestoneId: "M001", title: "First slice", status: "complete", depends: [], sequence: 1 });
255
+ insertSlice({ id: "S02", milestoneId: "M001", title: "Skipped slice", status: "skipped", depends: [], sequence: 2 });
256
+ writeSliceSummary(base, "M001", "S01", "# S01 Summary\nDelivered.");
257
+
258
+ const prompt = await buildCompleteMilestonePrompt("M001", "Test Milestone", base);
259
+ assert.match(prompt, /S01 Summary/i, "prompt should inline non-skipped slice summaries");
260
+ assert.doesNotMatch(prompt, /### S02 Summary/i, "prompt should not inline skipped slice summaries");
261
+ assert.doesNotMatch(prompt, /not found — file does not exist yet/i, "prompt should not emit skipped-slice missing-file placeholders");
262
+ } finally {
263
+ cleanup(base);
264
+ }
265
+ });
266
+
267
+ test("buildValidateMilestonePrompt skips skipped slices from DB-backed summary inlining", async () => {
268
+ const base = makeTmpBase();
269
+ try {
270
+ writeRoadmap(base, "M001", `# M001: Test Milestone
271
+
272
+ ## Vision
273
+ Test
274
+
275
+ ## Success Criteria
276
+ - It works
277
+
278
+ ## Slices
279
+
280
+ - [x] **S01: First slice** \`risk:low\` \`depends:[]\`
281
+ > Done
282
+ - [ ] **S02: Skipped slice** \`risk:low\` \`depends:[]\`
283
+ > Intentionally skipped
284
+
285
+ ## Boundary Map
286
+
287
+ | From | To | Produces | Consumes |
288
+ |------|-----|----------|----------|
289
+ | S01 | terminal | output | nothing |
290
+ `);
291
+ openTestDb(base);
292
+ insertMilestone({ id: "M001", title: "Test Milestone", status: "active" });
293
+ insertSlice({ id: "S01", milestoneId: "M001", title: "First slice", status: "complete", depends: [], sequence: 1 });
294
+ insertSlice({ id: "S02", milestoneId: "M001", title: "Skipped slice", status: "skipped", depends: [], sequence: 2 });
295
+ writeSliceSummary(base, "M001", "S01", "# S01 Summary\nDelivered.");
296
+ writeSliceAssessment(base, "M001", "S01", "---\nverdict: PASS\n---\n# Assessment\nEvidence captured.");
297
+
298
+ const prompt = await buildValidateMilestonePrompt("M001", "Test Milestone", base);
299
+ assert.match(prompt, /S01 Summary/i, "prompt should inline non-skipped slice summaries");
300
+ assert.doesNotMatch(prompt, /### S02 Summary/i, "prompt should not inline skipped slice summaries");
301
+ assert.doesNotMatch(prompt, /not found — file does not exist yet/i, "prompt should not emit skipped-slice missing-file placeholders");
302
+ } finally {
303
+ cleanup(base);
304
+ }
305
+ });
306
+
221
307
  // ─── Dispatch rule ────────────────────────────────────────────────────────
222
308
 
223
309
  test("dispatch rule matches validating-milestone phase", async () => {
@@ -13,6 +13,7 @@ import {
13
13
  getWorkflowTransportSupportError,
14
14
  getRequiredWorkflowToolsForAutoUnit,
15
15
  getRequiredWorkflowToolsForGuidedUnit,
16
+ supportsStructuredQuestions,
16
17
  usesWorkflowMcpTransport,
17
18
  } from "../workflow-mcp.ts";
18
19
 
@@ -291,6 +292,30 @@ test("usesWorkflowMcpTransport matches local externalCli providers", () => {
291
292
  assert.equal(usesWorkflowMcpTransport("oauth", "local://custom"), false);
292
293
  });
293
294
 
295
+ test("supportsStructuredQuestions disables structured ask flow on workflow MCP transports", () => {
296
+ assert.equal(
297
+ supportsStructuredQuestions(["ask_user_questions"], {
298
+ authMode: "externalCli",
299
+ baseUrl: "local://claude-code",
300
+ }),
301
+ false,
302
+ );
303
+ assert.equal(
304
+ supportsStructuredQuestions(["ask_user_questions"], {
305
+ authMode: "oauth",
306
+ baseUrl: "https://api.anthropic.com",
307
+ }),
308
+ true,
309
+ );
310
+ assert.equal(
311
+ supportsStructuredQuestions([], {
312
+ authMode: "oauth",
313
+ baseUrl: "https://api.anthropic.com",
314
+ }),
315
+ false,
316
+ );
317
+ });
318
+
294
319
  test("transport compatibility passes when required tools fit current MCP surface", () => {
295
320
  const error = getWorkflowTransportSupportError(
296
321
  "claude-code",
@@ -348,6 +348,21 @@ export function usesWorkflowMcpTransport(
348
348
  return authMode === "externalCli" && typeof baseUrl === "string" && baseUrl.startsWith("local://");
349
349
  }
350
350
 
351
+ export function supportsStructuredQuestions(
352
+ activeTools: string[],
353
+ options: Pick<WorkflowCapabilityOptions, "authMode" | "baseUrl"> = {},
354
+ ): boolean {
355
+ if (!activeTools.includes("ask_user_questions")) return false;
356
+
357
+ // Workflow MCP currently exposes ask_user_questions via MCP form elicitation.
358
+ // Local external CLI transports such as Claude Code can invoke the tool, but
359
+ // do not reliably complete that elicitation round-trip yet, so guided discuss
360
+ // prompts must fall back to plain-text questioning.
361
+ if (usesWorkflowMcpTransport(options.authMode, options.baseUrl)) return false;
362
+
363
+ return true;
364
+ }
365
+
351
366
  export function getWorkflowTransportSupportError(
352
367
  provider: string | undefined,
353
368
  requiredTools: string[],