gsd-pi 2.24.0 → 2.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -3
- package/dist/headless.js +24 -4
- 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/async-jobs/index.ts +9 -1
- package/dist/resources/extensions/bg-shell/index.ts +3 -2
- 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-recovery.ts +7 -4
- package/dist/resources/extensions/gsd/auto-worktree.ts +132 -3
- package/dist/resources/extensions/gsd/auto.ts +265 -48
- package/dist/resources/extensions/gsd/cache.ts +3 -1
- package/dist/resources/extensions/gsd/doctor-proactive.ts +7 -6
- package/dist/resources/extensions/gsd/doctor.ts +26 -1
- package/dist/resources/extensions/gsd/files.ts +13 -2
- 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 +54 -22
- package/dist/resources/extensions/gsd/index.ts +62 -8
- 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/migrate/writer.ts +39 -0
- package/dist/resources/extensions/gsd/parallel-orchestrator.ts +122 -4
- package/dist/resources/extensions/gsd/preferences.ts +2 -1
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +4 -4
- package/dist/resources/extensions/gsd/prompts/discuss.md +5 -5
- 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 +3 -3
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/dist/resources/extensions/gsd/roadmap-slices.ts +45 -1
- package/dist/resources/extensions/gsd/state.ts +17 -6
- 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/derive-state.test.ts +70 -0
- package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +23 -3
- 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/migrate-writer-integration.test.ts +13 -7
- package/dist/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +171 -0
- package/dist/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +8 -4
- 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/types.ts +2 -0
- 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 +19 -5
- package/dist/resources/extensions/shared/path-display.ts +19 -0
- package/package.json +1 -6
- 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 +64 -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 +65 -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/core/agent-session.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +32 -0
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.js +1 -1
- package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js +12 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/index.js +7 -0
- package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.d.ts +2 -2
- package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +8 -3
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +8 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
- package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +2 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +2 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +5 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts +41 -3
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +301 -62
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -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 +5 -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 +135 -30
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/tests/path-display.test.d.ts +8 -0
- package/packages/pi-coding-agent/dist/tests/path-display.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/tests/path-display.test.js +60 -0
- package/packages/pi-coding-agent/dist/tests/path-display.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/utils/clipboard-image.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/utils/clipboard-image.js +32 -6
- package/packages/pi-coding-agent/dist/utils/clipboard-image.js.map +1 -1
- package/packages/pi-coding-agent/dist/utils/path-display.d.ts +34 -0
- package/packages/pi-coding-agent/dist/utils/path-display.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/utils/path-display.js +36 -0
- package/packages/pi-coding-agent/dist/utils/path-display.js.map +1 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +36 -0
- package/packages/pi-coding-agent/src/core/keybindings.ts +1 -1
- package/packages/pi-coding-agent/src/core/lsp/client.ts +11 -1
- package/packages/pi-coding-agent/src/core/lsp/index.ts +7 -0
- package/packages/pi-coding-agent/src/core/sdk.ts +17 -1
- package/packages/pi-coding-agent/src/core/settings-manager.ts +11 -0
- package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
- package/packages/pi-coding-agent/src/core/system-prompt.ts +2 -1
- package/packages/pi-coding-agent/src/index.ts +15 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +347 -62
- 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 +124 -4
- package/packages/pi-coding-agent/src/tests/path-display.test.ts +85 -0
- package/packages/pi-coding-agent/src/utils/clipboard-image.ts +33 -6
- package/packages/pi-coding-agent/src/utils/path-display.ts +36 -0
- package/src/resources/GSD-WORKFLOW.md +12 -9
- package/src/resources/extensions/async-jobs/index.ts +9 -1
- package/src/resources/extensions/bg-shell/index.ts +3 -2
- 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-recovery.ts +7 -4
- package/src/resources/extensions/gsd/auto-worktree.ts +132 -3
- package/src/resources/extensions/gsd/auto.ts +265 -48
- package/src/resources/extensions/gsd/cache.ts +3 -1
- package/src/resources/extensions/gsd/doctor-proactive.ts +7 -6
- package/src/resources/extensions/gsd/doctor.ts +26 -1
- package/src/resources/extensions/gsd/files.ts +13 -2
- 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 +54 -22
- package/src/resources/extensions/gsd/index.ts +62 -8
- 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/migrate/writer.ts +39 -0
- package/src/resources/extensions/gsd/parallel-orchestrator.ts +122 -4
- package/src/resources/extensions/gsd/preferences.ts +2 -1
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +4 -4
- package/src/resources/extensions/gsd/prompts/discuss.md +5 -5
- 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 +3 -3
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/src/resources/extensions/gsd/roadmap-slices.ts +45 -1
- package/src/resources/extensions/gsd/state.ts +17 -6
- 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/derive-state.test.ts +70 -0
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +23 -3
- 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/migrate-writer-integration.test.ts +13 -7
- package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +171 -0
- package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +8 -4
- 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/types.ts +2 -0
- 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 +19 -5
- package/src/resources/extensions/shared/path-display.ts +19 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { createTestContext } from './test-helpers.ts';
|
|
2
|
+
import { parseMemoryResponse, _resetExtractionState } from '../memory-extractor.ts';
|
|
3
|
+
import {
|
|
4
|
+
openDatabase,
|
|
5
|
+
closeDatabase,
|
|
6
|
+
} from '../gsd-db.ts';
|
|
7
|
+
import {
|
|
8
|
+
getActiveMemories,
|
|
9
|
+
applyMemoryActions,
|
|
10
|
+
getActiveMemoriesRanked,
|
|
11
|
+
} from '../memory-store.ts';
|
|
12
|
+
import type { MemoryAction } from '../memory-store.ts';
|
|
13
|
+
|
|
14
|
+
const { assertEq, assertTrue, report } = createTestContext();
|
|
15
|
+
|
|
16
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
17
|
+
// memory-extractor: parse valid JSON response
|
|
18
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
19
|
+
|
|
20
|
+
console.log('\n=== memory-extractor: parse valid JSON ===');
|
|
21
|
+
{
|
|
22
|
+
const response = JSON.stringify([
|
|
23
|
+
{ action: 'CREATE', category: 'gotcha', content: 'esbuild drops binaries', confidence: 0.85 },
|
|
24
|
+
{ action: 'REINFORCE', id: 'MEM001' },
|
|
25
|
+
{ action: 'UPDATE', id: 'MEM002', content: 'revised content' },
|
|
26
|
+
{ action: 'SUPERSEDE', id: 'MEM003', superseded_by: 'MEM004' },
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
const actions = parseMemoryResponse(response);
|
|
30
|
+
assertEq(actions.length, 4, 'should parse 4 actions');
|
|
31
|
+
assertEq(actions[0].action, 'CREATE', 'first action should be CREATE');
|
|
32
|
+
assertEq((actions[0] as any).category, 'gotcha', 'CREATE category');
|
|
33
|
+
assertEq((actions[0] as any).confidence, 0.85, 'CREATE confidence');
|
|
34
|
+
assertEq(actions[1].action, 'REINFORCE', 'second action should be REINFORCE');
|
|
35
|
+
assertEq(actions[2].action, 'UPDATE', 'third action should be UPDATE');
|
|
36
|
+
assertEq(actions[3].action, 'SUPERSEDE', 'fourth action should be SUPERSEDE');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
40
|
+
// memory-extractor: parse fenced JSON response
|
|
41
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
42
|
+
|
|
43
|
+
console.log('\n=== memory-extractor: parse fenced JSON ===');
|
|
44
|
+
{
|
|
45
|
+
const response = '```json\n[\n {"action": "CREATE", "category": "convention", "content": "test memory"}\n]\n```';
|
|
46
|
+
|
|
47
|
+
const actions = parseMemoryResponse(response);
|
|
48
|
+
assertEq(actions.length, 1, 'should parse 1 action from fenced JSON');
|
|
49
|
+
assertEq(actions[0].action, 'CREATE', 'action should be CREATE');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
53
|
+
// memory-extractor: parse empty array response
|
|
54
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
55
|
+
|
|
56
|
+
console.log('\n=== memory-extractor: parse empty array ===');
|
|
57
|
+
{
|
|
58
|
+
const actions = parseMemoryResponse('[]');
|
|
59
|
+
assertEq(actions.length, 0, 'empty array should parse to empty actions');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
63
|
+
// memory-extractor: parse malformed response
|
|
64
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
65
|
+
|
|
66
|
+
console.log('\n=== memory-extractor: malformed responses ===');
|
|
67
|
+
{
|
|
68
|
+
assertEq(parseMemoryResponse('not json at all'), [], 'garbage text should return []');
|
|
69
|
+
assertEq(parseMemoryResponse('{"action": "CREATE"}'), [], 'non-array should return []');
|
|
70
|
+
assertEq(parseMemoryResponse(''), [], 'empty string should return []');
|
|
71
|
+
assertEq(parseMemoryResponse('```\nbroken\n```'), [], 'fenced non-JSON should return []');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
75
|
+
// memory-extractor: validation of required fields
|
|
76
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
77
|
+
|
|
78
|
+
console.log('\n=== memory-extractor: field validation ===');
|
|
79
|
+
{
|
|
80
|
+
const response = JSON.stringify([
|
|
81
|
+
// Valid CREATE
|
|
82
|
+
{ action: 'CREATE', category: 'gotcha', content: 'valid' },
|
|
83
|
+
// Invalid CREATE — missing content
|
|
84
|
+
{ action: 'CREATE', category: 'gotcha' },
|
|
85
|
+
// Invalid CREATE — missing category
|
|
86
|
+
{ action: 'CREATE', content: 'no category' },
|
|
87
|
+
// Valid REINFORCE
|
|
88
|
+
{ action: 'REINFORCE', id: 'MEM001' },
|
|
89
|
+
// Invalid REINFORCE — missing id
|
|
90
|
+
{ action: 'REINFORCE' },
|
|
91
|
+
// Valid UPDATE
|
|
92
|
+
{ action: 'UPDATE', id: 'MEM002', content: 'new content' },
|
|
93
|
+
// Invalid UPDATE — missing content
|
|
94
|
+
{ action: 'UPDATE', id: 'MEM002' },
|
|
95
|
+
// Valid SUPERSEDE
|
|
96
|
+
{ action: 'SUPERSEDE', id: 'MEM001', superseded_by: 'MEM002' },
|
|
97
|
+
// Invalid SUPERSEDE — missing superseded_by
|
|
98
|
+
{ action: 'SUPERSEDE', id: 'MEM001' },
|
|
99
|
+
// Unknown action
|
|
100
|
+
{ action: 'DELETE', id: 'MEM001' },
|
|
101
|
+
// Null entry
|
|
102
|
+
null,
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
const actions = parseMemoryResponse(response);
|
|
106
|
+
assertEq(actions.length, 4, 'should only accept 4 valid actions');
|
|
107
|
+
assertEq(actions[0].action, 'CREATE', 'first valid is CREATE');
|
|
108
|
+
assertEq(actions[1].action, 'REINFORCE', 'second valid is REINFORCE');
|
|
109
|
+
assertEq(actions[2].action, 'UPDATE', 'third valid is UPDATE');
|
|
110
|
+
assertEq(actions[3].action, 'SUPERSEDE', 'fourth valid is SUPERSEDE');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
114
|
+
// Integration: applyMemoryActions with mixed actions
|
|
115
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
116
|
+
|
|
117
|
+
console.log('\n=== integration: mixed action lifecycle ===');
|
|
118
|
+
{
|
|
119
|
+
openDatabase(':memory:');
|
|
120
|
+
|
|
121
|
+
// Phase 1: Create initial memories
|
|
122
|
+
applyMemoryActions([
|
|
123
|
+
{ action: 'CREATE', category: 'gotcha', content: 'npm run build needs tsc first', confidence: 0.7 },
|
|
124
|
+
{ action: 'CREATE', category: 'convention', content: 'all DB queries use named params', confidence: 0.8 },
|
|
125
|
+
{ action: 'CREATE', category: 'architecture', content: 'extensions loaded from two paths', confidence: 0.85 },
|
|
126
|
+
], 'plan-slice', 'M001/S01');
|
|
127
|
+
|
|
128
|
+
let active = getActiveMemoriesRanked(30);
|
|
129
|
+
assertEq(active.length, 3, 'phase 1: 3 active memories');
|
|
130
|
+
|
|
131
|
+
// Phase 2: Reinforce one, update another, create new
|
|
132
|
+
applyMemoryActions([
|
|
133
|
+
{ action: 'REINFORCE', id: 'MEM002' },
|
|
134
|
+
{ action: 'UPDATE', id: 'MEM001', content: 'npm run build requires tsc --noEmit first' },
|
|
135
|
+
{ action: 'CREATE', category: 'pattern', content: 'use INSERT OR IGNORE for idempotency', confidence: 0.75 },
|
|
136
|
+
], 'execute-task', 'M001/S01/T01');
|
|
137
|
+
|
|
138
|
+
active = getActiveMemoriesRanked(30);
|
|
139
|
+
assertEq(active.length, 4, 'phase 2: 4 active memories');
|
|
140
|
+
assertEq(
|
|
141
|
+
active.find(m => m.id === 'MEM001')?.content,
|
|
142
|
+
'npm run build requires tsc --noEmit first',
|
|
143
|
+
'MEM001 content should be updated',
|
|
144
|
+
);
|
|
145
|
+
assertEq(active.find(m => m.id === 'MEM002')?.hit_count, 1, 'MEM002 should be reinforced');
|
|
146
|
+
|
|
147
|
+
// Phase 3: Supersede MEM001 with MEM005
|
|
148
|
+
applyMemoryActions([
|
|
149
|
+
{ action: 'CREATE', category: 'gotcha', content: 'build script handles tsc automatically now', confidence: 0.9 },
|
|
150
|
+
{ action: 'SUPERSEDE', id: 'MEM001', superseded_by: 'MEM005' },
|
|
151
|
+
], 'execute-task', 'M001/S01/T02');
|
|
152
|
+
|
|
153
|
+
active = getActiveMemoriesRanked(30);
|
|
154
|
+
assertEq(active.length, 4, 'phase 3: 4 active (1 superseded, 1 created)');
|
|
155
|
+
assertTrue(!active.find(m => m.id === 'MEM001'), 'MEM001 should be superseded');
|
|
156
|
+
assertTrue(!!active.find(m => m.id === 'MEM005'), 'MEM005 should be active');
|
|
157
|
+
|
|
158
|
+
// Verify ranking: MEM003 (0.85) > MEM005 (0.9) but MEM002 has 1 hit
|
|
159
|
+
// MEM002: 0.8 * (1 + 1*0.1) = 0.88
|
|
160
|
+
// MEM003: 0.85 * 1.0 = 0.85
|
|
161
|
+
// MEM005: 0.9 * 1.0 = 0.9
|
|
162
|
+
// MEM004: 0.75 * 1.0 = 0.75
|
|
163
|
+
assertEq(active[0].id, 'MEM005', 'MEM005 should rank first (0.9)');
|
|
164
|
+
assertEq(active[1].id, 'MEM002', 'MEM002 should rank second (0.88)');
|
|
165
|
+
|
|
166
|
+
closeDatabase();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
170
|
+
// memory-extractor: _resetExtractionState
|
|
171
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
172
|
+
|
|
173
|
+
console.log('\n=== memory-extractor: reset extraction state ===');
|
|
174
|
+
{
|
|
175
|
+
// Just verify it doesn't throw
|
|
176
|
+
_resetExtractionState();
|
|
177
|
+
assertTrue(true, '_resetExtractionState should not throw');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
report();
|
|
@@ -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();
|
|
@@ -11,6 +11,7 @@ import { writeGSDDirectory } from '../migrate/writer.ts';
|
|
|
11
11
|
import { generatePreview } from '../migrate/preview.ts';
|
|
12
12
|
import { parseRoadmap, parsePlan, parseSummary } from '../files.ts';
|
|
13
13
|
import { deriveState } from '../state.ts';
|
|
14
|
+
import { invalidateAllCaches } from '../cache.ts';
|
|
14
15
|
import type {
|
|
15
16
|
GSDProject,
|
|
16
17
|
GSDMilestone,
|
|
@@ -207,6 +208,7 @@ async function main(): Promise<void> {
|
|
|
207
208
|
|
|
208
209
|
// (e) deriveState
|
|
209
210
|
console.log(' --- deriveState ---');
|
|
211
|
+
invalidateAllCaches();
|
|
210
212
|
const state = await deriveState(base);
|
|
211
213
|
assertEq(state.phase, 'executing', 'incomplete: deriveState phase is executing');
|
|
212
214
|
assertTrue(state.activeMilestone !== null, 'incomplete: deriveState has activeMilestone');
|
|
@@ -262,14 +264,18 @@ async function main(): Promise<void> {
|
|
|
262
264
|
assertTrue(!existsSync(join(m, 'M001-RESEARCH.md')), 'complete: M001-RESEARCH.md NOT written (null)');
|
|
263
265
|
// No REQUIREMENTS.md since empty requirements
|
|
264
266
|
assertTrue(!existsSync(join(base, '.gsd', 'REQUIREMENTS.md')), 'complete: REQUIREMENTS.md NOT written (empty)');
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
267
|
+
// Completed milestone should have VALIDATION and SUMMARY from migration (#819)
|
|
268
|
+
assertTrue(existsSync(join(m, 'M001-VALIDATION.md')), 'complete: M001-VALIDATION.md written for completed milestone');
|
|
269
|
+
assertTrue(existsSync(join(m, 'M001-SUMMARY.md')), 'complete: M001-SUMMARY.md written for completed milestone');
|
|
270
|
+
|
|
271
|
+
// deriveState: all slices done, all tasks done — migration now writes
|
|
272
|
+
// VALIDATION.md and SUMMARY.md for completed milestones (#819),
|
|
273
|
+
// so the milestone should be fully complete.
|
|
274
|
+
invalidateAllCaches();
|
|
268
275
|
const state = await deriveState(base);
|
|
269
|
-
|
|
270
|
-
//
|
|
271
|
-
|
|
272
|
-
assertTrue(state.activeMilestone !== null, 'complete: deriveState has activeMilestone');
|
|
276
|
+
assertEq(state.phase, 'complete', 'complete: deriveState phase is complete (validation + summary written by migration)');
|
|
277
|
+
// When all milestones are complete, activeMilestone points to the last entry (for display)
|
|
278
|
+
assertTrue(state.activeMilestone !== null, 'complete: deriveState has activeMilestone (last entry)');
|
|
273
279
|
assertEq(state.activeMilestone!.id, 'M001', 'complete: deriveState activeMilestone is M001');
|
|
274
280
|
|
|
275
281
|
// generatePreview for complete project
|