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.
- package/README.md +241 -191
- package/VERSION +1 -1
- package/assets/CLAUDE-unified.md +11 -11
- package/assets/CLAUDE.md +29 -29
- package/package.json +7 -3
- package/packs/backend/node.json +173 -173
- package/packs/core/javascript.json +176 -176
- package/packs/core/typescript.json +222 -222
- package/packs/frontend/react.json +254 -254
- package/packs/meta/testing.json +172 -172
- package/scripts/postinstall.mjs +531 -531
- package/src/automation/decision-detector.ts +452 -452
- package/src/automation/phase12-manager.ts +456 -456
- package/src/automation/proactive-recall.ts +373 -373
- package/src/automation/project-detector.ts +310 -310
- package/src/automation/repo-scanner.ts +210 -205
- package/src/cli/auto-setup.ts +75 -75
- package/src/cli/auto-start.ts +266 -266
- package/src/cli/bin.ts +264 -264
- package/src/cli/commands/autostart.ts +90 -90
- package/src/cli/commands/chroma.ts +578 -577
- package/src/cli/commands/export-training.ts +70 -70
- package/src/cli/commands/export.ts +130 -130
- package/src/cli/commands/git-hook.ts +183 -183
- package/src/cli/commands/hooks.ts +217 -217
- package/src/cli/commands/init.ts +123 -123
- package/src/cli/commands/install-mcp.ts +122 -111
- package/src/cli/commands/models.ts +979 -979
- package/src/cli/commands/pack.ts +200 -200
- package/src/cli/commands/refresh.ts +344 -339
- package/src/cli/commands/reindex.ts +120 -120
- package/src/cli/commands/serve.ts +466 -463
- package/src/cli/commands/start.ts +44 -44
- package/src/cli/commands/status.ts +220 -203
- package/src/cli/commands/uninstall-mcp.ts +45 -41
- package/src/cli/commands/update.ts +130 -124
- package/src/cli/migrate-chroma.ts +106 -106
- package/src/cli/ui/animations.ts +80 -80
- package/src/cli/ui/components.ts +82 -82
- package/src/cli/ui/index.ts +4 -4
- package/src/cli/ui/logo.ts +36 -36
- package/src/cli/ui/theme.ts +55 -55
- package/src/code-intelligence/indexer.ts +352 -352
- package/src/code-intelligence/linker.ts +178 -178
- package/src/code-intelligence/parser.ts +484 -484
- package/src/code-intelligence/query.ts +291 -291
- package/src/code-intelligence/schema.ts +83 -83
- package/src/code-intelligence/types.ts +95 -95
- package/src/config/defaults.ts +52 -52
- package/src/config/home.ts +56 -56
- package/src/config/index.ts +5 -5
- package/src/config/loader.ts +192 -192
- package/src/config/schema.ts +446 -415
- package/src/config/validator.ts +182 -182
- package/src/context/assembler.ts +407 -400
- package/src/context/index.ts +79 -79
- package/src/context/progress-tracker.ts +174 -174
- package/src/context/standards-manager.ts +287 -287
- package/src/context/validator.ts +58 -58
- package/src/diagnostics/index.ts +122 -121
- package/src/health/index.ts +233 -232
- package/src/hooks/brain-hook.ts +134 -131
- package/src/hooks/capture.ts +168 -168
- package/src/hooks/claude-code-mastery.md +112 -112
- package/src/hooks/context-hook.ts +260 -245
- package/src/hooks/deduplicator.ts +72 -72
- package/src/hooks/git-capture.ts +109 -109
- package/src/hooks/git-hook-installer.ts +211 -207
- package/src/hooks/index.ts +20 -20
- package/src/hooks/installer.ts +306 -288
- package/src/hooks/interceptor-hook.ts +204 -201
- package/src/hooks/passive-classifier.ts +397 -397
- package/src/hooks/queue.ts +160 -129
- package/src/hooks/session-tracker.ts +312 -312
- package/src/hooks/types.ts +52 -52
- package/src/index.ts +7 -7
- package/src/intelligence/cross-project/generalizer.ts +283 -283
- package/src/intelligence/cross-project/index.ts +7 -7
- package/src/intelligence/hf-downloader.ts +222 -222
- package/src/intelligence/hf-manifest.json +78 -78
- package/src/intelligence/index.ts +24 -24
- package/src/intelligence/inference-router.ts +762 -762
- package/src/intelligence/model-manager.ts +263 -245
- package/src/intelligence/optimization/index.ts +10 -10
- package/src/intelligence/optimization/precompute.ts +202 -202
- package/src/intelligence/optimization/semantic-cache.ts +213 -207
- package/src/intelligence/prediction/index.ts +7 -7
- package/src/intelligence/prediction/recommender.ts +276 -268
- package/src/intelligence/reasoning/chain-retrieval.ts +243 -247
- package/src/intelligence/reasoning/index.ts +7 -7
- package/src/intelligence/temporal/evolution.ts +193 -197
- package/src/intelligence/temporal/index.ts +16 -16
- package/src/intelligence/temporal/query-processor.ts +190 -190
- package/src/intelligence/temporal/timeline.ts +272 -259
- package/src/intelligence/temporal/trends.ts +263 -263
- package/src/intelligence/tokenizer.ts +118 -118
- package/src/knowledge/entity-extractor.ts +447 -443
- package/src/knowledge/graph/builder.ts +185 -185
- package/src/knowledge/graph/linker.ts +201 -201
- package/src/knowledge/graph/memory-graph.ts +359 -359
- package/src/knowledge/graph/schema.ts +99 -99
- package/src/knowledge/graph/search.ts +166 -166
- package/src/knowledge/relationship-extractor.ts +108 -108
- package/src/memory/chroma/client.ts +211 -192
- package/src/memory/chroma/collection-manager.ts +92 -92
- package/src/memory/chroma/config.ts +57 -57
- package/src/memory/chroma/embeddings.ts +177 -175
- package/src/memory/chroma/index.ts +82 -82
- package/src/memory/chroma/migration.ts +270 -270
- package/src/memory/chroma/schemas.ts +69 -69
- package/src/memory/chroma/search.ts +319 -315
- package/src/memory/chroma/store.ts +755 -747
- package/src/memory/compression.ts +121 -121
- package/src/memory/consolidation/archiver.ts +162 -165
- package/src/memory/consolidation/merger.ts +182 -186
- package/src/memory/consolidation/scorer.ts +136 -136
- package/src/memory/database.ts +9 -0
- package/src/memory/dual-write.ts +145 -0
- package/src/memory/embeddings.ts +226 -226
- package/src/memory/episodic/detector.ts +108 -108
- package/src/memory/episodic/manager.ts +347 -351
- package/src/memory/episodic/summarizer.ts +179 -179
- package/src/memory/episodic/types.ts +52 -52
- package/src/memory/fts5-search.ts +692 -633
- package/src/memory/index.ts +943 -1060
- package/src/memory/migrations/add-fts5.ts +118 -108
- package/src/memory/patterns.ts +438 -438
- package/src/memory/pruning.ts +60 -60
- package/src/memory/schema.ts +88 -88
- package/src/memory/store.ts +911 -787
- package/src/orchestrator/handlers/decision-handler.ts +204 -204
- package/src/packs/index.ts +9 -9
- package/src/packs/loader.ts +134 -134
- package/src/packs/manager.ts +204 -204
- package/src/packs/ranker.ts +78 -78
- package/src/packs/types.ts +81 -81
- package/src/phase12/index.ts +5 -5
- package/src/retrieval/bm25/index.ts +300 -297
- package/src/retrieval/bm25/tokenizer.ts +184 -184
- package/src/retrieval/feedback/adaptive.ts +221 -221
- package/src/retrieval/feedback/index.ts +16 -16
- package/src/retrieval/feedback/metrics.ts +221 -221
- package/src/retrieval/feedback/store.ts +283 -283
- package/src/retrieval/fusion/index.ts +194 -194
- package/src/retrieval/fusion/rrf.ts +165 -165
- package/src/retrieval/index.ts +12 -12
- package/src/retrieval/pipeline.ts +375 -375
- package/src/retrieval/query/expander.ts +203 -203
- package/src/retrieval/query/index.ts +27 -27
- package/src/retrieval/query/intent-classifier.ts +252 -252
- package/src/retrieval/query/temporal-parser.ts +295 -295
- package/src/retrieval/reranker/index.ts +189 -188
- package/src/retrieval/reranker/model.ts +99 -95
- package/src/retrieval/service.ts +125 -125
- package/src/retrieval/types.ts +162 -162
- package/src/routing/entity-extractor.ts +454 -454
- package/src/routing/handlers/exploration-handler.ts +369 -0
- package/src/routing/handlers/index.ts +19 -0
- package/src/routing/handlers/memory-handler.ts +273 -0
- package/src/routing/handlers/mutation-handler.ts +241 -0
- package/src/routing/handlers/recall-handler.ts +642 -0
- package/src/routing/handlers/shared.ts +515 -0
- package/src/routing/handlers/types.ts +48 -0
- package/src/routing/intent-classifier.ts +552 -552
- package/src/routing/response-filter.ts +399 -391
- package/src/routing/router.ts +245 -2193
- package/src/routing/search-engine.ts +521 -514
- package/src/routing/types.ts +104 -94
- package/src/scripts/health-check.ts +118 -118
- package/src/scripts/setup.ts +122 -122
- package/src/server/auto-updater.ts +283 -276
- package/src/server/handlers/call-tool.ts +159 -159
- package/src/server/handlers/list-tools.ts +35 -35
- package/src/server/handlers/tools/auto-remember.ts +165 -165
- package/src/server/handlers/tools/brain.ts +86 -86
- package/src/server/handlers/tools/create-project.ts +135 -135
- package/src/server/handlers/tools/get-code-standards.ts +123 -123
- package/src/server/handlers/tools/get-corrections.ts +152 -152
- package/src/server/handlers/tools/get-patterns.ts +156 -156
- package/src/server/handlers/tools/get-project-context.ts +75 -75
- package/src/server/handlers/tools/index.ts +30 -30
- package/src/server/handlers/tools/init-project.ts +756 -756
- package/src/server/handlers/tools/list-projects.ts +126 -126
- package/src/server/handlers/tools/recall-similar.ts +87 -87
- package/src/server/handlers/tools/recognize-pattern.ts +132 -132
- package/src/server/handlers/tools/record-correction.ts +131 -131
- package/src/server/handlers/tools/remember-decision.ts +168 -168
- package/src/server/handlers/tools/schemas.ts +179 -179
- package/src/server/handlers/tools/search-code.ts +122 -122
- package/src/server/handlers/tools/smart-context.ts +146 -146
- package/src/server/handlers/tools/update-progress.ts +131 -131
- package/src/server/http-api.ts +215 -1229
- package/src/server/mcp-proxy.ts +85 -84
- package/src/server/mcp-server.ts +285 -284
- package/src/server/middleware/auth.ts +39 -0
- package/src/server/middleware/error-handler.ts +37 -0
- package/src/server/middleware/rate-limit.ts +53 -0
- package/src/server/middleware/validate.ts +42 -0
- package/src/server/pid-manager.ts +137 -136
- package/src/server/providers/resources.ts +581 -581
- package/src/server/routes/code.ts +228 -0
- package/src/server/routes/context.ts +26 -0
- package/src/server/routes/health.ts +19 -0
- package/src/server/routes/helpers.ts +100 -0
- package/src/server/routes/hooks.ts +197 -0
- package/src/server/routes/mcp.ts +47 -0
- package/src/server/routes/memory.ts +397 -0
- package/src/server/routes/models.ts +96 -0
- package/src/server/routes/projects.ts +89 -0
- package/src/server/routes/types.ts +21 -0
- package/src/server/schemas/api-schemas.ts +202 -0
- package/src/server/services.ts +720 -720
- package/src/server/utils/memory-indicator.ts +84 -84
- package/src/server/utils/response-formatter.ts +129 -129
- package/src/server/web-viewer.ts +1145 -1115
- package/src/setup/index.ts +38 -38
- package/src/tools/registry.ts +115 -115
- package/src/tools/schemas.ts +666 -666
- package/src/tools/types.ts +412 -412
- package/src/training/data-store.ts +320 -298
- package/src/training/retrain-pipeline.ts +399 -394
- package/src/utils/error-handler.ts +136 -136
- package/src/utils/index.ts +58 -58
- package/src/utils/kill-port.ts +55 -53
- package/src/utils/phase12-helper.ts +56 -56
- package/src/utils/safe-path.ts +43 -0
- package/src/utils/timing.ts +47 -47
- package/src/utils/transaction.ts +63 -63
- package/src/vault/index.ts +4 -3
- package/src/vault/paths.ts +106 -106
- package/src/vault/query.ts +4 -1
- package/src/vault/reader.ts +44 -1
- package/src/vault/watcher.ts +24 -1
- package/src/vault/writer.ts +487 -413
- package/skills/persistent-memory/SKILL.md +0 -148
- package/skills/persistent-memory/references/tool-reference.md +0 -90
package/src/memory/index.ts
CHANGED
|
@@ -1,1060 +1,943 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Memory System - Main Module
|
|
3
|
-
* Phase 3: Memory and Embedding System
|
|
4
|
-
*
|
|
5
|
-
* Unified memory system manager that combines all components
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { randomUUID } from 'crypto'
|
|
9
|
-
import type { Logger } from 'pino'
|
|
10
|
-
import { MemoryDatabase } from './database'
|
|
11
|
-
import { EmbeddingService } from './embeddings'
|
|
12
|
-
import { MemoryStore } from './store'
|
|
13
|
-
import { SemanticSearch } from './search'
|
|
14
|
-
import { MemoryContextBuilder } from './context-builder'
|
|
15
|
-
import type { MemorySystemStats } from './types'
|
|
16
|
-
import { ChromaManager, DEFAULT_CHROMA_CONFIG, getChromaConfigFromEnv, ChromaMigration, type MigrationOptions } from './chroma'
|
|
17
|
-
import { FTS5Search } from './fts5-search'
|
|
18
|
-
import { addFTS5Tables } from './migrations/add-fts5'
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export
|
|
22
|
-
export
|
|
23
|
-
export {
|
|
24
|
-
export {
|
|
25
|
-
export {
|
|
26
|
-
export {
|
|
27
|
-
export {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
export {
|
|
42
|
-
export {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
export
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
*
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
readonly
|
|
55
|
-
readonly
|
|
56
|
-
readonly
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
private
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
private
|
|
67
|
-
private
|
|
68
|
-
private
|
|
69
|
-
private
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
this.
|
|
83
|
-
this.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
this.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
this.
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
this.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
this.
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
this.
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
return
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
if (
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
const collection = await
|
|
770
|
-
const results = await collection.get({
|
|
771
|
-
where: project ? { project } : undefined
|
|
772
|
-
})
|
|
773
|
-
if (results && results.ids) {
|
|
774
|
-
return results.ids.map((id: string, i: number) => ({
|
|
775
|
-
id,
|
|
776
|
-
content: results.documents?.[i] || '',
|
|
777
|
-
date: results.metadatas?.[i]?.created_at || new Date().toISOString(),
|
|
778
|
-
project: results.metadatas?.[i]?.project || project || 'unknown',
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
reasoning: results.metadatas?.[i]?.reasoning || '',
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
}))
|
|
785
|
-
}
|
|
786
|
-
return []
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
})
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
const results = this._fts5.searchWithConfidence(query, options?.project, options?.limit || 10)
|
|
945
|
-
const corrections = results.filter(r => r.category === 'correction')
|
|
946
|
-
if (corrections.length > 0) {
|
|
947
|
-
return corrections.map(r => ({
|
|
948
|
-
id: r.id,
|
|
949
|
-
content: r.content,
|
|
950
|
-
metadata: {
|
|
951
|
-
project: r.project,
|
|
952
|
-
reasoning: r.reasoning || '',
|
|
953
|
-
context: r.context || '',
|
|
954
|
-
confidence: r.confidence,
|
|
955
|
-
created_at: r.created_at
|
|
956
|
-
},
|
|
957
|
-
similarity: r.score
|
|
958
|
-
}))
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
if (this.useChromaDB) {
|
|
963
|
-
return this.chroma.store.searchCorrections(query, options)
|
|
964
|
-
}
|
|
965
|
-
return this.store.searchCorrections(query, options)
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
/**
|
|
969
|
-
* Delete a decision by ID — removes from FTS5 + embeddings + ChromaDB/SQLite
|
|
970
|
-
*/
|
|
971
|
-
async deleteDecision(id: string): Promise<void> {
|
|
972
|
-
// Phase 26: Delete from FTS5 and embeddings
|
|
973
|
-
if (this._fts5) {
|
|
974
|
-
try {
|
|
975
|
-
this._fts5.delete(id)
|
|
976
|
-
this._fts5.deleteEmbedding(id)
|
|
977
|
-
} catch (error) {
|
|
978
|
-
this.logger.warn({ error, id }, 'FTS5 delete failed')
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
if (this.useChromaDB) {
|
|
983
|
-
await this.chroma.store.deleteDecision(id)
|
|
984
|
-
} else {
|
|
985
|
-
this.store.deleteMemory(id)
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
// Notify listeners (e.g., knowledge graph builder) about deletion
|
|
989
|
-
for (const cb of this.onDecisionDeletedCallbacks) {
|
|
990
|
-
try {
|
|
991
|
-
cb(id)
|
|
992
|
-
} catch {}
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
/**
|
|
997
|
-
* Update a decision by deleting the old one and storing a new version.
|
|
998
|
-
* Phase 20: Ensures both ChromaDB and knowledge graph are atomically updated.
|
|
999
|
-
*/
|
|
1000
|
-
async updateDecision(
|
|
1001
|
-
oldId: string,
|
|
1002
|
-
project: string,
|
|
1003
|
-
context: string,
|
|
1004
|
-
decision: string,
|
|
1005
|
-
reasoning: string,
|
|
1006
|
-
options?: { alternatives?: string; tags?: string[] }
|
|
1007
|
-
): Promise<string> {
|
|
1008
|
-
// BUG-001: True in-place update using FTS5 (preserves original ID)
|
|
1009
|
-
if (this._fts5) {
|
|
1010
|
-
try {
|
|
1011
|
-
this._fts5.update(oldId, {
|
|
1012
|
-
content: decision,
|
|
1013
|
-
reasoning,
|
|
1014
|
-
context,
|
|
1015
|
-
tags: options?.tags
|
|
1016
|
-
})
|
|
1017
|
-
this.logger.debug({ oldId }, 'Decision updated in-place via FTS5')
|
|
1018
|
-
|
|
1019
|
-
// Also update ChromaDB if available (best-effort)
|
|
1020
|
-
if (this.useChromaDB) {
|
|
1021
|
-
try {
|
|
1022
|
-
await this.chroma.store.deleteDecision(oldId)
|
|
1023
|
-
await this.chroma.store.storeDecision({
|
|
1024
|
-
project,
|
|
1025
|
-
context,
|
|
1026
|
-
decision,
|
|
1027
|
-
reasoning,
|
|
1028
|
-
alternatives: options?.alternatives,
|
|
1029
|
-
tags: options?.tags
|
|
1030
|
-
})
|
|
1031
|
-
} catch {
|
|
1032
|
-
// ChromaDB sync failed, FTS5 is source of truth
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
return oldId // SAME ID preserved
|
|
1037
|
-
} catch (error) {
|
|
1038
|
-
this.logger.warn({ error, oldId }, 'FTS5 in-place update failed, falling back to delete+store')
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
// Fallback: delete + store (legacy behavior for non-FTS5 backends)
|
|
1043
|
-
try {
|
|
1044
|
-
await this.deleteDecision(oldId)
|
|
1045
|
-
this.logger.debug({ oldId }, 'Old decision deleted for update')
|
|
1046
|
-
} catch (error) {
|
|
1047
|
-
this.logger.warn({ error, oldId }, 'Failed to delete old decision during update, storing new version anyway')
|
|
1048
|
-
}
|
|
1049
|
-
const newId = await this.rememberDecision(project, context, decision, reasoning, options)
|
|
1050
|
-
this.logger.debug({ oldId, newId }, 'Decision updated: old deleted, new stored')
|
|
1051
|
-
return newId
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
/**
|
|
1056
|
-
* Create a memory manager instance
|
|
1057
|
-
*/
|
|
1058
|
-
export function createMemoryManager(dbPath: string, logger: Logger): MemoryManager {
|
|
1059
|
-
return new MemoryManager(dbPath, logger)
|
|
1060
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Memory System - Main Module
|
|
3
|
+
* Phase 3: Memory and Embedding System
|
|
4
|
+
*
|
|
5
|
+
* Unified memory system manager that combines all components
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { randomUUID } from 'crypto'
|
|
9
|
+
import type { Logger } from 'pino'
|
|
10
|
+
import { MemoryDatabase } from './database'
|
|
11
|
+
import { EmbeddingService } from './embeddings'
|
|
12
|
+
import { MemoryStore } from './store'
|
|
13
|
+
import { SemanticSearch } from './search'
|
|
14
|
+
import { MemoryContextBuilder } from './context-builder'
|
|
15
|
+
import type { MemorySystemStats } from './types'
|
|
16
|
+
import { ChromaManager, DEFAULT_CHROMA_CONFIG, getChromaConfigFromEnv, ChromaMigration, type MigrationOptions } from './chroma'
|
|
17
|
+
import { FTS5Search } from './fts5-search'
|
|
18
|
+
import { addFTS5Tables } from './migrations/add-fts5'
|
|
19
|
+
import { DualWriteManager } from './dual-write'
|
|
20
|
+
|
|
21
|
+
// Re-export all types and classes for external use
|
|
22
|
+
export * from './types'
|
|
23
|
+
export { MemoryDatabase } from './database'
|
|
24
|
+
export { EmbeddingService } from './embeddings'
|
|
25
|
+
export { MemoryStore } from './store'
|
|
26
|
+
export { SemanticSearch } from './search'
|
|
27
|
+
export { MemoryContextBuilder, type ContextOptions } from './context-builder'
|
|
28
|
+
export {
|
|
29
|
+
embeddingToBuffer,
|
|
30
|
+
bufferToEmbedding,
|
|
31
|
+
normalizeEmbedding,
|
|
32
|
+
euclideanDistance,
|
|
33
|
+
cosineSimilarity,
|
|
34
|
+
dotProduct,
|
|
35
|
+
magnitude,
|
|
36
|
+
averageEmbeddings,
|
|
37
|
+
topKSimilar
|
|
38
|
+
} from './embedding-utils'
|
|
39
|
+
|
|
40
|
+
// Phase 12: Advanced Memory Features
|
|
41
|
+
export { PatternRecognizer, type Pattern } from './patterns'
|
|
42
|
+
export { LearningSystem, type Correction, type Preference, type LearningInsights } from './learning'
|
|
43
|
+
export { KnowledgeExtractor, type ExtractedKnowledge, type ExtractionResult } from './knowledge-extractor'
|
|
44
|
+
|
|
45
|
+
// Phase 26: FTS5 Search
|
|
46
|
+
export { FTS5Search } from './fts5-search'
|
|
47
|
+
export type { ObservationCategory, NewObservation, ObservationResult, ScoredResult } from './fts5-search'
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Unified memory system manager
|
|
51
|
+
* Combines database, embeddings, store, search, and context building
|
|
52
|
+
*/
|
|
53
|
+
export class MemoryManager {
|
|
54
|
+
readonly database: MemoryDatabase
|
|
55
|
+
readonly embeddings: EmbeddingService
|
|
56
|
+
readonly contextBuilder: MemoryContextBuilder
|
|
57
|
+
readonly chroma: ChromaManager
|
|
58
|
+
|
|
59
|
+
// Phase 26: FTS5 search (always available, no external deps)
|
|
60
|
+
private _fts5: FTS5Search | null = null
|
|
61
|
+
|
|
62
|
+
// Store and search are initialized after database is ready
|
|
63
|
+
private _store: MemoryStore | null = null
|
|
64
|
+
private _search: SemanticSearch | null = null
|
|
65
|
+
|
|
66
|
+
private logger: Logger
|
|
67
|
+
private initialized: boolean = false
|
|
68
|
+
private useChromaDB: boolean = true
|
|
69
|
+
private onDecisionStoredCallbacks: ((input: Record<string, unknown>) => void)[] = []
|
|
70
|
+
private onDecisionDeletedCallbacks: ((id: string) => void)[] = []
|
|
71
|
+
|
|
72
|
+
// Task 3.5: Extracted dual-write logic
|
|
73
|
+
private dualWrite: DualWriteManager
|
|
74
|
+
|
|
75
|
+
constructor(
|
|
76
|
+
dbPath: string,
|
|
77
|
+
logger: Logger,
|
|
78
|
+
useChromaDB: boolean = true,
|
|
79
|
+
chromaConfig?: Record<string, unknown>,
|
|
80
|
+
customEmbeddings?: EmbeddingService
|
|
81
|
+
) {
|
|
82
|
+
this.logger = logger.child({ component: 'memory-manager' })
|
|
83
|
+
this.useChromaDB = useChromaDB
|
|
84
|
+
|
|
85
|
+
this.database = new MemoryDatabase(dbPath, logger)
|
|
86
|
+
this.embeddings = customEmbeddings || new EmbeddingService(logger)
|
|
87
|
+
this.contextBuilder = new MemoryContextBuilder(logger)
|
|
88
|
+
|
|
89
|
+
const envConfig = getChromaConfigFromEnv()
|
|
90
|
+
const config = { ...DEFAULT_CHROMA_CONFIG, ...envConfig, ...chromaConfig }
|
|
91
|
+
this.chroma = new ChromaManager(logger, config)
|
|
92
|
+
|
|
93
|
+
this.dualWrite = new DualWriteManager(
|
|
94
|
+
this.logger,
|
|
95
|
+
() => this._fts5,
|
|
96
|
+
() => ({ manager: this.chroma, enabled: this.useChromaDB }),
|
|
97
|
+
() => this.embeddings
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Initialize memory system
|
|
103
|
+
* Must be called before using store or search
|
|
104
|
+
*/
|
|
105
|
+
async initialize(): Promise<void> {
|
|
106
|
+
if (this.initialized) {
|
|
107
|
+
this.logger.warn('Memory system already initialized')
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
this.logger.info('Initializing memory system...')
|
|
113
|
+
|
|
114
|
+
await this.database.initialize()
|
|
115
|
+
|
|
116
|
+
await this.embeddings.initialize()
|
|
117
|
+
|
|
118
|
+
const db = this.database.getDb()
|
|
119
|
+
this._store = new MemoryStore(db, this.embeddings, this.logger)
|
|
120
|
+
this._search = new SemanticSearch(db, this.embeddings, this.logger)
|
|
121
|
+
|
|
122
|
+
// Phase 26: Always initialize FTS5 (just SQLite, no external deps)
|
|
123
|
+
try {
|
|
124
|
+
addFTS5Tables(db)
|
|
125
|
+
this._fts5 = new FTS5Search(db, this.logger)
|
|
126
|
+
this.logger.info('FTS5 search initialized')
|
|
127
|
+
|
|
128
|
+
// Phase 26b: Backfill embeddings for existing observations (async, non-blocking)
|
|
129
|
+
if (this.embeddings.isReady()) {
|
|
130
|
+
this.backfillEmbeddings().then(count => {
|
|
131
|
+
if (count > 0) {
|
|
132
|
+
this.logger.info({ count }, 'Backfilled embeddings for existing observations')
|
|
133
|
+
}
|
|
134
|
+
}).catch((error) => {
|
|
135
|
+
this.logger.warn({ error }, 'Background embedding backfill failed')
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
} catch (error) {
|
|
139
|
+
this.logger.warn({ error }, 'Failed to initialize FTS5, continuing without it')
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (this.useChromaDB) {
|
|
143
|
+
try {
|
|
144
|
+
await this.chroma.initialize()
|
|
145
|
+
this.logger.info('ChromaDB backend initialized successfully')
|
|
146
|
+
} catch (error) {
|
|
147
|
+
this.logger.warn({ error }, 'Failed to initialize ChromaDB, falling back to SQLite backend')
|
|
148
|
+
this.useChromaDB = false
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
this.initialized = true
|
|
153
|
+
this.logger.info('Memory system initialized successfully')
|
|
154
|
+
} catch (error) {
|
|
155
|
+
this.logger.error({ error }, 'Failed to initialize memory system')
|
|
156
|
+
throw error
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Get the memory store (throws if not initialized)
|
|
162
|
+
*/
|
|
163
|
+
get store(): MemoryStore {
|
|
164
|
+
if (!this._store) {
|
|
165
|
+
throw new Error('Memory system not initialized. Call initialize() first.')
|
|
166
|
+
}
|
|
167
|
+
return this._store
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get the semantic search engine (throws if not initialized)
|
|
172
|
+
*/
|
|
173
|
+
get search(): SemanticSearch {
|
|
174
|
+
if (!this._search) {
|
|
175
|
+
throw new Error('Memory system not initialized. Call initialize() first.')
|
|
176
|
+
}
|
|
177
|
+
return this._search
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Get the FTS5 search engine (null if not initialized)
|
|
182
|
+
*/
|
|
183
|
+
get fts5(): FTS5Search | null {
|
|
184
|
+
return this._fts5
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Check if memory system is initialized
|
|
189
|
+
*/
|
|
190
|
+
isInitialized(): boolean {
|
|
191
|
+
return this.initialized
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Backfill embeddings for existing observations that don't have them.
|
|
196
|
+
* Call this after initialization to migrate existing data.
|
|
197
|
+
*/
|
|
198
|
+
async backfillEmbeddings(batchSize: number = 50): Promise<number> {
|
|
199
|
+
if (!this._fts5 || !this.embeddings.isReady()) return 0
|
|
200
|
+
return this._fts5.backfillEmbeddings(this.embeddings, batchSize)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
close(): void {
|
|
204
|
+
if (this.useChromaDB) {
|
|
205
|
+
this.chroma.close()
|
|
206
|
+
}
|
|
207
|
+
this.database.close()
|
|
208
|
+
this._store = null
|
|
209
|
+
this._search = null
|
|
210
|
+
this.initialized = false
|
|
211
|
+
this.logger.info('Memory system closed')
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Get system statistics
|
|
216
|
+
*/
|
|
217
|
+
getStats(): MemorySystemStats {
|
|
218
|
+
if (!this.initialized) {
|
|
219
|
+
throw new Error('Memory system not initialized')
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
database: this.database.getStats(),
|
|
224
|
+
embeddings: this.embeddings.getCacheStats()
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Health check
|
|
230
|
+
*/
|
|
231
|
+
async healthCheck(): Promise<boolean> {
|
|
232
|
+
if (!this.initialized) {
|
|
233
|
+
return false
|
|
234
|
+
}
|
|
235
|
+
return this.database.healthCheck()
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Check if ChromaDB backend is enabled and connected
|
|
240
|
+
*/
|
|
241
|
+
isChromaDBEnabled(): boolean {
|
|
242
|
+
return this.useChromaDB
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Add a listener that fires when a decision is stored (from any backend)
|
|
247
|
+
*/
|
|
248
|
+
addDecisionStoredListener(callback: (input: Record<string, unknown>) => void): () => void {
|
|
249
|
+
this.onDecisionStoredCallbacks.push(callback)
|
|
250
|
+
let chromaUnsub: (() => void) | undefined
|
|
251
|
+
if (this.useChromaDB) {
|
|
252
|
+
chromaUnsub = this.chroma.store.addDecisionStoredListener(callback)
|
|
253
|
+
}
|
|
254
|
+
return () => {
|
|
255
|
+
const idx = this.onDecisionStoredCallbacks.indexOf(callback)
|
|
256
|
+
if (idx >= 0) this.onDecisionStoredCallbacks.splice(idx, 1)
|
|
257
|
+
chromaUnsub?.()
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Add a listener that fires when a decision is deleted
|
|
263
|
+
*/
|
|
264
|
+
addDecisionDeletedListener(callback: (id: string) => void): () => void {
|
|
265
|
+
this.onDecisionDeletedCallbacks.push(callback)
|
|
266
|
+
return () => {
|
|
267
|
+
const idx = this.onDecisionDeletedCallbacks.indexOf(callback)
|
|
268
|
+
if (idx >= 0) this.onDecisionDeletedCallbacks.splice(idx, 1)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async rememberDecision(
|
|
273
|
+
project: string,
|
|
274
|
+
context: string,
|
|
275
|
+
decision: string,
|
|
276
|
+
reasoning: string,
|
|
277
|
+
options?: { alternatives?: string; tags?: string[] }
|
|
278
|
+
): Promise<string> {
|
|
279
|
+
const sharedId = randomUUID()
|
|
280
|
+
|
|
281
|
+
// rememberDecision has a special duplicate check before the standard dual-write
|
|
282
|
+
if (this._fts5) {
|
|
283
|
+
const dup = this._fts5.searchForDuplicates(decision, project)
|
|
284
|
+
if (dup) {
|
|
285
|
+
this.logger.info({ existingId: dup.id, score: dup.score }, 'FTS5 skipping duplicate decision')
|
|
286
|
+
return dup.id
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const id = await this.dualWrite.store({
|
|
291
|
+
sharedId,
|
|
292
|
+
fts5Fn: (fts5, id) => fts5.store({
|
|
293
|
+
project,
|
|
294
|
+
category: 'decision',
|
|
295
|
+
content: decision,
|
|
296
|
+
reasoning,
|
|
297
|
+
context,
|
|
298
|
+
tags: options?.tags
|
|
299
|
+
}, id),
|
|
300
|
+
chromaFn: async (chroma, id) => chroma.store.storeDecision({
|
|
301
|
+
id,
|
|
302
|
+
project,
|
|
303
|
+
context,
|
|
304
|
+
decision,
|
|
305
|
+
reasoning,
|
|
306
|
+
alternatives: options?.alternatives,
|
|
307
|
+
tags: options?.tags
|
|
308
|
+
}),
|
|
309
|
+
sqliteFn: () => this.store.storeDecision({
|
|
310
|
+
project,
|
|
311
|
+
context,
|
|
312
|
+
decision,
|
|
313
|
+
reasoning,
|
|
314
|
+
alternatives: options?.alternatives,
|
|
315
|
+
tags: options?.tags
|
|
316
|
+
}),
|
|
317
|
+
embeddingText: [decision, reasoning, context].filter(Boolean).join(' ')
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
// Notify listeners (e.g., knowledge graph builder)
|
|
321
|
+
for (const cb of this.onDecisionStoredCallbacks) {
|
|
322
|
+
try {
|
|
323
|
+
cb({ project, context, decision, reasoning, alternatives: options?.alternatives, tags: options?.tags, id })
|
|
324
|
+
} catch {}
|
|
325
|
+
}
|
|
326
|
+
return id
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Get raw search results - uses FTS5 as primary, embeddings as semantic fallback
|
|
331
|
+
* Use this for internal operations that need raw results
|
|
332
|
+
*/
|
|
333
|
+
async searchRaw(
|
|
334
|
+
query: string,
|
|
335
|
+
options?: { project?: string; limit?: number; minSimilarity?: number }
|
|
336
|
+
): Promise<Record<string, unknown>[]> {
|
|
337
|
+
const limit = options?.limit || 5
|
|
338
|
+
const LOW_CONFIDENCE_THRESHOLD = 0.25
|
|
339
|
+
|
|
340
|
+
// Phase 26: Try FTS5 first (always available)
|
|
341
|
+
if (this._fts5) {
|
|
342
|
+
const ftsResults = this._fts5.searchWithConfidence(query, options?.project, limit)
|
|
343
|
+
|
|
344
|
+
// Check if FTS5 returned good results
|
|
345
|
+
const hasGoodResults = ftsResults.length > 0 &&
|
|
346
|
+
ftsResults.some(r => r.score >= LOW_CONFIDENCE_THRESHOLD)
|
|
347
|
+
|
|
348
|
+
if (hasGoodResults) {
|
|
349
|
+
return this.transformFtsResults(ftsResults)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Phase 26b: FTS5 returned nothing or low-confidence — try semantic search
|
|
353
|
+
if (this.embeddings.isReady() && this._fts5.hasEmbeddings()) {
|
|
354
|
+
try {
|
|
355
|
+
const queryEmbedding = await this.embeddings.generateEmbedding(query)
|
|
356
|
+
const semanticResults = await this._fts5.semanticSearch(
|
|
357
|
+
queryEmbedding,
|
|
358
|
+
options?.project,
|
|
359
|
+
limit,
|
|
360
|
+
options?.minSimilarity || 0.3
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
if (semanticResults.length > 0) {
|
|
364
|
+
this.logger.debug(
|
|
365
|
+
{ query, ftsCount: ftsResults.length, semanticCount: semanticResults.length },
|
|
366
|
+
'Semantic search found results where FTS5 did not'
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
// If we had some low-confidence FTS5 results, merge with semantic results
|
|
370
|
+
if (ftsResults.length > 0) {
|
|
371
|
+
return this.mergeSearchResults(ftsResults, semanticResults, limit)
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return this.transformFtsResults(semanticResults)
|
|
375
|
+
}
|
|
376
|
+
} catch (error) {
|
|
377
|
+
this.logger.warn({ error }, 'Semantic search fallback failed')
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Return whatever FTS5 found (even if low confidence)
|
|
382
|
+
if (ftsResults.length > 0) {
|
|
383
|
+
return this.transformFtsResults(ftsResults)
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Fallback: Try ChromaDB if available
|
|
388
|
+
if (this.useChromaDB) {
|
|
389
|
+
const chromaResults = await this.chroma.search.searchDecisions(query, {
|
|
390
|
+
project: options?.project,
|
|
391
|
+
limit,
|
|
392
|
+
minSimilarity: options?.minSimilarity || 0.3
|
|
393
|
+
})
|
|
394
|
+
return chromaResults.map(r => {
|
|
395
|
+
const memoryContent = typeof r.content === 'string' ? r.content : JSON.stringify(r.content)
|
|
396
|
+
const decisionObj = r.metadata.decision ? {
|
|
397
|
+
id: r.id,
|
|
398
|
+
project: r.metadata.project || options?.project || 'unknown',
|
|
399
|
+
context: r.metadata.context || '',
|
|
400
|
+
decision: r.metadata.decision || memoryContent,
|
|
401
|
+
reasoning: r.metadata.reasoning || '',
|
|
402
|
+
alternatives: r.metadata.alternatives_considered || '',
|
|
403
|
+
tags: r.metadata.tags || [],
|
|
404
|
+
outcome: r.metadata.outcome,
|
|
405
|
+
createdAt: r.metadata.created_at ? new Date(r.metadata.created_at) : new Date()
|
|
406
|
+
} : undefined
|
|
407
|
+
|
|
408
|
+
return {
|
|
409
|
+
id: r.id,
|
|
410
|
+
content: decisionObj ? decisionObj.decision : memoryContent,
|
|
411
|
+
memory: {
|
|
412
|
+
id: r.id,
|
|
413
|
+
project: r.metadata.project || options?.project || 'unknown',
|
|
414
|
+
content: memoryContent,
|
|
415
|
+
createdAt: r.metadata.created_at ? new Date(r.metadata.created_at) : new Date(),
|
|
416
|
+
metadata: r.metadata
|
|
417
|
+
},
|
|
418
|
+
similarity: r.similarity,
|
|
419
|
+
decision: decisionObj,
|
|
420
|
+
metadata: r.metadata
|
|
421
|
+
}
|
|
422
|
+
})
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Final fallback: legacy SQLite search
|
|
426
|
+
return await this.search.search(query, {
|
|
427
|
+
project: options?.project,
|
|
428
|
+
limit,
|
|
429
|
+
minSimilarity: options?.minSimilarity || 0.3
|
|
430
|
+
})
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Transform FTS5/semantic ScoredResults to the expected MemorySearchResult structure
|
|
435
|
+
*/
|
|
436
|
+
private transformFtsResults(results: import('./fts5-search').ScoredResult[]): Record<string, unknown>[] {
|
|
437
|
+
return results.map(r => ({
|
|
438
|
+
id: r.id,
|
|
439
|
+
content: r.content,
|
|
440
|
+
memory: {
|
|
441
|
+
id: r.id,
|
|
442
|
+
project: r.project,
|
|
443
|
+
content: r.content,
|
|
444
|
+
createdAt: new Date(r.created_at),
|
|
445
|
+
metadata: {
|
|
446
|
+
project: r.project,
|
|
447
|
+
category: r.category,
|
|
448
|
+
context: r.context || '',
|
|
449
|
+
reasoning: r.reasoning || '',
|
|
450
|
+
tags: r.tags,
|
|
451
|
+
created_at: r.created_at
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
similarity: r.score,
|
|
455
|
+
decision: r.category === 'decision' ? {
|
|
456
|
+
id: r.id,
|
|
457
|
+
project: r.project,
|
|
458
|
+
context: r.context || '',
|
|
459
|
+
decision: r.content,
|
|
460
|
+
reasoning: r.reasoning || '',
|
|
461
|
+
alternatives: '',
|
|
462
|
+
tags: r.tags,
|
|
463
|
+
createdAt: new Date(r.created_at)
|
|
464
|
+
} : undefined,
|
|
465
|
+
metadata: {
|
|
466
|
+
project: r.project,
|
|
467
|
+
category: r.category,
|
|
468
|
+
context: r.context || '',
|
|
469
|
+
reasoning: r.reasoning || '',
|
|
470
|
+
tags: r.tags,
|
|
471
|
+
created_at: r.created_at
|
|
472
|
+
}
|
|
473
|
+
}))
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Merge FTS5 and semantic search results, deduplicating by ID.
|
|
478
|
+
* Semantic results get a small boost since they matched conceptually.
|
|
479
|
+
*/
|
|
480
|
+
private mergeSearchResults(
|
|
481
|
+
ftsResults: import('./fts5-search').ScoredResult[],
|
|
482
|
+
semanticResults: import('./fts5-search').ScoredResult[],
|
|
483
|
+
limit: number
|
|
484
|
+
): Record<string, unknown>[] {
|
|
485
|
+
const seen = new Set<string>()
|
|
486
|
+
const merged: import('./fts5-search').ScoredResult[] = []
|
|
487
|
+
|
|
488
|
+
// Add semantic results first (they matched conceptually when FTS5 didn't)
|
|
489
|
+
for (const r of semanticResults) {
|
|
490
|
+
if (!seen.has(r.id)) {
|
|
491
|
+
seen.add(r.id)
|
|
492
|
+
merged.push(r)
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Then add FTS5 results that aren't already included
|
|
497
|
+
for (const r of ftsResults) {
|
|
498
|
+
if (!seen.has(r.id)) {
|
|
499
|
+
seen.add(r.id)
|
|
500
|
+
merged.push(r)
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Sort by score descending and limit
|
|
505
|
+
merged.sort((a, b) => b.score - a.score)
|
|
506
|
+
return this.transformFtsResults(merged.slice(0, limit))
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
async recallSimilar(
|
|
510
|
+
query: string,
|
|
511
|
+
options?: { project?: string; limit?: number; minSimilarity?: number }
|
|
512
|
+
): Promise<string> {
|
|
513
|
+
const results = await this.searchRaw(query, options)
|
|
514
|
+
return this.contextBuilder.buildDecisionContext(results)
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
async getRecommendations(
|
|
518
|
+
currentContext: string,
|
|
519
|
+
project: string,
|
|
520
|
+
limit: number = 3
|
|
521
|
+
): Promise<string> {
|
|
522
|
+
const results = await this.search.getRecommendations(currentContext, project, limit)
|
|
523
|
+
return this.contextBuilder.buildRecommendationContext(results)
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
async migrateToChromaDB(options: MigrationOptions = {}): Promise<unknown> {
|
|
527
|
+
if (!this.useChromaDB) {
|
|
528
|
+
throw new Error('ChromaDB is not enabled')
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const migration = new ChromaMigration(
|
|
532
|
+
this.logger,
|
|
533
|
+
this.database.getDb(),
|
|
534
|
+
this.chroma.store,
|
|
535
|
+
this.chroma.collections
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
return migration.migrate(options)
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Store a pattern in memory — dual-writes to FTS5 + ChromaDB/SQLite
|
|
543
|
+
*/
|
|
544
|
+
async storePattern(input: {
|
|
545
|
+
project: string
|
|
546
|
+
pattern_type: 'solution' | 'anti-pattern' | 'best-practice' | 'common-issue'
|
|
547
|
+
description: string
|
|
548
|
+
example?: string
|
|
549
|
+
confidence: number
|
|
550
|
+
context?: string
|
|
551
|
+
source?: string
|
|
552
|
+
}): Promise<string> {
|
|
553
|
+
const sharedId = randomUUID()
|
|
554
|
+
|
|
555
|
+
return this.dualWrite.store({
|
|
556
|
+
sharedId,
|
|
557
|
+
fts5Fn: (fts5, id) => fts5.store({
|
|
558
|
+
project: input.project,
|
|
559
|
+
category: 'pattern',
|
|
560
|
+
content: input.description,
|
|
561
|
+
context: input.context,
|
|
562
|
+
confidence: input.confidence,
|
|
563
|
+
source: input.source
|
|
564
|
+
}, id),
|
|
565
|
+
chromaFn: async (chroma, id) => chroma.store.storePattern({ ...input, id }),
|
|
566
|
+
sqliteFn: () => this.store.storePattern(input),
|
|
567
|
+
embeddingText: [input.description, input.context].filter(Boolean).join(' ')
|
|
568
|
+
})
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Store a correction/lesson learned — dual-writes to FTS5 + ChromaDB/SQLite
|
|
573
|
+
*/
|
|
574
|
+
async storeCorrection(input: {
|
|
575
|
+
project: string
|
|
576
|
+
original: string
|
|
577
|
+
correction: string
|
|
578
|
+
reasoning: string
|
|
579
|
+
context?: string
|
|
580
|
+
confidence: number
|
|
581
|
+
}): Promise<string> {
|
|
582
|
+
const sharedId = randomUUID()
|
|
583
|
+
|
|
584
|
+
return this.dualWrite.store({
|
|
585
|
+
sharedId,
|
|
586
|
+
fts5Fn: (fts5, id) => fts5.store({
|
|
587
|
+
project: input.project,
|
|
588
|
+
category: 'correction',
|
|
589
|
+
content: input.correction,
|
|
590
|
+
reasoning: input.reasoning,
|
|
591
|
+
context: input.context,
|
|
592
|
+
confidence: input.confidence
|
|
593
|
+
}, id),
|
|
594
|
+
chromaFn: async (chroma, id) => chroma.store.storeCorrection({ ...input, id }),
|
|
595
|
+
sqliteFn: () => this.store.storeCorrection(input),
|
|
596
|
+
embeddingText: [input.correction, input.reasoning, input.context].filter(Boolean).join(' ')
|
|
597
|
+
})
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Get patterns for a project — routes to FTS5, ChromaDB, or legacy SQLite
|
|
602
|
+
*/
|
|
603
|
+
async getPatterns(
|
|
604
|
+
project?: string,
|
|
605
|
+
options?: {
|
|
606
|
+
pattern_type?: 'solution' | 'anti-pattern' | 'best-practice' | 'common-issue'
|
|
607
|
+
limit?: number
|
|
608
|
+
}
|
|
609
|
+
): Promise<Record<string, unknown>[]> {
|
|
610
|
+
return this.dualWrite.fetch({
|
|
611
|
+
fts5Fn: (fts5) => fts5.fetchAll(project, 'pattern').map(r => ({
|
|
612
|
+
id: r.id,
|
|
613
|
+
description: r.content,
|
|
614
|
+
metadata: {
|
|
615
|
+
project: r.project,
|
|
616
|
+
pattern_type: r.category,
|
|
617
|
+
confidence: r.confidence,
|
|
618
|
+
context: r.context || '',
|
|
619
|
+
created_at: r.created_at
|
|
620
|
+
}
|
|
621
|
+
})),
|
|
622
|
+
chromaFn: async (chroma) => {
|
|
623
|
+
if (project) {
|
|
624
|
+
return chroma.store.getPatternsByProject(project, options)
|
|
625
|
+
}
|
|
626
|
+
return chroma.store.searchPatterns('', { limit: options?.limit || 10 })
|
|
627
|
+
},
|
|
628
|
+
sqliteFn: () => {
|
|
629
|
+
if (project) {
|
|
630
|
+
return this.store.getPatternsByProject(project, options)
|
|
631
|
+
}
|
|
632
|
+
return this.store.searchPatterns('', { limit: options?.limit || 10 })
|
|
633
|
+
}
|
|
634
|
+
})
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Get corrections for a project — routes to FTS5, ChromaDB, or legacy SQLite
|
|
639
|
+
*/
|
|
640
|
+
async getCorrections(
|
|
641
|
+
project?: string,
|
|
642
|
+
options?: { limit?: number }
|
|
643
|
+
): Promise<Record<string, unknown>[]> {
|
|
644
|
+
return this.dualWrite.fetch({
|
|
645
|
+
fts5Fn: (fts5) => fts5.fetchAll(project, 'correction').map(r => ({
|
|
646
|
+
id: r.id,
|
|
647
|
+
correction: r.content,
|
|
648
|
+
metadata: {
|
|
649
|
+
project: r.project,
|
|
650
|
+
reasoning: r.reasoning || '',
|
|
651
|
+
context: r.context || '',
|
|
652
|
+
confidence: r.confidence,
|
|
653
|
+
created_at: r.created_at
|
|
654
|
+
}
|
|
655
|
+
})),
|
|
656
|
+
chromaFn: async (chroma) => {
|
|
657
|
+
if (project) {
|
|
658
|
+
return chroma.store.getCorrectionsByProject(project, options?.limit || 10)
|
|
659
|
+
}
|
|
660
|
+
return chroma.store.searchCorrections('', { limit: options?.limit || 10 })
|
|
661
|
+
},
|
|
662
|
+
sqliteFn: () => {
|
|
663
|
+
if (project) {
|
|
664
|
+
return this.store.getCorrectionsByProject(project, options?.limit || 10)
|
|
665
|
+
}
|
|
666
|
+
return this.store.searchCorrections('', { limit: options?.limit || 10 })
|
|
667
|
+
}
|
|
668
|
+
})
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Fetch all decisions with content — routes to FTS5, ChromaDB, or legacy SQLite
|
|
673
|
+
* Used by analytical tools that need bulk access to decision data
|
|
674
|
+
*/
|
|
675
|
+
async fetchAllDecisions(project?: string): Promise<Record<string, unknown>[]> {
|
|
676
|
+
return this.dualWrite.fetch({
|
|
677
|
+
fts5Fn: (fts5) => fts5.fetchAll(project, 'decision').map(r => ({
|
|
678
|
+
id: r.id,
|
|
679
|
+
content: r.content,
|
|
680
|
+
date: r.created_at,
|
|
681
|
+
project: r.project,
|
|
682
|
+
context: r.context || '',
|
|
683
|
+
decision: r.content,
|
|
684
|
+
reasoning: r.reasoning || '',
|
|
685
|
+
alternatives: '',
|
|
686
|
+
tags: r.tags
|
|
687
|
+
})),
|
|
688
|
+
chromaFn: async (chroma) => {
|
|
689
|
+
const collection = await chroma.collections.getDecisions()
|
|
690
|
+
const results = await collection.get({
|
|
691
|
+
where: project ? { project } : undefined
|
|
692
|
+
})
|
|
693
|
+
if (results && results.ids) {
|
|
694
|
+
return results.ids.map((id: string, i: number) => ({
|
|
695
|
+
id,
|
|
696
|
+
content: results.documents?.[i] || '',
|
|
697
|
+
date: results.metadatas?.[i]?.created_at || new Date().toISOString(),
|
|
698
|
+
project: results.metadatas?.[i]?.project || project || 'unknown',
|
|
699
|
+
context: results.metadatas?.[i]?.context || '',
|
|
700
|
+
decision: results.metadatas?.[i]?.decision || results.documents?.[i] || '',
|
|
701
|
+
reasoning: results.metadatas?.[i]?.reasoning || '',
|
|
702
|
+
alternatives: results.metadatas?.[i]?.alternatives_considered || '',
|
|
703
|
+
tags: results.metadatas?.[i]?.tags || []
|
|
704
|
+
}))
|
|
705
|
+
}
|
|
706
|
+
return []
|
|
707
|
+
},
|
|
708
|
+
sqliteFn: () => this.store.getAllDecisionsWithContent(project)
|
|
709
|
+
})
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Fetch all patterns with content — routes to FTS5, ChromaDB, or legacy SQLite
|
|
714
|
+
*/
|
|
715
|
+
async fetchAllPatterns(project?: string): Promise<Record<string, unknown>[]> {
|
|
716
|
+
return this.dualWrite.fetch({
|
|
717
|
+
fts5Fn: (fts5) => fts5.fetchAll(project, 'pattern').map(r => ({
|
|
718
|
+
id: r.id,
|
|
719
|
+
content: r.content,
|
|
720
|
+
date: r.created_at,
|
|
721
|
+
project: r.project,
|
|
722
|
+
pattern_type: '',
|
|
723
|
+
description: r.content,
|
|
724
|
+
example: '',
|
|
725
|
+
confidence: r.confidence,
|
|
726
|
+
context: r.context || ''
|
|
727
|
+
})),
|
|
728
|
+
chromaFn: async (chroma) => {
|
|
729
|
+
const collection = await chroma.collections.getPatterns()
|
|
730
|
+
const results = await collection.get({
|
|
731
|
+
where: project ? { project } : undefined
|
|
732
|
+
})
|
|
733
|
+
if (results && results.ids) {
|
|
734
|
+
return results.ids.map((id: string, i: number) => ({
|
|
735
|
+
id,
|
|
736
|
+
content: results.documents?.[i] || '',
|
|
737
|
+
date: results.metadatas?.[i]?.created_at || new Date().toISOString(),
|
|
738
|
+
project: results.metadatas?.[i]?.project || project || 'unknown',
|
|
739
|
+
pattern_type: results.metadatas?.[i]?.pattern_type || '',
|
|
740
|
+
description: results.metadatas?.[i]?.description || results.documents?.[i] || '',
|
|
741
|
+
example: results.metadatas?.[i]?.example || '',
|
|
742
|
+
confidence: results.metadatas?.[i]?.confidence || 0,
|
|
743
|
+
context: results.metadatas?.[i]?.context || ''
|
|
744
|
+
}))
|
|
745
|
+
}
|
|
746
|
+
return []
|
|
747
|
+
},
|
|
748
|
+
sqliteFn: () => this.store.getAllPatternsWithContent(project)
|
|
749
|
+
})
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* Fetch all corrections with content — routes to FTS5, ChromaDB, or legacy SQLite
|
|
754
|
+
*/
|
|
755
|
+
async fetchAllCorrections(project?: string): Promise<Record<string, unknown>[]> {
|
|
756
|
+
return this.dualWrite.fetch({
|
|
757
|
+
fts5Fn: (fts5) => fts5.fetchAll(project, 'correction').map(r => ({
|
|
758
|
+
id: r.id,
|
|
759
|
+
content: r.content,
|
|
760
|
+
date: r.created_at,
|
|
761
|
+
project: r.project,
|
|
762
|
+
original: '',
|
|
763
|
+
correction: r.content,
|
|
764
|
+
reasoning: r.reasoning || '',
|
|
765
|
+
context: r.context || '',
|
|
766
|
+
confidence: r.confidence
|
|
767
|
+
})),
|
|
768
|
+
chromaFn: async (chroma) => {
|
|
769
|
+
const collection = await chroma.collections.getCorrections()
|
|
770
|
+
const results = await collection.get({
|
|
771
|
+
where: project ? { project } : undefined
|
|
772
|
+
})
|
|
773
|
+
if (results && results.ids) {
|
|
774
|
+
return results.ids.map((id: string, i: number) => ({
|
|
775
|
+
id,
|
|
776
|
+
content: results.documents?.[i] || '',
|
|
777
|
+
date: results.metadatas?.[i]?.created_at || new Date().toISOString(),
|
|
778
|
+
project: results.metadatas?.[i]?.project || project || 'unknown',
|
|
779
|
+
original: results.metadatas?.[i]?.original || '',
|
|
780
|
+
correction: results.metadatas?.[i]?.correction || results.documents?.[i] || '',
|
|
781
|
+
reasoning: results.metadatas?.[i]?.reasoning || '',
|
|
782
|
+
context: results.metadatas?.[i]?.context || '',
|
|
783
|
+
confidence: results.metadatas?.[i]?.confidence || 0
|
|
784
|
+
}))
|
|
785
|
+
}
|
|
786
|
+
return []
|
|
787
|
+
},
|
|
788
|
+
sqliteFn: () => this.store.getAllCorrectionsWithContent(project)
|
|
789
|
+
})
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* Search patterns by query — routes to FTS5, ChromaDB, or legacy SQLite
|
|
794
|
+
*/
|
|
795
|
+
async searchPatterns(
|
|
796
|
+
query: string,
|
|
797
|
+
options?: {
|
|
798
|
+
project?: string
|
|
799
|
+
pattern_type?: 'solution' | 'anti-pattern' | 'best-practice' | 'common-issue'
|
|
800
|
+
limit?: number
|
|
801
|
+
minSimilarity?: number
|
|
802
|
+
}
|
|
803
|
+
): Promise<Record<string, unknown>[]> {
|
|
804
|
+
return this.dualWrite.fetch({
|
|
805
|
+
fts5Fn: query ? (fts5) => {
|
|
806
|
+
const results = fts5.searchWithConfidence(query, options?.project, options?.limit || 10)
|
|
807
|
+
return results.filter(r => r.category === 'pattern').map(r => ({
|
|
808
|
+
id: r.id,
|
|
809
|
+
content: r.content,
|
|
810
|
+
metadata: {
|
|
811
|
+
project: r.project,
|
|
812
|
+
pattern_type: '',
|
|
813
|
+
description: r.content,
|
|
814
|
+
confidence: r.confidence,
|
|
815
|
+
context: r.context || '',
|
|
816
|
+
created_at: r.created_at
|
|
817
|
+
},
|
|
818
|
+
similarity: r.score
|
|
819
|
+
}))
|
|
820
|
+
} : undefined,
|
|
821
|
+
chromaFn: async (chroma) => chroma.store.searchPatterns(query, options),
|
|
822
|
+
sqliteFn: () => this.store.searchPatterns(query, options)
|
|
823
|
+
})
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* Search corrections by query — routes to FTS5, ChromaDB, or legacy SQLite
|
|
828
|
+
*/
|
|
829
|
+
async searchCorrections(
|
|
830
|
+
query: string,
|
|
831
|
+
options?: {
|
|
832
|
+
project?: string
|
|
833
|
+
limit?: number
|
|
834
|
+
minSimilarity?: number
|
|
835
|
+
}
|
|
836
|
+
): Promise<Record<string, unknown>[]> {
|
|
837
|
+
return this.dualWrite.fetch({
|
|
838
|
+
fts5Fn: query ? (fts5) => {
|
|
839
|
+
const results = fts5.searchWithConfidence(query, options?.project, options?.limit || 10)
|
|
840
|
+
return results.filter(r => r.category === 'correction').map(r => ({
|
|
841
|
+
id: r.id,
|
|
842
|
+
content: r.content,
|
|
843
|
+
metadata: {
|
|
844
|
+
project: r.project,
|
|
845
|
+
reasoning: r.reasoning || '',
|
|
846
|
+
context: r.context || '',
|
|
847
|
+
confidence: r.confidence,
|
|
848
|
+
created_at: r.created_at
|
|
849
|
+
},
|
|
850
|
+
similarity: r.score
|
|
851
|
+
}))
|
|
852
|
+
} : undefined,
|
|
853
|
+
chromaFn: async (chroma) => chroma.store.searchCorrections(query, options),
|
|
854
|
+
sqliteFn: () => this.store.searchCorrections(query, options)
|
|
855
|
+
})
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/**
|
|
859
|
+
* Delete a decision by ID — removes from FTS5 + embeddings + ChromaDB/SQLite
|
|
860
|
+
*/
|
|
861
|
+
async deleteDecision(id: string): Promise<void> {
|
|
862
|
+
await this.dualWrite.delete(id, {
|
|
863
|
+
fts5Fn: (fts5) => {
|
|
864
|
+
fts5.delete(id)
|
|
865
|
+
fts5.deleteEmbedding(id)
|
|
866
|
+
},
|
|
867
|
+
chromaFn: async (chroma) => chroma.store.deleteDecision(id),
|
|
868
|
+
sqliteFn: () => this.store.deleteMemory(id)
|
|
869
|
+
})
|
|
870
|
+
|
|
871
|
+
// Notify listeners (e.g., knowledge graph builder) about deletion
|
|
872
|
+
for (const cb of this.onDecisionDeletedCallbacks) {
|
|
873
|
+
try {
|
|
874
|
+
cb(id)
|
|
875
|
+
} catch {}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* Update a decision by deleting the old one and storing a new version.
|
|
881
|
+
* Phase 20: Ensures both ChromaDB and knowledge graph are atomically updated.
|
|
882
|
+
*/
|
|
883
|
+
async updateDecision(
|
|
884
|
+
oldId: string,
|
|
885
|
+
project: string,
|
|
886
|
+
context: string,
|
|
887
|
+
decision: string,
|
|
888
|
+
reasoning: string,
|
|
889
|
+
options?: { alternatives?: string; tags?: string[] }
|
|
890
|
+
): Promise<string> {
|
|
891
|
+
// BUG-001: True in-place update using FTS5 (preserves original ID)
|
|
892
|
+
if (this._fts5) {
|
|
893
|
+
try {
|
|
894
|
+
this._fts5.update(oldId, {
|
|
895
|
+
content: decision,
|
|
896
|
+
reasoning,
|
|
897
|
+
context,
|
|
898
|
+
tags: options?.tags
|
|
899
|
+
})
|
|
900
|
+
this.logger.debug({ oldId }, 'Decision updated in-place via FTS5')
|
|
901
|
+
|
|
902
|
+
// Also update ChromaDB if available (best-effort)
|
|
903
|
+
if (this.useChromaDB) {
|
|
904
|
+
try {
|
|
905
|
+
await this.chroma.store.deleteDecision(oldId)
|
|
906
|
+
await this.chroma.store.storeDecision({
|
|
907
|
+
project,
|
|
908
|
+
context,
|
|
909
|
+
decision,
|
|
910
|
+
reasoning,
|
|
911
|
+
alternatives: options?.alternatives,
|
|
912
|
+
tags: options?.tags
|
|
913
|
+
})
|
|
914
|
+
} catch {
|
|
915
|
+
// ChromaDB sync failed, FTS5 is source of truth
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
return oldId // SAME ID preserved
|
|
920
|
+
} catch (error) {
|
|
921
|
+
this.logger.warn({ error, oldId }, 'FTS5 in-place update failed, falling back to delete+store')
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// Fallback: delete + store (legacy behavior for non-FTS5 backends)
|
|
926
|
+
try {
|
|
927
|
+
await this.deleteDecision(oldId)
|
|
928
|
+
this.logger.debug({ oldId }, 'Old decision deleted for update')
|
|
929
|
+
} catch (error) {
|
|
930
|
+
this.logger.warn({ error, oldId }, 'Failed to delete old decision during update, storing new version anyway')
|
|
931
|
+
}
|
|
932
|
+
const newId = await this.rememberDecision(project, context, decision, reasoning, options)
|
|
933
|
+
this.logger.debug({ oldId, newId }, 'Decision updated: old deleted, new stored')
|
|
934
|
+
return newId
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* Create a memory manager instance
|
|
940
|
+
*/
|
|
941
|
+
export function createMemoryManager(dbPath: string, logger: Logger): MemoryManager {
|
|
942
|
+
return new MemoryManager(dbPath, logger)
|
|
943
|
+
}
|