@tuan_son.dinh/gsd 2.6.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 (227) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +453 -0
  3. package/dist/app-paths.d.ts +4 -0
  4. package/dist/app-paths.js +6 -0
  5. package/dist/cli.d.ts +1 -0
  6. package/dist/cli.js +269 -0
  7. package/dist/loader.d.ts +2 -0
  8. package/dist/loader.js +70 -0
  9. package/dist/logo.d.ts +16 -0
  10. package/dist/logo.js +25 -0
  11. package/dist/onboarding.d.ts +43 -0
  12. package/dist/onboarding.js +418 -0
  13. package/dist/pi-migration.d.ts +14 -0
  14. package/dist/pi-migration.js +57 -0
  15. package/dist/resource-loader.d.ts +22 -0
  16. package/dist/resource-loader.js +60 -0
  17. package/dist/tool-bootstrap.d.ts +4 -0
  18. package/dist/tool-bootstrap.js +74 -0
  19. package/dist/wizard.d.ts +7 -0
  20. package/dist/wizard.js +25 -0
  21. package/package.json +60 -0
  22. package/patches/@mariozechner+pi-coding-agent+0.57.1.patch +108 -0
  23. package/patches/@mariozechner+pi-tui+0.57.1.patch +47 -0
  24. package/pkg/dist/modes/interactive/theme/dark.json +85 -0
  25. package/pkg/dist/modes/interactive/theme/light.json +84 -0
  26. package/pkg/dist/modes/interactive/theme/theme-schema.json +335 -0
  27. package/pkg/dist/modes/interactive/theme/theme.d.ts +78 -0
  28. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -0
  29. package/pkg/dist/modes/interactive/theme/theme.js +949 -0
  30. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -0
  31. package/pkg/package.json +8 -0
  32. package/scripts/postinstall.js +127 -0
  33. package/src/resources/GSD-WORKFLOW.md +661 -0
  34. package/src/resources/agents/researcher.md +29 -0
  35. package/src/resources/agents/scout.md +56 -0
  36. package/src/resources/agents/worker.md +31 -0
  37. package/src/resources/extensions/ask-user-questions.ts +249 -0
  38. package/src/resources/extensions/bg-shell/index.ts +2808 -0
  39. package/src/resources/extensions/browser-tools/BROWSER-TOOLS-V2-PROPOSAL.md +1277 -0
  40. package/src/resources/extensions/browser-tools/core.js +1057 -0
  41. package/src/resources/extensions/browser-tools/index.ts +4989 -0
  42. package/src/resources/extensions/browser-tools/package.json +20 -0
  43. package/src/resources/extensions/context7/index.ts +428 -0
  44. package/src/resources/extensions/context7/package.json +11 -0
  45. package/src/resources/extensions/get-secrets-from-user.ts +352 -0
  46. package/src/resources/extensions/google-search/index.ts +323 -0
  47. package/src/resources/extensions/google-search/package.json +9 -0
  48. package/src/resources/extensions/gsd/activity-log.ts +69 -0
  49. package/src/resources/extensions/gsd/auto.ts +2744 -0
  50. package/src/resources/extensions/gsd/commands.ts +313 -0
  51. package/src/resources/extensions/gsd/crash-recovery.ts +85 -0
  52. package/src/resources/extensions/gsd/dashboard-overlay.ts +521 -0
  53. package/src/resources/extensions/gsd/docs/preferences-reference.md +176 -0
  54. package/src/resources/extensions/gsd/doctor.ts +690 -0
  55. package/src/resources/extensions/gsd/files.ts +732 -0
  56. package/src/resources/extensions/gsd/git-service.ts +597 -0
  57. package/src/resources/extensions/gsd/gitignore.ts +168 -0
  58. package/src/resources/extensions/gsd/guided-flow.ts +817 -0
  59. package/src/resources/extensions/gsd/index.ts +558 -0
  60. package/src/resources/extensions/gsd/metrics.ts +374 -0
  61. package/src/resources/extensions/gsd/migrate/command.ts +218 -0
  62. package/src/resources/extensions/gsd/migrate/index.ts +42 -0
  63. package/src/resources/extensions/gsd/migrate/parser.ts +323 -0
  64. package/src/resources/extensions/gsd/migrate/parsers.ts +624 -0
  65. package/src/resources/extensions/gsd/migrate/preview.ts +48 -0
  66. package/src/resources/extensions/gsd/migrate/transformer.ts +346 -0
  67. package/src/resources/extensions/gsd/migrate/types.ts +370 -0
  68. package/src/resources/extensions/gsd/migrate/validator.ts +55 -0
  69. package/src/resources/extensions/gsd/migrate/writer.ts +539 -0
  70. package/src/resources/extensions/gsd/observability-validator.ts +408 -0
  71. package/src/resources/extensions/gsd/package.json +11 -0
  72. package/src/resources/extensions/gsd/paths.ts +308 -0
  73. package/src/resources/extensions/gsd/preferences.ts +757 -0
  74. package/src/resources/extensions/gsd/prompt-loader.ts +50 -0
  75. package/src/resources/extensions/gsd/prompts/complete-milestone.md +25 -0
  76. package/src/resources/extensions/gsd/prompts/complete-slice.md +29 -0
  77. package/src/resources/extensions/gsd/prompts/discuss.md +189 -0
  78. package/src/resources/extensions/gsd/prompts/doctor-heal.md +29 -0
  79. package/src/resources/extensions/gsd/prompts/execute-task.md +61 -0
  80. package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -0
  81. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -0
  82. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +59 -0
  83. package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -0
  84. package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +23 -0
  85. package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -0
  86. package/src/resources/extensions/gsd/prompts/guided-research-slice.md +11 -0
  87. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -0
  88. package/src/resources/extensions/gsd/prompts/plan-milestone.md +65 -0
  89. package/src/resources/extensions/gsd/prompts/plan-slice.md +51 -0
  90. package/src/resources/extensions/gsd/prompts/queue.md +85 -0
  91. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +48 -0
  92. package/src/resources/extensions/gsd/prompts/replan-slice.md +39 -0
  93. package/src/resources/extensions/gsd/prompts/research-milestone.md +37 -0
  94. package/src/resources/extensions/gsd/prompts/research-slice.md +28 -0
  95. package/src/resources/extensions/gsd/prompts/review-migration.md +66 -0
  96. package/src/resources/extensions/gsd/prompts/run-uat.md +109 -0
  97. package/src/resources/extensions/gsd/prompts/system.md +187 -0
  98. package/src/resources/extensions/gsd/prompts/worktree-merge.md +123 -0
  99. package/src/resources/extensions/gsd/session-forensics.ts +487 -0
  100. package/src/resources/extensions/gsd/skill-discovery.ts +137 -0
  101. package/src/resources/extensions/gsd/state.ts +460 -0
  102. package/src/resources/extensions/gsd/templates/context.md +76 -0
  103. package/src/resources/extensions/gsd/templates/decisions.md +8 -0
  104. package/src/resources/extensions/gsd/templates/milestone-summary.md +73 -0
  105. package/src/resources/extensions/gsd/templates/plan.md +131 -0
  106. package/src/resources/extensions/gsd/templates/preferences.md +24 -0
  107. package/src/resources/extensions/gsd/templates/project.md +31 -0
  108. package/src/resources/extensions/gsd/templates/reassessment.md +28 -0
  109. package/src/resources/extensions/gsd/templates/requirements.md +81 -0
  110. package/src/resources/extensions/gsd/templates/research.md +46 -0
  111. package/src/resources/extensions/gsd/templates/roadmap.md +118 -0
  112. package/src/resources/extensions/gsd/templates/slice-context.md +58 -0
  113. package/src/resources/extensions/gsd/templates/slice-summary.md +99 -0
  114. package/src/resources/extensions/gsd/templates/state.md +19 -0
  115. package/src/resources/extensions/gsd/templates/task-plan.md +52 -0
  116. package/src/resources/extensions/gsd/templates/task-summary.md +57 -0
  117. package/src/resources/extensions/gsd/templates/uat.md +54 -0
  118. package/src/resources/extensions/gsd/tests/activity-log-prune.test.ts +327 -0
  119. package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +56 -0
  120. package/src/resources/extensions/gsd/tests/auto-supervisor.test.mjs +53 -0
  121. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +225 -0
  122. package/src/resources/extensions/gsd/tests/cost-projection.test.ts +160 -0
  123. package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +341 -0
  124. package/src/resources/extensions/gsd/tests/derive-state.test.ts +689 -0
  125. package/src/resources/extensions/gsd/tests/discuss-prompt.test.ts +38 -0
  126. package/src/resources/extensions/gsd/tests/doctor.test.ts +505 -0
  127. package/src/resources/extensions/gsd/tests/git-service.test.ts +1313 -0
  128. package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +308 -0
  129. package/src/resources/extensions/gsd/tests/metrics-io.test.ts +201 -0
  130. package/src/resources/extensions/gsd/tests/metrics.test.ts +217 -0
  131. package/src/resources/extensions/gsd/tests/migrate-command.test.ts +390 -0
  132. package/src/resources/extensions/gsd/tests/migrate-parser.test.ts +786 -0
  133. package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +657 -0
  134. package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +443 -0
  135. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +318 -0
  136. package/src/resources/extensions/gsd/tests/migrate-writer.test.ts +420 -0
  137. package/src/resources/extensions/gsd/tests/must-have-parser.test.ts +309 -0
  138. package/src/resources/extensions/gsd/tests/parsers.test.ts +1351 -0
  139. package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +163 -0
  140. package/src/resources/extensions/gsd/tests/plan-quality-validator.test.ts +386 -0
  141. package/src/resources/extensions/gsd/tests/reassess-prompt.test.ts +171 -0
  142. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +155 -0
  143. package/src/resources/extensions/gsd/tests/remote-status.test.ts +99 -0
  144. package/src/resources/extensions/gsd/tests/replan-slice.test.ts +521 -0
  145. package/src/resources/extensions/gsd/tests/requirements.test.ts +125 -0
  146. package/src/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +34 -0
  147. package/src/resources/extensions/gsd/tests/resolve-ts.mjs +11 -0
  148. package/src/resources/extensions/gsd/tests/run-uat.test.ts +348 -0
  149. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +247 -0
  150. package/src/resources/extensions/gsd/tests/workflow-config.test.mjs +53 -0
  151. package/src/resources/extensions/gsd/tests/workspace-index.test.ts +94 -0
  152. package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +253 -0
  153. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +160 -0
  154. package/src/resources/extensions/gsd/tests/worktree.test.ts +264 -0
  155. package/src/resources/extensions/gsd/types.ts +159 -0
  156. package/src/resources/extensions/gsd/unit-runtime.ts +184 -0
  157. package/src/resources/extensions/gsd/workspace-index.ts +203 -0
  158. package/src/resources/extensions/gsd/worktree-command.ts +845 -0
  159. package/src/resources/extensions/gsd/worktree-manager.ts +392 -0
  160. package/src/resources/extensions/gsd/worktree.ts +183 -0
  161. package/src/resources/extensions/mac-tools/index.ts +852 -0
  162. package/src/resources/extensions/mac-tools/swift-cli/Package.swift +22 -0
  163. package/src/resources/extensions/mac-tools/swift-cli/Sources/main.swift +1318 -0
  164. package/src/resources/extensions/mcporter/index.ts +429 -0
  165. package/src/resources/extensions/remote-questions/config.ts +81 -0
  166. package/src/resources/extensions/remote-questions/discord-adapter.ts +128 -0
  167. package/src/resources/extensions/remote-questions/format.ts +163 -0
  168. package/src/resources/extensions/remote-questions/manager.ts +192 -0
  169. package/src/resources/extensions/remote-questions/remote-command.ts +307 -0
  170. package/src/resources/extensions/remote-questions/slack-adapter.ts +92 -0
  171. package/src/resources/extensions/remote-questions/status.ts +31 -0
  172. package/src/resources/extensions/remote-questions/store.ts +77 -0
  173. package/src/resources/extensions/remote-questions/types.ts +75 -0
  174. package/src/resources/extensions/search-the-web/cache.ts +78 -0
  175. package/src/resources/extensions/search-the-web/command-search-provider.ts +95 -0
  176. package/src/resources/extensions/search-the-web/format.ts +258 -0
  177. package/src/resources/extensions/search-the-web/http.ts +238 -0
  178. package/src/resources/extensions/search-the-web/index.ts +65 -0
  179. package/src/resources/extensions/search-the-web/native-search.ts +157 -0
  180. package/src/resources/extensions/search-the-web/provider.ts +118 -0
  181. package/src/resources/extensions/search-the-web/tavily.ts +116 -0
  182. package/src/resources/extensions/search-the-web/tool-fetch-page.ts +519 -0
  183. package/src/resources/extensions/search-the-web/tool-llm-context.ts +561 -0
  184. package/src/resources/extensions/search-the-web/tool-search.ts +576 -0
  185. package/src/resources/extensions/search-the-web/url-utils.ts +91 -0
  186. package/src/resources/extensions/shared/confirm-ui.ts +126 -0
  187. package/src/resources/extensions/shared/interview-ui.ts +613 -0
  188. package/src/resources/extensions/shared/next-action-ui.ts +197 -0
  189. package/src/resources/extensions/shared/progress-widget.ts +282 -0
  190. package/src/resources/extensions/shared/terminal.ts +23 -0
  191. package/src/resources/extensions/shared/thinking-widget.ts +107 -0
  192. package/src/resources/extensions/shared/ui.ts +400 -0
  193. package/src/resources/extensions/shared/wizard-ui.ts +551 -0
  194. package/src/resources/extensions/slash-commands/audit.ts +88 -0
  195. package/src/resources/extensions/slash-commands/clear.ts +10 -0
  196. package/src/resources/extensions/slash-commands/create-extension.ts +297 -0
  197. package/src/resources/extensions/slash-commands/create-slash-command.ts +234 -0
  198. package/src/resources/extensions/slash-commands/index.ts +12 -0
  199. package/src/resources/extensions/subagent/agents.ts +126 -0
  200. package/src/resources/extensions/subagent/index.ts +1020 -0
  201. package/src/resources/extensions/voice/index.ts +195 -0
  202. package/src/resources/extensions/voice/speech-recognizer.swift +154 -0
  203. package/src/resources/skills/debug-like-expert/SKILL.md +231 -0
  204. package/src/resources/skills/debug-like-expert/references/debugging-mindset.md +253 -0
  205. package/src/resources/skills/debug-like-expert/references/hypothesis-testing.md +373 -0
  206. package/src/resources/skills/debug-like-expert/references/investigation-techniques.md +337 -0
  207. package/src/resources/skills/debug-like-expert/references/verification-patterns.md +425 -0
  208. package/src/resources/skills/debug-like-expert/references/when-to-research.md +361 -0
  209. package/src/resources/skills/frontend-design/SKILL.md +45 -0
  210. package/src/resources/skills/swiftui/SKILL.md +208 -0
  211. package/src/resources/skills/swiftui/references/animations.md +921 -0
  212. package/src/resources/skills/swiftui/references/architecture.md +1561 -0
  213. package/src/resources/skills/swiftui/references/layout-system.md +1186 -0
  214. package/src/resources/skills/swiftui/references/navigation.md +1492 -0
  215. package/src/resources/skills/swiftui/references/networking-async.md +214 -0
  216. package/src/resources/skills/swiftui/references/performance.md +1706 -0
  217. package/src/resources/skills/swiftui/references/platform-integration.md +204 -0
  218. package/src/resources/skills/swiftui/references/state-management.md +1443 -0
  219. package/src/resources/skills/swiftui/references/swiftdata.md +297 -0
  220. package/src/resources/skills/swiftui/references/testing-debugging.md +247 -0
  221. package/src/resources/skills/swiftui/references/uikit-appkit-interop.md +218 -0
  222. package/src/resources/skills/swiftui/workflows/add-feature.md +191 -0
  223. package/src/resources/skills/swiftui/workflows/build-new-app.md +311 -0
  224. package/src/resources/skills/swiftui/workflows/debug-swiftui.md +192 -0
  225. package/src/resources/skills/swiftui/workflows/optimize-performance.md +197 -0
  226. package/src/resources/skills/swiftui/workflows/ship-app.md +203 -0
  227. package/src/resources/skills/swiftui/workflows/write-tests.md +235 -0
