claude-brain 0.30.2 → 0.30.3

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 (236) hide show
  1. package/README.md +241 -191
  2. package/VERSION +1 -1
  3. package/assets/CLAUDE-unified.md +11 -11
  4. package/assets/CLAUDE.md +29 -29
  5. package/package.json +7 -3
  6. package/packs/backend/node.json +173 -173
  7. package/packs/core/javascript.json +176 -176
  8. package/packs/core/typescript.json +222 -222
  9. package/packs/frontend/react.json +254 -254
  10. package/packs/meta/testing.json +172 -172
  11. package/scripts/postinstall.mjs +531 -531
  12. package/src/automation/decision-detector.ts +452 -452
  13. package/src/automation/phase12-manager.ts +456 -456
  14. package/src/automation/proactive-recall.ts +373 -373
  15. package/src/automation/project-detector.ts +310 -310
  16. package/src/automation/repo-scanner.ts +210 -205
  17. package/src/cli/auto-setup.ts +75 -75
  18. package/src/cli/auto-start.ts +266 -266
  19. package/src/cli/bin.ts +264 -264
  20. package/src/cli/commands/autostart.ts +90 -90
  21. package/src/cli/commands/chroma.ts +578 -577
  22. package/src/cli/commands/export-training.ts +70 -70
  23. package/src/cli/commands/export.ts +130 -130
  24. package/src/cli/commands/git-hook.ts +183 -183
  25. package/src/cli/commands/hooks.ts +217 -217
  26. package/src/cli/commands/init.ts +123 -123
  27. package/src/cli/commands/install-mcp.ts +122 -111
  28. package/src/cli/commands/models.ts +979 -979
  29. package/src/cli/commands/pack.ts +200 -200
  30. package/src/cli/commands/refresh.ts +344 -339
  31. package/src/cli/commands/reindex.ts +120 -120
  32. package/src/cli/commands/serve.ts +466 -463
  33. package/src/cli/commands/start.ts +44 -44
  34. package/src/cli/commands/status.ts +220 -203
  35. package/src/cli/commands/uninstall-mcp.ts +45 -41
  36. package/src/cli/commands/update.ts +130 -124
  37. package/src/cli/migrate-chroma.ts +106 -106
  38. package/src/cli/ui/animations.ts +80 -80
  39. package/src/cli/ui/components.ts +82 -82
  40. package/src/cli/ui/index.ts +4 -4
  41. package/src/cli/ui/logo.ts +36 -36
  42. package/src/cli/ui/theme.ts +55 -55
  43. package/src/code-intelligence/indexer.ts +352 -352
  44. package/src/code-intelligence/linker.ts +178 -178
  45. package/src/code-intelligence/parser.ts +484 -484
  46. package/src/code-intelligence/query.ts +291 -291
  47. package/src/code-intelligence/schema.ts +83 -83
  48. package/src/code-intelligence/types.ts +95 -95
  49. package/src/config/defaults.ts +52 -52
  50. package/src/config/home.ts +56 -56
  51. package/src/config/index.ts +5 -5
  52. package/src/config/loader.ts +192 -192
  53. package/src/config/schema.ts +446 -415
  54. package/src/config/validator.ts +182 -182
  55. package/src/context/assembler.ts +407 -400
  56. package/src/context/index.ts +79 -79
  57. package/src/context/progress-tracker.ts +174 -174
  58. package/src/context/standards-manager.ts +287 -287
  59. package/src/context/validator.ts +58 -58
  60. package/src/diagnostics/index.ts +122 -121
  61. package/src/health/index.ts +233 -232
  62. package/src/hooks/brain-hook.ts +134 -131
  63. package/src/hooks/capture.ts +168 -168
  64. package/src/hooks/claude-code-mastery.md +112 -112
  65. package/src/hooks/context-hook.ts +260 -245
  66. package/src/hooks/deduplicator.ts +72 -72
  67. package/src/hooks/git-capture.ts +109 -109
  68. package/src/hooks/git-hook-installer.ts +211 -207
  69. package/src/hooks/index.ts +20 -20
  70. package/src/hooks/installer.ts +306 -288
  71. package/src/hooks/interceptor-hook.ts +204 -201
  72. package/src/hooks/passive-classifier.ts +397 -397
  73. package/src/hooks/queue.ts +160 -129
  74. package/src/hooks/session-tracker.ts +312 -312
  75. package/src/hooks/types.ts +52 -52
  76. package/src/index.ts +7 -7
  77. package/src/intelligence/cross-project/generalizer.ts +283 -283
  78. package/src/intelligence/cross-project/index.ts +7 -7
  79. package/src/intelligence/hf-downloader.ts +222 -222
  80. package/src/intelligence/hf-manifest.json +78 -78
  81. package/src/intelligence/index.ts +24 -24
  82. package/src/intelligence/inference-router.ts +762 -762
  83. package/src/intelligence/model-manager.ts +263 -245
  84. package/src/intelligence/optimization/index.ts +10 -10
  85. package/src/intelligence/optimization/precompute.ts +202 -202
  86. package/src/intelligence/optimization/semantic-cache.ts +213 -207
  87. package/src/intelligence/prediction/index.ts +7 -7
  88. package/src/intelligence/prediction/recommender.ts +276 -268
  89. package/src/intelligence/reasoning/chain-retrieval.ts +243 -247
  90. package/src/intelligence/reasoning/index.ts +7 -7
  91. package/src/intelligence/temporal/evolution.ts +193 -197
  92. package/src/intelligence/temporal/index.ts +16 -16
  93. package/src/intelligence/temporal/query-processor.ts +190 -190
  94. package/src/intelligence/temporal/timeline.ts +272 -259
  95. package/src/intelligence/temporal/trends.ts +263 -263
  96. package/src/intelligence/tokenizer.ts +118 -118
  97. package/src/knowledge/entity-extractor.ts +447 -443
  98. package/src/knowledge/graph/builder.ts +185 -185
  99. package/src/knowledge/graph/linker.ts +201 -201
  100. package/src/knowledge/graph/memory-graph.ts +359 -359
  101. package/src/knowledge/graph/schema.ts +99 -99
  102. package/src/knowledge/graph/search.ts +166 -166
  103. package/src/knowledge/relationship-extractor.ts +108 -108
  104. package/src/memory/chroma/client.ts +211 -192
  105. package/src/memory/chroma/collection-manager.ts +92 -92
  106. package/src/memory/chroma/config.ts +57 -57
  107. package/src/memory/chroma/embeddings.ts +177 -175
  108. package/src/memory/chroma/index.ts +82 -82
  109. package/src/memory/chroma/migration.ts +270 -270
  110. package/src/memory/chroma/schemas.ts +69 -69
  111. package/src/memory/chroma/search.ts +319 -315
  112. package/src/memory/chroma/store.ts +755 -747
  113. package/src/memory/compression.ts +121 -121
  114. package/src/memory/consolidation/archiver.ts +162 -165
  115. package/src/memory/consolidation/merger.ts +182 -186
  116. package/src/memory/consolidation/scorer.ts +136 -136
  117. package/src/memory/database.ts +9 -0
  118. package/src/memory/dual-write.ts +145 -0
  119. package/src/memory/embeddings.ts +226 -226
  120. package/src/memory/episodic/detector.ts +108 -108
  121. package/src/memory/episodic/manager.ts +347 -351
  122. package/src/memory/episodic/summarizer.ts +179 -179
  123. package/src/memory/episodic/types.ts +52 -52
  124. package/src/memory/fts5-search.ts +692 -633
  125. package/src/memory/index.ts +943 -1060
  126. package/src/memory/migrations/add-fts5.ts +118 -108
  127. package/src/memory/patterns.ts +438 -438
  128. package/src/memory/pruning.ts +60 -60
  129. package/src/memory/schema.ts +88 -88
  130. package/src/memory/store.ts +911 -787
  131. package/src/orchestrator/handlers/decision-handler.ts +204 -204
  132. package/src/packs/index.ts +9 -9
  133. package/src/packs/loader.ts +134 -134
  134. package/src/packs/manager.ts +204 -204
  135. package/src/packs/ranker.ts +78 -78
  136. package/src/packs/types.ts +81 -81
  137. package/src/phase12/index.ts +5 -5
  138. package/src/retrieval/bm25/index.ts +300 -297
  139. package/src/retrieval/bm25/tokenizer.ts +184 -184
  140. package/src/retrieval/feedback/adaptive.ts +221 -221
  141. package/src/retrieval/feedback/index.ts +16 -16
  142. package/src/retrieval/feedback/metrics.ts +221 -221
  143. package/src/retrieval/feedback/store.ts +283 -283
  144. package/src/retrieval/fusion/index.ts +194 -194
  145. package/src/retrieval/fusion/rrf.ts +165 -165
  146. package/src/retrieval/index.ts +12 -12
  147. package/src/retrieval/pipeline.ts +375 -375
  148. package/src/retrieval/query/expander.ts +203 -203
  149. package/src/retrieval/query/index.ts +27 -27
  150. package/src/retrieval/query/intent-classifier.ts +252 -252
  151. package/src/retrieval/query/temporal-parser.ts +295 -295
  152. package/src/retrieval/reranker/index.ts +189 -188
  153. package/src/retrieval/reranker/model.ts +99 -95
  154. package/src/retrieval/service.ts +125 -125
  155. package/src/retrieval/types.ts +162 -162
  156. package/src/routing/entity-extractor.ts +454 -454
  157. package/src/routing/handlers/exploration-handler.ts +369 -0
  158. package/src/routing/handlers/index.ts +19 -0
  159. package/src/routing/handlers/memory-handler.ts +273 -0
  160. package/src/routing/handlers/mutation-handler.ts +241 -0
  161. package/src/routing/handlers/recall-handler.ts +642 -0
  162. package/src/routing/handlers/shared.ts +515 -0
  163. package/src/routing/handlers/types.ts +48 -0
  164. package/src/routing/intent-classifier.ts +552 -552
  165. package/src/routing/response-filter.ts +399 -391
  166. package/src/routing/router.ts +245 -2193
  167. package/src/routing/search-engine.ts +521 -514
  168. package/src/routing/types.ts +104 -94
  169. package/src/scripts/health-check.ts +118 -118
  170. package/src/scripts/setup.ts +122 -122
  171. package/src/server/auto-updater.ts +283 -276
  172. package/src/server/handlers/call-tool.ts +159 -159
  173. package/src/server/handlers/list-tools.ts +35 -35
  174. package/src/server/handlers/tools/auto-remember.ts +165 -165
  175. package/src/server/handlers/tools/brain.ts +86 -86
  176. package/src/server/handlers/tools/create-project.ts +135 -135
  177. package/src/server/handlers/tools/get-code-standards.ts +123 -123
  178. package/src/server/handlers/tools/get-corrections.ts +152 -152
  179. package/src/server/handlers/tools/get-patterns.ts +156 -156
  180. package/src/server/handlers/tools/get-project-context.ts +75 -75
  181. package/src/server/handlers/tools/index.ts +30 -30
  182. package/src/server/handlers/tools/init-project.ts +756 -756
  183. package/src/server/handlers/tools/list-projects.ts +126 -126
  184. package/src/server/handlers/tools/recall-similar.ts +87 -87
  185. package/src/server/handlers/tools/recognize-pattern.ts +132 -132
  186. package/src/server/handlers/tools/record-correction.ts +131 -131
  187. package/src/server/handlers/tools/remember-decision.ts +168 -168
  188. package/src/server/handlers/tools/schemas.ts +179 -179
  189. package/src/server/handlers/tools/search-code.ts +122 -122
  190. package/src/server/handlers/tools/smart-context.ts +146 -146
  191. package/src/server/handlers/tools/update-progress.ts +131 -131
  192. package/src/server/http-api.ts +215 -1229
  193. package/src/server/mcp-proxy.ts +85 -84
  194. package/src/server/mcp-server.ts +285 -284
  195. package/src/server/middleware/auth.ts +39 -0
  196. package/src/server/middleware/error-handler.ts +37 -0
  197. package/src/server/middleware/rate-limit.ts +53 -0
  198. package/src/server/middleware/validate.ts +42 -0
  199. package/src/server/pid-manager.ts +137 -136
  200. package/src/server/providers/resources.ts +581 -581
  201. package/src/server/routes/code.ts +228 -0
  202. package/src/server/routes/context.ts +26 -0
  203. package/src/server/routes/health.ts +19 -0
  204. package/src/server/routes/helpers.ts +100 -0
  205. package/src/server/routes/hooks.ts +197 -0
  206. package/src/server/routes/mcp.ts +47 -0
  207. package/src/server/routes/memory.ts +397 -0
  208. package/src/server/routes/models.ts +96 -0
  209. package/src/server/routes/projects.ts +89 -0
  210. package/src/server/routes/types.ts +21 -0
  211. package/src/server/schemas/api-schemas.ts +202 -0
  212. package/src/server/services.ts +720 -720
  213. package/src/server/utils/memory-indicator.ts +84 -84
  214. package/src/server/utils/response-formatter.ts +129 -129
  215. package/src/server/web-viewer.ts +1145 -1115
  216. package/src/setup/index.ts +38 -38
  217. package/src/tools/registry.ts +115 -115
  218. package/src/tools/schemas.ts +666 -666
  219. package/src/tools/types.ts +412 -412
  220. package/src/training/data-store.ts +320 -298
  221. package/src/training/retrain-pipeline.ts +399 -394
  222. package/src/utils/error-handler.ts +136 -136
  223. package/src/utils/index.ts +58 -58
  224. package/src/utils/kill-port.ts +55 -53
  225. package/src/utils/phase12-helper.ts +56 -56
  226. package/src/utils/safe-path.ts +43 -0
  227. package/src/utils/timing.ts +47 -47
  228. package/src/utils/transaction.ts +63 -63
  229. package/src/vault/index.ts +4 -3
  230. package/src/vault/paths.ts +106 -106
  231. package/src/vault/query.ts +4 -1
  232. package/src/vault/reader.ts +44 -1
  233. package/src/vault/watcher.ts +24 -1
  234. package/src/vault/writer.ts +487 -413
  235. package/skills/persistent-memory/SKILL.md +0 -148
  236. package/skills/persistent-memory/references/tool-reference.md +0 -90
