gsd-pi 2.22.0 → 2.24.0

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 (228) hide show
  1. package/README.md +25 -1
  2. package/dist/cli.js +74 -7
  3. package/dist/headless.d.ts +25 -0
  4. package/dist/headless.js +454 -0
  5. package/dist/help-text.js +47 -0
  6. package/dist/mcp-server.d.ts +20 -3
  7. package/dist/mcp-server.js +21 -1
  8. package/dist/models-resolver.d.ts +32 -0
  9. package/dist/models-resolver.js +50 -0
  10. package/dist/resource-loader.js +64 -9
  11. package/dist/resources/extensions/bg-shell/output-formatter.ts +36 -16
  12. package/dist/resources/extensions/bg-shell/process-manager.ts +6 -4
  13. package/dist/resources/extensions/bg-shell/types.ts +33 -1
  14. package/dist/resources/extensions/browser-tools/capture.ts +18 -16
  15. package/dist/resources/extensions/browser-tools/index.ts +20 -0
  16. package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +25 -0
  17. package/dist/resources/extensions/browser-tools/tools/action-cache.ts +216 -0
  18. package/dist/resources/extensions/browser-tools/tools/codegen.ts +274 -0
  19. package/dist/resources/extensions/browser-tools/tools/device.ts +183 -0
  20. package/dist/resources/extensions/browser-tools/tools/extract.ts +229 -0
  21. package/dist/resources/extensions/browser-tools/tools/injection-detect.ts +221 -0
  22. package/dist/resources/extensions/browser-tools/tools/network-mock.ts +244 -0
  23. package/dist/resources/extensions/browser-tools/tools/pdf.ts +92 -0
  24. package/dist/resources/extensions/browser-tools/tools/state-persistence.ts +202 -0
  25. package/dist/resources/extensions/browser-tools/tools/visual-diff.ts +209 -0
  26. package/dist/resources/extensions/browser-tools/tools/zoom.ts +104 -0
  27. package/dist/resources/extensions/gsd/auto-dashboard.ts +2 -0
  28. package/dist/resources/extensions/gsd/auto-dispatch.ts +51 -2
  29. package/dist/resources/extensions/gsd/auto-prompts.ts +73 -0
  30. package/dist/resources/extensions/gsd/auto-recovery.ts +51 -2
  31. package/dist/resources/extensions/gsd/auto-worktree.ts +15 -3
  32. package/dist/resources/extensions/gsd/auto.ts +560 -52
  33. package/dist/resources/extensions/gsd/captures.ts +49 -0
  34. package/dist/resources/extensions/gsd/commands.ts +194 -11
  35. package/dist/resources/extensions/gsd/complexity.ts +1 -0
  36. package/dist/resources/extensions/gsd/dashboard-overlay.ts +54 -2
  37. package/dist/resources/extensions/gsd/diff-context.ts +73 -80
  38. package/dist/resources/extensions/gsd/doctor.ts +76 -12
  39. package/dist/resources/extensions/gsd/exit-command.ts +2 -2
  40. package/dist/resources/extensions/gsd/forensics.ts +95 -52
  41. package/dist/resources/extensions/gsd/gitignore.ts +1 -0
  42. package/dist/resources/extensions/gsd/guided-flow.ts +85 -5
  43. package/dist/resources/extensions/gsd/index.ts +34 -1
  44. package/dist/resources/extensions/gsd/mcp-server.ts +33 -12
  45. package/dist/resources/extensions/gsd/parallel-eligibility.ts +233 -0
  46. package/dist/resources/extensions/gsd/parallel-merge.ts +156 -0
  47. package/dist/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
  48. package/dist/resources/extensions/gsd/post-unit-hooks.ts +2 -1
  49. package/dist/resources/extensions/gsd/preferences.ts +65 -1
  50. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
  51. package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -0
  52. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +104 -1
  53. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -0
  54. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  55. package/dist/resources/extensions/gsd/prompts/system.md +2 -1
  56. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +70 -0
  57. package/dist/resources/extensions/gsd/provider-error-pause.ts +29 -2
  58. package/dist/resources/extensions/gsd/roadmap-slices.ts +41 -1
  59. package/dist/resources/extensions/gsd/session-forensics.ts +36 -2
  60. package/dist/resources/extensions/gsd/session-status-io.ts +197 -0
  61. package/dist/resources/extensions/gsd/state.ts +72 -30
  62. package/dist/resources/extensions/gsd/templates/milestone-validation.md +62 -0
  63. package/dist/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
  64. package/dist/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
  65. package/dist/resources/extensions/gsd/tests/auto-lock-creation.test.ts +186 -0
  66. package/dist/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
  67. package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +264 -0
  68. package/dist/resources/extensions/gsd/tests/auto-skip-loop.test.ts +123 -0
  69. package/dist/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
  70. package/dist/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
  71. package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
  72. package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
  73. package/dist/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
  74. package/dist/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
  75. package/dist/resources/extensions/gsd/tests/doctor.test.ts +58 -0
  76. package/dist/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +17 -6
  77. package/dist/resources/extensions/gsd/tests/integration/headless-command.ts +534 -0
  78. package/dist/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
  79. package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
  80. package/dist/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
  81. package/dist/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
  82. package/dist/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
  83. package/dist/resources/extensions/gsd/tests/roadmap-slices.test.ts +43 -1
  84. package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +120 -0
  85. package/dist/resources/extensions/gsd/tests/triage-resolution.test.ts +203 -2
  86. package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
  87. package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +8 -3
  88. package/dist/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
  89. package/dist/resources/extensions/gsd/triage-resolution.ts +83 -0
  90. package/dist/resources/extensions/gsd/types.ts +15 -1
  91. package/dist/resources/extensions/gsd/visualizer-overlay.ts +8 -1
  92. package/dist/resources/extensions/gsd/workspace-index.ts +34 -6
  93. package/dist/resources/extensions/subagent/index.ts +5 -0
  94. package/dist/resources/extensions/subagent/worker-registry.ts +99 -0
  95. package/dist/update-check.d.ts +9 -0
  96. package/dist/update-check.js +97 -0
  97. package/package.json +6 -1
  98. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  99. package/packages/pi-ai/dist/providers/anthropic.js +16 -7
  100. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  101. package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  102. package/packages/pi-ai/dist/providers/azure-openai-responses.js +12 -4
  103. package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  104. package/packages/pi-ai/dist/providers/google-vertex.d.ts.map +1 -1
  105. package/packages/pi-ai/dist/providers/google-vertex.js +21 -9
  106. package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
  107. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  108. package/packages/pi-ai/dist/providers/openai-completions.js +12 -4
  109. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  110. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  111. package/packages/pi-ai/dist/providers/openai-responses.js +12 -4
  112. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  113. package/packages/pi-ai/src/providers/anthropic.ts +21 -8
  114. package/packages/pi-ai/src/providers/azure-openai-responses.ts +16 -4
  115. package/packages/pi-ai/src/providers/google-vertex.ts +32 -17
  116. package/packages/pi-ai/src/providers/openai-completions.ts +16 -4
  117. package/packages/pi-ai/src/providers/openai-responses.ts +16 -4
  118. package/packages/pi-coding-agent/dist/core/agent-session.js +1 -1
  119. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  120. package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
  121. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/core/tools/bash-background.test.d.ts +10 -0
  123. package/packages/pi-coding-agent/dist/core/tools/bash-background.test.d.ts.map +1 -0
  124. package/packages/pi-coding-agent/dist/core/tools/bash-background.test.js +79 -0
  125. package/packages/pi-coding-agent/dist/core/tools/bash-background.test.js.map +1 -0
  126. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts +18 -0
  127. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
  128. package/packages/pi-coding-agent/dist/core/tools/bash.js +77 -1
  129. package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  130. package/packages/pi-coding-agent/dist/core/tools/index.d.ts +1 -1
  131. package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/tools/index.js +1 -1
  133. package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  135. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  136. package/packages/pi-coding-agent/dist/index.js +1 -1
  137. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  138. package/packages/pi-coding-agent/src/core/agent-session.ts +1 -1
  139. package/packages/pi-coding-agent/src/core/settings-manager.ts +2 -2
  140. package/packages/pi-coding-agent/src/core/tools/bash-background.test.ts +91 -0
  141. package/packages/pi-coding-agent/src/core/tools/bash.ts +83 -1
  142. package/packages/pi-coding-agent/src/core/tools/index.ts +1 -0
  143. package/packages/pi-coding-agent/src/index.ts +1 -0
  144. package/scripts/postinstall.js +7 -109
  145. package/src/resources/extensions/bg-shell/output-formatter.ts +36 -16
  146. package/src/resources/extensions/bg-shell/process-manager.ts +6 -4
  147. package/src/resources/extensions/bg-shell/types.ts +33 -1
  148. package/src/resources/extensions/browser-tools/capture.ts +18 -16
  149. package/src/resources/extensions/browser-tools/index.ts +20 -0
  150. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +25 -0
  151. package/src/resources/extensions/browser-tools/tools/action-cache.ts +216 -0
  152. package/src/resources/extensions/browser-tools/tools/codegen.ts +274 -0
  153. package/src/resources/extensions/browser-tools/tools/device.ts +183 -0
  154. package/src/resources/extensions/browser-tools/tools/extract.ts +229 -0
  155. package/src/resources/extensions/browser-tools/tools/injection-detect.ts +221 -0
  156. package/src/resources/extensions/browser-tools/tools/network-mock.ts +244 -0
  157. package/src/resources/extensions/browser-tools/tools/pdf.ts +92 -0
  158. package/src/resources/extensions/browser-tools/tools/state-persistence.ts +202 -0
  159. package/src/resources/extensions/browser-tools/tools/visual-diff.ts +209 -0
  160. package/src/resources/extensions/browser-tools/tools/zoom.ts +104 -0
  161. package/src/resources/extensions/gsd/auto-dashboard.ts +2 -0
  162. package/src/resources/extensions/gsd/auto-dispatch.ts +51 -2
  163. package/src/resources/extensions/gsd/auto-prompts.ts +73 -0
  164. package/src/resources/extensions/gsd/auto-recovery.ts +51 -2
  165. package/src/resources/extensions/gsd/auto-worktree.ts +15 -3
  166. package/src/resources/extensions/gsd/auto.ts +560 -52
  167. package/src/resources/extensions/gsd/captures.ts +49 -0
  168. package/src/resources/extensions/gsd/commands.ts +194 -11
  169. package/src/resources/extensions/gsd/complexity.ts +1 -0
  170. package/src/resources/extensions/gsd/dashboard-overlay.ts +54 -2
  171. package/src/resources/extensions/gsd/diff-context.ts +73 -80
  172. package/src/resources/extensions/gsd/doctor.ts +76 -12
  173. package/src/resources/extensions/gsd/exit-command.ts +2 -2
  174. package/src/resources/extensions/gsd/forensics.ts +95 -52
  175. package/src/resources/extensions/gsd/gitignore.ts +1 -0
  176. package/src/resources/extensions/gsd/guided-flow.ts +85 -5
  177. package/src/resources/extensions/gsd/index.ts +34 -1
  178. package/src/resources/extensions/gsd/mcp-server.ts +33 -12
  179. package/src/resources/extensions/gsd/parallel-eligibility.ts +233 -0
  180. package/src/resources/extensions/gsd/parallel-merge.ts +156 -0
  181. package/src/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
  182. package/src/resources/extensions/gsd/post-unit-hooks.ts +2 -1
  183. package/src/resources/extensions/gsd/preferences.ts +65 -1
  184. package/src/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
  185. package/src/resources/extensions/gsd/prompts/execute-task.md +5 -0
  186. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +104 -1
  187. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -0
  188. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  189. package/src/resources/extensions/gsd/prompts/system.md +2 -1
  190. package/src/resources/extensions/gsd/prompts/validate-milestone.md +70 -0
  191. package/src/resources/extensions/gsd/provider-error-pause.ts +29 -2
  192. package/src/resources/extensions/gsd/roadmap-slices.ts +41 -1
  193. package/src/resources/extensions/gsd/session-forensics.ts +36 -2
  194. package/src/resources/extensions/gsd/session-status-io.ts +197 -0
  195. package/src/resources/extensions/gsd/state.ts +72 -30
  196. package/src/resources/extensions/gsd/templates/milestone-validation.md +62 -0
  197. package/src/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
  198. package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
  199. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +186 -0
  200. package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
  201. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +264 -0
  202. package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +123 -0
  203. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
  204. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
  205. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
  206. package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
  207. package/src/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
  208. package/src/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
  209. package/src/resources/extensions/gsd/tests/doctor.test.ts +58 -0
  210. package/src/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +17 -6
  211. package/src/resources/extensions/gsd/tests/integration/headless-command.ts +534 -0
  212. package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
  213. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
  214. package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
  215. package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
  216. package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
  217. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +43 -1
  218. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +120 -0
  219. package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +203 -2
  220. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
  221. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +8 -3
  222. package/src/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
  223. package/src/resources/extensions/gsd/triage-resolution.ts +83 -0
  224. package/src/resources/extensions/gsd/types.ts +15 -1
  225. package/src/resources/extensions/gsd/visualizer-overlay.ts +8 -1
  226. package/src/resources/extensions/gsd/workspace-index.ts +34 -6
  227. package/src/resources/extensions/subagent/index.ts +5 -0
  228. package/src/resources/extensions/subagent/worker-registry.ts +99 -0
