gsd-pi 2.24.0 → 2.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/README.md +13 -3
  2. package/dist/headless.js +24 -4
  3. package/dist/models-resolver.d.ts +0 -11
  4. package/dist/models-resolver.js +0 -15
  5. package/dist/resource-loader.d.ts +0 -1
  6. package/dist/resource-loader.js +0 -9
  7. package/dist/resources/GSD-WORKFLOW.md +12 -9
  8. package/dist/resources/extensions/async-jobs/index.ts +9 -1
  9. package/dist/resources/extensions/bg-shell/index.ts +3 -2
  10. package/dist/resources/extensions/bg-shell/overlay.ts +18 -17
  11. package/dist/resources/extensions/get-secrets-from-user.ts +5 -23
  12. package/dist/resources/extensions/gsd/activity-log.ts +5 -3
  13. package/dist/resources/extensions/gsd/auto-prompts.ts +14 -0
  14. package/dist/resources/extensions/gsd/auto-recovery.ts +7 -4
  15. package/dist/resources/extensions/gsd/auto-worktree.ts +132 -3
  16. package/dist/resources/extensions/gsd/auto.ts +265 -48
  17. package/dist/resources/extensions/gsd/cache.ts +3 -1
  18. package/dist/resources/extensions/gsd/doctor-proactive.ts +7 -6
  19. package/dist/resources/extensions/gsd/doctor.ts +26 -1
  20. package/dist/resources/extensions/gsd/files.ts +13 -2
  21. package/dist/resources/extensions/gsd/git-service.ts +74 -14
  22. package/dist/resources/extensions/gsd/gsd-db.ts +78 -1
  23. package/dist/resources/extensions/gsd/guided-flow.ts +54 -22
  24. package/dist/resources/extensions/gsd/index.ts +62 -8
  25. package/dist/resources/extensions/gsd/memory-extractor.ts +352 -0
  26. package/dist/resources/extensions/gsd/memory-store.ts +441 -0
  27. package/dist/resources/extensions/gsd/migrate/command.ts +2 -2
  28. package/dist/resources/extensions/gsd/migrate/writer.ts +39 -0
  29. package/dist/resources/extensions/gsd/parallel-orchestrator.ts +122 -4
  30. package/dist/resources/extensions/gsd/preferences.ts +2 -1
  31. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  32. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +4 -4
  33. package/dist/resources/extensions/gsd/prompts/discuss.md +5 -5
  34. package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
  35. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  36. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  37. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  38. package/dist/resources/extensions/gsd/prompts/queue.md +3 -3
  39. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  40. package/dist/resources/extensions/gsd/roadmap-slices.ts +45 -1
  41. package/dist/resources/extensions/gsd/state.ts +17 -6
  42. package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
  43. package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
  44. package/dist/resources/extensions/gsd/tests/derive-state.test.ts +70 -0
  45. package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +23 -3
  46. package/dist/resources/extensions/gsd/tests/git-service.test.ts +70 -4
  47. package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
  48. package/dist/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
  49. package/dist/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
  50. package/dist/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
  51. package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +13 -7
  52. package/dist/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +171 -0
  53. package/dist/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
  54. package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +8 -4
  55. package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
  56. package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
  57. package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
  58. package/dist/resources/extensions/gsd/triage-ui.ts +1 -1
  59. package/dist/resources/extensions/gsd/types.ts +2 -0
  60. package/dist/resources/extensions/gsd/visualizer-data.ts +291 -10
  61. package/dist/resources/extensions/gsd/visualizer-overlay.ts +237 -28
  62. package/dist/resources/extensions/gsd/visualizer-views.ts +462 -48
  63. package/dist/resources/extensions/gsd/worktree.ts +9 -2
  64. package/dist/resources/extensions/search-the-web/native-search.ts +19 -5
  65. package/dist/resources/extensions/shared/path-display.ts +19 -0
  66. package/package.json +1 -6
  67. package/packages/pi-agent-core/dist/agent-loop.js +2 -0
  68. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  69. package/packages/pi-agent-core/src/agent-loop.ts +2 -0
  70. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  71. package/packages/pi-ai/dist/providers/anthropic.js +64 -0
  72. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  73. package/packages/pi-ai/dist/providers/mistral.js +3 -0
  74. package/packages/pi-ai/dist/providers/mistral.js.map +1 -1
  75. package/packages/pi-ai/dist/types.d.ts +23 -1
  76. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  77. package/packages/pi-ai/dist/types.js.map +1 -1
  78. package/packages/pi-ai/src/providers/anthropic.ts +65 -1
  79. package/packages/pi-ai/src/providers/mistral.ts +3 -0
  80. package/packages/pi-ai/src/types.ts +19 -1
  81. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +7 -0
  82. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  83. package/packages/pi-coding-agent/dist/core/agent-session.js +32 -0
  84. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  85. package/packages/pi-coding-agent/dist/core/keybindings.js +1 -1
  86. package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
  87. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  88. package/packages/pi-coding-agent/dist/core/lsp/client.js +12 -1
  89. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  90. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
  91. package/packages/pi-coding-agent/dist/core/lsp/index.js +7 -0
  92. package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
  93. package/packages/pi-coding-agent/dist/core/sdk.d.ts +2 -2
  94. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  95. package/packages/pi-coding-agent/dist/core/sdk.js +8 -3
  96. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  97. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
  98. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  99. package/packages/pi-coding-agent/dist/core/settings-manager.js +8 -0
  100. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  101. package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
  102. package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
  103. package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
  104. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  105. package/packages/pi-coding-agent/dist/core/system-prompt.js +2 -1
  106. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/index.d.ts +2 -1
  108. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  109. package/packages/pi-coding-agent/dist/index.js +5 -1
  110. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  111. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts +41 -3
  112. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  113. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +301 -62
  114. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  115. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -0
  117. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +5 -0
  119. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  120. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +135 -30
  121. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/tests/path-display.test.d.ts +8 -0
  123. package/packages/pi-coding-agent/dist/tests/path-display.test.d.ts.map +1 -0
  124. package/packages/pi-coding-agent/dist/tests/path-display.test.js +60 -0
  125. package/packages/pi-coding-agent/dist/tests/path-display.test.js.map +1 -0
  126. package/packages/pi-coding-agent/dist/utils/clipboard-image.d.ts.map +1 -1
  127. package/packages/pi-coding-agent/dist/utils/clipboard-image.js +32 -6
  128. package/packages/pi-coding-agent/dist/utils/clipboard-image.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/utils/path-display.d.ts +34 -0
  130. package/packages/pi-coding-agent/dist/utils/path-display.d.ts.map +1 -0
  131. package/packages/pi-coding-agent/dist/utils/path-display.js +36 -0
  132. package/packages/pi-coding-agent/dist/utils/path-display.js.map +1 -0
  133. package/packages/pi-coding-agent/src/core/agent-session.ts +36 -0
  134. package/packages/pi-coding-agent/src/core/keybindings.ts +1 -1
  135. package/packages/pi-coding-agent/src/core/lsp/client.ts +11 -1
  136. package/packages/pi-coding-agent/src/core/lsp/index.ts +7 -0
  137. package/packages/pi-coding-agent/src/core/sdk.ts +17 -1
  138. package/packages/pi-coding-agent/src/core/settings-manager.ts +11 -0
  139. package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
  140. package/packages/pi-coding-agent/src/core/system-prompt.ts +2 -1
  141. package/packages/pi-coding-agent/src/index.ts +15 -0
  142. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +347 -62
  143. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -0
  144. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +124 -4
  145. package/packages/pi-coding-agent/src/tests/path-display.test.ts +85 -0
  146. package/packages/pi-coding-agent/src/utils/clipboard-image.ts +33 -6
  147. package/packages/pi-coding-agent/src/utils/path-display.ts +36 -0
  148. package/src/resources/GSD-WORKFLOW.md +12 -9
  149. package/src/resources/extensions/async-jobs/index.ts +9 -1
  150. package/src/resources/extensions/bg-shell/index.ts +3 -2
  151. package/src/resources/extensions/bg-shell/overlay.ts +18 -17
  152. package/src/resources/extensions/get-secrets-from-user.ts +5 -23
  153. package/src/resources/extensions/gsd/activity-log.ts +5 -3
  154. package/src/resources/extensions/gsd/auto-prompts.ts +14 -0
  155. package/src/resources/extensions/gsd/auto-recovery.ts +7 -4
  156. package/src/resources/extensions/gsd/auto-worktree.ts +132 -3
  157. package/src/resources/extensions/gsd/auto.ts +265 -48
  158. package/src/resources/extensions/gsd/cache.ts +3 -1
  159. package/src/resources/extensions/gsd/doctor-proactive.ts +7 -6
  160. package/src/resources/extensions/gsd/doctor.ts +26 -1
  161. package/src/resources/extensions/gsd/files.ts +13 -2
  162. package/src/resources/extensions/gsd/git-service.ts +74 -14
  163. package/src/resources/extensions/gsd/gsd-db.ts +78 -1
  164. package/src/resources/extensions/gsd/guided-flow.ts +54 -22
  165. package/src/resources/extensions/gsd/index.ts +62 -8
  166. package/src/resources/extensions/gsd/memory-extractor.ts +352 -0
  167. package/src/resources/extensions/gsd/memory-store.ts +441 -0
  168. package/src/resources/extensions/gsd/migrate/command.ts +2 -2
  169. package/src/resources/extensions/gsd/migrate/writer.ts +39 -0
  170. package/src/resources/extensions/gsd/parallel-orchestrator.ts +122 -4
  171. package/src/resources/extensions/gsd/preferences.ts +2 -1
  172. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  173. package/src/resources/extensions/gsd/prompts/discuss-headless.md +4 -4
  174. package/src/resources/extensions/gsd/prompts/discuss.md +5 -5
  175. package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
  176. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  177. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  178. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  179. package/src/resources/extensions/gsd/prompts/queue.md +3 -3
  180. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  181. package/src/resources/extensions/gsd/roadmap-slices.ts +45 -1
  182. package/src/resources/extensions/gsd/state.ts +17 -6
  183. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
  184. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
  185. package/src/resources/extensions/gsd/tests/derive-state.test.ts +70 -0
  186. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +23 -3
  187. package/src/resources/extensions/gsd/tests/git-service.test.ts +70 -4
  188. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
  189. package/src/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
  190. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
  191. package/src/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
  192. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +13 -7
  193. package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +171 -0
  194. package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
  195. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +8 -4
  196. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
  197. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
  198. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
  199. package/src/resources/extensions/gsd/triage-ui.ts +1 -1
  200. package/src/resources/extensions/gsd/types.ts +2 -0
  201. package/src/resources/extensions/gsd/visualizer-data.ts +291 -10
  202. package/src/resources/extensions/gsd/visualizer-overlay.ts +237 -28
  203. package/src/resources/extensions/gsd/visualizer-views.ts +462 -48
  204. package/src/resources/extensions/gsd/worktree.ts +9 -2
  205. package/src/resources/extensions/search-the-web/native-search.ts +19 -5
  206. package/src/resources/extensions/shared/path-display.ts +19 -0
@@ -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
- // deriveState: all slices done, all tasks done needs validation then milestone summary
267
- // Without VALIDATION file, it should be 'validating-milestone'
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
- // All slices are done in roadmap. No VALIDATION or SUMMARY exists.
270
- // deriveState should return 'validating-milestone' since validation gate precedes completion.
271
- assertEq(state.phase, 'validating-milestone', 'complete: deriveState phase is validating-milestone');
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