@@ -1,787 +1,911 @@
1
- /**
2
- * Memory Store - CRUD Operations
3
- * Phase 3: Memory and Embedding System
4
- *
5
- * Handles storing and retrieving memories with embeddings
6
- */
7
-
8
- import { randomUUID } from 'crypto'
9
- import type { Database } from 'bun:sqlite'
10
- import type { Logger } from 'pino'
11
- import type {
12
- Memory,
13
- Decision,
14
- StoreMemoryInput,
15
- StoreDecisionInput,
16
- MemoryRow,
17
- DecisionRow
18
- } from './types'
19
- import { EmbeddingService } from './embeddings'
20
- import { embeddingToBuffer, bufferToEmbedding, cosineSimilarity } from './embedding-utils'
21
-
22
- export class MemoryStore {
23
- private db: Database
24
- private embeddings: EmbeddingService
25
- private logger: Logger
26
-
27
- constructor(db: Database, embeddings: EmbeddingService, logger: Logger) {
28
- this.db = db
29
- this.embeddings = embeddings
30
- this.logger = logger.child({ component: 'memory-store' })
31
- }
32
-
33
- /**
34
- * Store a new memory with embedding
35
- */
36
- async storeMemory(input: StoreMemoryInput): Promise<string> {
37
- try {
38
- // Generate embedding
39
- const embedding = await this.embeddings.generateEmbedding(input.content)
40
-
41
- // Create memory record
42
- const id = randomUUID()
43
- const now = new Date().toISOString()
44
-
45
- const stmt = this.db.prepare(`
46
- INSERT INTO memories (id, project, content, embedding, created_at, updated_at, metadata)
47
- VALUES (?, ?, ?, ?, ?, ?, ?)
48
- `)
49
-
50
- stmt.run(
51
- id,
52
- input.project,
53
- input.content,
54
- embeddingToBuffer(embedding),
55
- now,
56
- now,
57
- input.metadata ? JSON.stringify(input.metadata) : null
58
- )
59
-
60
- this.logger.info({ id, project: input.project }, 'Memory stored')
61
- return id
62
- } catch (error) {
63
- this.logger.error({ error, input }, 'Failed to store memory')
64
- throw error
65
- }
66
- }
67
-
68
- /**
69
- * Store a decision (creates memory + decision record)
70
- */
71
- async storeDecision(input: StoreDecisionInput): Promise<string> {
72
- try {
73
- // Combine context, decision, and reasoning for embedding
74
- const content = `Context: ${input.context}\nDecision: ${input.decision}\nReasoning: ${input.reasoning}`
75
-
76
- // Store as memory
77
- const memoryId = await this.storeMemory({
78
- project: input.project,
79
- content,
80
- metadata: {
81
- type: 'decision',
82
- tags: input.tags || []
83
- }
84
- })
85
-
86
- // Store decision details
87
- const decisionId = randomUUID()
88
- const stmt = this.db.prepare(`
89
- INSERT INTO decisions (id, memory_id, context, decision, reasoning, alternatives, tags)
90
- VALUES (?, ?, ?, ?, ?, ?, ?)
91
- `)
92
-
93
- stmt.run(
94
- decisionId,
95
- memoryId,
96
- input.context,
97
- input.decision,
98
- input.reasoning,
99
- input.alternatives || null,
100
- input.tags ? JSON.stringify(input.tags) : null
101
- )
102
-
103
- this.logger.info({ decisionId, memoryId, project: input.project }, 'Decision stored')
104
- return decisionId
105
- } catch (error) {
106
- this.logger.error({ error, input }, 'Failed to store decision')
107
- throw error
108
- }
109
- }
110
-
111
- /**
112
- * Retrieve memory by ID
113
- */
114
- getMemory(id: string): Memory | null {
115
- try {
116
- const stmt = this.db.prepare(`
117
- SELECT * FROM memories WHERE id = ?
118
- `)
119
-
120
- const row = stmt.get(id) as MemoryRow | null
121
-
122
- if (!row) {
123
- return null
124
- }
125
-
126
- return this.rowToMemory(row)
127
- } catch (error) {
128
- this.logger.error({ error, id }, 'Failed to get memory')
129
- return null
130
- }
131
- }
132
-
133
- /**
134
- * Retrieve decision by ID
135
- */
136
- getDecision(id: string): Decision | null {
137
- try {
138
- const stmt = this.db.prepare(`
139
- SELECT * FROM decisions WHERE id = ?
140
- `)
141
-
142
- const row = stmt.get(id) as DecisionRow | null
143
-
144
- if (!row) {
145
- return null
146
- }
147
-
148
- return this.rowToDecision(row)
149
- } catch (error) {
150
- this.logger.error({ error, id }, 'Failed to get decision')
151
- return null
152
- }
153
- }
154
-
155
- /**
156
- * Get decision by memory ID
157
- */
158
- getDecisionByMemoryId(memoryId: string): Decision | null {
159
- try {
160
- const stmt = this.db.prepare(`
161
- SELECT * FROM decisions WHERE memory_id = ?
162
- `)
163
-
164
- const row = stmt.get(memoryId) as DecisionRow | null
165
-
166
- if (!row) {
167
- return null
168
- }
169
-
170
- return this.rowToDecision(row)
171
- } catch (error) {
172
- this.logger.error({ error, memoryId }, 'Failed to get decision by memory ID')
173
- return null
174
- }
175
- }
176
-
177
- /**
178
- * Update memory content (regenerates embedding)
179
- */
180
- async updateMemory(id: string, content: string): Promise<void> {
181
- try {
182
- // Generate new embedding
183
- const embedding = await this.embeddings.generateEmbedding(content)
184
-
185
- const stmt = this.db.prepare(`
186
- UPDATE memories
187
- SET content = ?, embedding = ?, updated_at = ?
188
- WHERE id = ?
189
- `)
190
-
191
- stmt.run(content, embeddingToBuffer(embedding), new Date().toISOString(), id)
192
-
193
- this.logger.info({ id }, 'Memory updated')
194
- } catch (error) {
195
- this.logger.error({ error, id }, 'Failed to update memory')
196
- throw error
197
- }
198
- }
199
-
200
- /**
201
- * Update decision outcome
202
- */
203
- updateDecisionOutcome(id: string, outcome: string): void {
204
- try {
205
- const stmt = this.db.prepare(`
206
- UPDATE decisions
207
- SET outcome = ?
208
- WHERE id = ?
209
- `)
210
-
211
- stmt.run(outcome, id)
212
-
213
- this.logger.info({ id }, 'Decision outcome updated')
214
- } catch (error) {
215
- this.logger.error({ error, id }, 'Failed to update decision outcome')
216
- throw error
217
- }
218
- }
219
-
220
- /**
221
- * Delete memory (cascades to decisions)
222
- */
223
- deleteMemory(id: string): void {
224
- try {
225
- const stmt = this.db.prepare('DELETE FROM memories WHERE id = ?')
226
- stmt.run(id)
227
- this.logger.info({ id }, 'Memory deleted')
228
- } catch (error) {
229
- this.logger.error({ error, id }, 'Failed to delete memory')
230
- throw error
231
- }
232
- }
233
-
234
- /**
235
- * Get all memories for a project
236
- */
237
- getProjectMemories(project: string): Memory[] {
238
- try {
239
- const stmt = this.db.prepare(`
240
- SELECT * FROM memories WHERE project = ? ORDER BY created_at DESC
241
- `)
242
-
243
- const rows = stmt.all(project) as MemoryRow[]
244
-
245
- return rows.map((row) => this.rowToMemory(row))
246
- } catch (error) {
247
- this.logger.error({ error, project }, 'Failed to get project memories')
248
- return []
249
- }
250
- }
251
-
252
- /**
253
- * Get all decisions for a project
254
- */
255
- getProjectDecisions(project: string): Decision[] {
256
- try {
257
- const stmt = this.db.prepare(`
258
- SELECT d.* FROM decisions d
259
- JOIN memories m ON d.memory_id = m.id
260
- WHERE m.project = ?
261
- ORDER BY m.created_at DESC
262
- `)
263
-
264
- const rows = stmt.all(project) as DecisionRow[]
265
-
266
- return rows.map((row) => this.rowToDecision(row))
267
- } catch (error) {
268
- this.logger.error({ error, project }, 'Failed to get project decisions')
269
- return []
270
- }
271
- }
272
-
273
- /**
274
- * Get memory count by project
275
- */
276
- getMemoryCount(project?: string): number {
277
- try {
278
- let query = 'SELECT COUNT(*) as count FROM memories'
279
- const params: string[] = []
280
-
281
- if (project) {
282
- query += ' WHERE project = ?'
283
- params.push(project)
284
- }
285
-
286
- const stmt = this.db.prepare(query)
287
- const result = stmt.get(...params) as { count: number }
288
-
289
- return result.count
290
- } catch (error) {
291
- this.logger.error({ error, project }, 'Failed to get memory count')
292
- return 0
293
- }
294
- }
295
-
296
- /**
297
- * Get all unique project names
298
- */
299
- getProjects(): string[] {
300
- try {
301
- const stmt = this.db.prepare(`
302
- SELECT DISTINCT project FROM memories ORDER BY project
303
- `)
304
-
305
- const rows = stmt.all() as Array<{ project: string }>
306
-
307
- return rows.map((row) => row.project)
308
- } catch (error) {
309
- this.logger.error({ error }, 'Failed to get projects')
310
- return []
311
- }
312
- }
313
-
314
- /**
315
- * Convert database row to Memory object
316
- */
317
- private rowToMemory(row: MemoryRow): Memory {
318
- return {
319
- id: row.id,
320
- project: row.project,
321
- content: row.content,
322
- embedding: bufferToEmbedding(row.embedding),
323
- createdAt: new Date(row.created_at),
324
- updatedAt: new Date(row.updated_at),
325
- metadata: row.metadata ? JSON.parse(row.metadata) : undefined
326
- }
327
- }
328
-
329
- /**
330
- * Store a pattern (creates memory + pattern record)
331
- */
332
- async storePattern(input: {
333
- project: string
334
- pattern_type: 'solution' | 'anti-pattern' | 'best-practice' | 'common-issue'
335
- description: string
336
- example?: string
337
- confidence: number
338
- context?: string
339
- source?: string
340
- }): Promise<string> {
341
- try {
342
- const content = `Pattern (${input.pattern_type}): ${input.description}${input.context ? `\nContext: ${input.context}` : ''}${input.example ? `\nExample: ${input.example}` : ''}`
343
-
344
- const memoryId = await this.storeMemory({
345
- project: input.project,
346
- content,
347
- metadata: {
348
- type: 'pattern',
349
- pattern_type: input.pattern_type,
350
- confidence: input.confidence
351
- }
352
- })
353
-
354
- const patternId = randomUUID()
355
- const now = new Date().toISOString()
356
- const stmt = this.db.prepare(`
357
- INSERT INTO patterns (id, memory_id, project, pattern_type, description, example, confidence, context, created_at)
358
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
359
- `)
360
-
361
- stmt.run(
362
- patternId,
363
- memoryId,
364
- input.project,
365
- input.pattern_type,
366
- input.description,
367
- input.example || null,
368
- input.confidence,
369
- input.context || null,
370
- now
371
- )
372
-
373
- this.logger.info({ patternId, memoryId, project: input.project }, 'Pattern stored')
374
- return patternId
375
- } catch (error) {
376
- this.logger.error({ error, input }, 'Failed to store pattern')
377
- throw error
378
- }
379
- }
380
-
381
- /**
382
- * Store a correction (creates memory + correction record)
383
- */
384
- async storeCorrection(input: {
385
- project: string
386
- original: string
387
- correction: string
388
- reasoning: string
389
- context?: string
390
- confidence: number
391
- }): Promise<string> {
392
- try {
393
- const content = `Correction: ${input.correction}\nOriginal: ${input.original}\nReasoning: ${input.reasoning}${input.context ? `\nContext: ${input.context}` : ''}`
394
-
395
- const memoryId = await this.storeMemory({
396
- project: input.project,
397
- content,
398
- metadata: {
399
- type: 'correction',
400
- confidence: input.confidence
401
- }
402
- })
403
-
404
- const correctionId = randomUUID()
405
- const now = new Date().toISOString()
406
- const stmt = this.db.prepare(`
407
- INSERT INTO corrections (id, memory_id, project, original, correction, reasoning, context, confidence, created_at)
408
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
409
- `)
410
-
411
- stmt.run(
412
- correctionId,
413
- memoryId,
414
- input.project,
415
- input.original,
416
- input.correction,
417
- input.reasoning,
418
- input.context || null,
419
- input.confidence,
420
- now
421
- )
422
-
423
- this.logger.info({ correctionId, memoryId, project: input.project }, 'Correction stored')
424
- return correctionId
425
- } catch (error) {
426
- this.logger.error({ error, input }, 'Failed to store correction')
427
- throw error
428
- }
429
- }
430
-
431
- /**
432
- * Get patterns by project with optional type filter
433
- */
434
- getPatternsByProject(project: string, options?: {
435
- pattern_type?: 'solution' | 'anti-pattern' | 'best-practice' | 'common-issue'
436
- limit?: number
437
- }): any[] {
438
- try {
439
- let sql = 'SELECT * FROM patterns WHERE project = ?'
440
- const params: any[] = [project]
441
-
442
- if (options?.pattern_type) {
443
- sql += ' AND pattern_type = ?'
444
- params.push(options.pattern_type)
445
- }
446
-
447
- sql += ' ORDER BY created_at DESC'
448
-
449
- if (options?.limit) {
450
- sql += ' LIMIT ?'
451
- params.push(options.limit)
452
- }
453
-
454
- const stmt = this.db.prepare(sql)
455
- const rows = stmt.all(...params) as any[]
456
-
457
- return rows.map(row => ({
458
- id: row.id,
459
- description: row.description,
460
- metadata: {
461
- project: row.project,
462
- pattern_type: row.pattern_type,
463
- description: row.description,
464
- example: row.example || '',
465
- confidence: row.confidence,
466
- context: row.context || '',
467
- created_at: row.created_at,
468
- first_seen: row.created_at,
469
- last_seen: row.created_at,
470
- occurrences: 1
471
- }
472
- }))
473
- } catch (error) {
474
- this.logger.error({ error, project }, 'Failed to get patterns by project')
475
- return []
476
- }
477
- }
478
-
479
- /**
480
- * Get corrections by project
481
- */
482
- getCorrectionsByProject(project: string, limit: number = 10): any[] {
483
- try {
484
- const stmt = this.db.prepare(
485
- 'SELECT * FROM corrections WHERE project = ? ORDER BY created_at DESC LIMIT ?'
486
- )
487
- const rows = stmt.all(project, limit) as any[]
488
-
489
- return rows.map(row => ({
490
- id: row.id,
491
- correction: row.correction,
492
- description: row.correction,
493
- metadata: {
494
- project: row.project,
495
- original: row.original,
496
- correction: row.correction,
497
- description: row.correction,
498
- reasoning: row.reasoning,
499
- context: row.context || '',
500
- confidence: row.confidence,
501
- created_at: row.created_at,
502
- first_seen: row.created_at,
503
- last_seen: row.created_at,
504
- occurrences: 1
505
- }
506
- }))
507
- } catch (error) {
508
- this.logger.error({ error, project }, 'Failed to get corrections by project')
509
- return []
510
- }
511
- }
512
-
513
- /**
514
- * Search patterns using semantic search on underlying memories
515
- */
516
- async searchPatterns(
517
- query: string,
518
- options?: {
519
- project?: string
520
- pattern_type?: 'solution' | 'anti-pattern' | 'best-practice' | 'common-issue'
521
- limit?: number
522
- minSimilarity?: number
523
- }
524
- ): Promise<any[]> {
525
- try {
526
- // If no query, return all patterns for the project
527
- if (!query && options?.project) {
528
- return this.getPatternsByProject(options.project, {
529
- pattern_type: options?.pattern_type,
530
- limit: options?.limit
531
- })
532
- }
533
-
534
- // Generate embedding for semantic search
535
- const queryEmbedding = await this.embeddings.generateEmbedding(query || 'pattern')
536
-
537
- let sql = `
538
- SELECT p.*, m.embedding, m.content as memory_content
539
- FROM patterns p
540
- JOIN memories m ON p.memory_id = m.id
541
- WHERE 1=1
542
- `
543
- const params: any[] = []
544
-
545
- if (options?.project) {
546
- sql += ' AND p.project = ?'
547
- params.push(options.project)
548
- }
549
- if (options?.pattern_type) {
550
- sql += ' AND p.pattern_type = ?'
551
- params.push(options.pattern_type)
552
- }
553
-
554
- const stmt = this.db.prepare(sql)
555
- const rows = stmt.all(...params) as any[]
556
-
557
- const results = rows.map(row => {
558
- const embedding = bufferToEmbedding(row.embedding)
559
- const similarity = cosineSimilarity(queryEmbedding, embedding)
560
- return {
561
- id: row.id,
562
- content: row.description,
563
- metadata: {
564
- project: row.project,
565
- pattern_type: row.pattern_type,
566
- description: row.description,
567
- example: row.example || '',
568
- confidence: row.confidence,
569
- context: row.context || '',
570
- created_at: row.created_at,
571
- first_seen: row.created_at,
572
- last_seen: row.created_at,
573
- occurrences: 1
574
- },
575
- similarity
576
- }
577
- })
578
- .filter(r => r.similarity >= (options?.minSimilarity || 0.3))
579
- .sort((a, b) => b.similarity - a.similarity)
580
- .slice(0, options?.limit || 10)
581
-
582
- return results
583
- } catch (error) {
584
- this.logger.error({ error, query }, 'Pattern search failed')
585
- return []
586
- }
587
- }
588
-
589
- /**
590
- * Search corrections using semantic search on underlying memories
591
- */
592
- async searchCorrections(
593
- query: string,
594
- options?: {
595
- project?: string
596
- limit?: number
597
- minSimilarity?: number
598
- }
599
- ): Promise<any[]> {
600
- try {
601
- // If no query, return all corrections for the project
602
- if (!query && options?.project) {
603
- return this.getCorrectionsByProject(options.project, options?.limit || 10)
604
- }
605
-
606
- const queryEmbedding = await this.embeddings.generateEmbedding(query || 'correction')
607
-
608
- let sql = `
609
- SELECT c.*, m.embedding, m.content as memory_content
610
- FROM corrections c
611
- JOIN memories m ON c.memory_id = m.id
612
- WHERE 1=1
613
- `
614
- const params: any[] = []
615
-
616
- if (options?.project) {
617
- sql += ' AND c.project = ?'
618
- params.push(options.project)
619
- }
620
-
621
- const stmt = this.db.prepare(sql)
622
- const rows = stmt.all(...params) as any[]
623
-
624
- const results = rows.map(row => {
625
- const embedding = bufferToEmbedding(row.embedding)
626
- const similarity = cosineSimilarity(queryEmbedding, embedding)
627
- return {
628
- id: row.id,
629
- content: row.correction,
630
- description: row.correction,
631
- metadata: {
632
- project: row.project,
633
- original: row.original,
634
- correction: row.correction,
635
- description: row.correction,
636
- reasoning: row.reasoning,
637
- context: row.context || '',
638
- confidence: row.confidence,
639
- created_at: row.created_at,
640
- first_seen: row.created_at,
641
- last_seen: row.created_at,
642
- occurrences: 1
643
- },
644
- similarity
645
- }
646
- })
647
- .filter(r => r.similarity >= (options?.minSimilarity || 0.3))
648
- .sort((a, b) => b.similarity - a.similarity)
649
- .slice(0, options?.limit || 10)
650
-
651
- return results
652
- } catch (error) {
653
- this.logger.error({ error, query }, 'Correction search failed')
654
- return []
655
- }
656
- }
657
-
658
- /**
659
- * Get all decisions with full content (for analytical tools without ChromaDB)
660
- */
661
- getAllDecisionsWithContent(project?: string): any[] {
662
- try {
663
- let sql = `
664
- SELECT d.id, d.context, d.decision, d.reasoning, d.alternatives, d.tags,
665
- m.content, m.project, m.created_at, m.metadata
666
- FROM decisions d
667
- JOIN memories m ON d.memory_id = m.id
668
- `
669
- const params: any[] = []
670
- if (project) {
671
- sql += ' WHERE m.project = ?'
672
- params.push(project)
673
- }
674
- sql += ' ORDER BY m.created_at DESC'
675
-
676
- const stmt = this.db.prepare(sql)
677
- const rows = stmt.all(...params) as any[]
678
-
679
- return rows.map(row => ({
680
- id: row.id,
681
- content: row.content,
682
- date: row.created_at,
683
- project: row.project,
684
- context: row.context,
685
- decision: row.decision,
686
- reasoning: row.reasoning,
687
- alternatives: row.alternatives,
688
- tags: row.tags ? JSON.parse(row.tags) : []
689
- }))
690
- } catch (error) {
691
- this.logger.error({ error, project }, 'Failed to get all decisions with content')
692
- return []
693
- }
694
- }
695
-
696
- /**
697
- * Get all patterns with full content (for analytical tools without ChromaDB)
698
- */
699
- getAllPatternsWithContent(project?: string): any[] {
700
- try {
701
- let sql = `
702
- SELECT p.id, p.pattern_type, p.description, p.example, p.confidence, p.context,
703
- m.content, m.project, m.created_at
704
- FROM patterns p
705
- JOIN memories m ON p.memory_id = m.id
706
- `
707
- const params: any[] = []
708
- if (project) {
709
- sql += ' WHERE p.project = ?'
710
- params.push(project)
711
- }
712
- sql += ' ORDER BY m.created_at DESC'
713
-
714
- const stmt = this.db.prepare(sql)
715
- const rows = stmt.all(...params) as any[]
716
-
717
- return rows.map(row => ({
718
- id: row.id,
719
- content: row.content,
720
- date: row.created_at,
721
- project: row.project,
722
- pattern_type: row.pattern_type,
723
- description: row.description,
724
- example: row.example,
725
- confidence: row.confidence,
726
- context: row.context
727
- }))
728
- } catch (error) {
729
- this.logger.error({ error, project }, 'Failed to get all patterns with content')
730
- return []
731
- }
732
- }
733
-
734
- /**
735
- * Get all corrections with full content (for analytical tools without ChromaDB)
736
- */
737
- getAllCorrectionsWithContent(project?: string): any[] {
738
- try {
739
- let sql = `
740
- SELECT c.id, c.original, c.correction, c.reasoning, c.context as correction_context,
741
- c.confidence, m.content, m.project, m.created_at
742
- FROM corrections c
743
- JOIN memories m ON c.memory_id = m.id
744
- `
745
- const params: any[] = []
746
- if (project) {
747
- sql += ' WHERE c.project = ?'
748
- params.push(project)
749
- }
750
- sql += ' ORDER BY m.created_at DESC'
751
-
752
- const stmt = this.db.prepare(sql)
753
- const rows = stmt.all(...params) as any[]
754
-
755
- return rows.map(row => ({
756
- id: row.id,
757
- content: row.content,
758
- date: row.created_at,
759
- project: row.project,
760
- original: row.original,
761
- correction: row.correction,
762
- reasoning: row.reasoning,
763
- context: row.correction_context,
764
- confidence: row.confidence
765
- }))
766
- } catch (error) {
767
- this.logger.error({ error, project }, 'Failed to get all corrections with content')
768
- return []
769
- }
770
- }
771
-
772
- /**
773
- * Convert database row to Decision object
774
- */
775
- private rowToDecision(row: DecisionRow): Decision {
776
- return {
777
- id: row.id,
778
- memoryId: row.memory_id,
779
- context: row.context,
780
- decision: row.decision,
781
- reasoning: row.reasoning,
782
- alternatives: row.alternatives || undefined,
783
- outcome: row.outcome || undefined,
784
- tags: row.tags ? JSON.parse(row.tags) : undefined
785
- }
786
- }
787
- }
1
+ /**
2
+ * Memory Store - CRUD Operations
3
+ * Phase 3: Memory and Embedding System
4
+ *
5
+ * Handles storing and retrieving memories with embeddings
6
+ */
7
+
8
+ import { randomUUID } from 'crypto'
9
+ import type { Database } from 'bun:sqlite'
10
+ import type { Logger } from 'pino'
11
+ import type {
12
+ Memory,
13
+ Decision,
14
+ StoreMemoryInput,
15
+ StoreDecisionInput,
16
+ MemoryRow,
17
+ DecisionRow
18
+ } from './types'
19
+ import { EmbeddingService } from './embeddings'
20
+ import { embeddingToBuffer, bufferToEmbedding, cosineSimilarity } from './embedding-utils'
21
+
22
+ /** Raw row from the patterns table */
23
+ interface PatternRow {
24
+ id: string
25
+ memory_id: string
26
+ project: string
27
+ pattern_type: string
28
+ description: string
29
+ example: string | null
30
+ confidence: number
31
+ context: string | null
32
+ created_at: string
33
+ }
34
+
35
+ /** Raw row from the patterns table joined with memories (includes embedding) */
36
+ interface PatternWithEmbeddingRow extends PatternRow {
37
+ embedding: Buffer | Uint8Array
38
+ memory_content: string
39
+ }
40
+
41
+ /** Raw row from the corrections table */
42
+ interface CorrectionRow {
43
+ id: string
44
+ memory_id: string
45
+ project: string
46
+ original: string
47
+ correction: string
48
+ reasoning: string
49
+ context: string | null
50
+ confidence: number
51
+ created_at: string
52
+ }
53
+
54
+ /** Raw row from the corrections table joined with memories */
55
+ interface CorrectionWithEmbeddingRow extends CorrectionRow {
56
+ embedding: Buffer | Uint8Array
57
+ memory_content: string
58
+ }
59
+
60
+ /** Decision joined with memory content */
61
+ interface DecisionWithContentRow {
62
+ id: string
63
+ context: string
64
+ decision: string
65
+ reasoning: string
66
+ alternatives: string | null
67
+ tags: string | null
68
+ content: string
69
+ project: string
70
+ created_at: string
71
+ metadata: string | null
72
+ }
73
+
74
+ /** Pattern joined with memory content */
75
+ interface PatternWithContentRow {
76
+ id: string
77
+ pattern_type: string
78
+ description: string
79
+ example: string | null
80
+ confidence: number
81
+ context: string | null
82
+ content: string
83
+ project: string
84
+ created_at: string
85
+ }
86
+
87
+ /** Correction joined with memory content */
88
+ interface CorrectionWithContentRow {
89
+ id: string
90
+ original: string
91
+ correction: string
92
+ reasoning: string
93
+ correction_context: string | null
94
+ confidence: number
95
+ content: string
96
+ project: string
97
+ created_at: string
98
+ }
99
+
100
+ /** Formatted pattern result returned by getPatternsByProject */
101
+ interface FormattedPatternResult {
102
+ id: string
103
+ description: string
104
+ metadata: {
105
+ project: string
106
+ pattern_type: string
107
+ description: string
108
+ example: string
109
+ confidence: number
110
+ context: string
111
+ created_at: string
112
+ first_seen: string
113
+ last_seen: string
114
+ occurrences: number
115
+ }
116
+ }
117
+
118
+ /** Formatted correction result */
119
+ interface FormattedCorrectionResult {
120
+ id: string
121
+ correction: string
122
+ description: string
123
+ metadata: {
124
+ project: string
125
+ original: string
126
+ correction: string
127
+ description: string
128
+ reasoning: string
129
+ context: string
130
+ confidence: number
131
+ created_at: string
132
+ first_seen: string
133
+ last_seen: string
134
+ occurrences: number
135
+ }
136
+ }
137
+
138
+ /** Search result with similarity score */
139
+ interface ScoredSearchResult {
140
+ id: string
141
+ content: string
142
+ metadata: Record<string, unknown>
143
+ similarity: number
144
+ }
145
+
146
+ export class MemoryStore {
147
+ private db: Database
148
+ private embeddings: EmbeddingService
149
+ private logger: Logger
150
+
151
+ constructor(db: Database, embeddings: EmbeddingService, logger: Logger) {
152
+ this.db = db
153
+ this.embeddings = embeddings
154
+ this.logger = logger.child({ component: 'memory-store' })
155
+ }
156
+
157
+ /**
158
+ * Store a new memory with embedding
159
+ */
160
+ async storeMemory(input: StoreMemoryInput): Promise<string> {
161
+ try {
162
+ // Generate embedding
163
+ const embedding = await this.embeddings.generateEmbedding(input.content)
164
+
165
+ // Create memory record
166
+ const id = randomUUID()
167
+ const now = new Date().toISOString()
168
+
169
+ const stmt = this.db.prepare(`
170
+ INSERT INTO memories (id, project, content, embedding, created_at, updated_at, metadata)
171
+ VALUES (?, ?, ?, ?, ?, ?, ?)
172
+ `)
173
+
174
+ stmt.run(
175
+ id,
176
+ input.project,
177
+ input.content,
178
+ embeddingToBuffer(embedding),
179
+ now,
180
+ now,
181
+ input.metadata ? JSON.stringify(input.metadata) : null
182
+ )
183
+
184
+ this.logger.info({ id, project: input.project }, 'Memory stored')
185
+ return id
186
+ } catch (error) {
187
+ this.logger.error({ error, input }, 'Failed to store memory')
188
+ throw error
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Store a decision (creates memory + decision record)
194
+ */
195
+ async storeDecision(input: StoreDecisionInput): Promise<string> {
196
+ try {
197
+ // Combine context, decision, and reasoning for embedding
198
+ const content = `Context: ${input.context}\nDecision: ${input.decision}\nReasoning: ${input.reasoning}`
199
+
200
+ // Store as memory
201
+ const memoryId = await this.storeMemory({
202
+ project: input.project,
203
+ content,
204
+ metadata: {
205
+ type: 'decision',
206
+ tags: input.tags || []
207
+ }
208
+ })
209
+
210
+ // Store decision details
211
+ const decisionId = randomUUID()
212
+ const stmt = this.db.prepare(`
213
+ INSERT INTO decisions (id, memory_id, context, decision, reasoning, alternatives, tags)
214
+ VALUES (?, ?, ?, ?, ?, ?, ?)
215
+ `)
216
+
217
+ stmt.run(
218
+ decisionId,
219
+ memoryId,
220
+ input.context,
221
+ input.decision,
222
+ input.reasoning,
223
+ input.alternatives || null,
224
+ input.tags ? JSON.stringify(input.tags) : null
225
+ )
226
+
227
+ this.logger.info({ decisionId, memoryId, project: input.project }, 'Decision stored')
228
+ return decisionId
229
+ } catch (error) {
230
+ this.logger.error({ error, input }, 'Failed to store decision')
231
+ throw error
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Retrieve memory by ID
237
+ */
238
+ getMemory(id: string): Memory | null {
239
+ try {
240
+ const stmt = this.db.prepare(`
241
+ SELECT * FROM memories WHERE id = ?
242
+ `)
243
+
244
+ const row = stmt.get(id) as MemoryRow | null
245
+
246
+ if (!row) {
247
+ return null
248
+ }
249
+
250
+ return this.rowToMemory(row)
251
+ } catch (error) {
252
+ this.logger.error({ error, id }, 'Failed to get memory')
253
+ return null
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Retrieve decision by ID
259
+ */
260
+ getDecision(id: string): Decision | null {
261
+ try {
262
+ const stmt = this.db.prepare(`
263
+ SELECT * FROM decisions WHERE id = ?
264
+ `)
265
+
266
+ const row = stmt.get(id) as DecisionRow | null
267
+
268
+ if (!row) {
269
+ return null
270
+ }
271
+
272
+ return this.rowToDecision(row)
273
+ } catch (error) {
274
+ this.logger.error({ error, id }, 'Failed to get decision')
275
+ return null
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Get decision by memory ID
281
+ */
282
+ getDecisionByMemoryId(memoryId: string): Decision | null {
283
+ try {
284
+ const stmt = this.db.prepare(`
285
+ SELECT * FROM decisions WHERE memory_id = ?
286
+ `)
287
+
288
+ const row = stmt.get(memoryId) as DecisionRow | null
289
+
290
+ if (!row) {
291
+ return null
292
+ }
293
+
294
+ return this.rowToDecision(row)
295
+ } catch (error) {
296
+ this.logger.error({ error, memoryId }, 'Failed to get decision by memory ID')
297
+ return null
298
+ }
299
+ }
300
+
301
+ /**
302
+ * Update memory content (regenerates embedding)
303
+ */
304
+ async updateMemory(id: string, content: string): Promise<void> {
305
+ try {
306
+ // Generate new embedding
307
+ const embedding = await this.embeddings.generateEmbedding(content)
308
+
309
+ const stmt = this.db.prepare(`
310
+ UPDATE memories
311
+ SET content = ?, embedding = ?, updated_at = ?
312
+ WHERE id = ?
313
+ `)
314
+
315
+ stmt.run(content, embeddingToBuffer(embedding), new Date().toISOString(), id)
316
+
317
+ this.logger.info({ id }, 'Memory updated')
318
+ } catch (error) {
319
+ this.logger.error({ error, id }, 'Failed to update memory')
320
+ throw error
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Update decision outcome
326
+ */
327
+ updateDecisionOutcome(id: string, outcome: string): void {
328
+ try {
329
+ const stmt = this.db.prepare(`
330
+ UPDATE decisions
331
+ SET outcome = ?
332
+ WHERE id = ?
333
+ `)
334
+
335
+ stmt.run(outcome, id)
336
+
337
+ this.logger.info({ id }, 'Decision outcome updated')
338
+ } catch (error) {
339
+ this.logger.error({ error, id }, 'Failed to update decision outcome')
340
+ throw error
341
+ }
342
+ }
343
+
344
+ /**
345
+ * Delete memory (cascades to decisions)
346
+ */
347
+ deleteMemory(id: string): void {
348
+ try {
349
+ const stmt = this.db.prepare('DELETE FROM memories WHERE id = ?')
350
+ stmt.run(id)
351
+ this.logger.info({ id }, 'Memory deleted')
352
+ } catch (error) {
353
+ this.logger.error({ error, id }, 'Failed to delete memory')
354
+ throw error
355
+ }
356
+ }
357
+
358
+ /**
359
+ * Get all memories for a project
360
+ */
361
+ getProjectMemories(project: string): Memory[] {
362
+ try {
363
+ const stmt = this.db.prepare(`
364
+ SELECT * FROM memories WHERE project = ? ORDER BY created_at DESC
365
+ `)
366
+
367
+ const rows = stmt.all(project) as MemoryRow[]
368
+
369
+ return rows.map((row) => this.rowToMemory(row))
370
+ } catch (error) {
371
+ this.logger.error({ error, project }, 'Failed to get project memories')
372
+ return []
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Get all decisions for a project
378
+ */
379
+ getProjectDecisions(project: string): Decision[] {
380
+ try {
381
+ const stmt = this.db.prepare(`
382
+ SELECT d.* FROM decisions d
383
+ JOIN memories m ON d.memory_id = m.id
384
+ WHERE m.project = ?
385
+ ORDER BY m.created_at DESC
386
+ `)
387
+
388
+ const rows = stmt.all(project) as DecisionRow[]
389
+
390
+ return rows.map((row) => this.rowToDecision(row))
391
+ } catch (error) {
392
+ this.logger.error({ error, project }, 'Failed to get project decisions')
393
+ return []
394
+ }
395
+ }
396
+
397
+ /**
398
+ * Get memory count by project
399
+ */
400
+ getMemoryCount(project?: string): number {
401
+ try {
402
+ let query = 'SELECT COUNT(*) as count FROM memories'
403
+ const params: string[] = []
404
+
405
+ if (project) {
406
+ query += ' WHERE project = ?'
407
+ params.push(project)
408
+ }
409
+
410
+ const stmt = this.db.prepare(query)
411
+ const result = stmt.get(...params) as { count: number }
412
+
413
+ return result.count
414
+ } catch (error) {
415
+ this.logger.error({ error, project }, 'Failed to get memory count')
416
+ return 0
417
+ }
418
+ }
419
+
420
+ /**
421
+ * Get all unique project names
422
+ */
423
+ getProjects(): string[] {
424
+ try {
425
+ const stmt = this.db.prepare(`
426
+ SELECT DISTINCT project FROM memories ORDER BY project
427
+ `)
428
+
429
+ const rows = stmt.all() as Array<{ project: string }>
430
+
431
+ return rows.map((row) => row.project)
432
+ } catch (error) {
433
+ this.logger.error({ error }, 'Failed to get projects')
434
+ return []
435
+ }
436
+ }
437
+
438
+ /**
439
+ * Convert database row to Memory object
440
+ */
441
+ private rowToMemory(row: MemoryRow): Memory {
442
+ return {
443
+ id: row.id,
444
+ project: row.project,
445
+ content: row.content,
446
+ embedding: bufferToEmbedding(row.embedding),
447
+ createdAt: new Date(row.created_at),
448
+ updatedAt: new Date(row.updated_at),
449
+ metadata: row.metadata ? JSON.parse(row.metadata) : undefined
450
+ }
451
+ }
452
+
453
+ /**
454
+ * Store a pattern (creates memory + pattern record)
455
+ */
456
+ async storePattern(input: {
457
+ project: string
458
+ pattern_type: 'solution' | 'anti-pattern' | 'best-practice' | 'common-issue'
459
+ description: string
460
+ example?: string
461
+ confidence: number
462
+ context?: string
463
+ source?: string
464
+ }): Promise<string> {
465
+ try {
466
+ const content = `Pattern (${input.pattern_type}): ${input.description}${input.context ? `\nContext: ${input.context}` : ''}${input.example ? `\nExample: ${input.example}` : ''}`
467
+
468
+ const memoryId = await this.storeMemory({
469
+ project: input.project,
470
+ content,
471
+ metadata: {
472
+ type: 'pattern',
473
+ pattern_type: input.pattern_type,
474
+ confidence: input.confidence
475
+ }
476
+ })
477
+
478
+ const patternId = randomUUID()
479
+ const now = new Date().toISOString()
480
+ const stmt = this.db.prepare(`
481
+ INSERT INTO patterns (id, memory_id, project, pattern_type, description, example, confidence, context, created_at)
482
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
483
+ `)
484
+
485
+ stmt.run(
486
+ patternId,
487
+ memoryId,
488
+ input.project,
489
+ input.pattern_type,
490
+ input.description,
491
+ input.example || null,
492
+ input.confidence,
493
+ input.context || null,
494
+ now
495
+ )
496
+
497
+ this.logger.info({ patternId, memoryId, project: input.project }, 'Pattern stored')
498
+ return patternId
499
+ } catch (error) {
500
+ this.logger.error({ error, input }, 'Failed to store pattern')
501
+ throw error
502
+ }
503
+ }
504
+
505
+ /**
506
+ * Store a correction (creates memory + correction record)
507
+ */
508
+ async storeCorrection(input: {
509
+ project: string
510
+ original: string
511
+ correction: string
512
+ reasoning: string
513
+ context?: string
514
+ confidence: number
515
+ }): Promise<string> {
516
+ try {
517
+ const content = `Correction: ${input.correction}\nOriginal: ${input.original}\nReasoning: ${input.reasoning}${input.context ? `\nContext: ${input.context}` : ''}`
518
+
519
+ const memoryId = await this.storeMemory({
520
+ project: input.project,
521
+ content,
522
+ metadata: {
523
+ type: 'correction',
524
+ confidence: input.confidence
525
+ }
526
+ })
527
+
528
+ const correctionId = randomUUID()
529
+ const now = new Date().toISOString()
530
+ const stmt = this.db.prepare(`
531
+ INSERT INTO corrections (id, memory_id, project, original, correction, reasoning, context, confidence, created_at)
532
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
533
+ `)
534
+
535
+ stmt.run(
536
+ correctionId,
537
+ memoryId,
538
+ input.project,
539
+ input.original,
540
+ input.correction,
541
+ input.reasoning,
542
+ input.context || null,
543
+ input.confidence,
544
+ now
545
+ )
546
+
547
+ this.logger.info({ correctionId, memoryId, project: input.project }, 'Correction stored')
548
+ return correctionId
549
+ } catch (error) {
550
+ this.logger.error({ error, input }, 'Failed to store correction')
551
+ throw error
552
+ }
553
+ }
554
+
555
+ /**
556
+ * Get patterns by project with optional type filter
557
+ */
558
+ getPatternsByProject(project: string, options?: {
559
+ pattern_type?: 'solution' | 'anti-pattern' | 'best-practice' | 'common-issue'
560
+ limit?: number
561
+ }): FormattedPatternResult[] {
562
+ try {
563
+ let sql = 'SELECT * FROM patterns WHERE project = ?'
564
+ const params: (string | number)[] = [project]
565
+
566
+ if (options?.pattern_type) {
567
+ sql += ' AND pattern_type = ?'
568
+ params.push(options.pattern_type)
569
+ }
570
+
571
+ sql += ' ORDER BY created_at DESC'
572
+
573
+ if (options?.limit) {
574
+ sql += ' LIMIT ?'
575
+ params.push(options.limit)
576
+ }
577
+
578
+ const stmt = this.db.prepare(sql)
579
+ const rows = stmt.all(...params) as PatternRow[]
580
+
581
+ return rows.map(row => ({
582
+ id: row.id,
583
+ description: row.description,
584
+ metadata: {
585
+ project: row.project,
586
+ pattern_type: row.pattern_type,
587
+ description: row.description,
588
+ example: row.example || '',
589
+ confidence: row.confidence,
590
+ context: row.context || '',
591
+ created_at: row.created_at,
592
+ first_seen: row.created_at,
593
+ last_seen: row.created_at,
594
+ occurrences: 1
595
+ }
596
+ }))
597
+ } catch (error) {
598
+ this.logger.error({ error, project }, 'Failed to get patterns by project')
599
+ return []
600
+ }
601
+ }
602
+
603
+ /**
604
+ * Get corrections by project
605
+ */
606
+ getCorrectionsByProject(project: string, limit: number = 10): FormattedCorrectionResult[] {
607
+ try {
608
+ const stmt = this.db.prepare(
609
+ 'SELECT * FROM corrections WHERE project = ? ORDER BY created_at DESC LIMIT ?'
610
+ )
611
+ const rows = stmt.all(project, limit) as CorrectionRow[]
612
+
613
+ return rows.map(row => ({
614
+ id: row.id,
615
+ correction: row.correction,
616
+ description: row.correction,
617
+ metadata: {
618
+ project: row.project,
619
+ original: row.original,
620
+ correction: row.correction,
621
+ description: row.correction,
622
+ reasoning: row.reasoning,
623
+ context: row.context || '',
624
+ confidence: row.confidence,
625
+ created_at: row.created_at,
626
+ first_seen: row.created_at,
627
+ last_seen: row.created_at,
628
+ occurrences: 1
629
+ }
630
+ }))
631
+ } catch (error) {
632
+ this.logger.error({ error, project }, 'Failed to get corrections by project')
633
+ return []
634
+ }
635
+ }
636
+
637
+ /**
638
+ * Search patterns using semantic search on underlying memories
639
+ */
640
+ async searchPatterns(
641
+ query: string,
642
+ options?: {
643
+ project?: string
644
+ pattern_type?: 'solution' | 'anti-pattern' | 'best-practice' | 'common-issue'
645
+ limit?: number
646
+ minSimilarity?: number
647
+ }
648
+ ): Promise<ScoredSearchResult[]> {
649
+ try {
650
+ // If no query, return all patterns for the project
651
+ if (!query && options?.project) {
652
+ return this.getPatternsByProject(options.project, {
653
+ pattern_type: options?.pattern_type,
654
+ limit: options?.limit
655
+ })
656
+ }
657
+
658
+ // Generate embedding for semantic search
659
+ const queryEmbedding = await this.embeddings.generateEmbedding(query || 'pattern')
660
+
661
+ let sql = `
662
+ SELECT p.*, m.embedding, m.content as memory_content
663
+ FROM patterns p
664
+ JOIN memories m ON p.memory_id = m.id
665
+ WHERE 1=1
666
+ `
667
+ const params: (string | number)[] = []
668
+
669
+ if (options?.project) {
670
+ sql += ' AND p.project = ?'
671
+ params.push(options.project)
672
+ }
673
+ if (options?.pattern_type) {
674
+ sql += ' AND p.pattern_type = ?'
675
+ params.push(options.pattern_type)
676
+ }
677
+
678
+ const stmt = this.db.prepare(sql)
679
+ const rows = stmt.all(...params) as PatternWithEmbeddingRow[]
680
+
681
+ const results = rows.map(row => {
682
+ const embedding = bufferToEmbedding(row.embedding)
683
+ const similarity = cosineSimilarity(queryEmbedding, embedding)
684
+ return {
685
+ id: row.id,
686
+ content: row.description,
687
+ metadata: {
688
+ project: row.project,
689
+ pattern_type: row.pattern_type,
690
+ description: row.description,
691
+ example: row.example || '',
692
+ confidence: row.confidence,
693
+ context: row.context || '',
694
+ created_at: row.created_at,
695
+ first_seen: row.created_at,
696
+ last_seen: row.created_at,
697
+ occurrences: 1
698
+ },
699
+ similarity
700
+ }
701
+ })
702
+ .filter(r => r.similarity >= (options?.minSimilarity || 0.3))
703
+ .sort((a, b) => b.similarity - a.similarity)
704
+ .slice(0, options?.limit || 10)
705
+
706
+ return results
707
+ } catch (error) {
708
+ this.logger.error({ error, query }, 'Pattern search failed')
709
+ return []
710
+ }
711
+ }
712
+
713
+ /**
714
+ * Search corrections using semantic search on underlying memories
715
+ */
716
+ async searchCorrections(
717
+ query: string,
718
+ options?: {
719
+ project?: string
720
+ limit?: number
721
+ minSimilarity?: number
722
+ }
723
+ ): Promise<ScoredSearchResult[]> {
724
+ try {
725
+ // If no query, return all corrections for the project
726
+ if (!query && options?.project) {
727
+ return this.getCorrectionsByProject(options.project, options?.limit || 10)
728
+ }
729
+
730
+ const queryEmbedding = await this.embeddings.generateEmbedding(query || 'correction')
731
+
732
+ let sql = `
733
+ SELECT c.*, m.embedding, m.content as memory_content
734
+ FROM corrections c
735
+ JOIN memories m ON c.memory_id = m.id
736
+ WHERE 1=1
737
+ `
738
+ const params: (string | number)[] = []
739
+
740
+ if (options?.project) {
741
+ sql += ' AND c.project = ?'
742
+ params.push(options.project)
743
+ }
744
+
745
+ const stmt = this.db.prepare(sql)
746
+ const rows = stmt.all(...params) as CorrectionWithEmbeddingRow[]
747
+
748
+ const results = rows.map(row => {
749
+ const embedding = bufferToEmbedding(row.embedding)
750
+ const similarity = cosineSimilarity(queryEmbedding, embedding)
751
+ return {
752
+ id: row.id,
753
+ content: row.correction,
754
+ description: row.correction,
755
+ metadata: {
756
+ project: row.project,
757
+ original: row.original,
758
+ correction: row.correction,
759
+ description: row.correction,
760
+ reasoning: row.reasoning,
761
+ context: row.context || '',
762
+ confidence: row.confidence,
763
+ created_at: row.created_at,
764
+ first_seen: row.created_at,
765
+ last_seen: row.created_at,
766
+ occurrences: 1
767
+ },
768
+ similarity
769
+ }
770
+ })
771
+ .filter(r => r.similarity >= (options?.minSimilarity || 0.3))
772
+ .sort((a, b) => b.similarity - a.similarity)
773
+ .slice(0, options?.limit || 10)
774
+
775
+ return results
776
+ } catch (error) {
777
+ this.logger.error({ error, query }, 'Correction search failed')
778
+ return []
779
+ }
780
+ }
781
+
782
+ /**
783
+ * Get all decisions with full content (for analytical tools without ChromaDB)
784
+ */
785
+ getAllDecisionsWithContent(project?: string): Record<string, unknown>[] {
786
+ try {
787
+ let sql = `
788
+ SELECT d.id, d.context, d.decision, d.reasoning, d.alternatives, d.tags,
789
+ m.content, m.project, m.created_at, m.metadata
790
+ FROM decisions d
791
+ JOIN memories m ON d.memory_id = m.id
792
+ `
793
+ const params: string[] = []
794
+ if (project) {
795
+ sql += ' WHERE m.project = ?'
796
+ params.push(project)
797
+ }
798
+ sql += ' ORDER BY m.created_at DESC'
799
+
800
+ const stmt = this.db.prepare(sql)
801
+ const rows = stmt.all(...params) as DecisionWithContentRow[]
802
+
803
+ return rows.map(row => ({
804
+ id: row.id,
805
+ content: row.content,
806
+ date: row.created_at,
807
+ project: row.project,
808
+ context: row.context,
809
+ decision: row.decision,
810
+ reasoning: row.reasoning,
811
+ alternatives: row.alternatives,
812
+ tags: row.tags ? JSON.parse(row.tags) : []
813
+ }))
814
+ } catch (error) {
815
+ this.logger.error({ error, project }, 'Failed to get all decisions with content')
816
+ return []
817
+ }
818
+ }
819
+
820
+ /**
821
+ * Get all patterns with full content (for analytical tools without ChromaDB)
822
+ */
823
+ getAllPatternsWithContent(project?: string): Record<string, unknown>[] {
824
+ try {
825
+ let sql = `
826
+ SELECT p.id, p.pattern_type, p.description, p.example, p.confidence, p.context,
827
+ m.content, m.project, m.created_at
828
+ FROM patterns p
829
+ JOIN memories m ON p.memory_id = m.id
830
+ `
831
+ const params: string[] = []
832
+ if (project) {
833
+ sql += ' WHERE p.project = ?'
834
+ params.push(project)
835
+ }
836
+ sql += ' ORDER BY m.created_at DESC'
837
+
838
+ const stmt = this.db.prepare(sql)
839
+ const rows = stmt.all(...params) as PatternWithContentRow[]
840
+
841
+ return rows.map(row => ({
842
+ id: row.id,
843
+ content: row.content,
844
+ date: row.created_at,
845
+ project: row.project,
846
+ pattern_type: row.pattern_type,
847
+ description: row.description,
848
+ example: row.example,
849
+ confidence: row.confidence,
850
+ context: row.context
851
+ }))
852
+ } catch (error) {
853
+ this.logger.error({ error, project }, 'Failed to get all patterns with content')
854
+ return []
855
+ }
856
+ }
857
+
858
+ /**
859
+ * Get all corrections with full content (for analytical tools without ChromaDB)
860
+ */
861
+ getAllCorrectionsWithContent(project?: string): Record<string, unknown>[] {
862
+ try {
863
+ let sql = `
864
+ SELECT c.id, c.original, c.correction, c.reasoning, c.context as correction_context,
865
+ c.confidence, m.content, m.project, m.created_at
866
+ FROM corrections c
867
+ JOIN memories m ON c.memory_id = m.id
868
+ `
869
+ const params: string[] = []
870
+ if (project) {
871
+ sql += ' WHERE c.project = ?'
872
+ params.push(project)
873
+ }
874
+ sql += ' ORDER BY m.created_at DESC'
875
+
876
+ const stmt = this.db.prepare(sql)
877
+ const rows = stmt.all(...params) as CorrectionWithContentRow[]
878
+
879
+ return rows.map(row => ({
880
+ id: row.id,
881
+ content: row.content,
882
+ date: row.created_at,
883
+ project: row.project,
884
+ original: row.original,
885
+ correction: row.correction,
886
+ reasoning: row.reasoning,
887
+ context: row.correction_context,
888
+ confidence: row.confidence
889
+ }))
890
+ } catch (error) {
891
+ this.logger.error({ error, project }, 'Failed to get all corrections with content')
892
+ return []
893
+ }
894
+ }
895
+
896
+ /**
897
+ * Convert database row to Decision object
898
+ */
899
+ private rowToDecision(row: DecisionRow): Decision {
900
+ return {
901
+ id: row.id,
902
+ memoryId: row.memory_id,
903
+ context: row.context,
904
+ decision: row.decision,
905
+ reasoning: row.reasoning,
906
+ alternatives: row.alternatives || undefined,
907
+ outcome: row.outcome || undefined,
908
+ tags: row.tags ? JSON.parse(row.tags) : undefined
909
+ }
910
+ }
911
+ }