@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,313 @@
1
+ /**
2
+ * GSD Command — /gsd
3
+ *
4
+ * One command, one wizard. Routes to smart entry or status.
5
+ */
6
+
7
+ import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
8
+ import { existsSync, readFileSync } from "node:fs";
9
+ import { join, dirname } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+ import { deriveState } from "./state.js";
12
+ import { GSDDashboardOverlay } from "./dashboard-overlay.js";
13
+ import { showQueue, showDiscuss } from "./guided-flow.js";
14
+ import { startAuto, stopAuto, isAutoActive, isAutoPaused, isStepMode } from "./auto.js";
15
+ import {
16
+ getGlobalGSDPreferencesPath,
17
+ getLegacyGlobalGSDPreferencesPath,
18
+ getProjectGSDPreferencesPath,
19
+ loadGlobalGSDPreferences,
20
+ loadProjectGSDPreferences,
21
+ loadEffectiveGSDPreferences,
22
+ resolveAllSkillReferences,
23
+ } from "./preferences.js";
24
+ import { loadFile, saveFile } from "./files.js";
25
+ import {
26
+ formatDoctorIssuesForPrompt,
27
+ formatDoctorReport,
28
+ runGSDDoctor,
29
+ selectDoctorScope,
30
+ filterDoctorIssues,
31
+ } from "./doctor.js";
32
+ import { loadPrompt } from "./prompt-loader.js";
33
+ import { handleMigrate } from "./migrate/command.js";
34
+ import { handleRemote } from "../remote-questions/remote-command.js";
35
+
36
+ function dispatchDoctorHeal(pi: ExtensionAPI, scope: string | undefined, reportText: string, structuredIssues: string): void {
37
+ const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".pi", "GSD-WORKFLOW.md");
38
+ const workflow = readFileSync(workflowPath, "utf-8");
39
+ const prompt = loadPrompt("doctor-heal", {
40
+ doctorSummary: reportText,
41
+ structuredIssues,
42
+ scopeLabel: scope ?? "active milestone / blocking scope",
43
+ doctorCommandSuffix: scope ? ` ${scope}` : "",
44
+ });
45
+
46
+ const content = `Read the following GSD workflow protocol and execute exactly.\n\n${workflow}\n\n## Your Task\n\n${prompt}`;
47
+
48
+ pi.sendMessage(
49
+ { customType: "gsd-doctor-heal", content, display: false },
50
+ { triggerTurn: true },
51
+ );
52
+ }
53
+
54
+ export function registerGSDCommand(pi: ExtensionAPI): void {
55
+ pi.registerCommand("gsd", {
56
+ description: "GSD — Get Shit Done: /gsd next|auto|stop|status|queue|prefs|doctor|migrate|remote",
57
+
58
+ getArgumentCompletions: (prefix: string) => {
59
+ const subcommands = ["next", "auto", "stop", "status", "queue", "discuss", "prefs", "doctor", "migrate", "remote"];
60
+ const parts = prefix.trim().split(/\s+/);
61
+
62
+ if (parts.length <= 1) {
63
+ return subcommands
64
+ .filter((cmd) => cmd.startsWith(parts[0] ?? ""))
65
+ .map((cmd) => ({ value: cmd, label: cmd }));
66
+ }
67
+
68
+ if (parts[0] === "auto" && parts.length <= 2) {
69
+ const flagPrefix = parts[1] ?? "";
70
+ return ["--verbose"]
71
+ .filter((f) => f.startsWith(flagPrefix))
72
+ .map((f) => ({ value: `auto ${f}`, label: f }));
73
+ }
74
+
75
+ if (parts[0] === "prefs" && parts.length <= 2) {
76
+ const subPrefix = parts[1] ?? "";
77
+ return ["global", "project", "status"]
78
+ .filter((cmd) => cmd.startsWith(subPrefix))
79
+ .map((cmd) => ({ value: `prefs ${cmd}`, label: cmd }));
80
+ }
81
+
82
+ if (parts[0] === "remote" && parts.length <= 2) {
83
+ const subPrefix = parts[1] ?? "";
84
+ return ["slack", "discord", "status", "disconnect"]
85
+ .filter((cmd) => cmd.startsWith(subPrefix))
86
+ .map((cmd) => ({ value: `remote ${cmd}`, label: cmd }));
87
+ }
88
+
89
+ if (parts[0] === "doctor") {
90
+ const modePrefix = parts[1] ?? "";
91
+ const modes = ["fix", "heal", "audit"];
92
+
93
+ if (parts.length <= 2) {
94
+ return modes
95
+ .filter((cmd) => cmd.startsWith(modePrefix))
96
+ .map((cmd) => ({ value: `doctor ${cmd}`, label: cmd }));
97
+ }
98
+
99
+ return [];
100
+ }
101
+
102
+ return [];
103
+ },
104
+
105
+ async handler(args: string, ctx: ExtensionCommandContext) {
106
+ const trimmed = (typeof args === "string" ? args : "").trim();
107
+
108
+ if (trimmed === "status") {
109
+ await handleStatus(ctx);
110
+ return;
111
+ }
112
+
113
+ if (trimmed === "prefs" || trimmed.startsWith("prefs ")) {
114
+ await handlePrefs(trimmed.replace(/^prefs\s*/, "").trim(), ctx);
115
+ return;
116
+ }
117
+
118
+ if (trimmed === "doctor" || trimmed.startsWith("doctor ")) {
119
+ await handleDoctor(trimmed.replace(/^doctor\s*/, "").trim(), ctx, pi);
120
+ return;
121
+ }
122
+
123
+ if (trimmed === "next" || trimmed.startsWith("next ")) {
124
+ const verboseMode = trimmed.includes("--verbose");
125
+ await startAuto(ctx, pi, process.cwd(), verboseMode, { step: true });
126
+ return;
127
+ }
128
+
129
+ if (trimmed === "auto" || trimmed.startsWith("auto ")) {
130
+ const verboseMode = trimmed.includes("--verbose");
131
+ await startAuto(ctx, pi, process.cwd(), verboseMode);
132
+ return;
133
+ }
134
+
135
+ if (trimmed === "stop") {
136
+ if (!isAutoActive() && !isAutoPaused()) {
137
+ ctx.ui.notify("Auto-mode is not running.", "info");
138
+ return;
139
+ }
140
+ await stopAuto(ctx, pi);
141
+ return;
142
+ }
143
+
144
+ if (trimmed === "queue") {
145
+ await showQueue(ctx, pi, process.cwd());
146
+ return;
147
+ }
148
+
149
+ if (trimmed === "discuss") {
150
+ await showDiscuss(ctx, pi, process.cwd());
151
+ return;
152
+ }
153
+
154
+ if (trimmed === "migrate" || trimmed.startsWith("migrate ")) {
155
+ await handleMigrate(trimmed.replace(/^migrate\s*/, "").trim(), ctx, pi);
156
+ return;
157
+ }
158
+
159
+ if (trimmed === "remote" || trimmed.startsWith("remote ")) {
160
+ await handleRemote(trimmed.replace(/^remote\s*/, "").trim(), ctx, pi);
161
+ return;
162
+ }
163
+
164
+ if (trimmed === "") {
165
+ // Bare /gsd defaults to step mode
166
+ await startAuto(ctx, pi, process.cwd(), false, { step: true });
167
+ return;
168
+ }
169
+
170
+ ctx.ui.notify(
171
+ `Unknown: /gsd ${trimmed}. Use /gsd, /gsd next, /gsd auto, /gsd stop, /gsd status, /gsd queue, /gsd discuss, /gsd prefs [global|project|status], /gsd doctor [audit|fix|heal] [M###/S##], /gsd migrate <path>, or /gsd remote [slack|discord|status|disconnect].`,
172
+ "warning",
173
+ );
174
+ },
175
+ });
176
+ }
177
+
178
+ async function handleStatus(ctx: ExtensionCommandContext): Promise<void> {
179
+ const basePath = process.cwd();
180
+ const state = await deriveState(basePath);
181
+
182
+ if (state.registry.length === 0) {
183
+ ctx.ui.notify("No GSD milestones found. Run /gsd to start.", "info");
184
+ return;
185
+ }
186
+
187
+ await ctx.ui.custom<void>(
188
+ (tui, theme, _kb, done) => {
189
+ return new GSDDashboardOverlay(tui, theme, () => done());
190
+ },
191
+ {
192
+ overlay: true,
193
+ overlayOptions: {
194
+ width: "70%",
195
+ minWidth: 60,
196
+ maxHeight: "90%",
197
+ anchor: "center",
198
+ },
199
+ },
200
+ );
201
+ }
202
+
203
+ export async function fireStatusViaCommand(
204
+ ctx: import("@mariozechner/pi-coding-agent").ExtensionContext,
205
+ ): Promise<void> {
206
+ await handleStatus(ctx as ExtensionCommandContext);
207
+ }
208
+
209
+ async function handlePrefs(args: string, ctx: ExtensionCommandContext): Promise<void> {
210
+ const trimmed = args.trim();
211
+
212
+ if (trimmed === "" || trimmed === "global") {
213
+ await ensurePreferencesFile(getGlobalGSDPreferencesPath(), ctx, "global");
214
+ return;
215
+ }
216
+
217
+ if (trimmed === "project") {
218
+ await ensurePreferencesFile(getProjectGSDPreferencesPath(), ctx, "project");
219
+ return;
220
+ }
221
+
222
+ if (trimmed === "status") {
223
+ const globalPrefs = loadGlobalGSDPreferences();
224
+ const projectPrefs = loadProjectGSDPreferences();
225
+ const canonicalGlobal = getGlobalGSDPreferencesPath();
226
+ const legacyGlobal = getLegacyGlobalGSDPreferencesPath();
227
+ const globalStatus = globalPrefs
228
+ ? `present: ${globalPrefs.path}${globalPrefs.path === legacyGlobal ? " (legacy fallback)" : ""}`
229
+ : `missing: ${canonicalGlobal}`;
230
+ const projectStatus = projectPrefs ? `present: ${projectPrefs.path}` : `missing: ${getProjectGSDPreferencesPath()}`;
231
+
232
+ const lines = [`GSD skill prefs — global ${globalStatus}; project ${projectStatus}`];
233
+
234
+ const effective = loadEffectiveGSDPreferences();
235
+ let hasUnresolved = false;
236
+ if (effective) {
237
+ const report = resolveAllSkillReferences(effective.preferences, process.cwd());
238
+ const resolved = [...report.resolutions.values()].filter(r => r.method !== "unresolved");
239
+ hasUnresolved = report.warnings.length > 0;
240
+ if (resolved.length > 0 || hasUnresolved) {
241
+ lines.push(`Skills: ${resolved.length} resolved, ${report.warnings.length} unresolved`);
242
+ }
243
+ if (hasUnresolved) {
244
+ lines.push(`Unresolved: ${report.warnings.join(", ")}`);
245
+ }
246
+ }
247
+
248
+ ctx.ui.notify(lines.join("\n"), hasUnresolved ? "warning" : "info");
249
+ return;
250
+ }
251
+
252
+ ctx.ui.notify("Usage: /gsd prefs [global|project|status]", "info");
253
+ }
254
+
255
+ async function handleDoctor(args: string, ctx: ExtensionCommandContext, pi: ExtensionAPI): Promise<void> {
256
+ const trimmed = args.trim();
257
+ const parts = trimmed ? trimmed.split(/\s+/) : [];
258
+ const mode = parts[0] === "fix" || parts[0] === "heal" || parts[0] === "audit" ? parts[0] : "doctor";
259
+ const requestedScope = mode === "doctor" ? parts[0] : parts[1];
260
+ const scope = await selectDoctorScope(process.cwd(), requestedScope);
261
+ const effectiveScope = mode === "audit" ? requestedScope : scope;
262
+ const report = await runGSDDoctor(process.cwd(), {
263
+ fix: mode === "fix" || mode === "heal",
264
+ scope: effectiveScope,
265
+ });
266
+
267
+ const reportText = formatDoctorReport(report, {
268
+ scope: effectiveScope,
269
+ includeWarnings: mode === "audit",
270
+ maxIssues: mode === "audit" ? 50 : 12,
271
+ title: mode === "audit" ? "GSD doctor audit." : mode === "heal" ? "GSD doctor heal prep." : undefined,
272
+ });
273
+
274
+ ctx.ui.notify(reportText, report.ok ? "info" : "warning");
275
+
276
+ if (mode === "heal") {
277
+ const unresolved = filterDoctorIssues(report.issues, {
278
+ scope: effectiveScope,
279
+ includeWarnings: true,
280
+ });
281
+ const actionable = unresolved.filter(issue => issue.severity === "error" || issue.code === "all_tasks_done_missing_slice_uat" || issue.code === "slice_checked_missing_uat");
282
+ if (actionable.length === 0) {
283
+ ctx.ui.notify("Doctor heal found nothing actionable to hand off to the LLM.", "info");
284
+ return;
285
+ }
286
+
287
+ const structuredIssues = formatDoctorIssuesForPrompt(actionable);
288
+ dispatchDoctorHeal(pi, effectiveScope, reportText, structuredIssues);
289
+ ctx.ui.notify(`Doctor heal dispatched ${actionable.length} issue(s) to the LLM.`, "info");
290
+ }
291
+ }
292
+
293
+ async function ensurePreferencesFile(
294
+ path: string,
295
+ ctx: ExtensionCommandContext,
296
+ scope: "global" | "project",
297
+ ): Promise<void> {
298
+ if (!existsSync(path)) {
299
+ const template = await loadFile(join(dirname(fileURLToPath(import.meta.url)), "templates", "preferences.md"));
300
+ if (!template) {
301
+ ctx.ui.notify("Could not load GSD preferences template.", "error");
302
+ return;
303
+ }
304
+ await saveFile(path, template);
305
+ ctx.ui.notify(`Created ${scope} GSD skill preferences at ${path}`, "info");
306
+ } else {
307
+ ctx.ui.notify(`Using existing ${scope} GSD skill preferences at ${path}`, "info");
308
+ }
309
+
310
+ await ctx.waitForIdle();
311
+ await ctx.reload();
312
+ ctx.ui.notify(`Edit ${path} to update ${scope} GSD skill preferences.`, "info");
313
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * GSD Crash Recovery
3
+ *
4
+ * Detects interrupted auto-mode sessions via a lock file.
5
+ * Written on auto-start, updated on each unit dispatch, deleted on clean stop.
6
+ * If the lock file exists on next startup, the previous session crashed.
7
+ *
8
+ * The lock records the pi session file path so crash recovery can read the
9
+ * surviving JSONL (pi appends entries incrementally via appendFileSync,
10
+ * so the file on disk reflects every tool call up to the crash point).
11
+ */
12
+
13
+ import { writeFileSync, readFileSync, unlinkSync, existsSync } from "node:fs";
14
+ import { join } from "node:path";
15
+ import { gsdRoot } from "./paths.js";
16
+
17
+ const LOCK_FILE = "auto.lock";
18
+
19
+ export interface LockData {
20
+ pid: number;
21
+ startedAt: string;
22
+ unitType: string;
23
+ unitId: string;
24
+ unitStartedAt: string;
25
+ completedUnits: number;
26
+ /** Path to the pi session JSONL file that was active when this unit started. */
27
+ sessionFile?: string;
28
+ }
29
+
30
+ function lockPath(basePath: string): string {
31
+ return join(gsdRoot(basePath), LOCK_FILE);
32
+ }
33
+
34
+ /** Write or update the lock file with current auto-mode state. */
35
+ export function writeLock(
36
+ basePath: string,
37
+ unitType: string,
38
+ unitId: string,
39
+ completedUnits: number,
40
+ sessionFile?: string,
41
+ ): void {
42
+ try {
43
+ const data: LockData = {
44
+ pid: process.pid,
45
+ startedAt: new Date().toISOString(),
46
+ unitType,
47
+ unitId,
48
+ unitStartedAt: new Date().toISOString(),
49
+ completedUnits,
50
+ sessionFile,
51
+ };
52
+ writeFileSync(lockPath(basePath), JSON.stringify(data, null, 2), "utf-8");
53
+ } catch { /* non-fatal */ }
54
+ }
55
+
56
+ /** Remove the lock file on clean stop. */
57
+ export function clearLock(basePath: string): void {
58
+ try {
59
+ const p = lockPath(basePath);
60
+ if (existsSync(p)) unlinkSync(p);
61
+ } catch { /* non-fatal */ }
62
+ }
63
+
64
+ /** Check if a crash lock exists and return its data. */
65
+ export function readCrashLock(basePath: string): LockData | null {
66
+ try {
67
+ const p = lockPath(basePath);
68
+ if (!existsSync(p)) return null;
69
+ const raw = readFileSync(p, "utf-8");
70
+ return JSON.parse(raw) as LockData;
71
+ } catch {
72
+ return null;
73
+ }
74
+ }
75
+
76
+ /** Format crash info for display or injection into a prompt. */
77
+ export function formatCrashInfo(lock: LockData): string {
78
+ return [
79
+ `Previous auto-mode session was interrupted.`,
80
+ ` Was executing: ${lock.unitType} (${lock.unitId})`,
81
+ ` Started at: ${lock.unitStartedAt}`,
82
+ ` Units completed before crash: ${lock.completedUnits}`,
83
+ ` PID: ${lock.pid}`,
84
+ ].join("\n");
85
+ }