@@ -26,6 +26,7 @@ export interface CaptureEntry {
26
26
  resolution?: string;
27
27
  rationale?: string;
28
28
  resolvedAt?: string;
29
+ executed?: boolean;
29
30
  }
30
31
 
31
32
  export interface TriageResult {
@@ -211,6 +212,52 @@ export function markCaptureResolved(
211
212
  writeFileSync(filePath, updated, "utf-8");
212
213
  }
213
214
 
215
+ /**
216
+ * Mark a resolved capture as executed — its resolution action was carried out.
217
+ * Appends `**Executed:** <timestamp>` to the capture's section in CAPTURES.md.
218
+ */
219
+ export function markCaptureExecuted(basePath: string, captureId: string): void {
220
+ const filePath = resolveCapturesPath(basePath);
221
+ if (!existsSync(filePath)) return;
222
+
223
+ const content = readFileSync(filePath, "utf-8");
224
+ const executedAt = new Date().toISOString();
225
+
226
+ const sectionRegex = new RegExp(
227
+ `(### ${escapeRegex(captureId)}\\n(?:(?!### ).)*?)(?=### |$)`,
228
+ "s",
229
+ );
230
+ const match = sectionRegex.exec(content);
231
+ if (!match) return;
232
+
233
+ let section = match[1];
234
+
235
+ // Remove any existing Executed field (in case of re-execution)
236
+ section = section.replace(/\*\*Executed:\*\*\s*.+\n?/g, "");
237
+
238
+ // Append Executed timestamp
239
+ section = section.trimEnd() + "\n" + `**Executed:** ${executedAt}` + "\n";
240
+
241
+ const updated = content.replace(sectionRegex, section);
242
+ writeFileSync(filePath, updated, "utf-8");
243
+ }
244
+
245
+ /**
246
+ * Load resolved captures that have actionable classifications (inject, replan,
247
+ * quick-task) but have NOT yet been executed.
248
+ * These are captures whose resolutions need to be carried out.
249
+ */
250
+ export function loadActionableCaptures(basePath: string): CaptureEntry[] {
251
+ return loadAllCaptures(basePath).filter(
252
+ c =>
253
+ c.status === "resolved" &&
254
+ !c.executed &&
255
+ (c.classification === "inject" ||
256
+ c.classification === "replan" ||
257
+ c.classification === "quick-task"),
258
+ );
259
+ }
260
+
214
261
  // ─── Parser ───────────────────────────────────────────────────────────────────
215
262
 
216
263
  /**
@@ -235,6 +282,7 @@ function parseCapturesContent(content: string): CaptureEntry[] {
235
282
  const resolution = extractBoldField(body, "Resolution");
236
283
  const rationale = extractBoldField(body, "Rationale");
237
284
  const resolvedAt = extractBoldField(body, "Resolved");
285
+ const executedAt = extractBoldField(body, "Executed");
238
286
 
239
287
  if (!text || !timestamp) continue;
240
288
 
@@ -251,6 +299,7 @@ function parseCapturesContent(content: string): CaptureEntry[] {
251
299
  ...(resolution ? { resolution } : {}),
252
300
  ...(rationale ? { rationale } : {}),
253
301
  ...(resolvedAt ? { resolvedAt } : {}),
302
+ ...(executedAt ? { executed: true } : {}),
254
303
  });
255
304
  }
256
305
 
@@ -6,15 +6,15 @@
6
6
 
7
7
  import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
8
8
  import { AuthStorage } from "@gsd/pi-coding-agent";
9
- import { existsSync, readFileSync, mkdirSync } from "node:fs";
9
+ import { existsSync, readFileSync, mkdirSync, unlinkSync } from "node:fs";
10
10
  import { join, dirname } from "node:path";
11
11
  import { enableDebug, isDebugEnabled } from "./debug-logger.js";
12
12
  import { fileURLToPath } from "node:url";
13
13
  import { deriveState } from "./state.js";
14
14
  import { GSDDashboardOverlay } from "./dashboard-overlay.js";
15
15
  import { GSDVisualizerOverlay } from "./visualizer-overlay.js";
16
- import { showQueue, showDiscuss } from "./guided-flow.js";
17
- import { startAuto, stopAuto, pauseAuto, isAutoActive, isAutoPaused, isStepMode, stopAutoRemote } from "./auto.js";
16
+ import { showQueue, showDiscuss, showHeadlessMilestoneCreation } from "./guided-flow.js";
17
+ import { startAuto, stopAuto, pauseAuto, isAutoActive, isAutoPaused, isStepMode, stopAutoRemote, dispatchDirectPhase } from "./auto.js";
18
18
  import { resolveProjectRoot } from "./worktree.js";
19
19
  import { appendCapture, hasPendingCaptures, loadPendingCaptures } from "./captures.js";
20
20
  import {
@@ -42,6 +42,14 @@ import { handleQuick } from "./quick.js";
42
42
  import { handleHistory } from "./history.js";
43
43
  import { handleUndo } from "./undo.js";
44
44
  import { handleExport } from "./export.js";
45
+ import {
46
+ isParallelActive, getOrchestratorState, getWorkerStatuses,
47
+ prepareParallelStart, startParallel, stopParallel,
48
+ pauseWorker, resumeWorker,
49
+ } from "./parallel-orchestrator.js";
50
+ import { formatEligibilityReport } from "./parallel-eligibility.js";
51
+ import { mergeAllCompleted, mergeCompletedMilestone, formatMergeResults } from "./parallel-merge.js";
52
+ import { resolveParallelConfig } from "./preferences.js";
45
53
  import { nativeBranchList, nativeDetectMainBranch, nativeBranchListMerged, nativeBranchDelete, nativeForEachRef, nativeUpdateRef } from "./native-git-bridge.js";
46
54
 
47
55
  export function dispatchDoctorHeal(pi: ExtensionAPI, scope: string | undefined, reportText: string, structuredIssues: string): void {
@@ -69,20 +77,53 @@ function projectRoot(): string {
69
77
 
70
78
  export function registerGSDCommand(pi: ExtensionAPI): void {
71
79
  pi.registerCommand("gsd", {
72
- description: "GSD — Get Shit Done: /gsd help|next|auto|stop|pause|status|visualize|queue|quick|capture|triage|history|undo|skip|export|cleanup|mode|prefs|config|hooks|run-hook|skill-health|doctor|forensics|migrate|remote|steer|knowledge",
80
+ description: "GSD — Get Shit Done: /gsd help|next|auto|stop|pause|status|visualize|queue|quick|capture|triage|dispatch|history|undo|skip|export|cleanup|mode|prefs|config|hooks|run-hook|skill-health|doctor|forensics|migrate|remote|steer|knowledge|new-milestone|parallel",
73
81
  getArgumentCompletions: (prefix: string) => {
74
82
  const subcommands = [
75
- "help", "next", "auto", "stop", "pause", "status", "visualize", "queue", "quick", "discuss",
76
- "capture", "triage",
77
- "history", "undo", "skip", "export", "cleanup", "mode", "prefs",
78
- "config", "hooks", "run-hook", "skill-health", "doctor", "forensics", "migrate", "remote", "steer", "inspect", "knowledge",
83
+ { cmd: "help", desc: "Categorized command reference with descriptions" },
84
+ { cmd: "next", desc: "Explicit step mode (same as /gsd)" },
85
+ { cmd: "auto", desc: "Autonomous mode — research, plan, execute, commit, repeat" },
86
+ { cmd: "stop", desc: "Stop auto mode gracefully" },
87
+ { cmd: "pause", desc: "Pause auto-mode (preserves state, /gsd auto to resume)" },
88
+ { cmd: "status", desc: "Progress dashboard" },
89
+ { cmd: "visualize", desc: "Open workflow visualizer (progress, deps, metrics, timeline)" },
90
+ { cmd: "queue", desc: "Queue and reorder future milestones" },
91
+ { cmd: "quick", desc: "Execute a quick task without full planning overhead" },
92
+ { cmd: "discuss", desc: "Discuss architecture and decisions" },
93
+ { cmd: "capture", desc: "Fire-and-forget thought capture" },
94
+ { cmd: "triage", desc: "Manually trigger triage of pending captures" },
95
+ { cmd: "dispatch", desc: "Dispatch a specific phase directly" },
96
+ { cmd: "history", desc: "View execution history" },
97
+ { cmd: "undo", desc: "Revert last completed unit" },
98
+ { cmd: "skip", desc: "Prevent a unit from auto-mode dispatch" },
99
+ { cmd: "export", desc: "Export milestone/slice results" },
100
+ { cmd: "cleanup", desc: "Remove merged branches or snapshots" },
101
+ { cmd: "mode", desc: "Switch workflow mode (solo/team)" },
102
+ { cmd: "prefs", desc: "Manage preferences (model selection, timeouts, etc.)" },
103
+ { cmd: "config", desc: "Set API keys for external tools" },
104
+ { cmd: "hooks", desc: "Show configured post-unit and pre-dispatch hooks" },
105
+ { cmd: "run-hook", desc: "Manually trigger a specific hook" },
106
+ { cmd: "skill-health", desc: "Skill lifecycle dashboard" },
107
+ { cmd: "doctor", desc: "Runtime health checks with auto-fix" },
108
+ { cmd: "forensics", desc: "Examine execution logs" },
109
+ { cmd: "migrate", desc: "Migrate a v1 .planning directory to .gsd format" },
110
+ { cmd: "remote", desc: "Control remote auto-mode" },
111
+ { cmd: "steer", desc: "Hard-steer plan documents during execution" },
112
+ { cmd: "inspect", desc: "Show SQLite DB diagnostics" },
113
+ { cmd: "knowledge", desc: "Add persistent project knowledge (rule, pattern, or lesson)" },
114
+ { cmd: "new-milestone", desc: "Create a milestone from a specification document (headless)" },
115
+ { cmd: "parallel", desc: "Parallel milestone orchestration (start, status, stop, merge)" },
79
116
  ];
80
117
  const parts = prefix.trim().split(/\s+/);
81
118
 
82
119
  if (parts.length <= 1) {
83
120
  return subcommands
84
- .filter((cmd) => cmd.startsWith(parts[0] ?? ""))
85
- .map((cmd) => ({ value: cmd, label: cmd }));
121
+ .filter((item) => item.cmd.startsWith(parts[0] ?? ""))
122
+ .map((item) => ({
123
+ value: item.cmd,
124
+ label: item.cmd,
125
+ description: item.desc
126
+ }));
86
127
  }
87
128
 
88
129
  if (parts[0] === "auto" && parts.length <= 2) {
@@ -99,6 +140,13 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
99
140
  .map((cmd) => ({ value: `mode ${cmd}`, label: cmd }));
100
141
  }
101
142
 
143
+ if (parts[0] === "parallel" && parts.length <= 2) {
144
+ const subPrefix = parts[1] ?? "";
145
+ return ["start", "status", "stop", "pause", "resume", "merge"]
146
+ .filter((cmd) => cmd.startsWith(subPrefix))
147
+ .map((cmd) => ({ value: `parallel ${cmd}`, label: cmd }));
148
+ }
149
+
102
150
  if (parts[0] === "prefs" && parts.length <= 2) {
103
151
  const subPrefix = parts[1] ?? "";
104
152
  return ["global", "project", "status", "wizard", "setup", "import-claude"]
@@ -165,6 +213,13 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
165
213
  return [];
166
214
  }
167
215
 
216
+ if (parts[0] === "dispatch" && parts.length <= 2) {
217
+ const phasePrefix = parts[1] ?? "";
218
+ return ["research", "plan", "execute", "complete", "reassess", "uat", "replan"]
219
+ .filter((cmd) => cmd.startsWith(phasePrefix))
220
+ .map((cmd) => ({ value: `dispatch ${cmd}`, label: cmd }));
221
+ }
222
+
168
223
  return [];
169
224
  },
170
225
 
@@ -244,7 +299,7 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
244
299
  }
245
300
  return;
246
301
  }
247
- await stopAuto(ctx, pi);
302
+ await stopAuto(ctx, pi, "User requested stop");
248
303
  return;
249
304
  }
250
305
 
@@ -281,6 +336,108 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
281
336
  return;
282
337
  }
283
338
 
339
+ // ─── Parallel Orchestration ────────────────────────────────────────
340
+ if (trimmed.startsWith("parallel")) {
341
+ const parallelArgs = trimmed.slice("parallel".length).trim();
342
+ const [subCmd = "", ...restParts] = parallelArgs.split(/\s+/);
343
+ const rest = restParts.join(" ");
344
+
345
+ if (subCmd === "start" || subCmd === "") {
346
+ const loaded = loadEffectiveGSDPreferences();
347
+ const config = resolveParallelConfig(loaded?.preferences);
348
+ if (!config.enabled) {
349
+ pi.sendMessage({
350
+ customType: "gsd-parallel",
351
+ content: "Parallel mode is not enabled. Set `parallel.enabled: true` in your preferences.",
352
+ display: false,
353
+ });
354
+ return;
355
+ }
356
+ const candidates = await prepareParallelStart(projectRoot(), loaded?.preferences);
357
+ const report = formatEligibilityReport(candidates);
358
+ if (candidates.eligible.length === 0) {
359
+ pi.sendMessage({ customType: "gsd-parallel", content: report + "\n\nNo milestones are eligible for parallel execution.", display: false });
360
+ return;
361
+ }
362
+ const result = await startParallel(
363
+ projectRoot(),
364
+ candidates.eligible.map(e => e.milestoneId),
365
+ loaded?.preferences,
366
+ );
367
+ const lines = [`Parallel orchestration started.`, `Workers: ${result.started.join(", ")}`];
368
+ if (result.errors.length > 0) {
369
+ lines.push(`Errors: ${result.errors.map(e => `${e.mid}: ${e.error}`).join("; ")}`);
370
+ }
371
+ pi.sendMessage({ customType: "gsd-parallel", content: report + "\n\n" + lines.join("\n"), display: false });
372
+ return;
373
+ }
374
+
375
+ if (subCmd === "status") {
376
+ if (!isParallelActive()) {
377
+ pi.sendMessage({ customType: "gsd-parallel", content: "No parallel orchestration is currently active.", display: false });
378
+ return;
379
+ }
380
+ const workers = getWorkerStatuses();
381
+ const lines = ["# Parallel Workers\n"];
382
+ for (const w of workers) {
383
+ lines.push(`- **${w.milestoneId}** (${w.title}) — ${w.state} — ${w.completedUnits} units — $${w.cost.toFixed(2)}`);
384
+ }
385
+ const orchState = getOrchestratorState();
386
+ if (orchState) {
387
+ lines.push(`\nTotal cost: $${orchState.totalCost.toFixed(2)}`);
388
+ }
389
+ pi.sendMessage({ customType: "gsd-parallel", content: lines.join("\n"), display: false });
390
+ return;
391
+ }
392
+
393
+ if (subCmd === "stop") {
394
+ const mid = rest.trim() || undefined;
395
+ await stopParallel(projectRoot(), mid);
396
+ pi.sendMessage({ customType: "gsd-parallel", content: mid ? `Stopped worker for ${mid}.` : "All parallel workers stopped.", display: false });
397
+ return;
398
+ }
399
+
400
+ if (subCmd === "pause") {
401
+ const mid = rest.trim() || undefined;
402
+ pauseWorker(projectRoot(), mid);
403
+ pi.sendMessage({ customType: "gsd-parallel", content: mid ? `Paused worker for ${mid}.` : "All parallel workers paused.", display: false });
404
+ return;
405
+ }
406
+
407
+ if (subCmd === "resume") {
408
+ const mid = rest.trim() || undefined;
409
+ resumeWorker(projectRoot(), mid);
410
+ pi.sendMessage({ customType: "gsd-parallel", content: mid ? `Resumed worker for ${mid}.` : "All parallel workers resumed.", display: false });
411
+ return;
412
+ }
413
+
414
+ if (subCmd === "merge") {
415
+ const mid = rest.trim() || undefined;
416
+ if (mid) {
417
+ // Merge a specific milestone
418
+ const result = await mergeCompletedMilestone(projectRoot(), mid);
419
+ pi.sendMessage({ customType: "gsd-parallel", content: formatMergeResults([result]), display: false });
420
+ return;
421
+ }
422
+ // Merge all completed milestones
423
+ const workers = getWorkerStatuses();
424
+ if (workers.length === 0) {
425
+ pi.sendMessage({ customType: "gsd-parallel", content: "No parallel workers to merge.", display: false });
426
+ return;
427
+ }
428
+ const results = await mergeAllCompleted(projectRoot(), workers);
429
+ pi.sendMessage({ customType: "gsd-parallel", content: formatMergeResults(results), display: false });
430
+ return;
431
+ }
432
+
433
+ pi.sendMessage({
434
+ customType: "gsd-parallel",
435
+ content: `Unknown parallel subcommand "${subCmd}". Usage: /gsd parallel [start|status|stop|pause|resume|merge]`,
436
+ display: false,
437
+ });
438
+ return;
439
+ }
440
+
284
441
  if (trimmed === "cleanup") {
285
442
  await handleCleanupBranches(ctx, projectRoot());
286
443
  await handleCleanupSnapshots(ctx, projectRoot());
@@ -307,6 +464,21 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
307
464
  return;
308
465
  }
309
466
 
467
+ if (trimmed === "new-milestone") {
468
+ const basePath = projectRoot();
469
+ const headlessContextPath = join(basePath, ".gsd", "runtime", "headless-context.md");
470
+ if (existsSync(headlessContextPath)) {
471
+ const seedContext = readFileSync(headlessContextPath, "utf-8");
472
+ try { unlinkSync(headlessContextPath); } catch { /* non-fatal */ }
473
+ await showHeadlessMilestoneCreation(ctx, pi, basePath, seedContext);
474
+ } else {
475
+ // No headless context — fall back to interactive smart entry
476
+ const { showSmartEntry } = await import("./guided-flow.js");
477
+ await showSmartEntry(ctx, pi, basePath);
478
+ }
479
+ return;
480
+ }
481
+
310
482
  if (trimmed.startsWith("capture ") || trimmed === "capture") {
311
483
  await handleCapture(trimmed.replace(/^capture\s*/, "").trim(), ctx);
312
484
  return;
@@ -388,6 +560,16 @@ Examples:
388
560
  return;
389
561
  }
