gsd-pi 2.24.0 → 2.26.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 (206) hide show
  1. package/README.md +13 -3
  2. package/dist/headless.js +24 -4
  3. package/dist/models-resolver.d.ts +0 -11
  4. package/dist/models-resolver.js +0 -15
  5. package/dist/resource-loader.d.ts +0 -1
  6. package/dist/resource-loader.js +0 -9
  7. package/dist/resources/GSD-WORKFLOW.md +12 -9
  8. package/dist/resources/extensions/async-jobs/index.ts +9 -1
  9. package/dist/resources/extensions/bg-shell/index.ts +3 -2
  10. package/dist/resources/extensions/bg-shell/overlay.ts +18 -17
  11. package/dist/resources/extensions/get-secrets-from-user.ts +5 -23
  12. package/dist/resources/extensions/gsd/activity-log.ts +5 -3
  13. package/dist/resources/extensions/gsd/auto-prompts.ts +14 -0
  14. package/dist/resources/extensions/gsd/auto-recovery.ts +7 -4
  15. package/dist/resources/extensions/gsd/auto-worktree.ts +132 -3
  16. package/dist/resources/extensions/gsd/auto.ts +265 -48
  17. package/dist/resources/extensions/gsd/cache.ts +3 -1
  18. package/dist/resources/extensions/gsd/doctor-proactive.ts +7 -6
  19. package/dist/resources/extensions/gsd/doctor.ts +26 -1
  20. package/dist/resources/extensions/gsd/files.ts +13 -2
  21. package/dist/resources/extensions/gsd/git-service.ts +74 -14
  22. package/dist/resources/extensions/gsd/gsd-db.ts +78 -1
  23. package/dist/resources/extensions/gsd/guided-flow.ts +54 -22
  24. package/dist/resources/extensions/gsd/index.ts +62 -8
  25. package/dist/resources/extensions/gsd/memory-extractor.ts +352 -0
  26. package/dist/resources/extensions/gsd/memory-store.ts +441 -0
  27. package/dist/resources/extensions/gsd/migrate/command.ts +2 -2
  28. package/dist/resources/extensions/gsd/migrate/writer.ts +39 -0
  29. package/dist/resources/extensions/gsd/parallel-orchestrator.ts +122 -4
  30. package/dist/resources/extensions/gsd/preferences.ts +2 -1
  31. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  32. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +4 -4
  33. package/dist/resources/extensions/gsd/prompts/discuss.md +5 -5
  34. package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
  35. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  36. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  37. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  38. package/dist/resources/extensions/gsd/prompts/queue.md +3 -3
  39. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  40. package/dist/resources/extensions/gsd/roadmap-slices.ts +45 -1
  41. package/dist/resources/extensions/gsd/state.ts +17 -6
  42. package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
  43. package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
  44. package/dist/resources/extensions/gsd/tests/derive-state.test.ts +70 -0
  45. package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +23 -3
  46. package/dist/resources/extensions/gsd/tests/git-service.test.ts +70 -4
  47. package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
  48. package/dist/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
  49. package/dist/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
  50. package/dist/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
  51. package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +13 -7
  52. package/dist/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +171 -0
  53. package/dist/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
  54. package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +8 -4
  55. package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
  56. package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
  57. package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
  58. package/dist/resources/extensions/gsd/triage-ui.ts +1 -1
  59. package/dist/resources/extensions/gsd/types.ts +2 -0
  60. package/dist/resources/extensions/gsd/visualizer-data.ts +291 -10
  61. package/dist/resources/extensions/gsd/visualizer-overlay.ts +237 -28
  62. package/dist/resources/extensions/gsd/visualizer-views.ts +462 -48
  63. package/dist/resources/extensions/gsd/worktree.ts +9 -2
  64. package/dist/resources/extensions/search-the-web/native-search.ts +19 -5
  65. package/dist/resources/extensions/shared/path-display.ts +19 -0
  66. package/package.json +1 -6
  67. package/packages/pi-agent-core/dist/agent-loop.js +2 -0
  68. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  69. package/packages/pi-agent-core/src/agent-loop.ts +2 -0
  70. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  71. package/packages/pi-ai/dist/providers/anthropic.js +64 -0
  72. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  73. package/packages/pi-ai/dist/providers/mistral.js +3 -0
  74. package/packages/pi-ai/dist/providers/mistral.js.map +1 -1
  75. package/packages/pi-ai/dist/types.d.ts +23 -1
  76. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  77. package/packages/pi-ai/dist/types.js.map +1 -1
  78. package/packages/pi-ai/src/providers/anthropic.ts +65 -1
  79. package/packages/pi-ai/src/providers/mistral.ts +3 -0
  80. package/packages/pi-ai/src/types.ts +19 -1
  81. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +7 -0
  82. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  83. package/packages/pi-coding-agent/dist/core/agent-session.js +32 -0
  84. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  85. package/packages/pi-coding-agent/dist/core/keybindings.js +1 -1
  86. package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
  87. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  88. package/packages/pi-coding-agent/dist/core/lsp/client.js +12 -1
  89. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  90. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
  91. package/packages/pi-coding-agent/dist/core/lsp/index.js +7 -0
  92. package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
  93. package/packages/pi-coding-agent/dist/core/sdk.d.ts +2 -2
  94. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  95. package/packages/pi-coding-agent/dist/core/sdk.js +8 -3
  96. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  97. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
  98. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  99. package/packages/pi-coding-agent/dist/core/settings-manager.js +8 -0
  100. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  101. package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
  102. package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
  103. package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
  104. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  105. package/packages/pi-coding-agent/dist/core/system-prompt.js +2 -1
  106. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/index.d.ts +2 -1
  108. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  109. package/packages/pi-coding-agent/dist/index.js +5 -1
  110. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  111. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts +41 -3
  112. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  113. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +301 -62
  114. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  115. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -0
  117. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +5 -0
  119. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  120. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +135 -30
  121. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/tests/path-display.test.d.ts +8 -0
  123. package/packages/pi-coding-agent/dist/tests/path-display.test.d.ts.map +1 -0
  124. package/packages/pi-coding-agent/dist/tests/path-display.test.js +60 -0
  125. package/packages/pi-coding-agent/dist/tests/path-display.test.js.map +1 -0
  126. package/packages/pi-coding-agent/dist/utils/clipboard-image.d.ts.map +1 -1
  127. package/packages/pi-coding-agent/dist/utils/clipboard-image.js +32 -6
  128. package/packages/pi-coding-agent/dist/utils/clipboard-image.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/utils/path-display.d.ts +34 -0
  130. package/packages/pi-coding-agent/dist/utils/path-display.d.ts.map +1 -0
  131. package/packages/pi-coding-agent/dist/utils/path-display.js +36 -0
  132. package/packages/pi-coding-agent/dist/utils/path-display.js.map +1 -0
  133. package/packages/pi-coding-agent/src/core/agent-session.ts +36 -0
  134. package/packages/pi-coding-agent/src/core/keybindings.ts +1 -1
  135. package/packages/pi-coding-agent/src/core/lsp/client.ts +11 -1
  136. package/packages/pi-coding-agent/src/core/lsp/index.ts +7 -0
  137. package/packages/pi-coding-agent/src/core/sdk.ts +17 -1
  138. package/packages/pi-coding-agent/src/core/settings-manager.ts +11 -0
  139. package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
  140. package/packages/pi-coding-agent/src/core/system-prompt.ts +2 -1
  141. package/packages/pi-coding-agent/src/index.ts +15 -0
  142. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +347 -62
  143. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -0
  144. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +124 -4
  145. package/packages/pi-coding-agent/src/tests/path-display.test.ts +85 -0
  146. package/packages/pi-coding-agent/src/utils/clipboard-image.ts +33 -6
  147. package/packages/pi-coding-agent/src/utils/path-display.ts +36 -0
  148. package/src/resources/GSD-WORKFLOW.md +12 -9
  149. package/src/resources/extensions/async-jobs/index.ts +9 -1
  150. package/src/resources/extensions/bg-shell/index.ts +3 -2
  151. package/src/resources/extensions/bg-shell/overlay.ts +18 -17
  152. package/src/resources/extensions/get-secrets-from-user.ts +5 -23
  153. package/src/resources/extensions/gsd/activity-log.ts +5 -3
  154. package/src/resources/extensions/gsd/auto-prompts.ts +14 -0
  155. package/src/resources/extensions/gsd/auto-recovery.ts +7 -4
  156. package/src/resources/extensions/gsd/auto-worktree.ts +132 -3
  157. package/src/resources/extensions/gsd/auto.ts +265 -48
  158. package/src/resources/extensions/gsd/cache.ts +3 -1
  159. package/src/resources/extensions/gsd/doctor-proactive.ts +7 -6
  160. package/src/resources/extensions/gsd/doctor.ts +26 -1
  161. package/src/resources/extensions/gsd/files.ts +13 -2
  162. package/src/resources/extensions/gsd/git-service.ts +74 -14
  163. package/src/resources/extensions/gsd/gsd-db.ts +78 -1
  164. package/src/resources/extensions/gsd/guided-flow.ts +54 -22
  165. package/src/resources/extensions/gsd/index.ts +62 -8
  166. package/src/resources/extensions/gsd/memory-extractor.ts +352 -0
  167. package/src/resources/extensions/gsd/memory-store.ts +441 -0
  168. package/src/resources/extensions/gsd/migrate/command.ts +2 -2
  169. package/src/resources/extensions/gsd/migrate/writer.ts +39 -0
  170. package/src/resources/extensions/gsd/parallel-orchestrator.ts +122 -4
  171. package/src/resources/extensions/gsd/preferences.ts +2 -1
  172. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  173. package/src/resources/extensions/gsd/prompts/discuss-headless.md +4 -4
  174. package/src/resources/extensions/gsd/prompts/discuss.md +5 -5
  175. package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
  176. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  177. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  178. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  179. package/src/resources/extensions/gsd/prompts/queue.md +3 -3
  180. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  181. package/src/resources/extensions/gsd/roadmap-slices.ts +45 -1
  182. package/src/resources/extensions/gsd/state.ts +17 -6
  183. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
  184. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
  185. package/src/resources/extensions/gsd/tests/derive-state.test.ts +70 -0
  186. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +23 -3
  187. package/src/resources/extensions/gsd/tests/git-service.test.ts +70 -4
  188. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
  189. package/src/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
  190. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
  191. package/src/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
  192. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +13 -7
  193. package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +171 -0
  194. package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
  195. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +8 -4
  196. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
  197. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
  198. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
  199. package/src/resources/extensions/gsd/triage-ui.ts +1 -1
  200. package/src/resources/extensions/gsd/types.ts +2 -0
  201. package/src/resources/extensions/gsd/visualizer-data.ts +291 -10
  202. package/src/resources/extensions/gsd/visualizer-overlay.ts +237 -28
  203. package/src/resources/extensions/gsd/visualizer-views.ts +462 -48
  204. package/src/resources/extensions/gsd/worktree.ts +9 -2
  205. package/src/resources/extensions/search-the-web/native-search.ts +19 -5
  206. package/src/resources/extensions/shared/path-display.ts +19 -0
