gsd-pi 2.44.0-dev.0b97ffd → 2.44.0-dev.73f2fd5

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 (173) hide show
  1. package/dist/resources/extensions/gsd/auto/infra-errors.js +3 -0
  2. package/dist/resources/extensions/gsd/auto/phases.js +36 -36
  3. package/dist/resources/extensions/gsd/auto-prompts.js +24 -1
  4. package/dist/resources/extensions/gsd/auto-timers.js +57 -3
  5. package/dist/resources/extensions/gsd/auto-worktree-sync.js +4 -0
  6. package/dist/resources/extensions/gsd/auto-worktree.js +9 -6
  7. package/dist/resources/extensions/gsd/auto.js +30 -3
  8. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +156 -0
  9. package/dist/resources/extensions/gsd/bootstrap/system-context.js +46 -12
  10. package/dist/resources/extensions/gsd/commands/catalog.js +6 -1
  11. package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -0
  12. package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
  13. package/dist/resources/extensions/gsd/commands-mcp-status.js +187 -0
  14. package/dist/resources/extensions/gsd/db-writer.js +34 -16
  15. package/dist/resources/extensions/gsd/doctor.js +8 -0
  16. package/dist/resources/extensions/gsd/git-service.js +8 -3
  17. package/dist/resources/extensions/gsd/gsd-db.js +12 -1
  18. package/dist/resources/extensions/gsd/markdown-renderer.js +1 -1
  19. package/dist/resources/extensions/gsd/preferences.js +9 -1
  20. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -4
  21. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  22. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -6
  23. package/dist/resources/extensions/gsd/prompts/replan-slice.md +3 -14
  24. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +7 -37
  25. package/dist/resources/extensions/gsd/provider-error-pause.js +7 -0
  26. package/dist/resources/extensions/gsd/state.js +19 -2
  27. package/dist/resources/extensions/gsd/tools/plan-slice.js +1 -0
  28. package/dist/resources/extensions/gsd/tools/plan-task.js +1 -0
  29. package/dist/resources/extensions/gsd/tools/replan-slice.js +2 -0
  30. package/dist/resources/extensions/gsd/tools/validate-milestone.js +88 -0
  31. package/dist/resources/extensions/gsd/worktree-resolver.js +6 -0
  32. package/dist/resources/extensions/mcp-client/index.js +14 -0
  33. package/dist/web/standalone/.next/BUILD_ID +1 -1
  34. package/dist/web/standalone/.next/app-path-routes-manifest.json +16 -16
  35. package/dist/web/standalone/.next/build-manifest.json +2 -2
  36. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  37. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  38. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  46. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/index.html +1 -1
  54. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app-paths-manifest.json +16 -16
  61. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  62. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  63. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  64. package/package.json +1 -1
  65. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +3 -1
  66. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  67. package/packages/pi-coding-agent/dist/core/auth-storage.js +15 -1
  68. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  69. package/packages/pi-coding-agent/dist/core/local-model-check.d.ts +15 -0
  70. package/packages/pi-coding-agent/dist/core/local-model-check.d.ts.map +1 -0
  71. package/packages/pi-coding-agent/dist/core/local-model-check.js +41 -0
  72. package/packages/pi-coding-agent/dist/core/local-model-check.js.map +1 -0
  73. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +11 -0
  74. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  75. package/packages/pi-coding-agent/dist/core/model-registry.js +20 -1
  76. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  77. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
  78. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  79. package/packages/pi-coding-agent/dist/core/settings-manager.js +6 -0
  80. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  81. package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
  82. package/packages/pi-coding-agent/dist/main.js +17 -0
  83. package/packages/pi-coding-agent/dist/main.js.map +1 -1
  84. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.d.ts +2 -0
  85. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.d.ts.map +1 -0
  86. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.js +32 -0
  87. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/timestamp.test.js.map +1 -0
  88. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +3 -1
  89. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  90. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +8 -1
  91. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  92. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  93. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  94. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +12 -0
  95. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  96. package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.d.ts +15 -0
  97. package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.d.ts.map +1 -0
  98. package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.js +40 -0
  99. package/packages/pi-coding-agent/dist/modes/interactive/components/timestamp.js.map +1 -0
  100. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  101. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +4 -1
  102. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  103. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts +5 -2
  104. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  105. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js +13 -2
  106. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  108. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +17 -8
  109. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  110. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  111. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +7 -3
  112. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  113. package/packages/pi-coding-agent/src/core/auth-storage.ts +15 -1
  114. package/packages/pi-coding-agent/src/core/local-model-check.ts +45 -0
  115. package/packages/pi-coding-agent/src/core/model-registry.ts +21 -1
  116. package/packages/pi-coding-agent/src/core/settings-manager.ts +9 -0
  117. package/packages/pi-coding-agent/src/main.ts +19 -0
  118. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/timestamp.test.ts +38 -0
  119. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +10 -0
  120. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +15 -0
  121. package/packages/pi-coding-agent/src/modes/interactive/components/timestamp.ts +48 -0
  122. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +3 -1
  123. package/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +18 -3
  124. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +16 -7
  125. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +8 -1
  126. package/src/resources/extensions/gsd/auto/infra-errors.ts +3 -0
  127. package/src/resources/extensions/gsd/auto/phases.ts +45 -48
  128. package/src/resources/extensions/gsd/auto-prompts.ts +24 -1
  129. package/src/resources/extensions/gsd/auto-timers.ts +64 -3
  130. package/src/resources/extensions/gsd/auto-worktree-sync.ts +5 -0
  131. package/src/resources/extensions/gsd/auto-worktree.ts +9 -6
  132. package/src/resources/extensions/gsd/auto.ts +37 -3
  133. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +148 -0
  134. package/src/resources/extensions/gsd/bootstrap/system-context.ts +48 -11
  135. package/src/resources/extensions/gsd/commands/catalog.ts +6 -1
  136. package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -0
  137. package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
  138. package/src/resources/extensions/gsd/commands-mcp-status.ts +247 -0
  139. package/src/resources/extensions/gsd/db-writer.ts +39 -17
  140. package/src/resources/extensions/gsd/doctor.ts +7 -1
  141. package/src/resources/extensions/gsd/git-service.ts +6 -2
  142. package/src/resources/extensions/gsd/gsd-db.ts +16 -1
  143. package/src/resources/extensions/gsd/markdown-renderer.ts +1 -1
  144. package/src/resources/extensions/gsd/preferences.ts +11 -1
  145. package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -4
  146. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  147. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -6
  148. package/src/resources/extensions/gsd/prompts/replan-slice.md +3 -14
  149. package/src/resources/extensions/gsd/prompts/validate-milestone.md +7 -37
  150. package/src/resources/extensions/gsd/provider-error-pause.ts +9 -0
  151. package/src/resources/extensions/gsd/state.ts +19 -1
  152. package/src/resources/extensions/gsd/tests/auto-pr-bugs.test.ts +88 -0
  153. package/src/resources/extensions/gsd/tests/completed-units-metrics-sync.test.ts +114 -0
  154. package/src/resources/extensions/gsd/tests/db-writer.test.ts +79 -0
  155. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +60 -0
  156. package/src/resources/extensions/gsd/tests/est-annotation-timeout.test.ts +120 -0
  157. package/src/resources/extensions/gsd/tests/infra-error.test.ts +20 -2
  158. package/src/resources/extensions/gsd/tests/knowledge.test.ts +89 -0
  159. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +103 -0
  160. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +66 -0
  161. package/src/resources/extensions/gsd/tests/preferences.test.ts +27 -0
  162. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +11 -7
  163. package/src/resources/extensions/gsd/tests/stop-auto-merge-back.test.ts +67 -0
  164. package/src/resources/extensions/gsd/tests/terminated-transient.test.ts +49 -0
  165. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +2 -1
  166. package/src/resources/extensions/gsd/tools/plan-slice.ts +2 -0
  167. package/src/resources/extensions/gsd/tools/plan-task.ts +2 -0
  168. package/src/resources/extensions/gsd/tools/replan-slice.ts +3 -0
  169. package/src/resources/extensions/gsd/tools/validate-milestone.ts +127 -0
  170. package/src/resources/extensions/gsd/worktree-resolver.ts +7 -0
  171. package/src/resources/extensions/mcp-client/index.ts +20 -0
  172. /package/dist/web/standalone/.next/static/{alS4hoANx0TK4UVZY27da → kxxAA66bah_yhPYqLBHE2}/_buildManifest.js +0 -0
  173. /package/dist/web/standalone/.next/static/{alS4hoANx0TK4UVZY27da → kxxAA66bah_yhPYqLBHE2}/_ssgManifest.js +0 -0