390
562
 
563
+ if (trimmed === "dispatch" || trimmed.startsWith("dispatch ")) {
564
+ const phase = trimmed.replace(/^dispatch\s*/, "").trim();
565
+ if (!phase) {
566
+ ctx.ui.notify("Usage: /gsd dispatch <phase> (research|plan|execute|complete|reassess|uat|replan)", "warning");
567
+ return;
568
+ }
569
+ await dispatchDirectPhase(ctx, pi, phase, projectRoot());
570
+ return;
571
+ }
572
+
391
573
  if (trimmed === "inspect") {
392
574
  await handleInspect(ctx);
393
575
  return;
@@ -417,6 +599,7 @@ function showHelp(ctx: ExtensionCommandContext): void {
417
599
  " /gsd stop Stop auto-mode gracefully",
418
600
  " /gsd pause Pause auto-mode (preserves state, /gsd auto to resume)",
419
601
  " /gsd discuss Start guided milestone/slice discussion",
602
+ " /gsd new-milestone Create milestone from headless context (used by gsd headless)",
420
603
  "",
421
604
  "VISIBILITY",
422
605
  " /gsd status Show progress dashboard (Ctrl+Alt+G)",
@@ -87,6 +87,7 @@ const UNIT_TYPE_TIERS: Record<string, ComplexityTier> = {
87
87
  "execute-task": "standard",
88
88
  "replan-slice": "heavy",
89
89
  "reassess-roadmap": "heavy",
90
+ "validate-milestone": "heavy",
90
91
  "complete-milestone": "standard",
91
92
  };
92
93
 
@@ -19,6 +19,7 @@ import {
19
19
  } from "./metrics.js";
20
20
  import { loadEffectiveGSDPreferences } from "./preferences.js";
21
21
  import { getActiveWorktreeName } from "./worktree-command.js";
22
+ import { getWorkerBatches, hasActiveWorkers, type WorkerEntry } from "../subagent/worker-registry.js";
22
23
 
23
24
  function formatDuration(ms: number): string {
24
25
  const s = Math.floor(ms / 1000);
@@ -319,16 +320,23 @@ export class GSDDashboardOverlay {
319
320
  const centered = (content: string) => row(centerLine(content, contentWidth));
320
321
 
321
322
  const title = th.fg("accent", th.bold("GSD Dashboard"));
323
+ const isRemote = !!this.dashData.remoteSession;
322
324
  const status = this.dashData.active
323
325
  ? `${Date.now() % 2000 < 1000 ? th.fg("success", "●") : th.fg("dim", "○")} ${th.fg("success", "AUTO")}`
324
326
  : this.dashData.paused
325
327
  ? th.fg("warning", "⏸ PAUSED")
326
- : th.fg("dim", "idle");
328
+ : isRemote
329
+ ? `${Date.now() % 2000 < 1000 ? th.fg("success", "●") : th.fg("dim", "○")} ${th.fg("success", "AUTO")} ${th.fg("dim", `(PID ${this.dashData.remoteSession!.pid})`)}`
330
+ : th.fg("dim", "idle");
327
331
  const worktreeName = getActiveWorktreeName();
328
332
  const worktreeTag = worktreeName
329
333
  ? ` ${th.fg("warning", `⎇ ${worktreeName}`)}`
330
334
  : "";
331
- const elapsed = th.fg("dim", formatDuration(this.dashData.elapsed));
335
+ const elapsed = this.dashData.active || this.dashData.paused
336
+ ? th.fg("dim", formatDuration(this.dashData.elapsed))
337
+ : isRemote
338
+ ? th.fg("dim", `since ${this.dashData.remoteSession!.startedAt.replace("T", " ").slice(0, 19)}`)
339
+ : "";
332
340
  lines.push(row(joinColumns(`${title} ${status}${worktreeTag}`, elapsed, contentWidth)));
333
341
  lines.push(blank());
334
342
 
@@ -344,11 +352,55 @@ export class GSDDashboardOverlay {
344
352
  } else if (this.dashData.paused) {
345
353
  lines.push(row(th.fg("dim", "/gsd auto to resume")));
346
354
  lines.push(blank());
355
+ } else if (isRemote) {
356
+ const rs = this.dashData.remoteSession!;
357
+ const unitDisplay = rs.unitType === "starting" || rs.unitType === "resuming"
358
+ ? rs.unitType
359
+ : `${unitLabel(rs.unitType)} ${rs.unitId}`;
360
+ lines.push(row(th.fg("text", `Remote session: ${unitDisplay}`)));
361
+ lines.push(blank());
347
362
  } else {
348
363
  lines.push(row(th.fg("dim", "No unit running · /gsd auto to start")));
349
364
  lines.push(blank());
350
365
  }
351
366
 
367
+ // Parallel workers section — shows active subagent sessions
368
+ if (hasActiveWorkers()) {
369
+ lines.push(hr());
370
+ lines.push(row(th.fg("text", th.bold("Parallel Workers"))));
371
+ lines.push(blank());
372
+
373
+ const batches = getWorkerBatches();
374
+ for (const [batchId, workers] of batches) {
375
+ const running = workers.filter(w => w.status === "running").length;
376
+ const done = workers.filter(w => w.status === "completed").length;
377
+ const failed = workers.filter(w => w.status === "failed").length;
378
+ const total = workers[0]?.batchSize ?? workers.length;
379
+
380
+ lines.push(row(joinColumns(
381
+ ` ${th.fg("accent", "⟐")} ${th.fg("text", `Batch ${batchId.slice(0, 8)}`)}`,
382
+ th.fg("dim", `${done + failed}/${total} done`),
383
+ contentWidth,
384
+ )));
385
+
386
+ for (const w of workers) {
387
+ const icon = w.status === "running"
388
+ ? th.fg("accent", "▸")
389
+ : w.status === "completed"
390
+ ? th.fg("success", "✓")
391
+ : th.fg("error", "✗");
392
+ const elapsed = th.fg("dim", formatDuration(Date.now() - w.startedAt));
393
+ const taskPreview = truncateToWidth(w.task, Math.max(20, contentWidth - 30));
394
+ lines.push(row(joinColumns(
395
+ ` ${icon} ${th.fg("text", w.agent)} ${th.fg("dim", taskPreview)}`,
396
+ elapsed,
397
+ contentWidth,
398
+ )));
399
+ }
400
+ }
401
+ lines.push(blank());
402
+ }
403
+
352
404
  // Pending captures badge — only shown when captures are waiting for triage
353
405
  if (this.dashData.pendingCaptureCount > 0) {
354
406
  const count = this.dashData.pendingCaptureCount;