gsd-pi 2.24.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 (113) hide show
  1. package/README.md +2 -1
  2. package/dist/models-resolver.d.ts +0 -11
  3. package/dist/models-resolver.js +0 -15
  4. package/dist/resource-loader.d.ts +0 -1
  5. package/dist/resource-loader.js +0 -9
  6. package/dist/resources/GSD-WORKFLOW.md +12 -9
  7. package/dist/resources/extensions/bg-shell/overlay.ts +18 -17
  8. package/dist/resources/extensions/get-secrets-from-user.ts +5 -23
  9. package/dist/resources/extensions/gsd/activity-log.ts +5 -3
  10. package/dist/resources/extensions/gsd/auto-prompts.ts +14 -0
  11. package/dist/resources/extensions/gsd/auto-worktree.ts +119 -1
  12. package/dist/resources/extensions/gsd/auto.ts +184 -36
  13. package/dist/resources/extensions/gsd/cache.ts +3 -1
  14. package/dist/resources/extensions/gsd/doctor.ts +2 -0
  15. package/dist/resources/extensions/gsd/git-service.ts +74 -14
  16. package/dist/resources/extensions/gsd/gsd-db.ts +78 -1
  17. package/dist/resources/extensions/gsd/guided-flow.ts +34 -12
  18. package/dist/resources/extensions/gsd/index.ts +14 -1
  19. package/dist/resources/extensions/gsd/memory-extractor.ts +352 -0
  20. package/dist/resources/extensions/gsd/memory-store.ts +441 -0
  21. package/dist/resources/extensions/gsd/migrate/command.ts +2 -2
  22. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  23. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +2 -2
  24. package/dist/resources/extensions/gsd/prompts/discuss.md +4 -4
  25. package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
  26. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  27. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  28. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  29. package/dist/resources/extensions/gsd/prompts/queue.md +1 -1
  30. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  31. package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
  32. package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
  33. package/dist/resources/extensions/gsd/tests/git-service.test.ts +70 -4
  34. package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
  35. package/dist/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
  36. package/dist/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
  37. package/dist/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
  38. package/dist/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
  39. package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
  40. package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
  41. package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
  42. package/dist/resources/extensions/gsd/triage-ui.ts +1 -1
  43. package/dist/resources/extensions/gsd/visualizer-data.ts +291 -10
  44. package/dist/resources/extensions/gsd/visualizer-overlay.ts +237 -28
  45. package/dist/resources/extensions/gsd/visualizer-views.ts +462 -48
  46. package/dist/resources/extensions/gsd/worktree.ts +9 -2
  47. package/dist/resources/extensions/search-the-web/native-search.ts +15 -5
  48. package/package.json +1 -1
  49. package/packages/pi-agent-core/dist/agent-loop.js +2 -0
  50. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  51. package/packages/pi-agent-core/src/agent-loop.ts +2 -0
  52. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  53. package/packages/pi-ai/dist/providers/anthropic.js +39 -0
  54. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  55. package/packages/pi-ai/dist/providers/mistral.js +3 -0
  56. package/packages/pi-ai/dist/providers/mistral.js.map +1 -1
  57. package/packages/pi-ai/dist/types.d.ts +23 -1
  58. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  59. package/packages/pi-ai/dist/types.js.map +1 -1
  60. package/packages/pi-ai/src/providers/anthropic.ts +38 -1
  61. package/packages/pi-ai/src/providers/mistral.ts +3 -0
  62. package/packages/pi-ai/src/types.ts +19 -1
  63. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  64. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -0
  65. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  66. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
  67. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  68. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +72 -0
  69. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  70. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -0
  71. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +84 -0
  72. package/src/resources/GSD-WORKFLOW.md +12 -9
  73. package/src/resources/extensions/bg-shell/overlay.ts +18 -17
  74. package/src/resources/extensions/get-secrets-from-user.ts +5 -23
  75. package/src/resources/extensions/gsd/activity-log.ts +5 -3
  76. package/src/resources/extensions/gsd/auto-prompts.ts +14 -0
  77. package/src/resources/extensions/gsd/auto-worktree.ts +119 -1
  78. package/src/resources/extensions/gsd/auto.ts +184 -36
  79. package/src/resources/extensions/gsd/cache.ts +3 -1
  80. package/src/resources/extensions/gsd/doctor.ts +2 -0
  81. package/src/resources/extensions/gsd/git-service.ts +74 -14
  82. package/src/resources/extensions/gsd/gsd-db.ts +78 -1
  83. package/src/resources/extensions/gsd/guided-flow.ts +34 -12
  84. package/src/resources/extensions/gsd/index.ts +14 -1
  85. package/src/resources/extensions/gsd/memory-extractor.ts +352 -0
  86. package/src/resources/extensions/gsd/memory-store.ts +441 -0
  87. package/src/resources/extensions/gsd/migrate/command.ts +2 -2
  88. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  89. package/src/resources/extensions/gsd/prompts/discuss-headless.md +2 -2
  90. package/src/resources/extensions/gsd/prompts/discuss.md +4 -4
  91. package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
  92. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  93. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  94. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  95. package/src/resources/extensions/gsd/prompts/queue.md +1 -1
  96. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  97. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
  98. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
  99. package/src/resources/extensions/gsd/tests/git-service.test.ts +70 -4
  100. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
  101. package/src/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
  102. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
  103. package/src/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
  104. package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
  105. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
  106. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
  107. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
  108. package/src/resources/extensions/gsd/triage-ui.ts +1 -1
  109. package/src/resources/extensions/gsd/visualizer-data.ts +291 -10
  110. package/src/resources/extensions/gsd/visualizer-overlay.ts +237 -28
  111. package/src/resources/extensions/gsd/visualizer-views.ts +462 -48
  112. package/src/resources/extensions/gsd/worktree.ts +9 -2
  113. package/src/resources/extensions/search-the-web/native-search.ts +15 -5
