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.
- package/README.md +2 -1
- package/dist/models-resolver.d.ts +0 -11
- package/dist/models-resolver.js +0 -15
- package/dist/resource-loader.d.ts +0 -1
- package/dist/resource-loader.js +0 -9
- package/dist/resources/GSD-WORKFLOW.md +12 -9
- package/dist/resources/extensions/bg-shell/overlay.ts +18 -17
- package/dist/resources/extensions/get-secrets-from-user.ts +5 -23
- package/dist/resources/extensions/gsd/activity-log.ts +5 -3
- package/dist/resources/extensions/gsd/auto-prompts.ts +14 -0
- package/dist/resources/extensions/gsd/auto-worktree.ts +119 -1
- package/dist/resources/extensions/gsd/auto.ts +184 -36
- package/dist/resources/extensions/gsd/cache.ts +3 -1
- package/dist/resources/extensions/gsd/doctor.ts +2 -0
- package/dist/resources/extensions/gsd/git-service.ts +74 -14
- package/dist/resources/extensions/gsd/gsd-db.ts +78 -1
- package/dist/resources/extensions/gsd/guided-flow.ts +34 -12
- package/dist/resources/extensions/gsd/index.ts +14 -1
- package/dist/resources/extensions/gsd/memory-extractor.ts +352 -0
- package/dist/resources/extensions/gsd/memory-store.ts +441 -0
- package/dist/resources/extensions/gsd/migrate/command.ts +2 -2
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +2 -2
- package/dist/resources/extensions/gsd/prompts/discuss.md +4 -4
- package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +1 -1
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
- package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
- package/dist/resources/extensions/gsd/tests/git-service.test.ts +70 -4
- package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
- package/dist/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
- package/dist/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
- package/dist/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
- package/dist/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
- package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
- package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
- package/dist/resources/extensions/gsd/triage-ui.ts +1 -1
- package/dist/resources/extensions/gsd/visualizer-data.ts +291 -10
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +237 -28
- package/dist/resources/extensions/gsd/visualizer-views.ts +462 -48
- package/dist/resources/extensions/gsd/worktree.ts +9 -2
- package/dist/resources/extensions/search-the-web/native-search.ts +15 -5
- package/package.json +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +2 -0
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.ts +2 -0
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +39 -0
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/mistral.js +3 -0
- package/packages/pi-ai/dist/providers/mistral.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +23 -1
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/src/providers/anthropic.ts +38 -1
- package/packages/pi-ai/src/providers/mistral.ts +3 -0
- package/packages/pi-ai/src/types.ts +19 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +72 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +84 -0
- package/src/resources/GSD-WORKFLOW.md +12 -9
- package/src/resources/extensions/bg-shell/overlay.ts +18 -17
- package/src/resources/extensions/get-secrets-from-user.ts +5 -23
- package/src/resources/extensions/gsd/activity-log.ts +5 -3
- package/src/resources/extensions/gsd/auto-prompts.ts +14 -0
- package/src/resources/extensions/gsd/auto-worktree.ts +119 -1
- package/src/resources/extensions/gsd/auto.ts +184 -36
- package/src/resources/extensions/gsd/cache.ts +3 -1
- package/src/resources/extensions/gsd/doctor.ts +2 -0
- package/src/resources/extensions/gsd/git-service.ts +74 -14
- package/src/resources/extensions/gsd/gsd-db.ts +78 -1
- package/src/resources/extensions/gsd/guided-flow.ts +34 -12
- package/src/resources/extensions/gsd/index.ts +14 -1
- package/src/resources/extensions/gsd/memory-extractor.ts +352 -0
- package/src/resources/extensions/gsd/memory-store.ts +441 -0
- package/src/resources/extensions/gsd/migrate/command.ts +2 -2
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +2 -2
- package/src/resources/extensions/gsd/prompts/discuss.md +4 -4
- package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/queue.md +1 -1
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
- package/src/resources/extensions/gsd/tests/git-service.test.ts +70 -4
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
- package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
- package/src/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
- package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
- package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
- package/src/resources/extensions/gsd/triage-ui.ts +1 -1
- package/src/resources/extensions/gsd/visualizer-data.ts +291 -10
- package/src/resources/extensions/gsd/visualizer-overlay.ts +237 -28
- package/src/resources/extensions/gsd/visualizer-views.ts +462 -48
- package/src/resources/extensions/gsd/worktree.ts +9 -2
- 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 +
|
|
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("
|
|
277
|
-
"overlay has
|
|
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,
|
|
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 =
|
|
18
|
-
"TAB_COUNT is
|
|
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-
|
|
87
|
+
// Supports 1-9,0 keys
|
|
73
88
|
assertTrue(
|
|
74
|
-
overlaySrc.includes('"
|
|
75
|
-
"supports keys 1-
|
|
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("
|
|
110
|
-
"footer hint shows
|
|
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("
|
|
115
|
-
"footer hint mentions
|
|
192
|
+
overlaySrc.includes("? help"),
|
|
193
|
+
"footer hint mentions ? for help",
|
|
116
194
|
);
|
|
117
195
|
|
|
118
196
|
console.log("\n=== Overlay: Scroll Offsets ===");
|