@@ -1307,6 +1307,12 @@ export async function buildCompleteMilestonePrompt(
1307
1307
  roadmapPath: roadmapRel,
1308
1308
  inlinedContext,
1309
1309
  milestoneSummaryPath,
1310
+ skillActivation: buildSkillActivationBlock({
1311
+ base,
1312
+ milestoneId: mid,
1313
+ milestoneTitle: midTitle,
1314
+ extraContext: [inlinedContext],
1315
+ }),
1310
1316
  });
1311
1317
  }
1312
1318
 
@@ -1390,6 +1396,12 @@ export async function buildValidateMilestonePrompt(
1390
1396
  inlinedContext,
1391
1397
  validationPath: validationOutputPath,
1392
1398
  remediationRound: String(remediationRound),
1399
+ skillActivation: buildSkillActivationBlock({
1400
+ base,
1401
+ milestoneId: mid,
1402
+ milestoneTitle: midTitle,
1403
+ extraContext: [inlinedContext],
1404
+ }),
1393
1405
  });
1394
1406
  }
1395
1407
 
@@ -1500,6 +1512,12 @@ export async function buildRunUatPrompt(
1500
1512
  uatResultPath,
1501
1513
  uatType,
1502
1514
  inlinedContext,
1515
+ skillActivation: buildSkillActivationBlock({
1516
+ base,
1517
+ milestoneId: mid,
1518
+ sliceId,
1519
+ extraContext: [inlinedContext],
1520
+ }),
1503
1521
  });
1504
1522
  }
1505
1523
 