@@ -9,11 +9,14 @@ import {
9
9
  renderAgentView,
10
10
  renderChangelogView,
11
11
  renderExportView,
12
+ renderKnowledgeView,
13
+ renderCapturesView,
14
+ renderHealthView,
12
15
  type ProgressFilter,
13
16
  } from "./visualizer-views.js";
14
17
  import { writeExportFile } from "./export.js";
15
18
 
16
- const TAB_COUNT = 7;
19
+ const TAB_COUNT = 10;
17
20
  const TAB_LABELS = [
18
21
  "1 Progress",
19
22
  "2 Deps",
@@ -22,8 +25,15 @@ const TAB_LABELS = [
22
25
  "5 Agent",
23
26
  "6 Changes",
24
27
  "7 Export",
28
+ "8 Knowledge",
29
+ "9 Captures",
30
+ "0 Health",
25
31
  ];
26
32
 
33
+ function stripAnsi(s: string): string {
34
+ return s.replace(/\x1b\[[0-9;]*m/g, "");
35
+ }
36
+
27
37
  export class GSDVisualizerOverlay {
28
38
  private tui: { requestRender: () => void };
29
39
  private theme: Theme;
@@ -39,7 +49,7 @@ export class GSDVisualizerOverlay {
39
49
  data: VisualizerData | null = null;
40
50
  basePath: string;
41
51
 
42
- // Filter state (Progress tab)
52
+ // Filter state
43
53
  filterMode = false;
44
54
  filterText = "";
45
55
  filterField: "all" | "status" | "risk" | "keyword" = "all";
@@ -48,6 +58,11 @@ export class GSDVisualizerOverlay {
48
58
  lastExportPath?: string;
49
59
  exportStatus?: string;
50
60
 
61
+ // New state
62
+ private lastVisibleRows = 20;
63
+ collapsedMilestones = new Set<string>();
64
+ showHelp = false;
65
+
51
66
  constructor(
52
67
  tui: { requestRender: () => void },
53
68
  theme: Theme,
@@ -58,6 +73,9 @@ export class GSDVisualizerOverlay {
58
73
  this.onClose = onClose;
59
74
  this.basePath = process.cwd();
60
75
 
76
+ // Enable SGR mouse tracking
77
+ process.stdout.write("\x1b[?1003h\x1b[?1006h");
78
+
61
79
  loadVisualizerData(this.basePath).then((d) => {
62
80
  this.data = d;
63
81
  this.loading = false;
@@ -74,6 +92,17 @@ export class GSDVisualizerOverlay {
74
92
  }, 2000);
75
93
  }
76
94
 
95
+ private parseSGRMouse(data: string): { button: number; x: number; y: number; press: boolean } | null {
96
+ const match = data.match(/^\x1b\[<(\d+);(\d+);(\d+)([Mm])$/);
97
+ if (!match) return null;
98
+ return {
99
+ button: parseInt(match[1], 10),
100
+ x: parseInt(match[2], 10),
101
+ y: parseInt(match[3], 10),
102
+ press: match[4] === "M",
103
+ };
104
+ }
105
+
77
106
  handleInput(data: string): void {
78
107
  // Filter mode input routing
79
108
  if (this.filterMode) {
@@ -106,6 +135,53 @@ export class GSDVisualizerOverlay {
106
135
  return;
107
136
  }
108
137
 
138
+ // Help overlay dismissal
139
+ if (this.showHelp) {
140
+ if (matchesKey(data, Key.escape) || data === "?") {
141
+ this.showHelp = false;
142
+ this.invalidate();
143
+ this.tui.requestRender();
144
+ return;
145
+ }
146
+ return;
147
+ }
148
+
149
+ // Mouse handling (before keyboard checks)
150
+ const mouse = this.parseSGRMouse(data);
151
+ if (mouse) {
152
+ if (mouse.button === 64) {
153
+ // Wheel up
154
+ this.scrollOffsets[this.activeTab] = Math.max(0, this.scrollOffsets[this.activeTab] - 3);
155
+ this.invalidate();
156
+ this.tui.requestRender();
157
+ return;
158
+ }
159
+ if (mouse.button === 65) {
160
+ // Wheel down
161
+ this.scrollOffsets[this.activeTab] += 3;
162
+ this.invalidate();
163
+ this.tui.requestRender();
164
+ return;
165
+ }
166
+ if (mouse.button === 0 && mouse.press) {
167
+ // Left click — check if on tab bar row
168
+ if (mouse.y === 2) {
169
+ let xPos = 3;
170
+ for (let i = 0; i < TAB_LABELS.length; i++) {
171
+ const tabWidth = TAB_LABELS[i].length + 2;
172
+ if (mouse.x >= xPos && mouse.x < xPos + tabWidth) {
173
+ this.activeTab = i;
174
+ this.invalidate();
175
+ this.tui.requestRender();
176
+ return;
177
+ }
178
+ xPos += tabWidth + 1;
179
+ }
180
+ }
181
+ }
182
+ return;
183
+ }
184
+
109
185
  if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c"))) {
110
186
  this.dispose();
111
187
  this.onClose();
@@ -126,15 +202,16 @@ export class GSDVisualizerOverlay {
126
202
  return;
127
203
  }
128
204
 
129
- if ("1234567".includes(data) && data.length === 1) {
130
- this.activeTab = parseInt(data, 10) - 1;
205
+ if ("1234567890".includes(data) && data.length === 1) {
206
+ const idx = data === "0" ? 9 : parseInt(data, 10) - 1;
207
+ this.activeTab = idx;
131
208
  this.invalidate();
132
209
  this.tui.requestRender();
133
210
  return;
134
211
  }
135
212
 
136
- // "/" enters filter mode on Progress tab
137
- if (data === "/" && this.activeTab === 0) {
213
+ // "/" enters filter mode on any tab
214
+ if (data === "/") {
138
215
  this.filterMode = true;
139
216
  this.filterText = "";
140
217
  this.invalidate();
@@ -142,16 +219,48 @@ export class GSDVisualizerOverlay {
142
219
  return;
143
220
  }
144
221
 
145
- // "f" cycles filter field on Progress tab (when not in filter mode)
146
- if (data === "f" && this.activeTab === 0) {
147
- const fields: Array<"all" | "status" | "risk" | "keyword"> = ["all", "status", "risk", "keyword"];
148
- const idx = fields.indexOf(this.filterField);
149
- this.filterField = fields[(idx + 1) % fields.length];
222
+ // "f" cycles filter field (limit to all/keyword on non-Progress tabs)
223
+ if (data === "f") {
224
+ if (this.activeTab === 0) {
225
+ const fields: Array<"all" | "status" | "risk" | "keyword"> = ["all", "status", "risk", "keyword"];
226
+ const idx = fields.indexOf(this.filterField);
227
+ this.filterField = fields[(idx + 1) % fields.length];
228
+ } else {
229
+ this.filterField = this.filterField === "all" ? "keyword" : "all";
230
+ }
150
231
  this.invalidate();
151
232
  this.tui.requestRender();
152
233
  return;
153
234
  }
154
235
 
236
+ // "?" toggles help overlay
237
+ if (data === "?") {
238
+ this.showHelp = true;
239
+ this.invalidate();
240
+ this.tui.requestRender();
241
+ return;
242
+ }
243
+
244
+ // Enter/Space toggles collapse on Progress tab
245
+ if ((matchesKey(data, Key.enter) || data === " ") && this.activeTab === 0 && this.data) {
246
+ const viewLines = this.renderTabContent(0, 80);
247
+ const offset = this.scrollOffsets[0];
248
+ for (const ms of this.data.milestones) {
249
+ const lineIdx = viewLines.findIndex(l => stripAnsi(l).includes(`${ms.id}:`));
250
+ if (lineIdx >= offset && lineIdx < offset + this.lastVisibleRows) {
251
+ if (this.collapsedMilestones.has(ms.id)) {
252
+ this.collapsedMilestones.delete(ms.id);
253
+ } else {
254
+ this.collapsedMilestones.add(ms.id);
255
+ }
256
+ this.invalidate();
257
+ this.tui.requestRender();
258
+ return;
259
+ }
260
+ }
261
+ return;
262
+ }
263
+
155
264
  // Export tab key handling
156
265
  if (this.activeTab === 6 && this.data) {
157
266
  if (data === "m" || data === "j" || data === "s") {
@@ -160,6 +269,40 @@ export class GSDVisualizerOverlay {
160
269
  }
161
270
  }
162
271
 
272
+ // Page Up/Down
273
+ if (matchesKey(data, Key.pageUp)) {
274
+ const amount = Math.max(1, this.lastVisibleRows - 2);
275
+ this.scrollOffsets[this.activeTab] = Math.max(0, this.scrollOffsets[this.activeTab] - amount);
276
+ this.invalidate();
277
+ this.tui.requestRender();
278
+ return;
279
+ }
280
+
281
+ if (matchesKey(data, Key.pageDown)) {
282
+ const amount = Math.max(1, this.lastVisibleRows - 2);
283
+ this.scrollOffsets[this.activeTab] += amount;
284
+ this.invalidate();
285
+ this.tui.requestRender();
286
+ return;
287
+ }
288
+
289
+ // Half-page scroll: Ctrl+U / Ctrl+D
290
+ if (matchesKey(data, Key.ctrl("u"))) {
291
+ const amount = Math.max(1, Math.floor(this.lastVisibleRows / 2));
292
+ this.scrollOffsets[this.activeTab] = Math.max(0, this.scrollOffsets[this.activeTab] - amount);
293
+ this.invalidate();
294
+ this.tui.requestRender();
295
+ return;
296
+ }
297
+
298
+ if (matchesKey(data, Key.ctrl("d"))) {
299
+ const amount = Math.max(1, Math.floor(this.lastVisibleRows / 2));
300
+ this.scrollOffsets[this.activeTab] += amount;
301
+ this.invalidate();
302
+ this.tui.requestRender();
303
+ return;
304
+ }
305
+
163
306
  if (matchesKey(data, Key.down) || matchesKey(data, "j")) {
164
307
  this.scrollOffsets[this.activeTab]++;
165
308
  this.invalidate();
@@ -226,7 +369,7 @@ export class GSDVisualizerOverlay {
226
369
  case 0: {
227
370
  const filter: ProgressFilter | undefined =
228
371
  this.filterText ? { text: this.filterText, field: this.filterField } : undefined;
229
- return renderProgressView(this.data, th, width, filter);
372
+ return renderProgressView(this.data, th, width, filter, this.collapsedMilestones);
230
373
  }
231
374
  case 1:
232
375
  return renderDepsView(this.data, th, width);
@@ -240,11 +383,46 @@ export class GSDVisualizerOverlay {
240
383
  return renderChangelogView(this.data, th, width);
241
384
  case 6:
242
385
  return renderExportView(this.data, th, width, this.lastExportPath);
386
+ case 7:
387
+ return renderKnowledgeView(this.data, th, width);
388
+ case 8:
389
+ return renderCapturesView(this.data, th, width);
390
+ case 9:
391
+ return renderHealthView(this.data, th, width);
243
392
  default:
244
393
  return [];
245
394
  }
246
395
  }
247
396
 
397
+ private renderHelpContent(width: number): string[] {
398
+ const th = this.theme;
399
+ const lines: string[] = [];
400
+ lines.push(th.fg("accent", th.bold("Keyboard Shortcuts")));
401
+ lines.push("");
402
+ const bindings: [string, string][] = [
403
+ ["Tab/Shift+Tab", "Next/Previous tab"],
404
+ ["1-9, 0", "Jump to tab"],
405
+ ["j/k, Up/Down", "Scroll line"],
406
+ ["PgUp/PgDn", "Scroll page"],
407
+ ["Ctrl+U/Ctrl+D", "Scroll half-page"],
408
+ ["g/G", "Top/Bottom"],
409
+ ["/", "Search/filter"],
410
+ ["f", "Cycle filter field"],
411
+ ["Enter/Space", "Toggle collapse (Progress)"],
412
+ ["Mouse wheel", "Scroll"],
413
+ ["Click tab", "Switch tab"],
414
+ ["?", "Toggle help"],
415
+ ["Esc", "Close"],
416
+ ];
417
+ for (const [key, desc] of bindings) {
418
+ const keyStr = th.fg("accent", key.padEnd(20));
419
+ lines.push(` ${keyStr} ${desc}`);
420
+ }
421
+ lines.push("");
422
+ lines.push(th.fg("dim", "Press ? or Esc to dismiss"));
423
+ return lines;
424
+ }
425
+
248
426
  render(width: number): string[] {
249
427
  if (this.cachedLines && this.cachedWidth === width) {
250
428
  return this.cachedLines;
@@ -257,9 +435,13 @@ export class GSDVisualizerOverlay {
257
435
  // Tab bar
258
436
  const tabs = TAB_LABELS.map((label, i) => {
259
437
  let displayLabel = label;
260
- // Show filter indicator on Progress tab
261
- if (i === 0 && this.filterText) {
262
- displayLabel += " ";
438
+ // Show filter indicator on active tab with filter
439
+ if (i === this.activeTab && this.filterText) {
440
+ displayLabel += " \u2731";
441
+ }
442
+ // Show captures badge
443
+ if (i === 8 && this.data?.captures?.pendingCount) {
444
+ displayLabel += ` (${this.data.captures.pendingCount})`;
263
445
  }
264
446
  if (i === this.activeTab) {
265
447
  return th.fg("accent", `[${displayLabel}]`);
@@ -269,21 +451,23 @@ export class GSDVisualizerOverlay {
269
451
  content.push(" " + tabs.join(" "));
270
452
  content.push("");
271
453
 
272
- // Filter bar (when in filter mode)
273
- if (this.filterMode && this.activeTab === 0) {
454
+ // Filter bar (when in filter mode on any tab)
455
+ if (this.filterMode) {
274
456
  content.push(
275
- th.fg("accent", `Filter (${this.filterField}): ${this.filterText}█`),
457
+ th.fg("accent", `Filter (${this.filterField}): ${this.filterText}\u2588`),
276
458
  );
277
459
  content.push("");
278
460
  }
279
461
 
280
- if (this.loading) {
281
- const loadingText = "Loading…";
462
+ if (this.showHelp) {
463
+ content.push(...this.renderHelpContent(innerWidth));
464
+ } else if (this.loading) {
465
+ const loadingText = "Loading\u2026";
282
466
  const vis = visibleWidth(loadingText);
283
467
  const leftPad = Math.max(0, Math.floor((innerWidth - vis) / 2));
284
468
  content.push(" ".repeat(leftPad) + loadingText);
285
469
  } else if (this.data) {
286
- const viewLines = this.renderTabContent(this.activeTab, innerWidth);
470
+ let viewLines = this.renderTabContent(this.activeTab, innerWidth);
287
471
 
288
472
  // Show export status message if present
289
473
  if (this.exportStatus && this.activeTab === 6) {
@@ -292,6 +476,12 @@ export class GSDVisualizerOverlay {
292
476
  this.exportStatus = undefined;
293
477
  }
294
478
 
479
+ // Apply cross-tab filter for non-Progress tabs
480
+ if (this.filterText && this.activeTab !== 0) {
481
+ const lowerFilter = this.filterText.toLowerCase();
482
+ viewLines = viewLines.filter(line => stripAnsi(line).toLowerCase().includes(lowerFilter));
483
+ }
484
+
295
485
  content.push(...viewLines);
296
486
  }
297
487
 
@@ -299,15 +489,17 @@ export class GSDVisualizerOverlay {
299
489
  const viewportHeight = Math.max(5, process.stdout.rows ? process.stdout.rows - 8 : 24);
300
490
  const chromeHeight = 2;
301
491
  const visibleContentRows = Math.max(1, viewportHeight - chromeHeight);
492
+ this.lastVisibleRows = visibleContentRows;
493
+ const totalLines = content.length;
302
494
  const maxScroll = Math.max(0, content.length - visibleContentRows);
303
495
  this.scrollOffsets[this.activeTab] = Math.min(this.scrollOffsets[this.activeTab], maxScroll);
304
496
  const offset = this.scrollOffsets[this.activeTab];
305
497
  const visibleContent = content.slice(offset, offset + visibleContentRows);
306
498
 
307
- const lines = this.wrapInBox(visibleContent, width);
499
+ const lines = this.wrapInBox(visibleContent, width, offset, visibleContentRows, totalLines);
308
500
 
309
501
  // Footer hint
310
- const hint = th.fg("dim", "Tab/Shift+Tab/1-7 switch · / filter · ↑↓ scroll · g/G top/end · esc close");
502
+ const hint = th.fg("dim", "Tab/Shift+Tab/1-9,0 switch \u00b7 / filter \u00b7 PgUp/PgDn scroll \u00b7 ? help \u00b7 esc close");
311
503
  const hintVis = visibleWidth(hint);
312
504
  const hintPad = Math.max(0, Math.floor((width - hintVis) / 2));
313
505
  lines.push(" ".repeat(hintPad) + hint);
@@ -317,18 +509,33 @@ export class GSDVisualizerOverlay {
317
509
  return lines;
318
510
  }
319
511
 
320
- private wrapInBox(inner: string[], width: number): string[] {
512
+ private wrapInBox(inner: string[], width: number, offset?: number, visibleRows?: number, totalLines?: number): string[] {
321
513
  const th = this.theme;
322
514
  const border = (s: string) => th.fg("borderAccent", s);
323
515
  const innerWidth = width - 4;
324
516
  const lines: string[] = [];
325
- lines.push(border("" + "".repeat(width - 2) + ""));
326
- for (const line of inner) {
517
+ lines.push(border("\u256d" + "\u2500".repeat(width - 2) + "\u256e"));
518
+
519
+ // Compute scroll indicator positions
520
+ const scrollable = totalLines !== undefined && visibleRows !== undefined && totalLines > visibleRows;
521
+ let thumbStart = -1;
522
+ let thumbLen = 0;
523
+ const innerRows = inner.length;
524
+ if (scrollable && innerRows > 0 && totalLines! > 0) {
525
+ thumbStart = Math.round(((offset ?? 0) / totalLines!) * innerRows);
526
+ thumbLen = Math.max(1, Math.round((visibleRows! / totalLines!) * innerRows));
527
+ }
528
+
529
+ for (let i = 0; i < inner.length; i++) {
530
+ const line = inner[i];
327
531
  const truncated = truncateToWidth(line, innerWidth);
328
532
  const padWidth = Math.max(0, innerWidth - visibleWidth(truncated));
329
- lines.push(border("│") + " " + truncated + " ".repeat(padWidth) + " " + border("│"));
533
+ const rightBorder = scrollable && i >= thumbStart && i < thumbStart + thumbLen
534
+ ? border("\u2503")
535
+ : border("\u2502");
536
+ lines.push(border("\u2502") + " " + truncated + " ".repeat(padWidth) + " " + rightBorder);
330
537
  }
331
- lines.push(border("" + "".repeat(width - 2) + ""));
538
+ lines.push(border("\u2570" + "\u2500".repeat(width - 2) + "\u256f"));
332
539
  return lines;
333
540
  }
334
541
 
@@ -340,5 +547,7 @@ export class GSDVisualizerOverlay {
340
547
  dispose(): void {
341
548
  this.disposed = true;
342
549
  clearInterval(this.refreshTimer);
550
+ // Disable SGR mouse tracking
551
+ process.stdout.write("\x1b[?1003l\x1b[?1006l");
343
552
  }
344
553
  }