@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,521 @@
1
+ /**
2
+ * GSD Dashboard Overlay
3
+ *
4
+ * Full-screen overlay showing auto-mode progress: milestone/slice/task
5
+ * breakdown, current unit, completed units, timing, and activity log.
6
+ * Toggled with Ctrl+Alt+G or opened from /gsd status.
7
+ */
8
+
9
+ import type { Theme } from "@mariozechner/pi-coding-agent";
10
+ import { truncateToWidth, visibleWidth, matchesKey, Key } from "@mariozechner/pi-tui";
11
+ import { deriveState } from "./state.js";
12
+ import { loadFile, parseRoadmap, parsePlan } from "./files.js";
13
+ import { resolveMilestoneFile, resolveSliceFile } from "./paths.js";
14
+ import { getAutoDashboardData, type AutoDashboardData } from "./auto.js";
15
+ import {
16
+ getLedger, getProjectTotals, aggregateByPhase, aggregateBySlice,
17
+ aggregateByModel, formatCost, formatTokenCount, formatCostProjection,
18
+ } from "./metrics.js";
19
+ import { loadEffectiveGSDPreferences } from "./preferences.js";
20
+ import { getActiveWorktreeName } from "./worktree-command.js";
21
+
22
+ function formatDuration(ms: number): string {
23
+ const s = Math.floor(ms / 1000);
24
+ if (s < 60) return `${s}s`;
25
+ const m = Math.floor(s / 60);
26
+ const rs = s % 60;
27
+ if (m < 60) return `${m}m ${rs}s`;
28
+ const h = Math.floor(m / 60);
29
+ const rm = m % 60;
30
+ return `${h}h ${rm}m`;
31
+ }
32
+
33
+ function unitLabel(type: string): string {
34
+ switch (type) {
35
+ case "research-milestone": return "Research";
36
+ case "plan-milestone": return "Plan";
37
+ case "research-slice": return "Research";
38
+ case "plan-slice": return "Plan";
39
+ case "execute-task": return "Execute";
40
+ case "complete-slice": return "Complete";
41
+ case "reassess-roadmap": return "Reassess";
42
+ default: return type;
43
+ }
44
+ }
45
+
46
+ function centerLine(content: string, width: number): string {
47
+ const vis = visibleWidth(content);
48
+ if (vis >= width) return truncateToWidth(content, width);
49
+ const leftPad = Math.floor((width - vis) / 2);
50
+ return " ".repeat(leftPad) + content;
51
+ }
52
+
53
+ function padRight(content: string, width: number): string {
54
+ const vis = visibleWidth(content);
55
+ return content + " ".repeat(Math.max(0, width - vis));
56
+ }
57
+
58
+ function joinColumns(left: string, right: string, width: number): string {
59
+ const leftW = visibleWidth(left);
60
+ const rightW = visibleWidth(right);
61
+ if (leftW + rightW + 2 > width) {
62
+ return truncateToWidth(`${left} ${right}`, width);
63
+ }
64
+ return left + " ".repeat(width - leftW - rightW) + right;
65
+ }
66
+
67
+ function fitColumns(parts: string[], width: number, separator = " "): string {
68
+ const filtered = parts.filter(Boolean);
69
+ if (filtered.length === 0) return "";
70
+ let result = filtered[0];
71
+ for (let i = 1; i < filtered.length; i++) {
72
+ const candidate = `${result}${separator}${filtered[i]}`;
73
+ if (visibleWidth(candidate) > width) break;
74
+ result = candidate;
75
+ }
76
+ return truncateToWidth(result, width);
77
+ }
78
+
79
+ export class GSDDashboardOverlay {
80
+ private tui: { requestRender: () => void };
81
+ private theme: Theme;
82
+ private onClose: () => void;
83
+ private cachedWidth?: number;
84
+ private cachedLines?: string[];
85
+ private refreshTimer: ReturnType<typeof setInterval>;
86
+ private scrollOffset = 0;
87
+ private dashData: AutoDashboardData;
88
+ private milestoneData: MilestoneView | null = null;
89
+ private loading = true;
90
+
91
+ constructor(
92
+ tui: { requestRender: () => void },
93
+ theme: Theme,
94
+ onClose: () => void,
95
+ ) {
96
+ this.tui = tui;
97
+ this.theme = theme;
98
+ this.onClose = onClose;
99
+ this.dashData = getAutoDashboardData();
100
+
101
+ this.loadData().then(() => {
102
+ this.loading = false;
103
+ this.invalidate();
104
+ this.tui.requestRender();
105
+ });
106
+
107
+ this.refreshTimer = setInterval(() => {
108
+ this.dashData = getAutoDashboardData();
109
+ this.loadData().then(() => {
110
+ this.invalidate();
111
+ this.tui.requestRender();
112
+ });
113
+ }, 2000);
114
+ }
115
+
116
+ private async loadData(): Promise<void> {
117
+ const base = this.dashData.basePath || process.cwd();
118
+ try {
119
+ const state = await deriveState(base);
120
+ if (!state.activeMilestone) {
121
+ this.milestoneData = null;
122
+ return;
123
+ }
124
+
125
+ const mid = state.activeMilestone.id;
126
+ const view: MilestoneView = {
127
+ id: mid,
128
+ title: state.activeMilestone.title,
129
+ slices: [],
130
+ phase: state.phase,
131
+ progress: {
132
+ milestones: {
133
+ total: state.progress?.milestones.total ?? state.registry.length,
134
+ done: state.progress?.milestones.done ?? state.registry.filter(entry => entry.status === "complete").length,
135
+ },
136
+ },
137
+ };
138
+
139
+ const roadmapFile = resolveMilestoneFile(base, mid, "ROADMAP");
140
+ const roadmapContent = roadmapFile ? await loadFile(roadmapFile) : null;
141
+ if (roadmapContent) {
142
+ const roadmap = parseRoadmap(roadmapContent);
143
+ for (const s of roadmap.slices) {
144
+ const sliceView: SliceView = {
145
+ id: s.id,
146
+ title: s.title,
147
+ done: s.done,
148
+ risk: s.risk,
149
+ active: state.activeSlice?.id === s.id,
150
+ tasks: [],
151
+ };
152
+
153
+ if (sliceView.active) {
154
+ const planFile = resolveSliceFile(base, mid, s.id, "PLAN");
155
+ const planContent = planFile ? await loadFile(planFile) : null;
156
+ if (planContent) {
157
+ const plan = parsePlan(planContent);
158
+ sliceView.taskProgress = {
159
+ done: plan.tasks.filter(t => t.done).length,
160
+ total: plan.tasks.length,
161
+ };
162
+ for (const t of plan.tasks) {
163
+ sliceView.tasks.push({
164
+ id: t.id,
165
+ title: t.title,
166
+ done: t.done,
167
+ active: state.activeTask?.id === t.id,
168
+ });
169
+ }
170
+ }
171
+ }
172
+
173
+ view.slices.push(sliceView);
174
+ }
175
+ }
176
+
177
+ this.milestoneData = view;
178
+ } catch {
179
+ // Don't crash the overlay
180
+ }
181
+ }
182
+
183
+ handleInput(data: string): void {
184
+ if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c")) || matchesKey(data, Key.ctrlAlt("g"))) {
185
+ clearInterval(this.refreshTimer);
186
+ this.onClose();
187
+ return;
188
+ }
189
+
190
+ if (matchesKey(data, Key.down) || matchesKey(data, "j")) {
191
+ this.scrollOffset++;
192
+ this.invalidate();
193
+ this.tui.requestRender();
194
+ return;
195
+ }
196
+
197
+ if (matchesKey(data, Key.up) || matchesKey(data, "k")) {
198
+ this.scrollOffset = Math.max(0, this.scrollOffset - 1);
199
+ this.invalidate();
200
+ this.tui.requestRender();
201
+ return;
202
+ }
203
+
204
+ if (data === "g") {
205
+ this.scrollOffset = 0;
206
+ this.invalidate();
207
+ this.tui.requestRender();
208
+ return;
209
+ }
210
+
211
+ if (data === "G") {
212
+ this.scrollOffset = 999;
213
+ this.invalidate();
214
+ this.tui.requestRender();
215
+ return;
216
+ }
217
+ }
218
+
219
+ render(width: number): string[] {
220
+ if (this.cachedLines && this.cachedWidth === width) {
221
+ return this.cachedLines;
222
+ }
223
+
224
+ const content = this.buildContentLines(width);
225
+ const viewportHeight = Math.max(5, process.stdout.rows ? process.stdout.rows - 8 : 24);
226
+ const chromeHeight = 2;
227
+ const visibleContentRows = Math.max(1, viewportHeight - chromeHeight);
228
+ const maxScroll = Math.max(0, content.length - visibleContentRows);
229
+ this.scrollOffset = Math.min(this.scrollOffset, maxScroll);
230
+ const visibleContent = content.slice(this.scrollOffset, this.scrollOffset + visibleContentRows);
231
+
232
+ const lines = this.wrapInBox(visibleContent, width);
233
+
234
+ this.cachedWidth = width;
235
+ this.cachedLines = lines;
236
+ return lines;
237
+ }
238
+
239
+ private wrapInBox(inner: string[], width: number): string[] {
240
+ const th = this.theme;
241
+ const border = (s: string) => th.fg("borderAccent", s);
242
+ const innerWidth = width - 4;
243
+ const lines: string[] = [];
244
+
245
+ lines.push(border("╭" + "─".repeat(width - 2) + "╮"));
246
+ for (const line of inner) {
247
+ const truncated = truncateToWidth(line, innerWidth);
248
+ const padWidth = Math.max(0, innerWidth - visibleWidth(truncated));
249
+ lines.push(border("│") + " " + truncated + " ".repeat(padWidth) + " " + border("│"));
250
+ }
251
+ lines.push(border("╰" + "─".repeat(width - 2) + "╯"));
252
+ return lines;
253
+ }
254
+
255
+ private buildContentLines(width: number): string[] {
256
+ const th = this.theme;
257
+ const shellWidth = width - 4;
258
+ const contentWidth = Math.min(shellWidth, 128);
259
+ const sidePad = Math.max(0, Math.floor((shellWidth - contentWidth) / 2));
260
+ const leftMargin = " ".repeat(sidePad);
261
+ const lines: string[] = [];
262
+
263
+ const row = (content = ""): string => {
264
+ const truncated = truncateToWidth(content, contentWidth);
265
+ return leftMargin + padRight(truncated, contentWidth);
266
+ };
267
+ const blank = () => row("");
268
+ const hr = () => row(th.fg("dim", "─".repeat(contentWidth)));
269
+ const centered = (content: string) => row(centerLine(content, contentWidth));
270
+
271
+ const title = th.fg("accent", th.bold("GSD Dashboard"));
272
+ const status = this.dashData.active
273
+ ? `${Date.now() % 2000 < 1000 ? th.fg("success", "●") : th.fg("dim", "○")} ${th.fg("success", "AUTO")}`
274
+ : this.dashData.paused
275
+ ? th.fg("warning", "⏸ PAUSED")
276
+ : th.fg("dim", "idle");
277
+ const worktreeName = getActiveWorktreeName();
278
+ const worktreeTag = worktreeName
279
+ ? ` ${th.fg("warning", `⎇ ${worktreeName}`)}`
280
+ : "";
281
+ const elapsed = th.fg("dim", formatDuration(this.dashData.elapsed));
282
+ lines.push(row(joinColumns(`${title} ${status}${worktreeTag}`, elapsed, contentWidth)));
283
+ lines.push(blank());
284
+
285
+ if (this.dashData.currentUnit) {
286
+ const cu = this.dashData.currentUnit;
287
+ const currentElapsed = th.fg("dim", formatDuration(Date.now() - cu.startedAt));
288
+ lines.push(row(joinColumns(
289
+ `${th.fg("text", "Now")}: ${th.fg("accent", unitLabel(cu.type))} ${th.fg("text", cu.id)}`,
290
+ currentElapsed,
291
+ contentWidth,
292
+ )));
293
+ lines.push(blank());
294
+ } else if (this.dashData.paused) {
295
+ lines.push(row(th.fg("dim", "/gsd auto to resume")));
296
+ lines.push(blank());
297
+ } else {
298
+ lines.push(row(th.fg("dim", "No unit running · /gsd auto to start")));
299
+ lines.push(blank());
300
+ }
301
+
302
+ if (this.loading) {
303
+ lines.push(centered(th.fg("dim", "Loading dashboard…")));
304
+ return lines;
305
+ }
306
+
307
+ if (this.milestoneData) {
308
+ const mv = this.milestoneData;
309
+ lines.push(row(th.fg("text", th.bold(`${mv.id}: ${mv.title}`))));
310
+ lines.push(blank());
311
+
312
+ const totalSlices = mv.slices.length;
313
+ const doneSlices = mv.slices.filter(s => s.done).length;
314
+ const totalMilestones = mv.progress.milestones.total;
315
+ const doneMilestones = mv.progress.milestones.done;
316
+ const activeSlice = mv.slices.find(s => s.active);
317
+
318
+ lines.push(blank());
319
+
320
+ if (activeSlice?.taskProgress) {
321
+ lines.push(row(this.renderProgressRow("Tasks", activeSlice.taskProgress.done, activeSlice.taskProgress.total, "accent", contentWidth)));
322
+ }
323
+ lines.push(row(this.renderProgressRow("Slices", doneSlices, totalSlices, "success", contentWidth)));
324
+ lines.push(row(this.renderProgressRow("Milestones", doneMilestones, totalMilestones, "warning", contentWidth)));
325
+
326
+ lines.push(blank());
327
+
328
+ for (const s of mv.slices) {
329
+ const icon = s.done ? th.fg("success", "✓")
330
+ : s.active ? th.fg("accent", "▸")
331
+ : th.fg("dim", "○");
332
+ const titleText = s.active ? th.fg("accent", `${s.id}: ${s.title}`)
333
+ : s.done ? th.fg("muted", `${s.id}: ${s.title}`)
334
+ : th.fg("dim", `${s.id}: ${s.title}`);
335
+ const risk = th.fg("dim", s.risk);
336
+ lines.push(row(joinColumns(` ${icon} ${titleText}`, risk, contentWidth)));
337
+
338
+ if (s.active && s.tasks.length > 0) {
339
+ for (const t of s.tasks) {
340
+ const tIcon = t.done ? th.fg("success", "✓")
341
+ : t.active ? th.fg("warning", "▸")
342
+ : th.fg("dim", "·");
343
+ const tTitle = t.active ? th.fg("warning", `${t.id}: ${t.title}`)
344
+ : t.done ? th.fg("muted", `${t.id}: ${t.title}`)
345
+ : th.fg("dim", `${t.id}: ${t.title}`);
346
+ lines.push(row(` ${tIcon} ${truncateToWidth(tTitle, contentWidth - 6)}`));
347
+ }
348
+ }
349
+ }
350
+ } else {
351
+ lines.push(centered(th.fg("dim", "No active milestone.")));
352
+ }
353
+
354
+ if (this.dashData.completedUnits.length > 0) {
355
+ lines.push(blank());
356
+ lines.push(hr());
357
+ lines.push(row(th.fg("text", th.bold("Completed"))));
358
+ lines.push(blank());
359
+
360
+ const recent = [...this.dashData.completedUnits].reverse().slice(0, 10);
361
+ for (const u of recent) {
362
+ const left = ` ${th.fg("success", "✓")} ${th.fg("muted", unitLabel(u.type))} ${th.fg("muted", u.id)}`;
363
+ const right = th.fg("dim", formatDuration(u.finishedAt - u.startedAt));
364
+ lines.push(row(joinColumns(left, right, contentWidth)));
365
+ }
366
+
367
+ if (this.dashData.completedUnits.length > 10) {
368
+ lines.push(row(th.fg("dim", ` ...and ${this.dashData.completedUnits.length - 10} more`)));
369
+ }
370
+ }
371
+
372
+ const ledger = getLedger();
373
+ if (ledger && ledger.units.length > 0) {
374
+ const totals = getProjectTotals(ledger.units);
375
+
376
+ lines.push(blank());
377
+ lines.push(hr());
378
+ lines.push(row(th.fg("text", th.bold("Cost & Usage"))));
379
+ lines.push(blank());
380
+
381
+ lines.push(row(fitColumns([
382
+ `${th.fg("warning", formatCost(totals.cost))} total`,
383
+ `${th.fg("text", formatTokenCount(totals.tokens.total))} tokens`,
384
+ `${th.fg("text", String(totals.toolCalls))} tools`,
385
+ `${th.fg("text", String(totals.units))} units`,
386
+ ], contentWidth, ` ${th.fg("dim", "·")} `)));
387
+
388
+ lines.push(row(fitColumns([
389
+ `${th.fg("dim", "in:")} ${th.fg("text", formatTokenCount(totals.tokens.input))}`,
390
+ `${th.fg("dim", "out:")} ${th.fg("text", formatTokenCount(totals.tokens.output))}`,
391
+ `${th.fg("dim", "cache-r:")} ${th.fg("text", formatTokenCount(totals.tokens.cacheRead))}`,
392
+ `${th.fg("dim", "cache-w:")} ${th.fg("text", formatTokenCount(totals.tokens.cacheWrite))}`,
393
+ ], contentWidth, " ")));
394
+
395
+ const phases = aggregateByPhase(ledger.units);
396
+ if (phases.length > 0) {
397
+ lines.push(blank());
398
+ lines.push(row(th.fg("dim", "By Phase")));
399
+ for (const p of phases) {
400
+ const pct = totals.cost > 0 ? Math.round((p.cost / totals.cost) * 100) : 0;
401
+ const left = ` ${th.fg("text", p.phase.padEnd(14))}${th.fg("warning", formatCost(p.cost).padStart(8))}`;
402
+ const right = th.fg("dim", `${String(pct).padStart(3)}% ${formatTokenCount(p.tokens.total)} tok ${p.units} units`);
403
+ lines.push(row(joinColumns(left, right, contentWidth)));
404
+ }
405
+ }
406
+
407
+ const slices = aggregateBySlice(ledger.units);
408
+ if (slices.length > 0) {
409
+ lines.push(blank());
410
+ lines.push(row(th.fg("dim", "By Slice")));
411
+ for (const s of slices) {
412
+ const pct = totals.cost > 0 ? Math.round((s.cost / totals.cost) * 100) : 0;
413
+ const left = ` ${th.fg("text", s.sliceId.padEnd(14))}${th.fg("warning", formatCost(s.cost).padStart(8))}`;
414
+ const right = th.fg("dim", `${String(pct).padStart(3)}% ${formatTokenCount(s.tokens.total)} tok ${formatDuration(s.duration)}`);
415
+ lines.push(row(joinColumns(left, right, contentWidth)));
416
+ }
417
+ }
418
+
419
+ // Cost projection — only when active milestone data is available
420
+ if (this.milestoneData) {
421
+ const mv = this.milestoneData;
422
+ const msTotalSlices = mv.slices.length;
423
+ const msDoneSlices = mv.slices.filter(s => s.done).length;
424
+ const remainingCount = msTotalSlices - msDoneSlices;
425
+ const overlayPrefs = loadEffectiveGSDPreferences()?.preferences;
426
+ const projLines = formatCostProjection(slices, remainingCount, overlayPrefs?.budget_ceiling);
427
+ if (projLines.length > 0) {
428
+ lines.push(blank());
429
+ for (const line of projLines) {
430
+ const colored = line.toLowerCase().includes('ceiling')
431
+ ? th.fg("warning", line)
432
+ : th.fg("dim", line);
433
+ lines.push(row(colored));
434
+ }
435
+ }
436
+ }
437
+
438
+ const models = aggregateByModel(ledger.units);
439
+ if (models.length > 1) {
440
+ lines.push(blank());
441
+ lines.push(row(th.fg("dim", "By Model")));
442
+ for (const m of models) {
443
+ const pct = totals.cost > 0 ? Math.round((m.cost / totals.cost) * 100) : 0;
444
+ const modelName = truncateToWidth(m.model, 38);
445
+ const left = ` ${th.fg("text", modelName.padEnd(38))}${th.fg("warning", formatCost(m.cost).padStart(8))}`;
446
+ const right = th.fg("dim", `${String(pct).padStart(3)}% ${m.units} units`);
447
+ lines.push(row(joinColumns(left, right, contentWidth)));
448
+ }
449
+ }
450
+
451
+ lines.push(blank());
452
+ lines.push(row(`${th.fg("dim", "avg/unit:")} ${th.fg("text", formatCost(totals.cost / totals.units))} ${th.fg("dim", "·")} ${th.fg("text", formatTokenCount(Math.round(totals.tokens.total / totals.units)))} tokens`));
453
+ }
454
+
455
+ lines.push(blank());
456
+ lines.push(hr());
457
+ lines.push(centered(th.fg("dim", "↑↓ scroll · g/G top/end · esc close")));
458
+
459
+ return lines;
460
+ }
461
+
462
+ private renderProgressRow(
463
+ label: string,
464
+ done: number,
465
+ total: number,
466
+ color: "success" | "accent" | "warning",
467
+ width: number,
468
+ ): string {
469
+ const th = this.theme;
470
+ const pct = total > 0 ? Math.round((done / total) * 100) : 0;
471
+ const labelWidth = 12;
472
+ const rightWidth = 14;
473
+ const gap = 2;
474
+ const labelText = truncateToWidth(label, labelWidth, "").padEnd(labelWidth);
475
+ const ratioText = `${done}/${total}`;
476
+ const rightText = `${String(pct).padStart(3)}% ${ratioText.padStart(rightWidth - 5)}`;
477
+ const barWidth = Math.max(12, width - labelWidth - rightWidth - gap * 2);
478
+ const filled = total > 0 ? Math.round((done / total) * barWidth) : 0;
479
+ const bar = th.fg(color, "█".repeat(filled)) + th.fg("dim", "░".repeat(Math.max(0, barWidth - filled)));
480
+ return `${th.fg("dim", labelText)}${" ".repeat(gap)}${bar}${" ".repeat(gap)}${th.fg("dim", rightText)}`;
481
+ }
482
+
483
+ invalidate(): void {
484
+ this.cachedWidth = undefined;
485
+ this.cachedLines = undefined;
486
+ }
487
+
488
+ dispose(): void {
489
+ clearInterval(this.refreshTimer);
490
+ }
491
+ }
492
+
493
+ interface MilestoneView {
494
+ id: string;
495
+ title: string;
496
+ slices: SliceView[];
497
+ phase: string;
498
+ progress: {
499
+ milestones: {
500
+ total: number;
501
+ done: number;
502
+ };
503
+ };
504
+ }
505
+
506
+ interface SliceView {
507
+ id: string;
508
+ title: string;
509
+ done: boolean;
510
+ risk: string;
511
+ active: boolean;
512
+ tasks: TaskView[];
513
+ taskProgress?: { done: number; total: number };
514
+ }
515
+
516
+ interface TaskView {
517
+ id: string;
518
+ title: string;
519
+ done: boolean;
520
+ active: boolean;
521
+ }
@@ -0,0 +1,176 @@
1
+ # GSD Preferences Reference
2
+
3
+ Full documentation for `~/.gsd/preferences.md` (global) and `.gsd/preferences.md` (project).
4
+
5
+ ---
6
+
7
+ ## Notes
8
+
9
+ - Keep this skill-first.
10
+ - Prefer explicit skill names or absolute paths.
11
+ - Use absolute paths for personal/local skills when you want zero ambiguity.
12
+ - These preferences guide which skills GSD should load and follow; they do not override higher-priority instructions in the current conversation.
13
+
14
+ ---
15
+
16
+ ## Field Guide
17
+
18
+ - `version`: schema version. Start at `1`.
19
+
20
+ - `always_use_skills`: skills GSD should use whenever they are relevant.
21
+
22
+ - `prefer_skills`: soft defaults GSD should prefer when relevant.
23
+
24
+ - `avoid_skills`: skills GSD should avoid unless clearly needed.
25
+
26
+ - `skill_rules`: situational rules with a human-readable `when` trigger and one or more of `use`, `prefer`, or `avoid`.
27
+
28
+ - `custom_instructions`: extra durable instructions related to skill use.
29
+
30
+ - `models`: per-stage model selection for auto-mode. Keys: `research`, `planning`, `execution`, `completion`. Values: model IDs (e.g. `claude-sonnet-4-6`, `claude-opus-4-6`). Omit a key to use whatever model is currently active.
31
+
32
+ - `skill_discovery`: controls how GSD discovers and applies skills during auto-mode. Valid values:
33
+ - `auto` — skills are found and applied automatically without prompting.
34
+ - `suggest` — (default) skills are identified during research but not installed automatically.
35
+ - `off` — skill discovery is disabled entirely.
36
+
37
+ - `auto_supervisor`: configures the auto-mode supervisor that monitors agent progress and enforces timeouts. Keys:
38
+ - `model`: model ID to use for the supervisor process (defaults to the currently active model).
39
+ - `soft_timeout_minutes`: minutes before the supervisor issues a soft warning (default: 20).
40
+ - `idle_timeout_minutes`: minutes of inactivity before the supervisor intervenes (default: 10).
41
+ - `hard_timeout_minutes`: minutes before the supervisor forces termination (default: 30).
42
+
43
+ - `git`: configures GSD's git behavior. All fields are optional — omit any to use defaults. Keys:
44
+ - `auto_push`: boolean — automatically push commits to the remote after committing. Default: `false`.
45
+ - `push_branches`: boolean — push newly created slice branches to the remote. Default: `false`.
46
+ - `remote`: string — git remote name to push to. Default: `"origin"`.
47
+ - `snapshots`: boolean — create snapshot commits (WIP saves) during long-running tasks. Default: `false`.
48
+ - `pre_merge_check`: boolean or `"auto"` — run pre-merge checks before merging a slice branch. `true` always runs, `false` never runs, `"auto"` runs when CI is detected. Default: `false`.
49
+ - `commit_type`: string — override the conventional commit type prefix. Must be one of: `feat`, `fix`, `refactor`, `docs`, `test`, `chore`, `perf`, `ci`, `build`, `style`. Default: inferred from diff content.
50
+ - `main_branch`: string — the primary branch name for new git repos (e.g., `"main"`, `"master"`, `"trunk"`). Also used by `getMainBranch()` as the preferred branch when auto-detection is ambiguous. Default: `"main"`.
51
+
52
+ - `planning_depth`: controls how much research and verification happens before execution. Valid values:
53
+ - `thorough` — (default) full research, planning with self-audit, observability, and reassessment.
54
+ - `standard` — skip separate research units, skip plan self-audit. Planning still does inline codebase exploration.
55
+ - `minimal` — all of standard, plus skip reassessment and observability planning.
56
+
57
+ - `workflow`: fine-grained overrides for the planning pipeline. Individual keys override `planning_depth`. Keys:
58
+ - `skip_milestone_research`: skip the research-milestone unit (default: false).
59
+ - `skip_slice_research`: skip the research-slice unit (default: false).
60
+ - `skip_plan_self_audit`: remove the 10-point self-audit from slice planning (default: false).
61
+ - `skip_reassessment`: skip roadmap reassessment after each slice (default: false).
62
+ - `skip_observability`: remove observability/diagnostics from plans and suppress warnings (default: false).
63
+
64
+ ---
65
+
66
+ ## Best Practices
67
+
68
+ - Keep `always_use_skills` short.
69
+ - Use `skill_rules` for situational routing, not broad personality preferences.
70
+ - Prefer skill names for stable built-in skills.
71
+ - Prefer absolute paths for local personal skills.
72
+
73
+ ---
74
+
75
+ ## Models Example
76
+
77
+ ```yaml
78
+ ---
79
+ version: 1
80
+ models:
81
+ research: claude-sonnet-4-6
82
+ planning: claude-opus-4-6
83
+ execution: claude-sonnet-4-6
84
+ completion: claude-sonnet-4-6
85
+ ---
86
+ ```
87
+
88
+ Opus for planning (where architectural decisions matter most), Sonnet for everything else (faster, cheaper). Omit any key to use the currently selected model.
89
+
90
+ ---
91
+
92
+ ## Example Variations
93
+
94
+ **Minimal — always load a UAT skill and route Clerk tasks:**
95
+
96
+ ```yaml
97
+ ---
98
+ version: 1
99
+ always_use_skills:
100
+ - /Users/you/.claude/skills/verify-uat
101
+ skill_rules:
102
+ - when: finishing implementation and human judgment matters
103
+ use:
104
+ - /Users/you/.claude/skills/verify-uat
105
+ ---
106
+ ```
107
+
108
+ **Richer routing — prefer cleanup and authentication skills:**
109
+
110
+ ```yaml
111
+ ---
112
+ version: 1
113
+ prefer_skills:
114
+ - commit-ignore
115
+ skill_rules:
116
+ - when: task involves Clerk authentication
117
+ use:
118
+ - clerk
119
+ - clerk-setup
120
+ - when: the user is looking for installable capability rather than implementation
121
+ prefer:
122
+ - find-skills
123
+ ---
124
+ ```
125
+
126
+ ---
127
+
128
+ ## Git Preferences Example
129
+
130
+ ```yaml
131
+ ---
132
+ version: 1
133
+ git:
134
+ auto_push: true
135
+ push_branches: true
136
+ remote: origin
137
+ snapshots: true
138
+ pre_merge_check: auto
139
+ commit_type: feat
140
+ ---
141
+ ```
142
+
143
+ All git fields are optional. Omit any field to use the default behavior. Project-level preferences override global preferences on a per-field basis.
144
+
145
+ ---
146
+
147
+ ## Workflow Examples
148
+
149
+ **Standard depth — skip research, streamline planning:**
150
+
151
+ ```yaml
152
+ ---
153
+ version: 1
154
+ planning_depth: standard
155
+ ---
156
+ ```
157
+
158
+ **Minimal depth — fastest pipeline, skip everything optional:**
159
+
160
+ ```yaml
161
+ ---
162
+ version: 1
163
+ planning_depth: minimal
164
+ ---
165
+ ```
166
+
167
+ **Custom mix — skip research but keep self-audit:**
168
+
169
+ ```yaml
170
+ ---
171
+ version: 1
172
+ planning_depth: standard
173
+ workflow:
174
+ skip_plan_self_audit: false
175
+ ---
176
+ ```