@@ -1552,11 +1570,16 @@ export async function buildReassessRoadmapPrompt(
1552
1570
  milestoneTitle: midTitle,
1553
1571
  completedSliceId,
1554
1572
  roadmapPath: roadmapRel,
1555
- completedSliceSummaryPath: summaryRel,
1556
1573
  assessmentPath,
1557
1574
  inlinedContext,
1558
1575
  deferredCaptures,
1559
1576
  commitInstruction: reassessCommitInstruction,
1577
+ skillActivation: buildSkillActivationBlock({
1578
+ base,
1579
+ milestoneId: mid,
1580
+ milestoneTitle: midTitle,
1581
+ extraContext: [inlinedContext, deferredCaptures],
1582
+ }),
1560
1583
  });
1561
1584
  }
1562
1585
 
@@ -8,6 +8,7 @@
8
8
 
9
9
  import type { ExtensionAPI, ExtensionContext } from "@gsd/pi-coding-agent";
10
10
  import { readUnitRuntimeRecord, writeUnitRuntimeRecord } from "./unit-runtime.js";
11
+ import { isDbAvailable, getMilestoneSlices, getSliceTasks } from "./gsd-db.js";
11
12
  import { resolveAutoSupervisorConfig } from "./preferences.js";
12
13
  import type { GSDPreferences } from "./preferences.js";
13
14
  import { computeBudgets, resolveExecutorContextWindow } from "./context-budget.js";
@@ -32,6 +33,8 @@ export interface SupervisionContext {
32
33
  buildSnapshotOpts: () => CloseoutOptions & Record<string, unknown>;
33
34
  buildRecoveryContext: () => RecoveryContext;
34
35
  pauseAuto: (ctx?: ExtensionContext, pi?: ExtensionAPI) => Promise<void>;
36
+ /** Optional task estimate string (e.g. "30m", "2h") for timeout scaling (#2243). */
37
+ taskEstimate?: string;
35
38
  }
36
39
 
37
40
  /**
@@ -41,13 +44,71 @@ export interface SupervisionContext {
41
44
  * 3. Hard timeout (pause + recovery)
42
45
  * 4. Context-pressure monitor (continue-here)
43
46
  */
