gsd-pi 2.23.0 → 2.25.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 (212) hide show
  1. package/README.md +2 -1
  2. package/dist/cli.js +12 -3
  3. package/dist/headless.d.ts +4 -0
  4. package/dist/headless.js +118 -10
  5. package/dist/help-text.js +22 -7
  6. package/dist/models-resolver.d.ts +0 -11
  7. package/dist/models-resolver.js +0 -15
  8. package/dist/resource-loader.d.ts +0 -1
  9. package/dist/resource-loader.js +64 -18
  10. package/dist/resources/GSD-WORKFLOW.md +12 -9
  11. package/dist/resources/extensions/bg-shell/overlay.ts +18 -17
  12. package/dist/resources/extensions/get-secrets-from-user.ts +5 -23
  13. package/dist/resources/extensions/gsd/activity-log.ts +5 -3
  14. package/dist/resources/extensions/gsd/auto-dispatch.ts +51 -2
  15. package/dist/resources/extensions/gsd/auto-prompts.ts +87 -0
  16. package/dist/resources/extensions/gsd/auto-recovery.ts +41 -2
  17. package/dist/resources/extensions/gsd/auto-worktree.ts +134 -4
  18. package/dist/resources/extensions/gsd/auto.ts +307 -77
  19. package/dist/resources/extensions/gsd/cache.ts +3 -1
  20. package/dist/resources/extensions/gsd/commands.ts +176 -10
  21. package/dist/resources/extensions/gsd/complexity.ts +1 -0
  22. package/dist/resources/extensions/gsd/dashboard-overlay.ts +38 -0
  23. package/dist/resources/extensions/gsd/doctor.ts +58 -11
  24. package/dist/resources/extensions/gsd/exit-command.ts +2 -2
  25. package/dist/resources/extensions/gsd/git-service.ts +74 -14
  26. package/dist/resources/extensions/gsd/gitignore.ts +1 -0
  27. package/dist/resources/extensions/gsd/gsd-db.ts +78 -1
  28. package/dist/resources/extensions/gsd/guided-flow.ts +109 -12
  29. package/dist/resources/extensions/gsd/index.ts +48 -2
  30. package/dist/resources/extensions/gsd/memory-extractor.ts +352 -0
  31. package/dist/resources/extensions/gsd/memory-store.ts +441 -0
  32. package/dist/resources/extensions/gsd/migrate/command.ts +2 -2
  33. package/dist/resources/extensions/gsd/parallel-eligibility.ts +233 -0
  34. package/dist/resources/extensions/gsd/parallel-merge.ts +156 -0
  35. package/dist/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
  36. package/dist/resources/extensions/gsd/preferences.ts +65 -1
  37. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  38. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
  39. package/dist/resources/extensions/gsd/prompts/discuss.md +4 -4
  40. package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
  41. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  42. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  43. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  44. package/dist/resources/extensions/gsd/prompts/queue.md +1 -1
  45. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  46. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  47. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +40 -61
  48. package/dist/resources/extensions/gsd/provider-error-pause.ts +29 -2
  49. package/dist/resources/extensions/gsd/session-status-io.ts +197 -0
  50. package/dist/resources/extensions/gsd/state.ts +72 -30
  51. package/dist/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
  52. package/dist/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
  53. package/dist/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
  54. package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +256 -2
  55. package/dist/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
  56. package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
  57. package/dist/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
  58. package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
  59. package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
  60. package/dist/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
  61. package/dist/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
  62. package/dist/resources/extensions/gsd/tests/git-service.test.ts +70 -4
  63. package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
  64. package/dist/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
  65. package/dist/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
  66. package/dist/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
  67. package/dist/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
  68. package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
  69. package/dist/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
  70. package/dist/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
  71. package/dist/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
  72. package/dist/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
  73. package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
  74. package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
  75. package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
  76. package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
  77. package/dist/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
  78. package/dist/resources/extensions/gsd/triage-ui.ts +1 -1
  79. package/dist/resources/extensions/gsd/types.ts +15 -1
  80. package/dist/resources/extensions/gsd/visualizer-data.ts +291 -10
  81. package/dist/resources/extensions/gsd/visualizer-overlay.ts +237 -28
  82. package/dist/resources/extensions/gsd/visualizer-views.ts +462 -48
  83. package/dist/resources/extensions/gsd/worktree.ts +9 -2
  84. package/dist/resources/extensions/search-the-web/native-search.ts +15 -5
  85. package/dist/resources/extensions/subagent/index.ts +5 -0
  86. package/dist/resources/extensions/subagent/worker-registry.ts +99 -0
  87. package/dist/update-check.d.ts +9 -0
  88. package/dist/update-check.js +97 -0
  89. package/package.json +6 -1
  90. package/packages/pi-agent-core/dist/agent-loop.js +2 -0
  91. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  92. package/packages/pi-agent-core/src/agent-loop.ts +2 -0
  93. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  94. package/packages/pi-ai/dist/providers/anthropic.js +55 -7
  95. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  96. package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  97. package/packages/pi-ai/dist/providers/azure-openai-responses.js +12 -4
  98. package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  99. package/packages/pi-ai/dist/providers/google-vertex.d.ts.map +1 -1
  100. package/packages/pi-ai/dist/providers/google-vertex.js +21 -9
  101. package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
  102. package/packages/pi-ai/dist/providers/mistral.js +3 -0
  103. package/packages/pi-ai/dist/providers/mistral.js.map +1 -1
  104. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  105. package/packages/pi-ai/dist/providers/openai-completions.js +12 -4
  106. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  107. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  108. package/packages/pi-ai/dist/providers/openai-responses.js +12 -4
  109. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  110. package/packages/pi-ai/dist/types.d.ts +23 -1
  111. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  112. package/packages/pi-ai/dist/types.js.map +1 -1
  113. package/packages/pi-ai/src/providers/anthropic.ts +59 -9
  114. package/packages/pi-ai/src/providers/azure-openai-responses.ts +16 -4
  115. package/packages/pi-ai/src/providers/google-vertex.ts +32 -17
  116. package/packages/pi-ai/src/providers/mistral.ts +3 -0
  117. package/packages/pi-ai/src/providers/openai-completions.ts +16 -4
  118. package/packages/pi-ai/src/providers/openai-responses.ts +16 -4
  119. package/packages/pi-ai/src/types.ts +19 -1
  120. package/packages/pi-coding-agent/dist/core/agent-session.js +1 -1
  121. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
  123. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  124. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -0
  126. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
  128. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  129. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +72 -0
  130. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  131. package/packages/pi-coding-agent/src/core/agent-session.ts +1 -1
  132. package/packages/pi-coding-agent/src/core/settings-manager.ts +2 -2
  133. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -0
  134. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +84 -0
  135. package/scripts/postinstall.js +7 -109
  136. package/src/resources/GSD-WORKFLOW.md +12 -9
  137. package/src/resources/extensions/bg-shell/overlay.ts +18 -17
  138. package/src/resources/extensions/get-secrets-from-user.ts +5 -23
  139. package/src/resources/extensions/gsd/activity-log.ts +5 -3
  140. package/src/resources/extensions/gsd/auto-dispatch.ts +51 -2
  141. package/src/resources/extensions/gsd/auto-prompts.ts +87 -0
  142. package/src/resources/extensions/gsd/auto-recovery.ts +41 -2
  143. package/src/resources/extensions/gsd/auto-worktree.ts +134 -4
  144. package/src/resources/extensions/gsd/auto.ts +307 -77
  145. package/src/resources/extensions/gsd/cache.ts +3 -1
  146. package/src/resources/extensions/gsd/commands.ts +176 -10
  147. package/src/resources/extensions/gsd/complexity.ts +1 -0
  148. package/src/resources/extensions/gsd/dashboard-overlay.ts +38 -0
  149. package/src/resources/extensions/gsd/doctor.ts +58 -11
  150. package/src/resources/extensions/gsd/exit-command.ts +2 -2
  151. package/src/resources/extensions/gsd/git-service.ts +74 -14
  152. package/src/resources/extensions/gsd/gitignore.ts +1 -0
  153. package/src/resources/extensions/gsd/gsd-db.ts +78 -1
  154. package/src/resources/extensions/gsd/guided-flow.ts +109 -12
  155. package/src/resources/extensions/gsd/index.ts +48 -2
  156. package/src/resources/extensions/gsd/memory-extractor.ts +352 -0
  157. package/src/resources/extensions/gsd/memory-store.ts +441 -0
  158. package/src/resources/extensions/gsd/migrate/command.ts +2 -2
  159. package/src/resources/extensions/gsd/parallel-eligibility.ts +233 -0
  160. package/src/resources/extensions/gsd/parallel-merge.ts +156 -0
  161. package/src/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
  162. package/src/resources/extensions/gsd/preferences.ts +65 -1
  163. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  164. package/src/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
  165. package/src/resources/extensions/gsd/prompts/discuss.md +4 -4
  166. package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
  167. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  168. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  169. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  170. package/src/resources/extensions/gsd/prompts/queue.md +1 -1
  171. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  172. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  173. package/src/resources/extensions/gsd/prompts/validate-milestone.md +40 -61
  174. package/src/resources/extensions/gsd/provider-error-pause.ts +29 -2
  175. package/src/resources/extensions/gsd/session-status-io.ts +197 -0
  176. package/src/resources/extensions/gsd/state.ts +72 -30
  177. package/src/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
  178. package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
  179. package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
  180. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +256 -2
  181. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
  182. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
  183. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
  184. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
  185. package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
  186. package/src/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
  187. package/src/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
  188. package/src/resources/extensions/gsd/tests/git-service.test.ts +70 -4
  189. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
  190. package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
  191. package/src/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
  192. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
  193. package/src/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
  194. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
  195. package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
  196. package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
  197. package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
  198. package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
  199. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
  200. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
  201. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
  202. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
  203. package/src/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
  204. package/src/resources/extensions/gsd/triage-ui.ts +1 -1
  205. package/src/resources/extensions/gsd/types.ts +15 -1
  206. package/src/resources/extensions/gsd/visualizer-data.ts +291 -10
  207. package/src/resources/extensions/gsd/visualizer-overlay.ts +237 -28
  208. package/src/resources/extensions/gsd/visualizer-views.ts +462 -48
  209. package/src/resources/extensions/gsd/worktree.ts +9 -2
  210. package/src/resources/extensions/search-the-web/native-search.ts +15 -5
  211. package/src/resources/extensions/subagent/index.ts +5 -0
  212. package/src/resources/extensions/subagent/worker-registry.ts +99 -0
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Tests for the parallel worker registry used by the dashboard overlay.
3
+ *
4
+ * Verifies worker lifecycle (register → update → cleanup), batch grouping,
5
+ * and the hasActiveWorkers() status check.
6
+ */
7
+
8
+ import { createTestContext } from './test-helpers.ts';
9
+ import {
10
+ registerWorker,
11
+ updateWorker,
12
+ getActiveWorkers,
13
+ getWorkerBatches,
14
+ hasActiveWorkers,
15
+ resetWorkerRegistry,
16
+ } from '../../subagent/worker-registry.ts';
17
+
18
+ const { assertEq, assertTrue, report } = createTestContext();
19
+
20
+ // ─── Setup ────────────────────────────────────────────────────────────────────
21
+
22
+ resetWorkerRegistry();
23
+
24
+ // ─── Registration ─────────────────────────────────────────────────────────────
25
+
26
+ console.log("\n=== Worker Registration ===");
27
+
28
+ {
29
+ resetWorkerRegistry();
30
+ const id = registerWorker("scout", "Explore codebase", 0, 3, "batch-1");
31
+ assertTrue(id.startsWith("worker-"), "worker ID has correct prefix");
32
+ const workers = getActiveWorkers();
33
+ assertEq(workers.length, 1, "one worker registered");
34
+ assertEq(workers[0].agent, "scout", "worker agent name correct");
35
+ assertEq(workers[0].task, "Explore codebase", "worker task correct");
36
+ assertEq(workers[0].status, "running", "worker starts as running");
37
+ assertEq(workers[0].index, 0, "worker index correct");
38
+ assertEq(workers[0].batchSize, 3, "worker batch size correct");
39
+ assertEq(workers[0].batchId, "batch-1", "worker batch ID correct");
40
+ }
41
+
42
+ // ─── Multiple workers in a batch ──────────────────────────────────────────────
43
+
44
+ console.log("\n=== Multiple Workers in a Batch ===");
45
+
46
+ {
47
+ resetWorkerRegistry();
48
+ const id1 = registerWorker("scout", "Task A", 0, 3, "batch-2");
49
+ const id2 = registerWorker("researcher", "Task B", 1, 3, "batch-2");
50
+ const id3 = registerWorker("worker", "Task C", 2, 3, "batch-2");
51
+
52
+ const workers = getActiveWorkers();
53
+ assertEq(workers.length, 3, "three workers registered");
54
+ assertTrue(hasActiveWorkers(), "has active workers");
55
+
56
+ const batches = getWorkerBatches();
57
+ assertEq(batches.size, 1, "one batch");
58
+ const batch = batches.get("batch-2");
59
+ assertTrue(batch !== undefined, "batch-2 exists");
60
+ assertEq(batch!.length, 3, "batch has 3 workers");
61
+ }
62
+
63
+ // ─── Worker status updates ────────────────────────────────────────────────────
64
+
65
+ console.log("\n=== Worker Status Updates ===");
66
+
67
+ {
68
+ resetWorkerRegistry();
69
+ const id1 = registerWorker("scout", "Task A", 0, 2, "batch-3");
70
+ const id2 = registerWorker("worker", "Task B", 1, 2, "batch-3");
71
+
72
+ updateWorker(id1, "completed");
73
+ const workers = getActiveWorkers();
74
+ const w1 = workers.find(w => w.id === id1);
75
+ assertEq(w1?.status, "completed", "worker 1 marked completed");
76
+
77
+ const w2 = workers.find(w => w.id === id2);
78
+ assertEq(w2?.status, "running", "worker 2 still running");
79
+ assertTrue(hasActiveWorkers(), "still has active workers (worker 2 running)");
80
+ }
81
+
82
+ // ─── Failed worker ────────────────────────────────────────────────────────────
83
+
84
+ console.log("\n=== Failed Worker ===");
85
+
86
+ {
87
+ resetWorkerRegistry();
88
+ const id = registerWorker("scout", "Task A", 0, 1, "batch-4");
89
+ updateWorker(id, "failed");
90
+ const workers = getActiveWorkers();
91
+ assertEq(workers[0].status, "failed", "worker marked failed");
92
+ }
93
+
94
+ // ─── Multiple batches ─────────────────────────────────────────────────────────
95
+
96
+ console.log("\n=== Multiple Batches ===");
97
+
98
+ {
99
+ resetWorkerRegistry();
100
+ registerWorker("scout", "Task A", 0, 2, "batch-5");
101
+ registerWorker("worker", "Task B", 1, 2, "batch-5");
102
+ registerWorker("researcher", "Task C", 0, 1, "batch-6");
103
+
104
+ const batches = getWorkerBatches();
105
+ assertEq(batches.size, 2, "two batches");
106
+ assertEq(batches.get("batch-5")!.length, 2, "batch-5 has 2 workers");
107
+ assertEq(batches.get("batch-6")!.length, 1, "batch-6 has 1 worker");
108
+ }
109
+
110
+ // ─── hasActiveWorkers with all completed ──────────────────────────────────────
111
+
112
+ console.log("\n=== hasActiveWorkers — all completed ===");
113
+
114
+ {
115
+ resetWorkerRegistry();
116
+ const id1 = registerWorker("scout", "Task A", 0, 2, "batch-7");
117
+ const id2 = registerWorker("worker", "Task B", 1, 2, "batch-7");
118
+ updateWorker(id1, "completed");
119
+ updateWorker(id2, "completed");
120
+ assertTrue(!hasActiveWorkers(), "no active workers when all completed");
121
+ }
122
+
123
+ // ─── Reset clears everything ─────────────────────────────────────────────────
124
+
125
+ console.log("\n=== Reset ===");
126
+
127
+ {
128
+ registerWorker("scout", "Task", 0, 1, "batch-8");
129
+ assertTrue(getActiveWorkers().length > 0, "workers exist before reset");
130
+ resetWorkerRegistry();
131
+ assertEq(getActiveWorkers().length, 0, "no workers after reset");
132
+ assertTrue(!hasActiveWorkers(), "hasActiveWorkers false after reset");
133
+ }
134
+
135
+ // ─── Update non-existent worker is no-op ──────────────────────────────────────
136
+
137
+ console.log("\n=== Update non-existent worker ===");
138
+
139
+ {
140
+ resetWorkerRegistry();
141
+ // Should not throw
142
+ updateWorker("nonexistent-id", "completed");
143
+ assertEq(getActiveWorkers().length, 0, "no workers created by updating nonexistent");
144
+ }
145
+
146
+ // ─── Summary ──────────────────────────────────────────────────────────────────
147
+
148
+ report();
@@ -135,7 +135,7 @@ export async function showTriageConfirmation(
135
135
  recommended: cls === proposed,
136
136
  }));