@@ -0,0 +1,345 @@
1
+ import { createTestContext } from './test-helpers.ts';
2
+ import {
3
+ openDatabase,
4
+ closeDatabase,
5
+ isDbAvailable,
6
+ _getAdapter,
7
+ } from '../gsd-db.ts';
8
+ import {
9
+ getActiveMemories,
10
+ getActiveMemoriesRanked,
11
+ nextMemoryId,
12
+ createMemory,
13
+ updateMemoryContent,
14
+ reinforceMemory,
15
+ supersedeMemory,
16
+ isUnitProcessed,
17
+ markUnitProcessed,
18
+ decayStaleMemories,
19
+ enforceMemoryCap,
20
+ applyMemoryActions,
21
+ formatMemoriesForPrompt,
22
+ } from '../memory-store.ts';
23
+ import type { MemoryAction } from '../memory-store.ts';
24
+
25
+ const { assertEq, assertTrue, assertMatch, report } = createTestContext();
26
+
27
+ // ═══════════════════════════════════════════════════════════════════════════
28
+ // memory-store: fallback when DB not open
29
+ // ═══════════════════════════════════════════════════════════════════════════
30
+
31
+ console.log('\n=== memory-store: fallback returns empty when DB not open ===');
32
+ {
33
+ closeDatabase();
34
+ assertTrue(!isDbAvailable(), 'DB should not be available');
35
+
36
+ assertEq(getActiveMemories(), [], 'getActiveMemories returns [] when DB closed');
37
+ assertEq(getActiveMemoriesRanked(), [], 'getActiveMemoriesRanked returns [] when DB closed');
38
+ assertEq(nextMemoryId(), 'MEM001', 'nextMemoryId returns MEM001 when DB closed');
39
+ assertEq(createMemory({ category: 'test', content: 'test' }), null, 'createMemory returns null when DB closed');
40
+ assertTrue(!reinforceMemory('MEM001'), 'reinforceMemory returns false when DB closed');
41
+ assertTrue(!isUnitProcessed('test/key'), 'isUnitProcessed returns false when DB closed');
42
+ }
43
+
44
+ // ═══════════════════════════════════════════════════════════════════════════
45
+ // memory-store: CRUD operations
46
+ // ═══════════════════════════════════════════════════════════════════════════
47
+
48
+ console.log('\n=== memory-store: create and query memories ===');
49
+ {
50
+ openDatabase(':memory:');
51
+
52
+ // Create memories
53
+ const id1 = createMemory({ category: 'gotcha', content: 'esbuild drops .node binaries' });
54
+ assertTrue(id1 !== null, 'createMemory should return an ID');
55
+ assertEq(id1, 'MEM001', 'first memory ID should be MEM001');
56
+
57
+ const id2 = createMemory({ category: 'convention', content: 'use :memory: for tests', confidence: 0.9 });
58
+ assertEq(id2, 'MEM002', 'second memory ID should be MEM002');
59
+
60
+ const id3 = createMemory({ category: 'architecture', content: 'extensions discovered from src/resources/' });
61
+ assertEq(id3, 'MEM003', 'third memory ID should be MEM003');
62
+
63
+ // Query all active
64
+ const active = getActiveMemories();
65
+ assertEq(active.length, 3, 'should have 3 active memories');
66
+ assertEq(active[0].category, 'gotcha', 'first memory category');
67
+ assertEq(active[0].content, 'esbuild drops .node binaries', 'first memory content');
68
+ assertEq(active[1].confidence, 0.9, 'second memory confidence');
69
+
70
+ closeDatabase();
71
+ }
72
+
73
+ // ═══════════════════════════════════════════════════════════════════════════
74
+ // memory-store: update and reinforce
75
+ // ═══════════════════════════════════════════════════════════════════════════
76
+
77
+ console.log('\n=== memory-store: update and reinforce ===');
78
+ {
79
+ openDatabase(':memory:');
80
+
81
+ createMemory({ category: 'gotcha', content: 'original content' });
82
+
83
+ // Update content
84
+ const updated = updateMemoryContent('MEM001', 'revised content', 0.95);
85
+ assertTrue(updated, 'updateMemoryContent should return true');
86
+
87
+ const active = getActiveMemories();
88
+ assertEq(active[0].content, 'revised content', 'content should be updated');
89
+ assertEq(active[0].confidence, 0.95, 'confidence should be updated');
90
+
91
+ // Reinforce
92
+ const reinforced = reinforceMemory('MEM001');
93
+ assertTrue(reinforced, 'reinforceMemory should return true');
94
+
95
+ const after = getActiveMemories();
96
+ assertEq(after[0].hit_count, 1, 'hit_count should be 1 after reinforce');
97
+
98
+ // Reinforce again
99
+ reinforceMemory('MEM001');
100
+ const after2 = getActiveMemories();
101
+ assertEq(after2[0].hit_count, 2, 'hit_count should be 2 after second reinforce');
102
+
103
+ closeDatabase();
104
+ }
105
+
106
+ // ═══════════════════════════════════════════════════════════════════════════
107
+ // memory-store: supersede
108
+ // ═══════════════════════════════════════════════════════════════════════════
109
+
110
+ console.log('\n=== memory-store: supersede ===');
111
+ {
112
+ openDatabase(':memory:');
113
+
114
+ createMemory({ category: 'convention', content: 'old convention' });
115
+ createMemory({ category: 'convention', content: 'new convention' });
116
+
117
+ supersedeMemory('MEM001', 'MEM002');
118
+
119
+ const active = getActiveMemories();
120
+ assertEq(active.length, 1, 'should have 1 active memory after supersede');
121
+ assertEq(active[0].id, 'MEM002', 'active memory should be MEM002');
122
+
123
+ closeDatabase();
124
+ }
125
+
126
+ // ═══════════════════════════════════════════════════════════════════════════
127
+ // memory-store: ranked query ordering
128
+ // ═══════════════════════════════════════════════════════════════════════════
129
+
130
+ console.log('\n=== memory-store: ranked query ordering ===');
131
+ {
132
+ openDatabase(':memory:');
133
+
134
+ // Low confidence, no hits
135
+ createMemory({ category: 'pattern', content: 'low ranking', confidence: 0.5 });
136
+ // High confidence, no hits
137
+ createMemory({ category: 'gotcha', content: 'high confidence', confidence: 0.95 });
138
+ // Medium confidence, many hits
139
+ createMemory({ category: 'convention', content: 'frequently used', confidence: 0.7 });
140
+
141
+ // Reinforce MEM003 multiple times to boost its ranking
142
+ for (let i = 0; i < 10; i++) reinforceMemory('MEM003');
143
+
144
+ const ranked = getActiveMemoriesRanked(10);
145
+ assertEq(ranked.length, 3, 'should have 3 ranked memories');
146
+ // MEM003: 0.7 * (1 + 10*0.1) = 0.7 * 2.0 = 1.4
147
+ // MEM002: 0.95 * (1 + 0*0.1) = 0.95
148
+ // MEM001: 0.5 * (1 + 0*0.1) = 0.5
149
+ assertEq(ranked[0].id, 'MEM003', 'highest ranked should be MEM003 (reinforced)');
150
+ assertEq(ranked[1].id, 'MEM002', 'second ranked should be MEM002 (high confidence)');
151
+ assertEq(ranked[2].id, 'MEM001', 'lowest ranked should be MEM001');
152
+
153
+ // Test limit
154
+ const limited = getActiveMemoriesRanked(2);
155
+ assertEq(limited.length, 2, 'limit should cap results');
156
+
157
+ closeDatabase();
158
+ }
159
+
160
+ // ═══════════════════════════════════════════════════════════════════════════
161
+ // memory-store: processed unit tracking
162
+ // ═══════════════════════════════════════════════════════════════════════════
163
+
164
+ console.log('\n=== memory-store: processed unit tracking ===');
165
+ {
166
+ openDatabase(':memory:');
167
+
168
+ assertTrue(!isUnitProcessed('execute-task/M001/S01/T01'), 'should not be processed initially');
169
+
170
+ markUnitProcessed('execute-task/M001/S01/T01', '/path/to/activity.jsonl');
171
+
172
+ assertTrue(isUnitProcessed('execute-task/M001/S01/T01'), 'should be processed after marking');
173
+ assertTrue(!isUnitProcessed('execute-task/M001/S01/T02'), 'different key should not be processed');
174
+
175
+ closeDatabase();
176
+ }
177
+
178
+ // ═══════════════════════════════════════════════════════════════════════════
179
+ // memory-store: enforce memory cap
180
+ // ═══════════════════════════════════════════════════════════════════════════
181
+
182
+ console.log('\n=== memory-store: enforce memory cap ===');
183
+ {
184
+ openDatabase(':memory:');
185
+
186
+ // Create 5 memories with varying confidence
187
+ createMemory({ category: 'gotcha', content: 'mem 1', confidence: 0.9 });
188
+ createMemory({ category: 'gotcha', content: 'mem 2', confidence: 0.5 });
189
+ createMemory({ category: 'gotcha', content: 'mem 3', confidence: 0.3 });
190
+ createMemory({ category: 'gotcha', content: 'mem 4', confidence: 0.95 });
191
+ createMemory({ category: 'gotcha', content: 'mem 5', confidence: 0.7 });
192
+
193
+ // Enforce cap of 3
194
+ enforceMemoryCap(3);
195
+
196
+ const active = getActiveMemories();
197
+ assertEq(active.length, 3, 'should have 3 active memories after cap enforcement');
198
+
199
+ // The 2 lowest-ranked (MEM003=0.3 and MEM002=0.5) should be superseded
200
+ const ids = active.map(m => m.id).sort();
201
+ assertTrue(ids.includes('MEM001'), 'MEM001 (0.9) should survive');
202
+ assertTrue(ids.includes('MEM004'), 'MEM004 (0.95) should survive');
203
+ assertTrue(ids.includes('MEM005'), 'MEM005 (0.7) should survive');
204
+
205
+ closeDatabase();
206
+ }
207
+
208
+ // ═══════════════════════════════════════════════════════════════════════════
209
+ // memory-store: applyMemoryActions transaction
210
+ // ═══════════════════════════════════════════════════════════════════════════
211
+
212
+ console.log('\n=== memory-store: applyMemoryActions ===');
213
+ {
214
+ openDatabase(':memory:');
215
+
216
+ const actions: MemoryAction[] = [
217
+ { action: 'CREATE', category: 'gotcha', content: 'first gotcha', confidence: 0.8 },
218
+ { action: 'CREATE', category: 'convention', content: 'first convention', confidence: 0.9 },
219
+ ];
220
+
221
+ applyMemoryActions(actions, 'execute-task', 'M001/S01/T01');
222
+
223
+ let active = getActiveMemories();
224
+ assertEq(active.length, 2, 'should have 2 memories after CREATE actions');
225
+
226
+ // Now apply UPDATE + REINFORCE
227
+ const updateActions: MemoryAction[] = [
228
+ { action: 'UPDATE', id: 'MEM001', content: 'updated gotcha' },
229
+ { action: 'REINFORCE', id: 'MEM002' },
230
+ ];
231
+
232
+ applyMemoryActions(updateActions, 'execute-task', 'M001/S01/T02');
233
+
234
+ active = getActiveMemories();
235
+ assertEq(active.find(m => m.id === 'MEM001')?.content, 'updated gotcha', 'MEM001 should be updated');
236
+ assertEq(active.find(m => m.id === 'MEM002')?.hit_count, 1, 'MEM002 should be reinforced');
237
+
238
+ // SUPERSEDE
239
+ const supersedeActions: MemoryAction[] = [
240
+ { action: 'CREATE', category: 'gotcha', content: 'better gotcha', confidence: 0.95 },
241
+ { action: 'SUPERSEDE', id: 'MEM001', superseded_by: 'MEM003' },
242
+ ];
243
+
244
+ applyMemoryActions(supersedeActions, 'execute-task', 'M001/S01/T03');
245
+
246
+ active = getActiveMemories();
247
+ assertEq(active.length, 2, 'should have 2 active after supersede');
248
+ assertTrue(!active.find(m => m.id === 'MEM001'), 'MEM001 should be superseded');
249
+ assertTrue(!!active.find(m => m.id === 'MEM003'), 'MEM003 should be active');
250
+
251
+ closeDatabase();
252
+ }
253
+
254
+ // ═══════════════════════════════════════════════════════════════════════════
255
+ // memory-store: formatMemoriesForPrompt
256
+ // ═══════════════════════════════════════════════════════════════════════════
257
+
258
+ console.log('\n=== memory-store: formatMemoriesForPrompt ===');
259
+ {
260
+ openDatabase(':memory:');
261
+
262
+ createMemory({ category: 'gotcha', content: 'esbuild drops .node binaries' });
263
+ createMemory({ category: 'convention', content: 'use :memory: for tests' });
264
+ createMemory({ category: 'architecture', content: 'extensions in src/resources/' });
265
+ createMemory({ category: 'gotcha', content: 'TypeScript path aliases need .js' });
266
+
267
+ const memories = getActiveMemoriesRanked(30);
268
+ const formatted = formatMemoriesForPrompt(memories);
269
+
270
+ assertTrue(formatted.includes('## Project Memory (auto-learned)'), 'should have header');
271
+ assertTrue(formatted.includes('### Gotcha'), 'should have gotcha category');
272
+ assertTrue(formatted.includes('### Convention'), 'should have convention category');
273
+ assertTrue(formatted.includes('### Architecture'), 'should have architecture category');
274
+ assertTrue(formatted.includes('- esbuild drops .node binaries'), 'should have gotcha content');
275
+ assertTrue(formatted.includes('- use :memory: for tests'), 'should have convention content');
276
+
277
+ // Test empty memories
278
+ closeDatabase();
279
+ openDatabase(':memory:');
280
+ const emptyFormatted = formatMemoriesForPrompt([]);
281
+ assertEq(emptyFormatted, '', 'empty memories should return empty string');
282
+
283
+ // Test token budget truncation
284
+ closeDatabase();
285
+ openDatabase(':memory:');
286
+ for (let i = 0; i < 20; i++) {
287
+ createMemory({ category: 'pattern', content: `A very long memory entry that takes up space #${i}: ${'x'.repeat(200)}` });
288
+ }
289
+ const budgetMemories = getActiveMemoriesRanked(30);
290
+ const truncated = formatMemoriesForPrompt(budgetMemories, 500);
291
+ assertTrue(truncated.length < 2500, `formatted length ${truncated.length} should be under budget`);
292
+
293
+ closeDatabase();
294
+ }
295
+
296
+ // ═══════════════════════════════════════════════════════════════════════════
297
+ // memory-store: ID generation
298
+ // ═══════════════════════════════════════════════════════════════════════════
299
+
300
+ console.log('\n=== memory-store: ID generation ===');
301
+ {
302
+ openDatabase(':memory:');
303
+
304
+ assertEq(nextMemoryId(), 'MEM001', 'first ID should be MEM001');
305
+
306
+ createMemory({ category: 'test', content: 'test' });
307
+ assertEq(nextMemoryId(), 'MEM002', 'after first create, next should be MEM002');
308
+
309
+ // Create several more
310
+ for (let i = 0; i < 98; i++) createMemory({ category: 'test', content: `test ${i}` });
311
+ assertEq(nextMemoryId(), 'MEM100', 'after 99 creates, next should be MEM100');
312
+
313
+ closeDatabase();
314
+ }
315
+
316
+ // ═══════════════════════════════════════════════════════════════════════════
317
+ // memory-store: schema migration (v2 → v3)
318
+ // ═══════════════════════════════════════════════════════════════════════════
319
+
320
+ console.log('\n=== memory-store: schema includes memories table ===');
321
+ {
322
+ openDatabase(':memory:');
323
+
324
+ const adapter = _getAdapter()!;
325
+
326
+ // Verify memories table exists
327
+ const memCount = adapter.prepare('SELECT count(*) as cnt FROM memories').get();
328
+ assertEq(memCount?.['cnt'], 0, 'memories table should exist and be empty');
329
+
330
+ // Verify memory_processed_units table exists
331
+ const procCount = adapter.prepare('SELECT count(*) as cnt FROM memory_processed_units').get();
332
+ assertEq(procCount?.['cnt'], 0, 'memory_processed_units table should exist and be empty');
333
+
334
+ // Verify active_memories view exists
335
+ const viewCount = adapter.prepare('SELECT count(*) as cnt FROM active_memories').get();
336
+ assertEq(viewCount?.['cnt'], 0, 'active_memories view should exist');
337
+
338
+ // Verify schema version is 3
339
+ const version = adapter.prepare('SELECT MAX(version) as v FROM schema_version').get();
340
+ assertEq(version?.['v'], 3, 'schema version should be 3');
341
+
342
+ closeDatabase();
343
+ }
344
+
345
+ report();
@@ -81,7 +81,7 @@ assert(
81
81
 
82
82
  // Check the branch has draft-aware menu options
83
83
  const branchIdx = guidedFlowSource.indexOf('state.phase === "needs-discussion"');
84
- const branchChunk = guidedFlowSource.slice(branchIdx, branchIdx + 3000);
84
+ const branchChunk = guidedFlowSource.slice(branchIdx, branchIdx + 4000);
85
85
 
86
86
  assert(
87
87
  branchChunk.includes("discuss_draft"),
@@ -56,6 +56,51 @@ assertTrue(
56
56
  "exports ChangelogInfo interface",
57
57
  );
58
58
 
59
+ assertTrue(
60
+ dataSrc.includes("export interface SliceVerification"),
61
+ "exports SliceVerification interface",
62
+ );
63
+
64
+ assertTrue(
65
+ dataSrc.includes("export interface KnowledgeInfo"),
66
+ "exports KnowledgeInfo interface",
67
+ );
68
+
69
+ assertTrue(
70
+ dataSrc.includes("export interface CapturesInfo"),
71
+ "exports CapturesInfo interface",
72
+ );
73
+
74
+ assertTrue(
75
+ dataSrc.includes("export interface HealthInfo"),
76
+ "exports HealthInfo interface",
77
+ );
78
+
79
+ assertTrue(
80
+ dataSrc.includes("export interface VisualizerDiscussionState"),
81
+ "exports VisualizerDiscussionState interface",
82
+ );
83
+
84
+ assertTrue(
85
+ dataSrc.includes("export type DiscussionState"),
86
+ "exports DiscussionState type",
87
+ );
88
+
89
+ assertTrue(
90
+ dataSrc.includes("export interface VisualizerSliceRef"),
91
+ "exports VisualizerSliceRef interface",
92
+ );
93
+
94
+ assertTrue(
95
+ dataSrc.includes("export interface VisualizerSliceActivity"),
96
+ "exports VisualizerSliceActivity interface",
97
+ );
98
+
99
+ assertTrue(
100
+ dataSrc.includes("export interface VisualizerStats"),
101
+ "exports VisualizerStats interface",
102
+ );
103
+
59
104
  // Function export
60
105
  assertTrue(
61
106
  dataSrc.includes("export async function loadVisualizerData"),
@@ -123,6 +168,36 @@ assertTrue(
123
168
  "uses aggregateByModel",
124
169
  );
125
170
 
171
+ assertTrue(
172
+ dataSrc.includes("aggregateByTier"),
173
+ "uses aggregateByTier",
174
+ );
175
+
176
+ assertTrue(
177
+ dataSrc.includes("formatTierSavings"),
178
+ "uses formatTierSavings",
179
+ );
180
+
181
+ assertTrue(
182
+ dataSrc.includes("loadAllCaptures"),
183
+ "uses loadAllCaptures",
184
+ );
185
+
186
+ assertTrue(
187
+ dataSrc.includes("countPendingCaptures"),
188
+ "uses countPendingCaptures",
189
+ );
190
+
191
+ assertTrue(
192
+ dataSrc.includes("loadEffectiveGSDPreferences"),
193
+ "uses loadEffectiveGSDPreferences",
194
+ );
195
+
196
+ assertTrue(
197
+ dataSrc.includes("resolveGsdRootFile"),
198
+ "uses resolveGsdRootFile for KNOWLEDGE path",
199
+ );
200
+
126
201
  // Interface fields
127
202
  assertTrue(
128
203
  dataSrc.includes("dependsOn: string[]"),
@@ -144,6 +219,11 @@ assertTrue(
144
219
  "VisualizerData has units array",
145
220
  );
146
221
 
222
+ assertTrue(
223
+ dataSrc.includes("estimate?: string"),
224
+ "VisualizerTask has optional estimate field",
225
+ );
226
+
147
227
  // New data model fields
148
228
  assertTrue(
149
229
  dataSrc.includes("criticalPath: CriticalPathInfo"),
@@ -165,6 +245,56 @@ assertTrue(
165
245
  "VisualizerData has changelog field",
166
246
  );
167
247
 
248
+ assertTrue(
249
+ dataSrc.includes("sliceVerifications: SliceVerification[]"),
250
+ "VisualizerData has sliceVerifications field",
251
+ );
252
+
253
+ assertTrue(
254
+ dataSrc.includes("knowledge: KnowledgeInfo"),
255
+ "VisualizerData has knowledge field",
256
+ );
257
+
258
+ assertTrue(
259
+ dataSrc.includes("captures: CapturesInfo"),
260
+ "VisualizerData has captures field",
261
+ );
262
+
263
+ assertTrue(
264
+ dataSrc.includes("health: HealthInfo"),
265
+ "VisualizerData has health field",
266
+ );
267
+
268
+ assertTrue(
269
+ dataSrc.includes("stats: VisualizerStats"),
270
+ "VisualizerData has stats field",
271
+ );
272
+
273
+ assertTrue(
274
+ dataSrc.includes("discussion: VisualizerDiscussionState[]"),
275
+ "VisualizerData has discussion field",
276
+ );
277
+
278
+ assertTrue(
279
+ dataSrc.includes("loadDiscussionState"),
280
+ "uses loadDiscussionState helper",
281
+ );
282
+
283
+ assertTrue(
284
+ dataSrc.includes("buildVisualizerStats"),
285
+ "uses buildVisualizerStats helper",
286
+ );
287
+
288
+ assertTrue(
289
+ dataSrc.includes("byTier: TierAggregate[]"),
290
+ "VisualizerData has byTier field",
291
+ );
292
+
293
+ assertTrue(
294
+ dataSrc.includes("tierSavingsLine: string"),
295
+ "VisualizerData has tierSavingsLine field",
296
+ );
297
+
168
298
  // completedAt must be coerced to String() to handle YAML Date objects (issue #644)
169
299
  assertTrue(
170
300
  dataSrc.includes("String(summary.frontmatter.completed_at"),
@@ -227,6 +357,21 @@ assertTrue(
227
357
  "overlay delegates to renderExportView",
228
358
  );
229
359
 
360
+ assertTrue(
361
+ overlaySrc.includes("renderKnowledgeView"),
362
+ "overlay delegates to renderKnowledgeView",
363
+ );
364
+
365
+ assertTrue(
366
+ overlaySrc.includes("renderCapturesView"),
367
+ "overlay delegates to renderCapturesView",
368
+ );
369
+
370
+ assertTrue(
371
+ overlaySrc.includes("renderHealthView"),
372
+ "overlay delegates to renderHealthView",
373
+ );
374
+
230
375
  assertTrue(
231
376
  overlaySrc.includes("handleInput"),
232
377
  "overlay has handleInput method",
@@ -273,8 +418,8 @@ assertTrue(
273
418
  );
274
419
 
275
420
  assertTrue(
276
- overlaySrc.includes("7 Export"),
277
- "overlay has 7 tab labels",
421
+ overlaySrc.includes("0 Health"),
422
+ "overlay has 10 tab labels",
278
423
  );
279
424
 
280
425
  // Verify commands.ts integration
@@ -1,5 +1,5 @@
1
1
  // Tests for GSD visualizer overlay.
2
- // Verifies filter mode, tab switching, including reverse tab navigation, and export key handling.
2
+ // Verifies filter mode, tab switching, mouse support, page scroll, help overlay, and 10-tab config.
3
3
 
4
4
  import { readFileSync } from "node:fs";
5
5
  import { join, dirname } from "node:path";
@@ -14,8 +14,8 @@ const overlaySrc = readFileSync(join(__dirname, "..", "visualizer-overlay.ts"),
14
14
  console.log("\n=== Overlay: Tab Configuration ===");
15
15
 
16
16
  assertTrue(
17
- overlaySrc.includes("TAB_COUNT = 7"),
18
- "TAB_COUNT is 7",
17
+ overlaySrc.includes("TAB_COUNT = 10"),
18
+ "TAB_COUNT is 10",
19
19
  );
20
20
 
21
21
  assertTrue(
@@ -38,6 +38,21 @@ assertTrue(
38
38
  "has Export tab label",
39
39
  );
40
40
 
41
+ assertTrue(
42
+ overlaySrc.includes('"8 Knowledge"'),
43
+ "has Knowledge tab label",
44
+ );
45
+
46
+ assertTrue(
47
+ overlaySrc.includes('"9 Captures"'),
48
+ "has Captures tab label",
49
+ );
50
+
51
+ assertTrue(
52
+ overlaySrc.includes('"0 Health"'),
53
+ "has Health tab label",
54
+ );
55
+
41
56
  console.log("\n=== Overlay: Filter Mode ===");
42
57
 
43
58
  assertTrue(
@@ -69,10 +84,10 @@ assertTrue(
69
84
 
70
85
  console.log("\n=== Overlay: Tab Switching ===");
71
86
 
72
- // Supports 1-7 keys
87
+ // Supports 1-9,0 keys
73
88
  assertTrue(
74
- overlaySrc.includes('"1234567"'),
75
- "supports keys 1-7 for tab switching",
89
+ overlaySrc.includes('"1234567890"'),
90
+ "supports keys 1-9,0 for tab switching",
76
91
  );
77
92
 
78
93
  // Tab wraps with TAB_COUNT
@@ -86,6 +101,64 @@ assertTrue(
86
101
  "supports Shift+Tab for reverse tab switching",
87
102
  );
88
103
 
104
+ console.log("\n=== Overlay: Page/Half-Page Scroll ===");
105
+
106
+ assertTrue(
107
+ overlaySrc.includes("Key.pageUp"),
108
+ "has Key.pageUp handler",
109
+ );
110
+
111
+ assertTrue(
112
+ overlaySrc.includes("Key.pageDown"),
113
+ "has Key.pageDown handler",
114
+ );
115
+
116
+ assertTrue(
117
+ overlaySrc.includes('Key.ctrl("u")'),
118
+ "has Ctrl+U half-page scroll",
119
+ );
120
+
121
+ assertTrue(
122
+ overlaySrc.includes('Key.ctrl("d")'),
123
+ "has Ctrl+D half-page scroll",
124
+ );
125
+
126
+ console.log("\n=== Overlay: Mouse Support ===");
127
+
128
+ assertTrue(
129
+ overlaySrc.includes("parseSGRMouse"),
130
+ "has parseSGRMouse method",
131
+ );
132
+
133
+ assertTrue(
134
+ overlaySrc.includes("?1003h"),
135
+ "enables mouse tracking in constructor",
136
+ );
137
+
138
+ assertTrue(
139
+ overlaySrc.includes("?1003l"),
140
+ "disables mouse tracking in dispose",
141
+ );
142
+
143
+ console.log("\n=== Overlay: Collapsible Milestones ===");
144
+
145
+ assertTrue(
146
+ overlaySrc.includes("collapsedMilestones"),
147
+ "has collapsedMilestones state",
148
+ );
149
+
150
+ console.log("\n=== Overlay: Help Overlay ===");
151
+
152
+ assertTrue(
153
+ overlaySrc.includes("showHelp"),
154
+ "has showHelp state",
155
+ );
156
+
157
+ assertTrue(
158
+ overlaySrc.includes('data === "?"'),
159
+ "? key toggles help",
160
+ );
161
+
89
162
  console.log("\n=== Overlay: Export Key Interception ===");
90
163
 
91
164
  assertTrue(
@@ -106,13 +179,18 @@ assertTrue(
106
179
  console.log("\n=== Overlay: Footer ===");
107
180
 
108
181
  assertTrue(
109
- overlaySrc.includes("Tab/Shift+Tab/1-7"),
110
- "footer hint shows Tab, Shift+Tab, and 1-7 tab range",
182
+ overlaySrc.includes("1-9,0"),
183
+ "footer hint shows 1-9,0 tab range",
184
+ );
185
+
186
+ assertTrue(
187
+ overlaySrc.includes("PgUp/PgDn"),
188
+ "footer hint mentions PgUp/PgDn",
111
189
  );
112
190
 
113
191
  assertTrue(
114
- overlaySrc.includes("/ filter"),
115
- "footer hint mentions filter",
192
+ overlaySrc.includes("? help"),
193
+ "footer hint mentions ? for help",
116
194
  );
117
195
 
118
196
  console.log("\n=== Overlay: Scroll Offsets ===");