47
+
48
+ /**
49
+ * Parse a task estimate string (e.g. "30m", "2h", "1h30m") into minutes.
50
+ * Returns null if the string cannot be parsed.
51
+ */
52
+ export function parseEstimateMinutes(estimate: string): number | null {
53
+ if (!estimate || typeof estimate !== "string") return null;
54
+ const trimmed = estimate.trim();
55
+ if (!trimmed) return null;
56
+
57
+ let totalMinutes = 0;
58
+ let matched = false;
59
+
60
+ // Match hours component
61
+ const hoursMatch = trimmed.match(/(\d+)\s*h/i);
62
+ if (hoursMatch) {
63
+ totalMinutes += Number(hoursMatch[1]) * 60;
64
+ matched = true;
65
+ }
66
+
67
+ // Match minutes component
68
+ const minutesMatch = trimmed.match(/(\d+)\s*m/i);
69
+ if (minutesMatch) {
70
+ totalMinutes += Number(minutesMatch[1]);
71
+ matched = true;
72
+ }
73
+
74
+ return matched ? totalMinutes : null;
75
+ }
76
+
44
77
  export function startUnitSupervision(sctx: SupervisionContext): void {
45
78
  const { s, ctx, pi, unitType, unitId, prefs, buildSnapshotOpts, buildRecoveryContext, pauseAuto } = sctx;
46
79
 
47
80
  const supervisor = resolveAutoSupervisorConfig();
48
- const softTimeoutMs = (supervisor.soft_timeout_minutes ?? 0) * 60 * 1000;
49
- const idleTimeoutMs = (supervisor.idle_timeout_minutes ?? 0) * 60 * 1000;
50
- const hardTimeoutMs = (supervisor.hard_timeout_minutes ?? 0) * 60 * 1000;
81
+
82
+ // Scale timeouts based on task estimate annotations (#2243).
83
+ // If the task has an est: annotation, use it to extend the hard and soft timeouts
84
+ // so longer tasks don't get prematurely timed out.
85
+ let taskEstimate = sctx.taskEstimate;
86
+ if (!taskEstimate && unitType === "task" && isDbAvailable()) {
87
+ // Look up the task estimate from the DB (#2243).
88
+ try {
89
+ if (s.currentMilestoneId) {
90
+ const slices = getMilestoneSlices(s.currentMilestoneId);
91
+ for (const slice of slices) {
92
+ const tasks = getSliceTasks(s.currentMilestoneId, slice.id);
93
+ const task = tasks.find(t => t.id === unitId);
94
+ if (task?.estimate) {
95
+ taskEstimate = task.estimate;
96
+ break;
97
+ }
98
+ }
99
+ }
100
+ } catch {
101
+ // Non-fatal — fall through with no estimate
102
+ }
103
+ }
104
+ const estimateMinutes = taskEstimate ? parseEstimateMinutes(taskEstimate) : null;
105
+ const timeoutScale = estimateMinutes && estimateMinutes > 0
106
+ ? Math.max(1, estimateMinutes / 10) // 10min task = 1x, 30min = 3x, 2h = 12x
107
+ : 1;
108
+
109
+ const softTimeoutMs = (supervisor.soft_timeout_minutes ?? 0) * 60 * 1000 * timeoutScale;
110
+ const idleTimeoutMs = (supervisor.idle_timeout_minutes ?? 0) * 60 * 1000; // idle not scaled — idle is idle
111
+ const hardTimeoutMs = (supervisor.hard_timeout_minutes ?? 0) * 60 * 1000 * timeoutScale;
51
112
 
52
113
  // ── 1. Soft timeout warning ──
53
114
  s.wrapupWarningHandle = setTimeout(() => {
@@ -93,6 +93,11 @@ export function syncStateToProjectRoot(
93
93
  { force: true },
94
94
  );
95
95
 
96
+ // 3. metrics.json — session cost/token tracking (#2313).
97
+ // Without this, metrics accumulated in the worktree are invisible from the
98
+ // project root and never appear in the dashboard or skill-health reports.
99
+ safeCopy(join(wtGsd, "metrics.json"), join(prGsd, "metrics.json"), { force: true });
100
+
96
101
  // 4. Runtime records — unit dispatch state used by selfHealRuntimeRecords().
97
102
  // Without this, a crash during a unit leaves the runtime record only in the
98
103
  // worktree. If the next session resolves basePath before worktree re-entry,
@@ -162,6 +162,7 @@ export function syncGsdStateToWorktree(
162
162
  "OVERRIDES.md",
163
163
  "QUEUE.md",
164
164
  "completed-units.json",
165
+ "metrics.json",
165
166
  ];
166
167
  for (const f of rootFiles) {
167
168
  const src = join(mainGsd, f);
@@ -325,8 +326,9 @@ export function syncWorktreeStateBack(
325
326
  // ── 1. Sync root-level .gsd/ files back ──────────────────────────────
326
327
  // The worktree is authoritative — complete-milestone updates REQUIREMENTS,
327
328
  // PROJECT, etc. These must overwrite main's copies so they survive teardown.
328
- // Also includes QUEUE.md and completed-units.json which are written during
329
- // milestone closeout and lost on teardown without explicit sync (#1787).
329
+ // Also includes QUEUE.md, completed-units.json, and metrics.json which are
330
+ // written during milestone closeout and lost on teardown without explicit sync
331
+ // (#1787, #2313).
330
332
  const rootFiles = [
331
333
  "DECISIONS.md",
332
334
  "REQUIREMENTS.md",
@@ -335,6 +337,7 @@ export function syncWorktreeStateBack(
335
337
  "OVERRIDES.md",
336
338
  "QUEUE.md",
337
339
  "completed-units.json",
340
+ "metrics.json",
338
341
  ];
339
342
  for (const f of rootFiles) {
340
343
  const src = join(wtGsd, f);
@@ -1320,9 +1323,9 @@ export function mergeMilestoneToMain(
1320
1323
  }
1321
1324
  }
1322
1325
 
1323
- // 9b. Auto-create PR if enabled (requires push_branches + push succeeded)
1326
+ // 9b. Auto-create PR if enabled (#2302: no longer gated on pushed/auto_push)
1324
1327
  let prCreated = false;
1325
- if (prefs.auto_pr === true && pushed) {
1328
+ if (prefs.auto_pr === true && !nothingToCommit) {
1326
1329
  const remote = prefs.remote ?? "origin";
1327
1330
  const prTarget = prefs.pr_target_branch ?? mainBranch;
1328
1331
  try {
@@ -1332,9 +1335,9 @@ export function mergeMilestoneToMain(
1332
1335
  stdio: ["ignore", "pipe", "pipe"],
1333
1336
  encoding: "utf-8",
1334
1337
  });
1335
- // Create PR via gh CLI
1338
+ // Create PR via gh CLI with explicit --head and --base (#2302)
1336
1339
  execFileSync("gh", [
1337
- "pr", "create",
1340
+ "pr", "create", "--draft",
1338
1341
  "--base", prTarget,
1339
1342
  "--head", milestoneBranch,
1340
1343
  "--title", `Milestone ${milestoneId} complete`,
@@ -610,14 +610,48 @@ export async function stopAuto(
610
610
  }
611
611
 
612
612
  // ── Step 4: Auto-worktree exit ──
613
+ // When the milestone is complete (has a SUMMARY), merge the worktree branch
614
+ // back to main so code isn't stranded on the worktree branch (#2317).
615
+ // For incomplete milestones, preserve the branch for later resumption.
613
616
  try {
614
617
  if (s.currentMilestoneId) {
615
618
  const notifyCtx = ctx
616
619
  ? { notify: ctx.ui.notify.bind(ctx.ui) }
617
620
  : { notify: () => {} };
618
- buildResolver().exitMilestone(s.currentMilestoneId, notifyCtx, {
619
- preserveBranch: true,
620
- });
621
+ const resolver = buildResolver();
622
+
623
+ // Check if the milestone is complete — SUMMARY file is the authoritative signal.
624
+ let milestoneComplete = false;
625
+ try {
626
+ const summaryPath = resolveMilestoneFile(
627
+ s.originalBasePath || s.basePath,
628
+ s.currentMilestoneId,
629
+ "SUMMARY",
630
+ );
631
+ if (!summaryPath) {
632
+ // Also check in the worktree path (SUMMARY may not be synced yet)
633
+ const wtSummaryPath = resolveMilestoneFile(
634
+ s.basePath,
635
+ s.currentMilestoneId,
636
+ "SUMMARY",
637
+ );
638
+ milestoneComplete = wtSummaryPath !== null;
639
+ } else {
640
+ milestoneComplete = true;
641
+ }
642
+ } catch {
643
+ // Non-fatal — fall through to preserveBranch path
644
+ }
645
+
646
+ if (milestoneComplete) {
647
+ // Milestone is complete — merge worktree branch back to main
648
+ resolver.mergeAndExit(s.currentMilestoneId, notifyCtx);
649
+ } else {
650
+ // Milestone still in progress — preserve branch for later resumption
651
+ resolver.exitMilestone(s.currentMilestoneId, notifyCtx, {
652
+ preserveBranch: true,
653
+ });
654
+ }
621
655
  }
622
656
  } catch (e) {
623
657
  debugLog("stop-cleanup-worktree", { error: e instanceof Error ? e.message : String(e) });
@@ -1,5 +1,6 @@
1
1
  import { Type } from "@sinclair/typebox";
2
2
  import type { ExtensionAPI } from "@gsd/pi-coding-agent";
3
+ import { Text } from "@gsd/pi-tui";
3
4
 
4
5
  import { findMilestoneIds, nextMilestoneId, claimReservedId, getReservedMilestoneIds } from "../guided-flow.js";
5
6
  import { loadEffectiveGSDPreferences } from "../preferences.js";
@@ -87,6 +88,22 @@ export function registerDbTools(pi: ExtensionAPI): void {
87
88
  ], { description: "Who made this decision: 'human' (user directed), 'agent' (LLM decided autonomously), or 'collaborative' (discussed and agreed). Default: 'agent'" })),
88
89
  }),
89
90
  execute: decisionSaveExecute,
91
+ renderCall(args: any, theme: any) {
92
+ let text = theme.fg("toolTitle", theme.bold("decision_save "));
93
+ if (args.scope) text += theme.fg("accent", `[${args.scope}] `);
94
+ if (args.decision) text += theme.fg("muted", args.decision);
95
+ if (args.choice) text += theme.fg("dim", ` — ${args.choice}`);
96
+ return new Text(text, 0, 0);
97
+ },
98
+ renderResult(result: any, _options: any, theme: any) {
99
+ const d = result.details;
100
+ if (result.isError || d?.error) {
101
+ return new Text(theme.fg("error", `Error: ${d?.error ?? "unknown"}`), 0, 0);
102
+ }
103
+ let text = theme.fg("success", `Decision ${d?.id ?? ""} saved`);
104
+ if (d?.id) text += theme.fg("dim", ` → DECISIONS.md`);
105
+ return new Text(text, 0, 0);
106
+ },
90
107
  };
91
108
 
92
109
  pi.registerTool(decisionSaveTool);
@@ -157,6 +174,22 @@ export function registerDbTools(pi: ExtensionAPI): void {
157
174
  supporting_slices: Type.Optional(Type.String({ description: "Supporting slices" })),
158
175
  }),
159
176
  execute: requirementUpdateExecute,
177
+ renderCall(args: any, theme: any) {
178
+ let text = theme.fg("toolTitle", theme.bold("requirement_update "));
179
+ if (args.id) text += theme.fg("accent", args.id);
180
+ const fields = ["status", "validation", "notes", "description"].filter((f) => args[f]);
181
+ if (fields.length > 0) text += theme.fg("dim", ` (${fields.join(", ")})`);
182
+ return new Text(text, 0, 0);
183
+ },
184
+ renderResult(result: any, _options: any, theme: any) {
185
+ const d = result.details;
186
+ if (result.isError || d?.error) {
187
+ return new Text(theme.fg("error", `Error: ${d?.error ?? "unknown"}`), 0, 0);
188
+ }
189
+ let text = theme.fg("success", `Requirement ${d?.id ?? ""} updated`);
190
+ text += theme.fg("dim", ` → REQUIREMENTS.md`);
191
+ return new Text(text, 0, 0);
192
+ },
160
193
  };
161
194
 
162
195
  pi.registerTool(requirementUpdateTool);
@@ -235,6 +268,22 @@ export function registerDbTools(pi: ExtensionAPI): void {
235
268
  content: Type.String({ description: "The full markdown content of the artifact" }),
236
269
  }),
237
270
  execute: summarySaveExecute,
271
+ renderCall(args: any, theme: any) {
272
+ let text = theme.fg("toolTitle", theme.bold("summary_save "));
273
+ if (args.artifact_type) text += theme.fg("accent", args.artifact_type);
274
+ const path = [args.milestone_id, args.slice_id, args.task_id].filter(Boolean).join("/");
275
+ if (path) text += theme.fg("dim", ` ${path}`);
276
+ return new Text(text, 0, 0);
277
+ },
278
+ renderResult(result: any, _options: any, theme: any) {
279
+ const d = result.details;
280
+ if (result.isError || d?.error) {
281
+ return new Text(theme.fg("error", `Error: ${d?.error ?? "unknown"}`), 0, 0);
282
+ }
283
+ let text = theme.fg("success", `${d?.artifact_type ?? "Artifact"} saved`);
284
+ if (d?.path) text += theme.fg("dim", ` → ${d.path}`);
285
+ return new Text(text, 0, 0);
286
+ },
238
287
  };
239
288
 
240
289
  pi.registerTool(summarySaveTool);
@@ -248,6 +297,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
248
297
  // This guarantees the ID shown in the UI matches the one materialised on disk.
249
298
  const reserved = claimReservedId();
250
299
  if (reserved) {
300
+ await ensureMilestoneDbRow(reserved);
251
301
  return {
252
302
  content: [{ type: "text" as const, text: reserved }],
253
303
  details: { operation: "generate_milestone_id", id: reserved, source: "reserved" } as any,
@@ -259,6 +309,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
259
309
  const uniqueEnabled = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
260
310
  const allIds = [...new Set([...existingIds, ...getReservedMilestoneIds()])];
261
311
  const newId = nextMilestoneId(allIds, uniqueEnabled);
312
+ await ensureMilestoneDbRow(newId);
262
313
  return {
263
314
  content: [{ type: "text" as const, text: newId }],
264
315
  details: { operation: "generate_milestone_id", id: newId, existingCount: existingIds.length, uniqueEnabled } as any,
@@ -272,6 +323,23 @@ export function registerDbTools(pi: ExtensionAPI): void {
272
323
  }
273
324
  };
274
325
 
326
+ /**
327
+ * Insert a minimal DB row for a milestone ID so it's visible to the state
328
+ * machine. Uses INSERT OR IGNORE — safe to call even if gsd_plan_milestone
329
+ * later writes the full row. Silently skips if the DB isn't available yet
330
+ * (pre-migration).
331
+ */
332
+ async function ensureMilestoneDbRow(milestoneId: string): Promise<void> {
333
+ const dbAvailable = await ensureDbOpen();
334
+ if (!dbAvailable) return;
335
+ try {
336
+ const { insertMilestone } = await import("../gsd-db.js");
337
+ insertMilestone({ id: milestoneId, status: "queued" });
338
+ } catch {
339
+ // Non-fatal — the safety-net in deriveStateFromDb will catch this
340
+ }
341
+ }
342
+
275
343
  const milestoneGenerateIdTool = {
276
344
  name: "gsd_milestone_generate_id",
277
345
  label: "Generate Milestone ID",
@@ -288,6 +356,18 @@ export function registerDbTools(pi: ExtensionAPI): void {
288
356
  ],
289
357
  parameters: Type.Object({}),
290
358
  execute: milestoneGenerateIdExecute,
359
+ renderCall(_args: any, theme: any) {
360
+ return new Text(theme.fg("toolTitle", theme.bold("milestone_generate_id")), 0, 0);
361
+ },
362
+ renderResult(result: any, _options: any, theme: any) {
363
+ const d = result.details;
364
+ if (result.isError || d?.error) {
365
+ return new Text(theme.fg("error", `Error: ${d?.error ?? "unknown"}`), 0, 0);
366
+ }
367
+ let text = theme.fg("success", `Generated ${d?.id ?? "ID"}`);
368
+ if (d?.source === "reserved") text += theme.fg("dim", " (reserved)");
369
+ return new Text(text, 0, 0);
370
+ },
291
371
  };
292
372
 
293
373
  pi.registerTool(milestoneGenerateIdTool);
@@ -794,6 +874,74 @@ export function registerDbTools(pi: ExtensionAPI): void {
794
874
  pi.registerTool(milestoneCompleteTool);
795
875
  registerAlias(pi, milestoneCompleteTool, "gsd_milestone_complete", "gsd_complete_milestone");
796
876
 
877
+ // ─── gsd_validate_milestone (gsd_milestone_validate alias) ─────────────
878
+
879
+ const milestoneValidateExecute = async (_toolCallId: string, params: any, _signal: AbortSignal | undefined, _onUpdate: unknown, _ctx: unknown) => {
880
+ const dbAvailable = await ensureDbOpen();
881
+ if (!dbAvailable) {
882
+ return {
883
+ content: [{ type: "text" as const, text: "Error: GSD database is not available. Cannot validate milestone." }],
884
+ details: { operation: "validate_milestone", error: "db_unavailable" } as any,
885
+ };
886
+ }
887
+ try {
888
+ const { handleValidateMilestone } = await import("../tools/validate-milestone.js");
889
+ const result = await handleValidateMilestone(params, process.cwd());
890
+ if ("error" in result) {
891
+ return {
892
+ content: [{ type: "text" as const, text: `Error validating milestone: ${result.error}` }],
893
+ details: { operation: "validate_milestone", error: result.error } as any,
894
+ };
895
+ }
896
+ return {
897
+ content: [{ type: "text" as const, text: `Validated milestone ${result.milestoneId} — verdict: ${result.verdict}. Written to ${result.validationPath}` }],
898
+ details: {
899
+ operation: "validate_milestone",
900
+ milestoneId: result.milestoneId,
901
+ verdict: result.verdict,
902
+ validationPath: result.validationPath,
903
+ } as any,
904
+ };
905
+ } catch (err) {
906
+ const msg = err instanceof Error ? err.message : String(err);
907
+ process.stderr.write(`gsd-db: validate_milestone tool failed: ${msg}\n`);
908
+ return {
909
+ content: [{ type: "text" as const, text: `Error validating milestone: ${msg}` }],
910
+ details: { operation: "validate_milestone", error: msg } as any,
911
+ };
912
+ }
913
+ };
914
+
915
+ const milestoneValidateTool = {
916
+ name: "gsd_validate_milestone",
917
+ label: "Validate Milestone",
918
+ description:
919
+ "Validate a milestone before completion — persist validation results to the DB, render VALIDATION.md to disk. " +
920
+ "Records verdict (pass/needs-attention/needs-remediation) and rationale.",
921
+ promptSnippet: "Validate a GSD milestone (DB write + VALIDATION.md render)",
922
+ promptGuidelines: [
923
+ "Use gsd_validate_milestone when all slices are done and the milestone needs validation before completion.",
924
+ "Parameters: milestoneId, verdict, remediationRound, successCriteriaChecklist, sliceDeliveryAudit, crossSliceIntegration, requirementCoverage, verdictRationale, remediationPlan (optional).",
925
+ "If verdict is 'needs-remediation', also provide remediationPlan and use gsd_reassess_roadmap to add remediation slices to the roadmap.",
926
+ "On success, returns validationPath where VALIDATION.md was written.",
927
+ ],
928
+ parameters: Type.Object({
929
+ milestoneId: Type.String({ description: "Milestone ID (e.g. M001)" }),
930
+ verdict: StringEnum(["pass", "needs-attention", "needs-remediation"], { description: "Validation verdict" }),
931
+ remediationRound: Type.Number({ description: "Remediation round (0 for first validation)" }),
932
+ successCriteriaChecklist: Type.String({ description: "Markdown checklist of success criteria with pass/fail and evidence" }),
933
+ sliceDeliveryAudit: Type.String({ description: "Markdown table auditing each slice's claimed vs delivered output" }),
934
+ crossSliceIntegration: Type.String({ description: "Markdown describing any cross-slice boundary mismatches" }),
935
+ requirementCoverage: Type.String({ description: "Markdown describing any unaddressed requirements" }),
936
+ verdictRationale: Type.String({ description: "Why this verdict was chosen" }),
937
+ remediationPlan: Type.Optional(Type.String({ description: "Remediation plan (required if verdict is needs-remediation)" })),
938
+ }),
939
+ execute: milestoneValidateExecute,
940
+ };
941
+
942
+ pi.registerTool(milestoneValidateTool);
943
+ registerAlias(pi, milestoneValidateTool, "gsd_milestone_validate", "gsd_validate_milestone");
944
+
797
945
  // ─── gsd_replan_slice (gsd_slice_replan alias) ─────────────────────────
798
946
 
799
947
  const replanSliceExecute = async (_toolCallId: string, params: any, _signal: AbortSignal | undefined, _onUpdate: unknown, _ctx: unknown) => {
@@ -64,17 +64,12 @@ export async function buildBeforeAgentStartResult(
64
64
  }
65
65
  }
66
66
 
67
- let knowledgeBlock = "";
68
- const knowledgePath = resolveGsdRootFile(process.cwd(), "KNOWLEDGE");
69
- if (existsSync(knowledgePath)) {
70
- try {
71
- const content = readFileSync(knowledgePath, "utf-8").trim();
72
- if (content) {
73
- knowledgeBlock = `\n\n[PROJECT KNOWLEDGE — Rules, patterns, and lessons learned]\n\n${content}`;
74
- }
75
- } catch {
76
- // skip
77
- }
67
+ const { block: knowledgeBlock, globalSizeKb } = loadKnowledgeBlock(gsdHome, process.cwd());
68
+ if (globalSizeKb > 4) {
69
+ ctx.ui.notify(
70
+ `GSD: ~/.gsd/agent/KNOWLEDGE.md is ${globalSizeKb.toFixed(1)}KB — consider trimming to keep system prompt lean.`,
71
+ "warning",
72
+ );
78
73
  }
79
74
 
80
75
  let memoryBlock = "";
@@ -126,6 +121,48 @@ export async function buildBeforeAgentStartResult(
126
121
  };
127
122
  }
128
123
 
124
+ export function loadKnowledgeBlock(gsdHomeDir: string, cwd: string): { block: string; globalSizeKb: number } {
125
+ // 1. Global knowledge (~/.gsd/agent/KNOWLEDGE.md) — cross-project, user-maintained
126
+ let globalKnowledge = "";
127
+ let globalSizeKb = 0;
128
+ const globalKnowledgePath = join(gsdHomeDir, "agent", "KNOWLEDGE.md");
129
+ if (existsSync(globalKnowledgePath)) {
130
+ try {
131
+ const content = readFileSync(globalKnowledgePath, "utf-8").trim();
132
+ if (content) {
133
+ globalSizeKb = Buffer.byteLength(content, "utf-8") / 1024;
134
+ globalKnowledge = content;
135
+ }
136
+ } catch {
137
+ // skip
138
+ }
139
+ }
140
+
141
+ // 2. Project knowledge (.gsd/KNOWLEDGE.md) — project-specific
142
+ let projectKnowledge = "";
143
+ const knowledgePath = resolveGsdRootFile(cwd, "KNOWLEDGE");
144
+ if (existsSync(knowledgePath)) {
145
+ try {
146
+ const content = readFileSync(knowledgePath, "utf-8").trim();
147
+ if (content) projectKnowledge = content;
148
+ } catch {
149
+ // skip
150
+ }
151
+ }
152
+
153
+ if (!globalKnowledge && !projectKnowledge) {
154
+ return { block: "", globalSizeKb: 0 };
155
+ }
156
+
157
+ const parts: string[] = [];
158
+ if (globalKnowledge) parts.push(`## Global Knowledge\n\n${globalKnowledge}`);
159
+ if (projectKnowledge) parts.push(`## Project Knowledge\n\n${projectKnowledge}`);
160
+ return {
161
+ block: `\n\n[KNOWLEDGE — Rules, patterns, and lessons learned]\n\n${parts.join("\n\n")}`,
162
+ globalSizeKb,
163
+ };
164
+ }
165
+
129
166
  function buildWorktreeContextBlock(): string {
130
167
  const worktreeName = getActiveWorktreeName();
131
168
  const worktreeMainCwd = getWorktreeOriginalCwd();
@@ -15,7 +15,7 @@ export interface GsdCommandDefinition {
15
15
  type CompletionMap = Record<string, readonly GsdCommandDefinition[]>;
16
16
 
17
17
  export const GSD_COMMAND_DESCRIPTION =
18
- "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|queue|quick|discuss|capture|triage|dispatch|history|undo|undo-task|reset-slice|rate|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|park|unpark|init|setup|inspect|extensions|update|fast";
18
+ "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|queue|quick|discuss|capture|triage|dispatch|history|undo|undo-task|reset-slice|rate|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|park|unpark|init|setup|inspect|extensions|update|fast|mcp";
19
19
 
20
20
  export const TOP_LEVEL_SUBCOMMANDS: readonly GsdCommandDefinition[] = [
21
21
  { cmd: "help", desc: "Categorized command reference with descriptions" },
@@ -68,6 +68,7 @@ export const TOP_LEVEL_SUBCOMMANDS: readonly GsdCommandDefinition[] = [
68
68
  { cmd: "templates", desc: "List available workflow templates" },
69
69
  { cmd: "extensions", desc: "Manage extensions (list, enable, disable, info)" },
70
70
  { cmd: "fast", desc: "Toggle OpenAI service tier (on/off/flex/status)" },
71
+ { cmd: "mcp", desc: "MCP server status and connectivity check (status, check <server>)" },
71
72
  { cmd: "workflow", desc: "Custom workflow lifecycle (new, run, list, validate, pause, resume)" },
72
73
  ];
73
74
 
@@ -187,6 +188,10 @@ const NESTED_COMPLETIONS: CompletionMap = {
187
188
  { cmd: "flex", desc: "Flex tier (0.5x cost, slower)" },
188
189
  { cmd: "status", desc: "Show current service tier setting" },
189
190
  ],
191
+ mcp: [
192
+ { cmd: "status", desc: "Show all MCP server statuses (default)" },
193
+ { cmd: "check", desc: "Detailed status for a specific server" },
194
+ ],
190
195
  doctor: [
191
196
  { cmd: "fix", desc: "Auto-fix detected issues" },
192
197
  { cmd: "heal", desc: "AI-driven deep healing" },
@@ -53,6 +53,7 @@ export function showHelp(ctx: ExtensionCommandContext): void {
53
53
  " /gsd hooks Show post-unit hook configuration",
54
54
  " /gsd extensions Manage extensions [list|enable|disable|info]",
55
55
  " /gsd fast Toggle OpenAI service tier [on|off|flex|status]",
56
+ " /gsd mcp MCP server status and connectivity [status|check <server>]",
56
57
  "",
57
58
  "MAINTENANCE",
58
59
  " /gsd doctor Diagnose and repair .gsd/ state [audit|fix|heal] [scope]",
@@ -191,6 +191,11 @@ Examples:
191
191
  await handleFast(trimmed.replace(/^fast\s*/, "").trim(), ctx);
192
192
  return true;
193
193
  }
194
+ if (trimmed === "mcp" || trimmed.startsWith("mcp ")) {
195
+ const { handleMcpStatus } = await import("../../commands-mcp-status.js");
196
+ await handleMcpStatus(trimmed.replace(/^mcp\s*/, "").trim(), ctx);
197
+ return true;
198
+ }
194
199
  if (trimmed === "extensions" || trimmed.startsWith("extensions ")) {
195
200
  const { handleExtensions } = await import("../../commands-extensions.js");
196
201
  await handleExtensions(trimmed.replace(/^extensions\s*/, "").trim(), ctx);