137
137
 
138
- const choice = await showNextAction(ctx as any, {
138
+ const choice = await showNextAction(ctx, {
139
139
  title: `Triage: ${result.captureId}`,
140
140
  summary,
141
141
  actions,
@@ -5,7 +5,7 @@
5
5
  // ─── Enums & Literal Unions ────────────────────────────────────────────────
6
6
 
7
7
  export type RiskLevel = 'low' | 'medium' | 'high';
8
- export type Phase = 'pre-planning' | 'needs-discussion' | 'discussing' | 'researching' | 'planning' | 'executing' | 'verifying' | 'summarizing' | 'advancing' | 'completing-milestone' | 'replanning-slice' | 'complete' | 'paused' | 'blocked';
8
+ export type Phase = 'pre-planning' | 'needs-discussion' | 'discussing' | 'researching' | 'planning' | 'executing' | 'verifying' | 'summarizing' | 'advancing' | 'validating-milestone' | 'completing-milestone' | 'replanning-slice' | 'complete' | 'paused' | 'blocked';
9
9
  export type ContinueStatus = 'in_progress' | 'interrupted' | 'compacted';
10
10
 
11
11
  // ─── Roadmap (Milestone-level) ─────────────────────────────────────────────
@@ -264,6 +264,7 @@ export interface PhaseSkipPreferences {
264
264
  skip_research?: boolean;
265
265
  skip_reassess?: boolean;
266
266
  skip_slice_research?: boolean;
267
+ skip_milestone_validation?: boolean;
267
268
  }
268
269
 
269
270
  export interface NotificationPreferences {
@@ -363,3 +364,16 @@ export interface Requirement {
363
364
  full_content: string; // full requirement text
364
365
  superseded_by: string | null; // ID of superseding requirement, or null
365
366
  }
367
+
368
+ // ─── Parallel Orchestration Types ────────────────────────────────────────
369
+
370
+ export type MergeStrategy = "per-slice" | "per-milestone";
371
+ export type AutoMergeMode = "auto" | "confirm" | "manual";
372
+
373
+ export interface ParallelConfig {
374
+ enabled: boolean;
375
+ max_workers: number;
376
+ budget_ceiling?: number;
377
+ merge_strategy: MergeStrategy;
378
+ auto_merge: AutoMergeMode;
379
+ }
@@ -1,25 +1,32 @@
1
1
  // Data loader for workflow visualizer overlay — aggregates state + metrics.
2
2
 
3
+ import { existsSync, readFileSync, statSync } from 'node:fs';
3
4
  import { deriveState } from './state.js';
4
5
  import { parseRoadmap, parsePlan, parseSummary, loadFile } from './files.js';
5
6
  import { findMilestoneIds } from './guided-flow.js';
6
- import { resolveMilestoneFile, resolveSliceFile } from './paths.js';
7
+ import { resolveMilestoneFile, resolveSliceFile, resolveGsdRootFile } from './paths.js';
7
8
  import {
8
9
  getLedger,
9
10
  getProjectTotals,
10
11
  aggregateByPhase,
11
12
  aggregateBySlice,
12
13
  aggregateByModel,
14
+ aggregateByTier,
15
+ formatTierSavings,
13
16
  loadLedgerFromDisk,
14
17
  classifyUnitPhase,
15
18
  } from './metrics.js';
19
+ import { loadAllCaptures, countPendingCaptures } from './captures.js';
20
+ import { loadEffectiveGSDPreferences } from './preferences.js';
16
21
 
17
22
  import type { Phase } from './types.js';
23
+ import type { CaptureEntry } from './captures.js';
18
24
  import type {
19
25
  ProjectTotals,
20
26
  PhaseAggregate,
21
27
  SliceAggregate,
22
28
  ModelAggregate,
29
+ TierAggregate,
23
30
  UnitMetrics,
24
31
  } from './metrics.js';
25
32
 
@@ -48,6 +55,7 @@ export interface VisualizerTask {
48
55
  title: string;
49
56
  done: boolean;
50
57
  active: boolean;
58
+ estimate?: string;
51
59
  }
52
60
 
53
61
  export interface CriticalPathInfo {
@@ -81,6 +89,71 @@ export interface ChangelogInfo {
81
89
  entries: ChangelogEntry[];
82
90
  }
83
91
 
92
+ export interface VisualizerSliceRef {
93
+ milestoneId: string;
94
+ sliceId: string;
95
+ title: string;
96
+ }
97
+
98
+ export interface VisualizerSliceActivity extends VisualizerSliceRef {
99
+ completedAt: string;
100
+ }
101
+
102
+ export interface VisualizerStats {
103
+ missingCount: number;
104
+ missingSlices: VisualizerSliceRef[];
105
+ updatedCount: number;
106
+ updatedSlices: VisualizerSliceActivity[];
107
+ recentEntries: ChangelogEntry[];
108
+ }
109
+
110
+ export type DiscussionState = 'undiscussed' | 'draft' | 'discussed';
111
+
112
+ export interface VisualizerDiscussionState {
113
+ milestoneId: string;
114
+ title: string;
115
+ state: DiscussionState;
116
+ hasContext: boolean;
117
+ hasDraft: boolean;
118
+ lastUpdated: string | null;
119
+ }
120
+
121
+ export interface SliceVerification {
122
+ milestoneId: string;
123
+ sliceId: string;
124
+ verificationResult: string;
125
+ blockerDiscovered: boolean;
126
+ keyDecisions: string[];
127
+ patternsEstablished: string[];
128
+ provides: string[];
129
+ requires: { slice: string; provides: string }[];
130
+ }
131
+
132
+ export interface KnowledgeInfo {
133
+ rules: { id: string; scope: string; content: string }[];
134
+ patterns: { id: string; content: string }[];
135
+ lessons: { id: string; content: string }[];
136
+ exists: boolean;
137
+ }
138
+
139
+ export interface CapturesInfo {
140
+ entries: CaptureEntry[];
141
+ pendingCount: number;
142
+ totalCount: number;
143
+ }
144
+
145
+ export interface HealthInfo {
146
+ budgetCeiling: number | undefined;
147
+ tokenProfile: string;
148
+ truncationRate: number;
149
+ continueHereRate: number;
150
+ tierBreakdown: TierAggregate[];
151
+ tierSavingsLine: string;
152
+ toolCalls: number;
153
+ assistantMessages: number;
154
+ userMessages: number;
155
+ }
156
+
84
157
  export interface VisualizerData {
85
158
  milestones: VisualizerMilestone[];
86
159
  phase: Phase;
@@ -88,11 +161,19 @@ export interface VisualizerData {
88
161
  byPhase: PhaseAggregate[];
89
162
  bySlice: SliceAggregate[];
90
163
  byModel: ModelAggregate[];
164
+ byTier: TierAggregate[];
165
+ tierSavingsLine: string;
91
166
  units: UnitMetrics[];
92
167
  criticalPath: CriticalPathInfo;
93
168
  remainingSliceCount: number;
94
169
  agentActivity: AgentActivityInfo | null;
95
170
  changelog: ChangelogInfo;
171
+ sliceVerifications: SliceVerification[];
172
+ knowledge: KnowledgeInfo;
173
+ captures: CapturesInfo;
174
+ health: HealthInfo;
175
+ discussion: VisualizerDiscussionState[];
176
+ stats: VisualizerStats;
96
177
  }
97
178
 
98
179
  // ─── Critical Path ────────────────────────────────────────────────────────────
@@ -334,12 +415,18 @@ function loadAgentActivity(units: UnitMetrics[], milestones: VisualizerMilestone
334
415
  };
335
416
  }
336
417
 
337
- // ─── Changelog ───────────────────────────────────────────────────────────────
418
+ // ─── Changelog & Verifications ────────────────────────────────────────────────
338
419
 
339
- const changelogCache = new Map<string, { mtime: number; entry: ChangelogEntry }>();
420
+ const changelogCache = new Map<string, { mtime: number; entry: ChangelogEntry; verification: SliceVerification }>();
340
421
 
341
- async function loadChangelog(basePath: string, milestones: VisualizerMilestone[]): Promise<ChangelogInfo> {
422
+ interface ChangelogAndVerifications {
423
+ changelog: ChangelogInfo;
424
+ verifications: SliceVerification[];
425
+ }
426
+
427
+ async function loadChangelogAndVerifications(basePath: string, milestones: VisualizerMilestone[]): Promise<ChangelogAndVerifications> {
342
428
  const entries: ChangelogEntry[] = [];
429
+ const verifications: SliceVerification[] = [];
343
430
 
344
431
  for (const ms of milestones) {
345
432
  for (const sl of ms.slices) {
@@ -348,11 +435,9 @@ async function loadChangelog(basePath: string, milestones: VisualizerMilestone[]
348
435
  const summaryFile = resolveSliceFile(basePath, ms.id, sl.id, 'SUMMARY');
349
436
  if (!summaryFile) continue;
350
437
 
351
- // Check cache by file path
352
438
  const cacheKey = `${ms.id}/${sl.id}`;
353
439
  const cached = changelogCache.get(cacheKey);
354
440
 
355
- // Check mtime for cache invalidation
356
441
  let mtime = 0;
357
442
  try {
358
443
  const { statSync } = await import('node:fs');
@@ -363,6 +448,7 @@ async function loadChangelog(basePath: string, milestones: VisualizerMilestone[]
363
448
 
364
449
  if (cached && cached.mtime === mtime) {
365
450
  entries.push(cached.entry);
451
+ verifications.push(cached.verification);
366
452
  continue;
367
453
  }
368
454
 
@@ -382,15 +468,184 @@ async function loadChangelog(basePath: string, milestones: VisualizerMilestone[]
382
468
  completedAt: String(summary.frontmatter.completed_at ?? ''),
383
469
  };
384
470
 
385
- changelogCache.set(cacheKey, { mtime, entry });
471
+ const verification: SliceVerification = {
472
+ milestoneId: ms.id,
473
+ sliceId: sl.id,
474
+ verificationResult: summary.frontmatter.verification_result || '',
475
+ blockerDiscovered: summary.frontmatter.blocker_discovered,
476
+ keyDecisions: summary.frontmatter.key_decisions || [],
477
+ patternsEstablished: summary.frontmatter.patterns_established || [],
478
+ provides: summary.frontmatter.provides || [],
479
+ requires: (summary.frontmatter.requires || []).map(r => ({
480
+ slice: r.slice,
481
+ provides: r.provides,
482
+ })),
483
+ };
484
+
485
+ changelogCache.set(cacheKey, { mtime, entry, verification });
386
486
  entries.push(entry);
487
+ verifications.push(verification);
387
488
  }
388
489
  }
389
490
 
390
- // Sort by completedAt descending
391
491
  entries.sort((a, b) => String(b.completedAt || '').localeCompare(String(a.completedAt || '')));
392
492
 
393
- return { entries };
493
+ return { changelog: { entries }, verifications };
494
+ }
495
+
496
+ // ─── Knowledge Loader ─────────────────────────────────────────────────────────
497
+
498
+ function loadKnowledge(basePath: string): KnowledgeInfo {
499
+ const knowledgePath = resolveGsdRootFile(basePath, 'KNOWLEDGE');
500
+ if (!existsSync(knowledgePath)) {
501
+ return { rules: [], patterns: [], lessons: [], exists: false };
502
+ }
503
+
504
+ let content: string;
505
+ try {
506
+ content = readFileSync(knowledgePath, 'utf-8');
507
+ } catch {
508
+ return { rules: [], patterns: [], lessons: [], exists: false };
509
+ }
510
+
511
+ const rules: { id: string; scope: string; content: string }[] = [];
512
+ const patterns: { id: string; content: string }[] = [];
513
+ const lessons: { id: string; content: string }[] = [];
514
+
515
+ const lines = content.split('\n');
516
+ let currentSection = '';
517
+
518
+ for (const line of lines) {
519
+ if (line.startsWith('## Rules')) { currentSection = 'rules'; continue; }
520
+ if (line.startsWith('## Patterns')) { currentSection = 'patterns'; continue; }
521
+ if (line.startsWith('## Lessons')) { currentSection = 'lessons'; continue; }
522
+ if (line.startsWith('## ')) { currentSection = ''; continue; }
523
+
524
+ if (!line.startsWith('| ') || line.startsWith('| ---') || line.startsWith('| ID')) continue;
525
+ const cols = line.split('|').map(c => c.trim()).filter(c => c.length > 0);
526
+ if (cols.length < 2) continue;
527
+
528
+ if (currentSection === 'rules' && cols.length >= 3) {
529
+ rules.push({ id: cols[0], scope: cols[1], content: cols[2] });
530
+ } else if (currentSection === 'patterns' && cols.length >= 2) {
531
+ patterns.push({ id: cols[0], content: cols[1] });
532
+ } else if (currentSection === 'lessons' && cols.length >= 2) {
533
+ lessons.push({ id: cols[0], content: cols[1] });
534
+ }
535
+ }
536
+
537
+ return { rules, patterns, lessons, exists: true };
538
+ }
539
+
540
+ // ─── Health Loader ────────────────────────────────────────────────────────────
541
+
542
+ function loadHealth(units: UnitMetrics[], totals: ProjectTotals | null): HealthInfo {
543
+ const prefs = loadEffectiveGSDPreferences();
544
+ const budgetCeiling = prefs?.preferences?.budget_ceiling;
545
+ const tokenProfile = prefs?.preferences?.token_profile ?? 'standard';
546
+
547
+ let truncationRate = 0;
548
+ let continueHereRate = 0;
549
+ if (totals && totals.units > 0) {
550
+ truncationRate = (totals.totalTruncationSections / totals.units) * 100;
551
+ continueHereRate = (totals.continueHereFiredCount / totals.units) * 100;
552
+ }
553
+
554
+ const tierBreakdown = aggregateByTier(units);
555
+ const tierSavingsLine = formatTierSavings(units);
556
+
557
+ return {
558
+ budgetCeiling,
559
+ tokenProfile,
560
+ truncationRate,
561
+ continueHereRate,
562
+ tierBreakdown,
563
+ tierSavingsLine,
564
+ toolCalls: totals?.toolCalls ?? 0,
565
+ assistantMessages: totals?.assistantMessages ?? 0,
566
+ userMessages: totals?.userMessages ?? 0,
567
+ };
568
+ }
569
+
570
+ const RECENT_ENTRY_LIMIT = 3;
571
+ const FEATURE_PREVIEW_LIMIT = 5;
572
+ const UPDATED_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
573
+
574
+ function buildVisualizerStats(
575
+ milestones: VisualizerMilestone[],
576
+ entries: ChangelogEntry[],
577
+ ): VisualizerStats {
578
+ const missing: VisualizerSliceRef[] = [];
579
+ for (const ms of milestones) {
580
+ for (const sl of ms.slices) {
581
+ if (!sl.done) missing.push({ milestoneId: ms.id, sliceId: sl.id, title: sl.title });
582
+ }
583
+ }
584
+
585
+ const missingCount = missing.length;
586
+ const missingSlices = missing.slice(0, FEATURE_PREVIEW_LIMIT);
587
+
588
+ const now = Date.now();
589
+ const updatedEntries = entries.filter(entry => {
590
+ if (!entry.completedAt) return false;
591
+ const parsed = Date.parse(entry.completedAt);
592
+ return !Number.isNaN(parsed) && now - parsed <= UPDATED_WINDOW_MS;
593
+ });
594
+ const updatedCount = updatedEntries.length;
595
+ const updatedSlices = updatedEntries.slice(0, FEATURE_PREVIEW_LIMIT).map(entry => ({
596
+ milestoneId: entry.milestoneId,
597
+ sliceId: entry.sliceId,
598
+ title: entry.title,
599
+ completedAt: entry.completedAt,
600
+ }));
601
+
602
+ const recentEntries = entries.slice(0, RECENT_ENTRY_LIMIT);
603
+
604
+ return {
605
+ missingCount,
606
+ missingSlices,
607
+ updatedCount,
608
+ updatedSlices,
609
+ recentEntries,
610
+ };
611
+ }
612
+
613
+ function loadDiscussionState(
614
+ basePath: string,
615
+ milestones: VisualizerMilestone[],
616
+ ): VisualizerDiscussionState[] {
617
+ const states: VisualizerDiscussionState[] = [];
618
+
619
+ for (const ms of milestones) {
620
+ const contextPath = resolveMilestoneFile(basePath, ms.id, "CONTEXT");
621
+ const draftPath = resolveMilestoneFile(basePath, ms.id, "CONTEXT-DRAFT");
622
+ const state: DiscussionState = contextPath
623
+ ? "discussed"
624
+ : draftPath
625
+ ? "draft"
626
+ : "undiscussed";
627
+
628
+ let lastUpdated: string | null = null;
629
+ const target = contextPath ?? draftPath;
630
+ if (target) {
631
+ try {
632
+ lastUpdated = new Date(statSync(target).mtimeMs).toISOString();
633
+ } catch {
634
+ lastUpdated = null;
635
+ }
636
+ }
637
+
638
+ states.push({
639
+ milestoneId: ms.id,
640
+ title: ms.title,
641
+ state,
642
+ hasContext: !!contextPath,
643
+ hasDraft: !!draftPath,
644
+ lastUpdated,
645
+ });
646
+ }
647
+
648
+ return states;
394
649
  }
395
650
 
396
651
  // ─── Loader ───────────────────────────────────────────────────────────────────
@@ -433,6 +688,7 @@ export async function loadVisualizerData(basePath: string): Promise<VisualizerDa
433
688
  title: t.title,
434
689
  done: t.done,
435
690
  active: state.activeTask?.id === t.id,
691
+ estimate: t.estimate || undefined,
436
692
  });
437
693
  }
438
694
  }
@@ -464,6 +720,8 @@ export async function loadVisualizerData(basePath: string): Promise<VisualizerDa
464
720
  let byPhase: PhaseAggregate[] = [];
465
721
  let bySlice: SliceAggregate[] = [];
466
722
  let byModel: ModelAggregate[] = [];
723
+ let byTier: TierAggregate[] = [];
724
+ let tierSavingsLine = '';
467
725
  let units: UnitMetrics[] = [];
468
726
 
469
727
  const ledger = getLedger() ?? loadLedgerFromDisk(basePath);
@@ -474,6 +732,8 @@ export async function loadVisualizerData(basePath: string): Promise<VisualizerDa
474
732
  byPhase = aggregateByPhase(units);
475
733
  bySlice = aggregateBySlice(units);
476
734
  byModel = aggregateByModel(units);
735
+ byTier = aggregateByTier(units);
736
+ tierSavingsLine = formatTierSavings(units);
477
737
  }
478
738
 
479
739
  // Compute new fields
@@ -487,7 +747,20 @@ export async function loadVisualizerData(basePath: string): Promise<VisualizerDa
487
747
  }
488
748
 
489
749
  const agentActivity = loadAgentActivity(units, milestones);
490
- const changelog = await loadChangelog(basePath, milestones);
750
+ const { changelog, verifications: sliceVerifications } = await loadChangelogAndVerifications(basePath, milestones);
751
+
752
+ const knowledge = loadKnowledge(basePath);
753
+ const allCaptures = loadAllCaptures(basePath);
754
+ const pendingCount = countPendingCaptures(basePath);
755
+ const captures: CapturesInfo = {
756
+ entries: allCaptures,
757
+ pendingCount,
758
+ totalCount: allCaptures.length,
759
+ };
760
+
761
+ const health = loadHealth(units, totals);
762
+ const stats = buildVisualizerStats(milestones, changelog.entries);
763
+ const discussion = loadDiscussionState(basePath, milestones);
491
764
 
492
765
  return {
493
766
  milestones,
@@ -496,10 +769,18 @@ export async function loadVisualizerData(basePath: string): Promise<VisualizerDa
496
769
  byPhase,
497
770
  bySlice,
498
771
  byModel,
772
+ byTier,
773
+ tierSavingsLine,
499
774
  units,
500
775
  criticalPath,
501
776
  remainingSliceCount,
502
777
  agentActivity,
503
778
  changelog,
779
+ sliceVerifications,
780
+ knowledge,
781
+ captures,
782
+ health,
783
+ discussion,
784
+ stats,
504
785
  };
505
786
  }