@@ -0,0 +1,817 @@
1
+ /**
2
+ * GSD Guided Flow — Smart Entry Wizard
3
+ *
4
+ * One function: showSmartEntry(). Reads state from disk, shows a contextual
5
+ * wizard via showNextAction(), and dispatches through GSD-WORKFLOW.md.
6
+ * No execution state, no hooks, no tools — the LLM does the rest.
7
+ */
8
+
9
+ import type { ExtensionAPI, ExtensionContext, ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
10
+ import { showNextAction } from "../shared/next-action-ui.js";
11
+ import { loadFile, parseRoadmap } from "./files.js";
12
+ import { loadPrompt } from "./prompt-loader.js";
13
+ import { deriveState } from "./state.js";
14
+ import { startAuto } from "./auto.js";
15
+ import { readCrashLock, clearLock, formatCrashInfo } from "./crash-recovery.js";
16
+ import {
17
+ gsdRoot, milestonesDir, resolveMilestoneFile,
18
+ resolveSliceFile, resolveSlicePath, resolveGsdRootFile, relGsdRootFile,
19
+ relMilestoneFile, relSliceFile, relSlicePath,
20
+ } from "./paths.js";
21
+ import { join } from "node:path";
22
+ import { readFileSync, existsSync, mkdirSync, readdirSync } from "node:fs";
23
+ import { execSync, execFileSync } from "node:child_process";
24
+ import { ensureGitignore, ensurePreferences } from "./gitignore.js";
25
+ import { loadEffectiveGSDPreferences } from "./preferences.js";
26
+
27
+ // ─── Auto-start after discuss ─────────────────────────────────────────────────
28
+
29
+ /** Stashed context + flag for auto-starting after discuss phase completes */
30
+ let pendingAutoStart: {
31
+ ctx: ExtensionCommandContext;
32
+ pi: ExtensionAPI;
33
+ basePath: string;
34
+ milestoneId: string; // the milestone being discussed
35
+ step?: boolean; // preserve step mode through discuss → auto transition
36
+ } | null = null;
37
+
38
+ /** Called from agent_end to check if auto-mode should start after discuss */
39
+ export function checkAutoStartAfterDiscuss(): boolean {
40
+ if (!pendingAutoStart) return false;
41
+
42
+ const { ctx, pi, basePath, milestoneId, step } = pendingAutoStart;
43
+
44
+ // Don't fire until the discuss phase has actually produced a context file
45
+ // for the milestone being discussed. agent_end fires after every LLM turn,
46
+ // including the initial "What do you want to build?" response — we need to
47
+ // wait for the full conversation to complete and the LLM to write CONTEXT.md.
48
+ const contextFile = resolveMilestoneFile(basePath, milestoneId, "CONTEXT");
49
+ if (!contextFile) return false; // no context yet — keep waiting
50
+
51
+ pendingAutoStart = null;
52
+ startAuto(ctx, pi, basePath, false, { step }).catch(() => {});
53
+ return true;
54
+ }
55
+
56
+ // ─── Types ────────────────────────────────────────────────────────────────────
57
+
58
+ type UIContext = ExtensionContext;
59
+
60
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
61
+
62
+ /**
63
+ * Read GSD-WORKFLOW.md and dispatch it to the LLM with a contextual note.
64
+ * This is the only way the wizard triggers work — everything else is the LLM's job.
65
+ */
66
+ function dispatchWorkflow(pi: ExtensionAPI, note: string, customType = "gsd-run"): void {
67
+ const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".pi", "GSD-WORKFLOW.md");
68
+ const workflow = readFileSync(workflowPath, "utf-8");
69
+
70
+ pi.sendMessage(
71
+ {
72
+ customType,
73
+ content: `Read the following GSD workflow protocol and execute exactly.\n\n${workflow}\n\n## Your Task\n\n${note}`,
74
+ display: false,
75
+ },
76
+ { triggerTurn: true },
77
+ );
78
+ }
79
+
80
+ /**
81
+ * Build the discuss-and-plan prompt for a new milestone.
82
+ * Used by all three "new milestone" paths (first ever, no active, all complete).
83
+ */
84
+ function buildDiscussPrompt(nextId: string, preamble: string, basePath: string): string {
85
+ const milestoneDirAbs = join(basePath, ".gsd", "milestones", nextId);
86
+ return loadPrompt("discuss", {
87
+ milestoneId: nextId,
88
+ preamble,
89
+ contextAbsPath: join(milestoneDirAbs, `${nextId}-CONTEXT.md`),
90
+ roadmapAbsPath: join(milestoneDirAbs, `${nextId}-ROADMAP.md`),
91
+ });
92
+ }
93
+
94
+ function findMilestoneIds(basePath: string): string[] {
95
+ const dir = milestonesDir(basePath);
96
+ try {
97
+ return readdirSync(dir, { withFileTypes: true })
98
+ .filter((d) => d.isDirectory())
99
+ .map((d) => {
100
+ const match = d.name.match(/^(M\d+)/);
101
+ return match ? match[1] : d.name;
102
+ })
103
+ .sort();
104
+ } catch {
105
+ return [];
106
+ }
107
+ }
108
+
109
+ // ─── Queue ─────────────────────────────────────────────────────────────────────
110
+
111
+ /**
112
+ * Queue future milestones via conversational intake.
113
+ *
114
+ * Safe to run while auto-mode is executing — only writes to future milestone
115
+ * directories (which auto-mode won't touch until it reaches them) and appends
116
+ * to project.md / queue.md.
117
+ *
118
+ * The flow:
119
+ * 1. Build context about all existing milestones (complete, active, pending)
120
+ * 2. Dispatch the queue prompt — LLM discusses with the user, assesses scope
121
+ * 3. LLM writes CONTEXT.md files for new milestones (no roadmaps — JIT)
122
+ * 4. Auto-mode picks them up naturally when it advances past current work
123
+ *
124
+ * Root durable artifacts use uppercase names like PROJECT.md and QUEUE.md.
125
+ */
126
+ export async function showQueue(
127
+ ctx: ExtensionCommandContext,
128
+ pi: ExtensionAPI,
129
+ basePath: string,
130
+ ): Promise<void> {
131
+ // ── Ensure .gsd/ exists ─────────────────────────────────────────────
132
+ const gsd = gsdRoot(basePath);
133
+ if (!existsSync(gsd)) {
134
+ ctx.ui.notify("No GSD project found. Run /gsd to start one first.", "warning");
135
+ return;
136
+ }
137
+
138
+ const state = await deriveState(basePath);
139
+ const milestoneIds = findMilestoneIds(basePath);
140
+
141
+ if (milestoneIds.length === 0) {
142
+ ctx.ui.notify("No milestones exist yet. Run /gsd to create the first one.", "warning");
143
+ return;
144
+ }
145
+
146
+ // ── Build existing milestones context for the prompt ────────────────
147
+ const existingContext = await buildExistingMilestonesContext(basePath, milestoneIds, state);
148
+
149
+ // ── Determine next milestone ID ─────────────────────────────────────
150
+ const maxNum = milestoneIds.reduce((max, id) => {
151
+ const num = parseInt(id.replace(/^M/, ""), 10);
152
+ return num > max ? num : max;
153
+ }, 0);
154
+ const nextId = `M${String(maxNum + 1).padStart(3, "0")}`;
155
+ const nextIdPlus1 = `M${String(maxNum + 2).padStart(3, "0")}`;
156
+
157
+ // ── Build preamble ──────────────────────────────────────────────────
158
+ const activePart = state.activeMilestone
159
+ ? `Currently executing: ${state.activeMilestone.id} — ${state.activeMilestone.title} (phase: ${state.phase}).`
160
+ : "No milestone currently active.";
161
+
162
+ const pendingCount = state.registry.filter(m => m.status === "pending").length;
163
+ const completeCount = state.registry.filter(m => m.status === "complete").length;
164
+
165
+ const preamble = [
166
+ `Queuing new work onto an existing GSD project.`,
167
+ activePart,
168
+ `${completeCount} milestone(s) complete, ${pendingCount} pending.`,
169
+ `Next available milestone ID: ${nextId}.`,
170
+ ].join(" ");
171
+
172
+ // ── Dispatch the queue prompt ───────────────────────────────────────
173
+ const prompt = loadPrompt("queue", {
174
+ preamble,
175
+ nextId,
176
+ nextIdPlus1,
177
+ existingMilestonesContext: existingContext,
178
+ });
179
+
180
+ pi.sendMessage(
181
+ {
182
+ customType: "gsd-queue",
183
+ content: prompt,
184
+ display: false,
185
+ },
186
+ { triggerTurn: true },
187
+ );
188
+ }
189
+
190
+ /**
191
+ * Build a context block describing all existing milestones for the queue prompt.
192
+ * Gives the LLM enough information to dedup, sequence, and dependency-check.
193
+ */
194
+ async function buildExistingMilestonesContext(
195
+ basePath: string,
196
+ milestoneIds: string[],
197
+ state: import("./types.js").GSDState,
198
+ ): Promise<string> {
199
+ const sections: string[] = [];
200
+
201
+ // Include PROJECT.md if it exists — it has the milestone sequence and project description
202
+ const projectPath = resolveGsdRootFile(basePath, "PROJECT");
203
+ if (existsSync(projectPath)) {
204
+ const projectContent = await loadFile(projectPath);
205
+ if (projectContent) {
206
+ sections.push(`### Project Overview\nSource: \`${relGsdRootFile("PROJECT")}\`\n\n${projectContent.trim()}`);
207
+ }
208
+ }
209
+
210
+ // Include DECISIONS.md if it exists — architectural decisions inform new milestone scoping
211
+ const decisionsPath = resolveGsdRootFile(basePath, "DECISIONS");
212
+ if (existsSync(decisionsPath)) {
213
+ const decisionsContent = await loadFile(decisionsPath);
214
+ if (decisionsContent) {
215
+ sections.push(`### Decisions Register\nSource: \`${relGsdRootFile("DECISIONS")}\`\n\n${decisionsContent.trim()}`);
216
+ }
217
+ }
218
+
219
+ // For each milestone, include context and status
220
+ for (const mid of milestoneIds) {
221
+ const registryEntry = state.registry.find(m => m.id === mid);
222
+ const status = registryEntry?.status ?? "unknown";
223
+ const title = registryEntry?.title ?? mid;
224
+
225
+ const parts: string[] = [];
226
+ parts.push(`### ${mid}: ${title}\n**Status:** ${status}`);
227
+
228
+ // Include context file — this is the primary content for understanding scope
229
+ const contextFile = resolveMilestoneFile(basePath, mid, "CONTEXT");
230
+ if (contextFile) {
231
+ const content = await loadFile(contextFile);
232
+ if (content) {
233
+ parts.push(`\n**Context:**\n${content.trim()}`);
234
+ }
235
+ }
236
+
237
+ // For completed milestones, include the summary if it exists
238
+ if (status === "complete") {
239
+ const summaryFile = resolveMilestoneFile(basePath, mid, "SUMMARY");
240
+ if (summaryFile) {
241
+ const content = await loadFile(summaryFile);
242
+ if (content) {
243
+ parts.push(`\n**Summary:**\n${content.trim()}`);
244
+ }
245
+ }
246
+ }
247
+
248
+ // For active/pending milestones, include the roadmap if it exists
249
+ // (shows what's planned but not yet built)
250
+ if (status === "active" || status === "pending") {
251
+ const roadmapFile = resolveMilestoneFile(basePath, mid, "ROADMAP");
252
+ if (roadmapFile) {
253
+ const content = await loadFile(roadmapFile);
254
+ if (content) {
255
+ parts.push(`\n**Roadmap:**\n${content.trim()}`);
256
+ }
257
+ }
258
+ }
259
+
260
+ sections.push(parts.join("\n"));
261
+ }
262
+
263
+ // Include queue log if it exists — shows what's been queued before
264
+ const queuePath = resolveGsdRootFile(basePath, "QUEUE");
265
+ if (existsSync(queuePath)) {
266
+ const queueContent = await loadFile(queuePath);
267
+ if (queueContent) {
268
+ sections.push(`### Previous Queue Entries\nSource: \`${relGsdRootFile("QUEUE")}\`\n\n${queueContent.trim()}`);
269
+ }
270
+ }
271
+
272
+ return sections.join("\n\n---\n\n");
273
+ }
274
+
275
+ // ─── Discuss Flow ─────────────────────────────────────────────────────────────
276
+
277
+ /**
278
+ * Build a rich inlined-context prompt for discussing a specific slice.
279
+ * Preloads roadmap, milestone context, research, decisions, and completed
280
+ * slice summaries so the agent can ask grounded UX/behaviour questions
281
+ * without wasting a turn reading files.
282
+ */
283
+ async function buildDiscussSlicePrompt(
284
+ mid: string,
285
+ sid: string,
286
+ sTitle: string,
287
+ base: string,
288
+ ): Promise<string> {
289
+ const inlined: string[] = [];
290
+
291
+ // Roadmap — always included so the agent sees surrounding slices
292
+ const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
293
+ const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
294
+ const roadmapContent = roadmapPath ? await loadFile(roadmapPath) : null;
295
+ if (roadmapContent) {
296
+ inlined.push(`### Milestone Roadmap\nSource: \`${roadmapRel}\`\n\n${roadmapContent.trim()}`);
297
+ }
298
+
299
+ // Milestone context — understanding the full milestone intent
300
+ const contextPath = resolveMilestoneFile(base, mid, "CONTEXT");
301
+ const contextRel = relMilestoneFile(base, mid, "CONTEXT");
302
+ const contextContent = contextPath ? await loadFile(contextPath) : null;
303
+ if (contextContent) {
304
+ inlined.push(`### Milestone Context\nSource: \`${contextRel}\`\n\n${contextContent.trim()}`);
305
+ }
306
+
307
+ // Milestone research — technical grounding
308
+ const researchPath = resolveMilestoneFile(base, mid, "RESEARCH");
309
+ const researchRel = relMilestoneFile(base, mid, "RESEARCH");
310
+ const researchContent = researchPath ? await loadFile(researchPath) : null;
311
+ if (researchContent) {
312
+ inlined.push(`### Milestone Research\nSource: \`${researchRel}\`\n\n${researchContent.trim()}`);
313
+ }
314
+
315
+ // Decisions — architectural context that constrains this slice
316
+ const decisionsPath = resolveGsdRootFile(base, "DECISIONS");
317
+ if (existsSync(decisionsPath)) {
318
+ const decisionsContent = await loadFile(decisionsPath);
319
+ if (decisionsContent) {
320
+ inlined.push(`### Decisions Register\nSource: \`${relGsdRootFile("DECISIONS")}\`\n\n${decisionsContent.trim()}`);
321
+ }
322
+ }
323
+
324
+ // Completed slice summaries — what was already built that this slice builds on
325
+ if (roadmapContent) {
326
+ const roadmap = parseRoadmap(roadmapContent);
327
+ for (const s of roadmap.slices) {
328
+ if (!s.done || s.id === sid) continue;
329
+ const summaryPath = resolveSliceFile(base, mid, s.id, "SUMMARY");
330
+ const summaryRel = relSliceFile(base, mid, s.id, "SUMMARY");
331
+ const summaryContent = summaryPath ? await loadFile(summaryPath) : null;
332
+ if (summaryContent) {
333
+ inlined.push(`### ${s.id} Summary (completed)\nSource: \`${summaryRel}\`\n\n${summaryContent.trim()}`);
334
+ }
335
+ }
336
+ }
337
+
338
+ const inlinedContext = inlined.length > 0
339
+ ? `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`
340
+ : `## Inlined Context\n\n_(no context files found yet — go in blind and ask broad questions)_`;
341
+
342
+ const sliceDirAbsPath = join(base, ".gsd", "milestones", mid, "slices", sid);
343
+ const contextAbsPath = join(sliceDirAbsPath, `${sid}-CONTEXT.md`);
344
+
345
+ return loadPrompt("guided-discuss-slice", {
346
+ milestoneId: mid,
347
+ sliceId: sid,
348
+ sliceTitle: sTitle,
349
+ inlinedContext,
350
+ sliceDirAbsPath,
351
+ contextAbsPath,
352
+ projectRoot: base,
353
+ });
354
+ }
355
+
356
+ /**
357
+ * /gsd discuss — show a picker of non-done slices and run a slice interview.
358
+ * Loops back to the picker after each discussion so the user can chain
359
+ * multiple slice interviews in one session.
360
+ */
361
+ export async function showDiscuss(
362
+ ctx: ExtensionCommandContext,
363
+ pi: ExtensionAPI,
364
+ basePath: string,
365
+ ): Promise<void> {
366
+ // Guard: no .gsd/ project
367
+ if (!existsSync(join(basePath, ".gsd"))) {
368
+ ctx.ui.notify("No GSD project found. Run /gsd to start one first.", "warning");
369
+ return;
370
+ }
371
+
372
+ const state = await deriveState(basePath);
373
+
374
+ // Guard: no active milestone
375
+ if (!state.activeMilestone) {
376
+ ctx.ui.notify("No active milestone. Run /gsd to create one first.", "warning");
377
+ return;
378
+ }
379
+
380
+ const mid = state.activeMilestone.id;
381
+ const milestoneTitle = state.activeMilestone.title;
382
+
383
+ // Guard: no roadmap yet
384
+ const roadmapFile = resolveMilestoneFile(basePath, mid, "ROADMAP");
385
+ const roadmapContent = roadmapFile ? await loadFile(roadmapFile) : null;
386
+ if (!roadmapContent) {
387
+ ctx.ui.notify("No roadmap yet for this milestone. Run /gsd to plan first.", "warning");
388
+ return;
389
+ }
390
+
391
+ const roadmap = parseRoadmap(roadmapContent);
392
+ const pendingSlices = roadmap.slices.filter(s => !s.done);
393
+
394
+ if (pendingSlices.length === 0) {
395
+ ctx.ui.notify("All slices are complete — nothing to discuss.", "info");
396
+ return;
397
+ }
398
+
399
+ // Loop: show picker, dispatch discuss, repeat until "not_yet"
400
+ while (true) {
401
+ const actions = pendingSlices.map((s, i) => ({
402
+ id: s.id,
403
+ label: `${s.id}: ${s.title}`,
404
+ description: state.activeSlice?.id === s.id ? "active slice" : "upcoming",
405
+ recommended: i === 0,
406
+ }));
407
+
408
+ const choice = await showNextAction(ctx as any, {
409
+ title: "GSD — Discuss a slice",
410
+ summary: [
411
+ `${mid}: ${milestoneTitle}`,
412
+ "Pick a slice to interview. Context file will be written when done.",
413
+ ],
414
+ actions,
415
+ notYetMessage: "Run /gsd discuss when ready.",
416
+ });
417
+
418
+ if (choice === "not_yet") return;
419
+
420
+ const chosen = pendingSlices.find(s => s.id === choice);
421
+ if (!chosen) return;
422
+
423
+ const prompt = await buildDiscussSlicePrompt(mid, chosen.id, chosen.title, basePath);
424
+ dispatchWorkflow(pi, prompt, "gsd-discuss");
425
+
426
+ // Wait for the discuss session to finish, then loop back to the picker
427
+ await ctx.waitForIdle();
428
+ }
429
+ }
430
+
431
+ // ─── Smart Entry Point ────────────────────────────────────────────────────────
432
+
433
+ /**
434
+ * The one wizard. Reads state, shows contextual options, dispatches into the workflow doc.
435
+ */
436
+ export async function showSmartEntry(
437
+ ctx: ExtensionCommandContext,
438
+ pi: ExtensionAPI,
439
+ basePath: string,
440
+ options?: { step?: boolean },
441
+ ): Promise<void> {
442
+ const stepMode = options?.step;
443
+
444
+ // ── Ensure git repo exists — GSD needs it for branch-per-slice ──────
445
+ try {
446
+ execSync("git rev-parse --git-dir", { cwd: basePath, stdio: "pipe" });
447
+ } catch {
448
+ const mainBranch = loadEffectiveGSDPreferences()?.preferences?.git?.main_branch || "main";
449
+ execFileSync("git", ["init", "-b", mainBranch], { cwd: basePath, stdio: "pipe" });
450
+ }
451
+
452
+ // ── Ensure .gitignore has baseline patterns ──────────────────────────
453
+ ensureGitignore(basePath);
454
+
455
+ // ── No GSD project OR no milestone → Create first/next milestone ────
456
+ if (!existsSync(join(basePath, ".gsd"))) {
457
+ // Bootstrap .gsd/ silently — the user wants a milestone, not to "init"
458
+ const gsd = gsdRoot(basePath);
459
+ mkdirSync(join(gsd, "milestones"), { recursive: true });
460
+
461
+ // ── Create PREFERENCES.md template ────────────────────────────────
462
+ ensurePreferences(basePath);
463
+ try {
464
+ execSync("git add -A .gsd .gitignore && git commit -m 'chore: init gsd'", {
465
+ cwd: basePath,
466
+ stdio: "pipe",
467
+ });
468
+ } catch {
469
+ // nothing to commit — that's fine
470
+ }
471
+ }
472
+
473
+ // Check for crash from previous auto-mode session
474
+ const crashLock = readCrashLock(basePath);
475
+ if (crashLock) {
476
+ clearLock(basePath);
477
+ const resume = await showNextAction(ctx as any, {
478
+ title: "GSD — Interrupted Session Detected",
479
+ summary: [formatCrashInfo(crashLock)],
480
+ actions: [
481
+ { id: "resume", label: "Resume with /gsd auto", description: "Pick up where it left off", recommended: true },
482
+ { id: "continue", label: "Continue manually", description: "Open the wizard as normal" },
483
+ ],
484
+ });
485
+ if (resume === "resume") {
486
+ await startAuto(ctx, pi, basePath, false);
487
+ return;
488
+ }
489
+ }
490
+
491
+ const state = await deriveState(basePath);
492
+
493
+ if (!state.activeMilestone) {
494
+ // Guard: if a discuss session is already in flight, don't re-inject the prompt.
495
+ // Both /gsd and /gsd auto reach this branch when no milestone exists yet.
496
+ // Without this guard, every subsequent /gsd call overwrites pendingAutoStart
497
+ // and fires another dispatchWorkflow, resetting the conversation mid-interview.
498
+ if (pendingAutoStart) {
499
+ ctx.ui.notify("Discussion already in progress — answer the question above to continue.", "info");
500
+ return;
501
+ }
502
+
503
+ const milestoneIds = findMilestoneIds(basePath);
504
+ const nextId = `M${String(milestoneIds.length + 1).padStart(3, "0")}`;
505
+ const isFirst = milestoneIds.length === 0;
506
+
507
+ if (isFirst) {
508
+ // First ever — skip wizard, just ask directly
509
+ pendingAutoStart = { ctx, pi, basePath, milestoneId: nextId, step: stepMode };
510
+ dispatchWorkflow(pi, buildDiscussPrompt(nextId,
511
+ `New project, milestone ${nextId}. Do NOT read or explore .gsd/ — it's empty scaffolding.`,
512
+ basePath
513
+ ));
514
+ } else {
515
+ const choice = await showNextAction(ctx as any, {
516
+ title: "GSD — Get Shit Done",
517
+ summary: ["No active milestone."],
518
+ actions: [
519
+ {
520
+ id: "new_milestone",
521
+ label: "Create next milestone",
522
+ description: "Define what to build next.",
523
+ recommended: true,
524
+ },
525
+ ],
526
+ notYetMessage: "Run /gsd when ready.",
527
+ });
528
+
529
+ if (choice === "new_milestone") {
530
+ pendingAutoStart = { ctx, pi, basePath, milestoneId: nextId, step: stepMode };
531
+ dispatchWorkflow(pi, buildDiscussPrompt(nextId,
532
+ `New milestone ${nextId}.`,
533
+ basePath
534
+ ));
535
+ }
536
+ }
537
+ return;
538
+ }
539
+
540
+ const milestoneId = state.activeMilestone.id;
541
+ const milestoneTitle = state.activeMilestone.title;
542
+
543
+ // ── All milestones complete → New milestone ──────────────────────────
544
+ if (state.phase === "complete") {
545
+ const choice = await showNextAction(ctx as any, {
546
+ title: `GSD — ${milestoneId}: ${milestoneTitle}`,
547
+ summary: ["All milestones complete."],
548
+ actions: [
549
+ {
550
+ id: "new_milestone",
551
+ label: "Start new milestone",
552
+ description: "Define and plan the next milestone.",
553
+ recommended: true,
554
+ },
555
+ {
556
+ id: "status",
557
+ label: "View status",
558
+ description: "Review what was built.",
559
+ },
560
+ ],
561
+ notYetMessage: "Run /gsd when ready.",
562
+ });
563
+
564
+ if (choice === "new_milestone") {
565
+ const milestoneIds = findMilestoneIds(basePath);
566
+ const nextId = `M${String(milestoneIds.length + 1).padStart(3, "0")}`;
567
+
568
+ pendingAutoStart = { ctx, pi, basePath, milestoneId: nextId, step: stepMode };
569
+ dispatchWorkflow(pi, buildDiscussPrompt(nextId,
570
+ `New milestone ${nextId}.`,
571
+ basePath
572
+ ));
573
+ } else if (choice === "status") {
574
+ const { fireStatusViaCommand } = await import("./commands.js");
575
+ await fireStatusViaCommand(ctx);
576
+ }
577
+ return;
578
+ }
579
+
580
+ // ── No active slice ──────────────────────────────────────────────────
581
+ if (!state.activeSlice) {
582
+ const roadmapFile = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
583
+ const hasRoadmap = !!(roadmapFile && await loadFile(roadmapFile));
584
+
585
+ if (!hasRoadmap) {
586
+ // No roadmap → discuss or plan
587
+ const contextFile = resolveMilestoneFile(basePath, milestoneId, "CONTEXT");
588
+ const hasContext = !!(contextFile && await loadFile(contextFile));
589
+
590
+ const actions = [
591
+ {
592
+ id: "plan",
593
+ label: "Create roadmap",
594
+ description: hasContext
595
+ ? "Context captured. Decompose into slices with a boundary map."
596
+ : "Decompose the milestone into slices with a boundary map.",
597
+ recommended: true,
598
+ },
599
+ ...(!hasContext ? [{
600
+ id: "discuss",
601
+ label: "Discuss first",
602
+ description: "Capture decisions on gray areas before planning.",
603
+ }] : []),
604
+ ];
605
+
606
+ const choice = await showNextAction(ctx as any, {
607
+ title: `GSD — ${milestoneId}: ${milestoneTitle}`,
608
+ summary: [hasContext ? "Context captured. Ready to create roadmap." : "New milestone — no roadmap yet."],
609
+ actions,
610
+ notYetMessage: "Run /gsd when ready.",
611
+ });
612
+
613
+ if (choice === "plan") {
614
+ dispatchWorkflow(pi, loadPrompt("guided-plan-milestone", {
615
+ milestoneId, milestoneTitle,
616
+ }));
617
+ } else if (choice === "discuss") {
618
+ dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
619
+ milestoneId, milestoneTitle,
620
+ }));
621
+ }
622
+ } else {
623
+ // Roadmap exists — either blocked or ready for auto
624
+ const actions = [
625
+ {
626
+ id: "auto",
627
+ label: "Go auto",
628
+ description: "Execute everything automatically until milestone complete.",
629
+ recommended: true,
630
+ },
631
+ {
632
+ id: "status",
633
+ label: "View status",
634
+ description: "See milestone progress and blockers.",
635
+ },
636
+ ];
637
+
638
+ const choice = await showNextAction(ctx as any, {
639
+ title: `GSD — ${milestoneId}: ${milestoneTitle}`,
640
+ summary: ["Roadmap exists. Ready to execute."],
641
+ actions,
642
+ notYetMessage: "Run /gsd status for details.",
643
+ });
644
+
645
+ if (choice === "auto") {
646
+ await startAuto(ctx, pi, basePath, false);
647
+ } else if (choice === "status") {
648
+ const { fireStatusViaCommand } = await import("./commands.js");
649
+ await fireStatusViaCommand(ctx);
650
+ }
651
+ }
652
+ return;
653
+ }
654
+
655
+ const sliceId = state.activeSlice.id;
656
+ const sliceTitle = state.activeSlice.title;
657
+
658
+ // ── Slice needs planning ─────────────────────────────────────────────
659
+ if (state.phase === "planning") {
660
+ const contextFile = resolveSliceFile(basePath, milestoneId, sliceId, "CONTEXT");
661
+ const researchFile = resolveSliceFile(basePath, milestoneId, sliceId, "RESEARCH");
662
+ const hasContext = !!(contextFile && await loadFile(contextFile));
663
+ const hasResearch = !!(researchFile && await loadFile(researchFile));
664
+
665
+ const actions = [
666
+ {
667
+ id: "plan",
668
+ label: `Plan ${sliceId}`,
669
+ description: `Decompose "${sliceTitle}" into tasks with must-haves.`,
670
+ recommended: true,
671
+ },
672
+ ...(!hasContext ? [{
673
+ id: "discuss",
674
+ label: `Discuss ${sliceId} first`,
675
+ description: "Capture context and decisions for this slice.",
676
+ }] : []),
677
+ ...(!hasResearch ? [{
678
+ id: "research",
679
+ label: `Research ${sliceId} first`,
680
+ description: "Scout codebase and relevant docs.",
681
+ }] : []),
682
+ {
683
+ id: "status",
684
+ label: "View status",
685
+ description: "See milestone progress.",
686
+ },
687
+ ];
688
+
689
+ const summaryParts = [];
690
+ if (hasContext) summaryParts.push("context ✓");
691
+ if (hasResearch) summaryParts.push("research ✓");
692
+ const summaryLine = summaryParts.length > 0
693
+ ? `${sliceId}: ${sliceTitle} (${summaryParts.join(", ")})`
694
+ : `${sliceId}: ${sliceTitle} — ready for planning.`;
695
+
696
+ const choice = await showNextAction(ctx as any, {
697
+ title: `GSD — ${milestoneId} / ${sliceId}: ${sliceTitle}`,
698
+ summary: [summaryLine],
699
+ actions,
700
+ notYetMessage: "Run /gsd when ready.",
701
+ });
702
+
703
+ if (choice === "plan") {
704
+ dispatchWorkflow(pi, loadPrompt("guided-plan-slice", {
705
+ milestoneId, sliceId, sliceTitle,
706
+ }));
707
+ } else if (choice === "discuss") {
708
+ dispatchWorkflow(pi, await buildDiscussSlicePrompt(milestoneId, sliceId, sliceTitle, basePath));
709
+ } else if (choice === "research") {
710
+ dispatchWorkflow(pi, loadPrompt("guided-research-slice", {
711
+ milestoneId, sliceId, sliceTitle,
712
+ }));
713
+ } else if (choice === "status") {
714
+ const { fireStatusViaCommand } = await import("./commands.js");
715
+ await fireStatusViaCommand(ctx);
716
+ }
717
+ return;
718
+ }
719
+
720
+ // ── All tasks done → Complete slice ──────────────────────────────────
721
+ if (state.phase === "summarizing") {
722
+ const choice = await showNextAction(ctx as any, {
723
+ title: `GSD — ${milestoneId} / ${sliceId}: ${sliceTitle}`,
724
+ summary: ["All tasks complete. Ready for slice summary."],
725
+ actions: [
726
+ {
727
+ id: "complete",
728
+ label: `Complete ${sliceId}`,
729
+ description: "Write slice summary, UAT, mark done, and squash-merge to main.",
730
+ recommended: true,
731
+ },
732
+ {
733
+ id: "status",
734
+ label: "View status",
735
+ description: "Review tasks before completing.",
736
+ },
737
+ ],
738
+ notYetMessage: "Run /gsd when ready.",
739
+ });
740
+
741
+ if (choice === "complete") {
742
+ dispatchWorkflow(pi, loadPrompt("guided-complete-slice", {
743
+ milestoneId, sliceId, sliceTitle,
744
+ }));
745
+ } else if (choice === "status") {
746
+ const { fireStatusViaCommand } = await import("./commands.js");
747
+ await fireStatusViaCommand(ctx);
748
+ }
749
+ return;
750
+ }
751
+
752
+ // ── Active task → Execute ────────────────────────────────────────────
753
+ if (state.activeTask) {
754
+ const taskId = state.activeTask.id;
755
+ const taskTitle = state.activeTask.title;
756
+
757
+ const continueFile = resolveSliceFile(basePath, milestoneId, sliceId, "CONTINUE");
758
+ const sDir = resolveSlicePath(basePath, milestoneId, sliceId);
759
+ const hasInterrupted = !!(continueFile && await loadFile(continueFile)) ||
760
+ !!(sDir && await loadFile(join(sDir, "continue.md")));
761
+
762
+ const choice = await showNextAction(ctx as any, {
763
+ title: `GSD — ${milestoneId} / ${sliceId}: ${sliceTitle}`,
764
+ summary: [
765
+ hasInterrupted
766
+ ? `Resuming: ${taskId} — ${taskTitle}`
767
+ : `Next: ${taskId} — ${taskTitle}`,
768
+ ],
769
+ actions: [
770
+ {
771
+ id: "execute",
772
+ label: hasInterrupted ? `Resume ${taskId}` : `Execute ${taskId}`,
773
+ description: hasInterrupted
774
+ ? "Continue from where you left off."
775
+ : `Start working on "${taskTitle}".`,
776
+ recommended: true,
777
+ },
778
+ {
779
+ id: "auto",
780
+ label: "Go auto",
781
+ description: "Execute this and all remaining tasks automatically.",
782
+ },
783
+ {
784
+ id: "status",
785
+ label: "View status",
786
+ description: "See slice progress before starting.",
787
+ },
788
+ ],
789
+ notYetMessage: "Run /gsd when ready.",
790
+ });
791
+
792
+ if (choice === "auto") {
793
+ await startAuto(ctx, pi, basePath, false);
794
+ return;
795
+ }
796
+
797
+ if (choice === "execute") {
798
+ if (hasInterrupted) {
799
+ dispatchWorkflow(pi, loadPrompt("guided-resume-task", {
800
+ milestoneId, sliceId,
801
+ }));
802
+ } else {
803
+ dispatchWorkflow(pi, loadPrompt("guided-execute-task", {
804
+ milestoneId, sliceId, taskId, taskTitle,
805
+ }));
806
+ }
807
+ } else if (choice === "status") {
808
+ const { fireStatusViaCommand } = await import("./commands.js");
809
+ await fireStatusViaCommand(ctx);
810
+ }
811
+ return;
812
+ }
813
+
814
+ // ── Fallback: show status ────────────────────────────────────────────
815
+ const { fireStatusViaCommand } = await import("./commands.js");
816
+ await fireStatusViaCommand(ctx);
817
+ }