@mnemoai/core 1.1.0 → 1.1.1

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 (220) hide show
  1. package/dist/cli.d.ts +2 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +7 -0
  4. package/dist/cli.js.map +7 -0
  5. package/dist/index.d.ts +128 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/{index.ts → dist/index.js} +526 -1333
  8. package/dist/index.js.map +7 -0
  9. package/dist/src/access-tracker.d.ts +97 -0
  10. package/dist/src/access-tracker.d.ts.map +1 -0
  11. package/dist/src/access-tracker.js +184 -0
  12. package/dist/src/access-tracker.js.map +7 -0
  13. package/dist/src/adapters/chroma.d.ts +31 -0
  14. package/dist/src/adapters/chroma.d.ts.map +1 -0
  15. package/{src/adapters/chroma.ts → dist/src/adapters/chroma.js} +45 -107
  16. package/dist/src/adapters/chroma.js.map +7 -0
  17. package/dist/src/adapters/lancedb.d.ts +29 -0
  18. package/dist/src/adapters/lancedb.d.ts.map +1 -0
  19. package/{src/adapters/lancedb.ts → dist/src/adapters/lancedb.js} +41 -109
  20. package/dist/src/adapters/lancedb.js.map +7 -0
  21. package/dist/src/adapters/pgvector.d.ts +33 -0
  22. package/dist/src/adapters/pgvector.d.ts.map +1 -0
  23. package/{src/adapters/pgvector.ts → dist/src/adapters/pgvector.js} +42 -104
  24. package/dist/src/adapters/pgvector.js.map +7 -0
  25. package/dist/src/adapters/qdrant.d.ts +34 -0
  26. package/dist/src/adapters/qdrant.d.ts.map +1 -0
  27. package/dist/src/adapters/qdrant.js +132 -0
  28. package/dist/src/adapters/qdrant.js.map +7 -0
  29. package/dist/src/adaptive-retrieval.d.ts +14 -0
  30. package/dist/src/adaptive-retrieval.d.ts.map +1 -0
  31. package/dist/src/adaptive-retrieval.js +52 -0
  32. package/dist/src/adaptive-retrieval.js.map +7 -0
  33. package/dist/src/audit-log.d.ts +56 -0
  34. package/dist/src/audit-log.d.ts.map +1 -0
  35. package/dist/src/audit-log.js +139 -0
  36. package/dist/src/audit-log.js.map +7 -0
  37. package/dist/src/chunker.d.ts +45 -0
  38. package/dist/src/chunker.d.ts.map +1 -0
  39. package/dist/src/chunker.js +157 -0
  40. package/dist/src/chunker.js.map +7 -0
  41. package/dist/src/config.d.ts +70 -0
  42. package/dist/src/config.d.ts.map +1 -0
  43. package/dist/src/config.js +142 -0
  44. package/dist/src/config.js.map +7 -0
  45. package/dist/src/decay-engine.d.ts +73 -0
  46. package/dist/src/decay-engine.d.ts.map +1 -0
  47. package/dist/src/decay-engine.js +119 -0
  48. package/dist/src/decay-engine.js.map +7 -0
  49. package/dist/src/embedder.d.ts +94 -0
  50. package/dist/src/embedder.d.ts.map +1 -0
  51. package/{src/embedder.ts → dist/src/embedder.js} +119 -317
  52. package/dist/src/embedder.js.map +7 -0
  53. package/dist/src/extraction-prompts.d.ts +12 -0
  54. package/dist/src/extraction-prompts.d.ts.map +1 -0
  55. package/dist/src/extraction-prompts.js +311 -0
  56. package/dist/src/extraction-prompts.js.map +7 -0
  57. package/dist/src/license.d.ts +29 -0
  58. package/dist/src/license.d.ts.map +1 -0
  59. package/{src/license.ts → dist/src/license.js} +42 -113
  60. package/dist/src/license.js.map +7 -0
  61. package/dist/src/llm-client.d.ts +23 -0
  62. package/dist/src/llm-client.d.ts.map +1 -0
  63. package/{src/llm-client.ts → dist/src/llm-client.js} +22 -55
  64. package/dist/src/llm-client.js.map +7 -0
  65. package/dist/src/logger.d.ts +33 -0
  66. package/dist/src/logger.d.ts.map +1 -0
  67. package/dist/src/logger.js +35 -0
  68. package/dist/src/logger.js.map +7 -0
  69. package/dist/src/mcp-server.d.ts +16 -0
  70. package/dist/src/mcp-server.d.ts.map +1 -0
  71. package/{src/mcp-server.ts → dist/src/mcp-server.js} +81 -181
  72. package/dist/src/mcp-server.js.map +7 -0
  73. package/dist/src/memory-categories.d.ts +40 -0
  74. package/dist/src/memory-categories.d.ts.map +1 -0
  75. package/dist/src/memory-categories.js +33 -0
  76. package/dist/src/memory-categories.js.map +7 -0
  77. package/dist/src/memory-upgrader.d.ts +71 -0
  78. package/dist/src/memory-upgrader.d.ts.map +1 -0
  79. package/dist/src/memory-upgrader.js +238 -0
  80. package/dist/src/memory-upgrader.js.map +7 -0
  81. package/dist/src/migrate.d.ts +47 -0
  82. package/dist/src/migrate.d.ts.map +1 -0
  83. package/{src/migrate.ts → dist/src/migrate.js} +57 -165
  84. package/dist/src/migrate.js.map +7 -0
  85. package/dist/src/mnemo.d.ts +67 -0
  86. package/dist/src/mnemo.d.ts.map +1 -0
  87. package/dist/src/mnemo.js +66 -0
  88. package/dist/src/mnemo.js.map +7 -0
  89. package/dist/src/noise-filter.d.ts +23 -0
  90. package/dist/src/noise-filter.d.ts.map +1 -0
  91. package/dist/src/noise-filter.js +62 -0
  92. package/dist/src/noise-filter.js.map +7 -0
  93. package/dist/src/noise-prototypes.d.ts +40 -0
  94. package/dist/src/noise-prototypes.d.ts.map +1 -0
  95. package/dist/src/noise-prototypes.js +116 -0
  96. package/dist/src/noise-prototypes.js.map +7 -0
  97. package/dist/src/observability.d.ts +16 -0
  98. package/dist/src/observability.d.ts.map +1 -0
  99. package/dist/src/observability.js +53 -0
  100. package/dist/src/observability.js.map +7 -0
  101. package/dist/src/query-tracker.d.ts +27 -0
  102. package/dist/src/query-tracker.d.ts.map +1 -0
  103. package/dist/src/query-tracker.js +32 -0
  104. package/dist/src/query-tracker.js.map +7 -0
  105. package/dist/src/reflection-event-store.d.ts +44 -0
  106. package/dist/src/reflection-event-store.d.ts.map +1 -0
  107. package/dist/src/reflection-event-store.js +50 -0
  108. package/dist/src/reflection-event-store.js.map +7 -0
  109. package/dist/src/reflection-item-store.d.ts +58 -0
  110. package/dist/src/reflection-item-store.d.ts.map +1 -0
  111. package/dist/src/reflection-item-store.js +69 -0
  112. package/dist/src/reflection-item-store.js.map +7 -0
  113. package/dist/src/reflection-mapped-metadata.d.ts +47 -0
  114. package/dist/src/reflection-mapped-metadata.d.ts.map +1 -0
  115. package/dist/src/reflection-mapped-metadata.js +40 -0
  116. package/dist/src/reflection-mapped-metadata.js.map +7 -0
  117. package/dist/src/reflection-metadata.d.ts +11 -0
  118. package/dist/src/reflection-metadata.d.ts.map +1 -0
  119. package/dist/src/reflection-metadata.js +24 -0
  120. package/dist/src/reflection-metadata.js.map +7 -0
  121. package/dist/src/reflection-ranking.d.ts +13 -0
  122. package/dist/src/reflection-ranking.d.ts.map +1 -0
  123. package/{src/reflection-ranking.ts → dist/src/reflection-ranking.js} +12 -21
  124. package/dist/src/reflection-ranking.js.map +7 -0
  125. package/dist/src/reflection-retry.d.ts +30 -0
  126. package/dist/src/reflection-retry.d.ts.map +1 -0
  127. package/{src/reflection-retry.ts → dist/src/reflection-retry.js} +24 -64
  128. package/dist/src/reflection-retry.js.map +7 -0
  129. package/dist/src/reflection-slices.d.ts +42 -0
  130. package/dist/src/reflection-slices.d.ts.map +1 -0
  131. package/{src/reflection-slices.ts → dist/src/reflection-slices.js} +60 -136
  132. package/dist/src/reflection-slices.js.map +7 -0
  133. package/dist/src/reflection-store.d.ts +85 -0
  134. package/dist/src/reflection-store.d.ts.map +1 -0
  135. package/dist/src/reflection-store.js +407 -0
  136. package/dist/src/reflection-store.js.map +7 -0
  137. package/dist/src/resonance-state.d.ts +19 -0
  138. package/dist/src/resonance-state.d.ts.map +1 -0
  139. package/{src/resonance-state.ts → dist/src/resonance-state.js} +13 -42
  140. package/dist/src/resonance-state.js.map +7 -0
  141. package/dist/src/retriever.d.ts +228 -0
  142. package/dist/src/retriever.d.ts.map +1 -0
  143. package/dist/src/retriever.js +1006 -0
  144. package/dist/src/retriever.js.map +7 -0
  145. package/dist/src/scopes.d.ts +58 -0
  146. package/dist/src/scopes.d.ts.map +1 -0
  147. package/dist/src/scopes.js +252 -0
  148. package/dist/src/scopes.js.map +7 -0
  149. package/dist/src/self-improvement-files.d.ts +20 -0
  150. package/dist/src/self-improvement-files.d.ts.map +1 -0
  151. package/{src/self-improvement-files.ts → dist/src/self-improvement-files.js} +24 -49
  152. package/dist/src/self-improvement-files.js.map +7 -0
  153. package/dist/src/semantic-gate.d.ts +24 -0
  154. package/dist/src/semantic-gate.d.ts.map +1 -0
  155. package/dist/src/semantic-gate.js +86 -0
  156. package/dist/src/semantic-gate.js.map +7 -0
  157. package/dist/src/session-recovery.d.ts +9 -0
  158. package/dist/src/session-recovery.d.ts.map +1 -0
  159. package/{src/session-recovery.ts → dist/src/session-recovery.js} +40 -57
  160. package/dist/src/session-recovery.js.map +7 -0
  161. package/dist/src/smart-extractor.d.ts +107 -0
  162. package/dist/src/smart-extractor.d.ts.map +1 -0
  163. package/{src/smart-extractor.ts → dist/src/smart-extractor.js} +130 -383
  164. package/dist/src/smart-extractor.js.map +7 -0
  165. package/dist/src/smart-metadata.d.ts +103 -0
  166. package/dist/src/smart-metadata.d.ts.map +1 -0
  167. package/dist/src/smart-metadata.js +361 -0
  168. package/dist/src/smart-metadata.js.map +7 -0
  169. package/dist/src/storage-adapter.d.ts +102 -0
  170. package/dist/src/storage-adapter.d.ts.map +1 -0
  171. package/dist/src/storage-adapter.js +22 -0
  172. package/dist/src/storage-adapter.js.map +7 -0
  173. package/dist/src/store.d.ts +108 -0
  174. package/dist/src/store.d.ts.map +1 -0
  175. package/dist/src/store.js +939 -0
  176. package/dist/src/store.js.map +7 -0
  177. package/dist/src/tier-manager.d.ts +57 -0
  178. package/dist/src/tier-manager.d.ts.map +1 -0
  179. package/dist/src/tier-manager.js +80 -0
  180. package/dist/src/tier-manager.js.map +7 -0
  181. package/dist/src/tools.d.ts +43 -0
  182. package/dist/src/tools.d.ts.map +1 -0
  183. package/dist/src/tools.js +1075 -0
  184. package/dist/src/tools.js.map +7 -0
  185. package/dist/src/wal-recovery.d.ts +30 -0
  186. package/dist/src/wal-recovery.d.ts.map +1 -0
  187. package/{src/wal-recovery.ts → dist/src/wal-recovery.js} +26 -79
  188. package/dist/src/wal-recovery.js.map +7 -0
  189. package/package.json +21 -2
  190. package/openclaw.plugin.json +0 -815
  191. package/src/access-tracker.ts +0 -341
  192. package/src/adapters/README.md +0 -78
  193. package/src/adapters/qdrant.ts +0 -191
  194. package/src/adaptive-retrieval.ts +0 -90
  195. package/src/audit-log.ts +0 -238
  196. package/src/chunker.ts +0 -254
  197. package/src/config.ts +0 -271
  198. package/src/decay-engine.ts +0 -238
  199. package/src/extraction-prompts.ts +0 -339
  200. package/src/memory-categories.ts +0 -71
  201. package/src/memory-upgrader.ts +0 -388
  202. package/src/mnemo.ts +0 -142
  203. package/src/noise-filter.ts +0 -97
  204. package/src/noise-prototypes.ts +0 -164
  205. package/src/observability.ts +0 -81
  206. package/src/query-tracker.ts +0 -57
  207. package/src/reflection-event-store.ts +0 -98
  208. package/src/reflection-item-store.ts +0 -112
  209. package/src/reflection-mapped-metadata.ts +0 -84
  210. package/src/reflection-metadata.ts +0 -23
  211. package/src/reflection-store.ts +0 -602
  212. package/src/retriever.ts +0 -1510
  213. package/src/scopes.ts +0 -375
  214. package/src/semantic-gate.ts +0 -121
  215. package/src/smart-metadata.ts +0 -561
  216. package/src/storage-adapter.ts +0 -153
  217. package/src/store.ts +0 -1330
  218. package/src/tier-manager.ts +0 -189
  219. package/src/tools.ts +0 -1292
  220. package/test/core.test.mjs +0 -301
@@ -0,0 +1,184 @@
1
+ const MIN_ACCESS_COUNT = 0;
2
+ const MAX_ACCESS_COUNT = 1e4;
3
+ const ACCESS_DECAY_HALF_LIFE_DAYS = 30;
4
+ function clampAccessCount(value) {
5
+ if (!Number.isFinite(value)) return MIN_ACCESS_COUNT;
6
+ return Math.min(
7
+ MAX_ACCESS_COUNT,
8
+ Math.max(MIN_ACCESS_COUNT, Math.floor(value))
9
+ );
10
+ }
11
+ function parseAccessMetadata(metadata) {
12
+ if (metadata === void 0 || metadata === "") {
13
+ return { accessCount: 0, lastAccessedAt: 0 };
14
+ }
15
+ let parsed;
16
+ try {
17
+ parsed = JSON.parse(metadata);
18
+ } catch {
19
+ return { accessCount: 0, lastAccessedAt: 0 };
20
+ }
21
+ if (typeof parsed !== "object" || parsed === null) {
22
+ return { accessCount: 0, lastAccessedAt: 0 };
23
+ }
24
+ const obj = parsed;
25
+ const rawCountAny = obj.accessCount ?? obj.access_count;
26
+ const rawCount = typeof rawCountAny === "number" ? rawCountAny : Number(rawCountAny ?? 0);
27
+ const rawLastAny = obj.lastAccessedAt ?? obj.last_accessed_at;
28
+ const rawLastAccessed = typeof rawLastAny === "number" ? rawLastAny : Number(rawLastAny ?? 0);
29
+ return {
30
+ accessCount: clampAccessCount(rawCount),
31
+ lastAccessedAt: Number.isFinite(rawLastAccessed) && rawLastAccessed >= 0 ? rawLastAccessed : 0
32
+ };
33
+ }
34
+ function buildUpdatedMetadata(existingMetadata, accessDelta) {
35
+ let existing = {};
36
+ if (existingMetadata !== void 0 && existingMetadata !== "") {
37
+ try {
38
+ const parsed = JSON.parse(existingMetadata);
39
+ if (typeof parsed === "object" && parsed !== null) {
40
+ existing = { ...parsed };
41
+ }
42
+ } catch {
43
+ }
44
+ }
45
+ const prev = parseAccessMetadata(existingMetadata);
46
+ const newCount = clampAccessCount(prev.accessCount + accessDelta);
47
+ const now = Date.now();
48
+ return JSON.stringify({
49
+ ...existing,
50
+ // Write both camelCase and snake_case for compatibility.
51
+ accessCount: newCount,
52
+ lastAccessedAt: now,
53
+ access_count: newCount,
54
+ last_accessed_at: now
55
+ });
56
+ }
57
+ function computeEffectiveHalfLife(baseHalfLife, accessCount, lastAccessedAt, reinforcementFactor, maxMultiplier) {
58
+ if (reinforcementFactor === 0 || accessCount <= 0) {
59
+ return baseHalfLife;
60
+ }
61
+ const now = Date.now();
62
+ const daysSinceLastAccess = Math.max(
63
+ 0,
64
+ (now - lastAccessedAt) / (1e3 * 60 * 60 * 24)
65
+ );
66
+ const accessFreshness = Math.exp(
67
+ -daysSinceLastAccess * (Math.LN2 / ACCESS_DECAY_HALF_LIFE_DAYS)
68
+ );
69
+ const effectiveAccessCount = accessCount * accessFreshness;
70
+ const extension = baseHalfLife * reinforcementFactor * Math.log1p(effectiveAccessCount);
71
+ const result = baseHalfLife + extension;
72
+ const cap = baseHalfLife * maxMultiplier;
73
+ return Math.min(result, cap);
74
+ }
75
+ class AccessTracker {
76
+ pending = /* @__PURE__ */ new Map();
77
+ debounceTimer = null;
78
+ flushPromise = null;
79
+ debounceMs;
80
+ store;
81
+ logger;
82
+ constructor(options) {
83
+ this.store = options.store;
84
+ this.logger = options.logger;
85
+ this.debounceMs = options.debounceMs ?? 5e3;
86
+ }
87
+ /**
88
+ * Record one access for each of the given memory IDs.
89
+ * Synchronous — only updates the in-memory pending map.
90
+ */
91
+ recordAccess(ids) {
92
+ for (const id of ids) {
93
+ const current = this.pending.get(id) ?? 0;
94
+ this.pending.set(id, current + 1);
95
+ }
96
+ this.resetTimer();
97
+ }
98
+ /**
99
+ * Return a snapshot of all pending (id -> delta) entries.
100
+ */
101
+ getPendingUpdates() {
102
+ return new Map(this.pending);
103
+ }
104
+ /**
105
+ * Flush pending access deltas to the store.
106
+ *
107
+ * If a flush is already in progress, awaits the current flush to complete.
108
+ * If new pending data accumulated during the in-flight flush, a follow-up
109
+ * flush is automatically triggered.
110
+ */
111
+ async flush() {
112
+ this.clearTimer();
113
+ if (this.flushPromise) {
114
+ await this.flushPromise;
115
+ if (this.pending.size > 0) {
116
+ return this.flush();
117
+ }
118
+ return;
119
+ }
120
+ if (this.pending.size === 0) return;
121
+ this.flushPromise = this.doFlush();
122
+ try {
123
+ await this.flushPromise;
124
+ } finally {
125
+ this.flushPromise = null;
126
+ }
127
+ if (this.pending.size > 0) {
128
+ this.resetTimer();
129
+ }
130
+ }
131
+ /**
132
+ * Tear down the tracker — cancel timers and clear pending state.
133
+ */
134
+ destroy() {
135
+ this.clearTimer();
136
+ if (this.pending.size > 0) {
137
+ this.logger.warn(
138
+ `access-tracker: destroying with ${this.pending.size} pending writes`
139
+ );
140
+ }
141
+ this.pending.clear();
142
+ }
143
+ // --------------------------------------------------------------------------
144
+ // Internal helpers
145
+ // --------------------------------------------------------------------------
146
+ async doFlush() {
147
+ const batch = new Map(this.pending);
148
+ this.pending.clear();
149
+ for (const [id, delta] of batch) {
150
+ try {
151
+ const current = await this.store.getById(id);
152
+ if (!current) continue;
153
+ const updatedMeta = buildUpdatedMetadata(current.metadata, delta);
154
+ await this.store.update(id, { metadata: updatedMeta });
155
+ } catch (err) {
156
+ const existing = this.pending.get(id) ?? 0;
157
+ this.pending.set(id, existing + delta);
158
+ this.logger.warn(
159
+ `access-tracker: write-back failed for ${id.slice(0, 8)}:`,
160
+ err
161
+ );
162
+ }
163
+ }
164
+ }
165
+ resetTimer() {
166
+ this.clearTimer();
167
+ this.debounceTimer = setTimeout(() => {
168
+ void this.flush();
169
+ }, this.debounceMs);
170
+ }
171
+ clearTimer() {
172
+ if (this.debounceTimer !== null) {
173
+ clearTimeout(this.debounceTimer);
174
+ this.debounceTimer = null;
175
+ }
176
+ }
177
+ }
178
+ export {
179
+ AccessTracker,
180
+ buildUpdatedMetadata,
181
+ computeEffectiveHalfLife,
182
+ parseAccessMetadata
183
+ };
184
+ //# sourceMappingURL=access-tracker.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/access-tracker.ts"],
4
+ "sourcesContent": ["// SPDX-License-Identifier: LicenseRef-Mnemo-Pro\n/**\n * Access Tracker\n *\n * Tracks memory access patterns to support reinforcement-based decay.\n * Frequently accessed memories decay more slowly (longer effective half-life).\n *\n * Key exports:\n * - parseAccessMetadata \u2014 extract accessCount/lastAccessedAt from metadata JSON\n * - buildUpdatedMetadata \u2014 merge access fields into existing metadata JSON\n * - computeEffectiveHalfLife \u2014 compute reinforced half-life from access history\n * - AccessTracker \u2014 debounced write-back tracker for batch metadata updates\n */\n\nimport type { MemoryStore } from \"./store.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface AccessMetadata {\n readonly accessCount: number;\n readonly lastAccessedAt: number;\n}\n\nexport interface AccessTrackerOptions {\n readonly store: MemoryStore;\n readonly logger: {\n warn: (...args: unknown[]) => void;\n info?: (...args: unknown[]) => void;\n };\n readonly debounceMs?: number;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst MIN_ACCESS_COUNT = 0;\nconst MAX_ACCESS_COUNT = 10_000;\n\n/** Access count itself decays with a 30-day half-life */\nconst ACCESS_DECAY_HALF_LIFE_DAYS = 30;\n\n// ============================================================================\n// Utility\n// ============================================================================\n\nfunction clampAccessCount(value: number): number {\n if (!Number.isFinite(value)) return MIN_ACCESS_COUNT;\n return Math.min(\n MAX_ACCESS_COUNT,\n Math.max(MIN_ACCESS_COUNT, Math.floor(value)),\n );\n}\n\n// ============================================================================\n// Metadata Parsing\n// ============================================================================\n\n/**\n * Parse access-related fields from a metadata JSON string.\n *\n * Handles: undefined, empty string, malformed JSON, negative numbers,\n * numbers exceeding 10000. Always returns a valid AccessMetadata.\n */\nexport function parseAccessMetadata(\n metadata: string | undefined,\n): AccessMetadata {\n if (metadata === undefined || metadata === \"\") {\n return { accessCount: 0, lastAccessedAt: 0 };\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(metadata);\n } catch {\n return { accessCount: 0, lastAccessedAt: 0 };\n }\n\n if (typeof parsed !== \"object\" || parsed === null) {\n return { accessCount: 0, lastAccessedAt: 0 };\n }\n\n const obj = parsed as Record<string, unknown>;\n\n // Support both camelCase and snake_case keys (beta smart-memory uses snake_case).\n const rawCountAny = obj.accessCount ?? obj.access_count;\n const rawCount =\n typeof rawCountAny === \"number\" ? rawCountAny : Number(rawCountAny ?? 0);\n\n const rawLastAny = obj.lastAccessedAt ?? obj.last_accessed_at;\n const rawLastAccessed =\n typeof rawLastAny === \"number\" ? rawLastAny : Number(rawLastAny ?? 0);\n\n return {\n accessCount: clampAccessCount(rawCount),\n lastAccessedAt:\n Number.isFinite(rawLastAccessed) && rawLastAccessed >= 0\n ? rawLastAccessed\n : 0,\n };\n}\n\n// ============================================================================\n// Metadata Building\n// ============================================================================\n\n/**\n * Merge an access-count increment into existing metadata JSON.\n *\n * Preserves ALL existing fields in the metadata object \u2014 only overwrites\n * `accessCount` and `lastAccessedAt`. Returns a new JSON string.\n */\nexport function buildUpdatedMetadata(\n existingMetadata: string | undefined,\n accessDelta: number,\n): string {\n let existing: Record<string, unknown> = {};\n\n if (existingMetadata !== undefined && existingMetadata !== \"\") {\n try {\n const parsed = JSON.parse(existingMetadata);\n if (typeof parsed === \"object\" && parsed !== null) {\n existing = { ...parsed };\n }\n } catch {\n // malformed JSON \u2014 start fresh but preserve nothing\n }\n }\n\n const prev = parseAccessMetadata(existingMetadata);\n const newCount = clampAccessCount(prev.accessCount + accessDelta);\n\n const now = Date.now();\n\n return JSON.stringify({\n ...existing,\n // Write both camelCase and snake_case for compatibility.\n accessCount: newCount,\n lastAccessedAt: now,\n access_count: newCount,\n last_accessed_at: now,\n });\n}\n\n// ============================================================================\n// Effective Half-Life Computation\n// ============================================================================\n\n/**\n * Compute the effective half-life for a memory based on its access history.\n *\n * The access count itself decays over time (30-day half-life for access\n * freshness), so stale accesses contribute less reinforcement. The extension\n * uses a logarithmic curve (`Math.log1p`) to provide diminishing returns.\n *\n * @param baseHalfLife - Base half-life in days (e.g. 30)\n * @param accessCount - Raw number of times the memory was accessed\n * @param lastAccessedAt - Timestamp (ms) of last access\n * @param reinforcementFactor - Scaling factor for reinforcement (0 = disabled)\n * @param maxMultiplier - Hard cap: result <= baseHalfLife * maxMultiplier\n * @returns Effective half-life in days\n */\nexport function computeEffectiveHalfLife(\n baseHalfLife: number,\n accessCount: number,\n lastAccessedAt: number,\n reinforcementFactor: number,\n maxMultiplier: number,\n): number {\n // Short-circuit: no reinforcement or no accesses\n if (reinforcementFactor === 0 || accessCount <= 0) {\n return baseHalfLife;\n }\n\n const now = Date.now();\n const daysSinceLastAccess = Math.max(\n 0,\n (now - lastAccessedAt) / (1000 * 60 * 60 * 24),\n );\n\n // Access freshness decays exponentially with 30-day half-life\n const accessFreshness = Math.exp(\n -daysSinceLastAccess * (Math.LN2 / ACCESS_DECAY_HALF_LIFE_DAYS),\n );\n\n // Effective access count after freshness decay\n const effectiveAccessCount = accessCount * accessFreshness;\n\n // Logarithmic extension for diminishing returns\n const extension =\n baseHalfLife * reinforcementFactor * Math.log1p(effectiveAccessCount);\n\n const result = baseHalfLife + extension;\n\n // Hard cap\n const cap = baseHalfLife * maxMultiplier;\n return Math.min(result, cap);\n}\n\n// ============================================================================\n// AccessTracker Class\n// ============================================================================\n\n/**\n * Debounced write-back tracker for memory access events.\n *\n * `recordAccess()` is synchronous (Map update only, no I/O). Pending deltas\n * accumulate until `flush()` is called (or by a future scheduled callback).\n * On flush, each pending entry is read via `store.getById()`, its metadata\n * is merged with the accumulated access delta, and written back via\n * `store.update()`.\n */\nexport class AccessTracker {\n private readonly pending: Map<string, number> = new Map();\n private debounceTimer: ReturnType<typeof setTimeout> | null = null;\n private flushPromise: Promise<void> | null = null;\n private readonly debounceMs: number;\n private readonly store: MemoryStore;\n private readonly logger: {\n warn: (...args: unknown[]) => void;\n info?: (...args: unknown[]) => void;\n };\n\n constructor(options: AccessTrackerOptions) {\n this.store = options.store;\n this.logger = options.logger;\n this.debounceMs = options.debounceMs ?? 5_000;\n }\n\n /**\n * Record one access for each of the given memory IDs.\n * Synchronous \u2014 only updates the in-memory pending map.\n */\n recordAccess(ids: readonly string[]): void {\n for (const id of ids) {\n const current = this.pending.get(id) ?? 0;\n this.pending.set(id, current + 1);\n }\n\n // Reset debounce timer\n this.resetTimer();\n }\n\n /**\n * Return a snapshot of all pending (id -> delta) entries.\n */\n getPendingUpdates(): Map<string, number> {\n return new Map(this.pending);\n }\n\n /**\n * Flush pending access deltas to the store.\n *\n * If a flush is already in progress, awaits the current flush to complete.\n * If new pending data accumulated during the in-flight flush, a follow-up\n * flush is automatically triggered.\n */\n async flush(): Promise<void> {\n this.clearTimer();\n\n // If a flush is in progress, wait for it to finish\n if (this.flushPromise) {\n await this.flushPromise;\n // After the in-flight flush completes, check if new data accumulated\n if (this.pending.size > 0) {\n return this.flush();\n }\n return;\n }\n\n if (this.pending.size === 0) return;\n\n this.flushPromise = this.doFlush();\n try {\n await this.flushPromise;\n } finally {\n this.flushPromise = null;\n }\n\n // If new data accumulated during flush, schedule a follow-up\n if (this.pending.size > 0) {\n this.resetTimer();\n }\n }\n\n /**\n * Tear down the tracker \u2014 cancel timers and clear pending state.\n */\n destroy(): void {\n this.clearTimer();\n if (this.pending.size > 0) {\n this.logger.warn(\n `access-tracker: destroying with ${this.pending.size} pending writes`,\n );\n }\n this.pending.clear();\n }\n\n // --------------------------------------------------------------------------\n // Internal helpers\n // --------------------------------------------------------------------------\n\n private async doFlush(): Promise<void> {\n const batch = new Map(this.pending);\n this.pending.clear();\n\n for (const [id, delta] of batch) {\n try {\n const current = await this.store.getById(id);\n if (!current) continue;\n\n const updatedMeta = buildUpdatedMetadata(current.metadata, delta);\n await this.store.update(id, { metadata: updatedMeta });\n } catch (err) {\n // Requeue failed delta for retry on next flush\n const existing = this.pending.get(id) ?? 0;\n this.pending.set(id, existing + delta);\n this.logger.warn(\n `access-tracker: write-back failed for ${id.slice(0, 8)}:`,\n err,\n );\n }\n }\n }\n\n private resetTimer(): void {\n this.clearTimer();\n this.debounceTimer = setTimeout(() => {\n void this.flush();\n }, this.debounceMs);\n }\n\n private clearTimer(): void {\n if (this.debounceTimer !== null) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Chroma Storage Adapter for Mnemo
3
+ *
4
+ * Requirements:
5
+ * npm install chromadb
6
+ *
7
+ * Config:
8
+ * storage: "chroma"
9
+ * storageConfig: { url: "http://localhost:8000" } or { path: "./chroma-data" }
10
+ */
11
+ import type { StorageAdapter, MemoryRecord, SearchResult, QueryOptions } from "../storage-adapter.js";
12
+ export declare class ChromaAdapter implements StorageAdapter {
13
+ readonly name = "chroma";
14
+ private client;
15
+ private collection;
16
+ private config;
17
+ constructor(config?: Record<string, unknown>);
18
+ connect(dbPath: string): Promise<void>;
19
+ ensureTable(vectorDimensions: number): Promise<void>;
20
+ add(records: MemoryRecord[]): Promise<void>;
21
+ update(id: string, record: MemoryRecord): Promise<void>;
22
+ delete(filter: string): Promise<void>;
23
+ vectorSearch(vector: number[], limit: number, minScore?: number, scopeFilter?: string[]): Promise<SearchResult[]>;
24
+ fullTextSearch(query: string, limit: number, scopeFilter?: string[]): Promise<SearchResult[]>;
25
+ query(options: QueryOptions): Promise<MemoryRecord[]>;
26
+ count(): Promise<number>;
27
+ ensureFullTextIndex(): Promise<void>;
28
+ hasFullTextSearch(): boolean;
29
+ close(): Promise<void>;
30
+ }
31
+ //# sourceMappingURL=chroma.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chroma.d.ts","sourceRoot":"","sources":["../../../src/adapters/chroma.ts"],"names":[],"mappings":"AACA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EACV,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,YAAY,EACb,MAAM,uBAAuB,CAAC;AAK/B,qBAAa,aAAc,YAAW,cAAc;IAClD,QAAQ,CAAC,IAAI,YAAY;IAEzB,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,MAAM,CAA0B;gBAE5B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAItC,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAYtC,WAAW,CAAC,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOpD,GAAG,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB3C,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvD,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASrC,YAAY,CAChB,MAAM,EAAE,MAAM,EAAE,EAChB,KAAK,EAAE,MAAM,EACb,QAAQ,SAAI,EACZ,WAAW,CAAC,EAAE,MAAM,EAAE,GACrB,OAAO,CAAC,YAAY,EAAE,CAAC;IAqCpB,cAAc,CAClB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,MAAM,EAAE,GACrB,OAAO,CAAC,YAAY,EAAE,CAAC;IAmCpB,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAuBrD,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAKxB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAI1C,iBAAiB,IAAI,OAAO;IAItB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAI7B"}
@@ -1,58 +1,29 @@
1
- // SPDX-License-Identifier: MIT
2
- /**
3
- * Chroma Storage Adapter for Mnemo
4
- *
5
- * Requirements:
6
- * npm install chromadb
7
- *
8
- * Config:
9
- * storage: "chroma"
10
- * storageConfig: { url: "http://localhost:8000" } or { path: "./chroma-data" }
11
- */
12
-
13
- import type {
14
- StorageAdapter,
15
- MemoryRecord,
16
- SearchResult,
17
- QueryOptions,
18
- } from "../storage-adapter.js";
19
1
  import { registerAdapter } from "../storage-adapter.js";
20
-
21
2
  const COLLECTION = "mnemo_memories";
22
-
23
- export class ChromaAdapter implements StorageAdapter {
24
- readonly name = "chroma";
25
-
26
- private client: any = null;
27
- private collection: any = null;
28
- private config: Record<string, unknown>;
29
-
30
- constructor(config?: Record<string, unknown>) {
3
+ class ChromaAdapter {
4
+ name = "chroma";
5
+ client = null;
6
+ collection = null;
7
+ config;
8
+ constructor(config) {
31
9
  this.config = config || {};
32
10
  }
33
-
34
- async connect(dbPath: string): Promise<void> {
11
+ async connect(dbPath) {
35
12
  const chroma = await import("chromadb");
36
-
37
13
  if (this.config.url) {
38
- // Remote Chroma server
39
- this.client = new chroma.ChromaClient({ path: this.config.url as string });
14
+ this.client = new chroma.ChromaClient({ path: this.config.url });
40
15
  } else {
41
- // Persistent local (Chroma supports persistent storage)
42
- this.client = new chroma.ChromaClient({ path: dbPath || this.config.path as string });
16
+ this.client = new chroma.ChromaClient({ path: dbPath || this.config.path });
43
17
  }
44
18
  }
45
-
46
- async ensureTable(vectorDimensions: number): Promise<void> {
19
+ async ensureTable(vectorDimensions) {
47
20
  this.collection = await this.client.getOrCreateCollection({
48
21
  name: COLLECTION,
49
- metadata: { "hnsw:space": "cosine" },
22
+ metadata: { "hnsw:space": "cosine" }
50
23
  });
51
24
  }
52
-
53
- async add(records: MemoryRecord[]): Promise<void> {
25
+ async add(records) {
54
26
  if (!this.collection) throw new Error("Collection not initialized");
55
-
56
27
  await this.collection.upsert({
57
28
  ids: records.map((r) => r.id),
58
29
  embeddings: records.map((r) => r.vector),
@@ -62,87 +33,59 @@ export class ChromaAdapter implements StorageAdapter {
62
33
  scope: r.scope,
63
34
  importance: r.importance,
64
35
  category: r.category,
65
- metadata: r.metadata,
66
- })),
36
+ metadata: r.metadata
37
+ }))
67
38
  });
68
39
  }
69
-
70
- async update(id: string, record: MemoryRecord): Promise<void> {
40
+ async update(id, record) {
71
41
  await this.add([record]);
72
42
  }
73
-
74
- async delete(filter: string): Promise<void> {
43
+ async delete(filter) {
75
44
  if (!this.collection) throw new Error("Collection not initialized");
76
-
77
45
  const idMatch = filter.match(/id\s*=\s*'([^']+)'/);
78
46
  if (idMatch) {
79
47
  await this.collection.delete({ ids: [idMatch[1]] });
80
48
  }
81
49
  }
82
-
83
- async vectorSearch(
84
- vector: number[],
85
- limit: number,
86
- minScore = 0,
87
- scopeFilter?: string[],
88
- ): Promise<SearchResult[]> {
50
+ async vectorSearch(vector, limit, minScore = 0, scopeFilter) {
89
51
  if (!this.collection) throw new Error("Collection not initialized");
90
-
91
- const where = scopeFilter?.length
92
- ? { scope: { $in: scopeFilter } }
93
- : undefined;
94
-
52
+ const where = scopeFilter?.length ? { scope: { $in: scopeFilter } } : void 0;
95
53
  const results = await this.collection.query({
96
54
  queryEmbeddings: [vector],
97
55
  nResults: limit,
98
- ...(where ? { where } : {}),
56
+ ...where ? { where } : {}
99
57
  });
100
-
101
58
  if (!results.ids?.[0]) return [];
102
-
103
- return results.ids[0].map((id: string, i: number) => {
104
- // Chroma returns distances, convert to similarity
59
+ return results.ids[0].map((id, i) => {
105
60
  const distance = results.distances?.[0]?.[i] ?? 1;
106
- const score = 1 - distance; // cosine distance → similarity
61
+ const score = 1 - distance;
107
62
  const meta = results.metadatas?.[0]?.[i] || {};
108
-
109
63
  return {
110
64
  record: {
111
65
  id,
112
66
  text: results.documents?.[0]?.[i] ?? "",
113
- vector: [], // Chroma doesn't return vectors by default
67
+ vector: [],
68
+ // Chroma doesn't return vectors by default
114
69
  timestamp: meta.timestamp ?? 0,
115
70
  scope: meta.scope ?? "global",
116
71
  importance: meta.importance ?? 0.5,
117
72
  category: meta.category ?? "other",
118
- metadata: meta.metadata ?? "{}",
73
+ metadata: meta.metadata ?? "{}"
119
74
  },
120
- score,
75
+ score
121
76
  };
122
- }).filter((r: SearchResult) => r.score >= minScore);
77
+ }).filter((r) => r.score >= minScore);
123
78
  }
124
-
125
- async fullTextSearch(
126
- query: string,
127
- limit: number,
128
- scopeFilter?: string[],
129
- ): Promise<SearchResult[]> {
79
+ async fullTextSearch(query, limit, scopeFilter) {
130
80
  if (!this.collection) return [];
131
-
132
- const where = scopeFilter?.length
133
- ? { scope: { $in: scopeFilter } }
134
- : undefined;
135
-
136
- // Chroma supports document search via where_document
81
+ const where = scopeFilter?.length ? { scope: { $in: scopeFilter } } : void 0;
137
82
  const results = await this.collection.query({
138
83
  queryTexts: [query],
139
84
  nResults: limit,
140
- ...(where ? { where } : {}),
85
+ ...where ? { where } : {}
141
86
  });
142
-
143
87
  if (!results.ids?.[0]) return [];
144
-
145
- return results.ids[0].map((id: string, i: number) => {
88
+ return results.ids[0].map((id, i) => {
146
89
  const distance = results.distances?.[0]?.[i] ?? 1;
147
90
  const meta = results.metadatas?.[0]?.[i] || {};
148
91
  return {
@@ -154,22 +97,19 @@ export class ChromaAdapter implements StorageAdapter {
154
97
  scope: meta.scope ?? "global",
155
98
  importance: meta.importance ?? 0.5,
156
99
  category: meta.category ?? "other",
157
- metadata: meta.metadata ?? "{}",
100
+ metadata: meta.metadata ?? "{}"
158
101
  },
159
- score: 1 - distance,
102
+ score: 1 - distance
160
103
  };
161
104
  });
162
105
  }
163
-
164
- async query(options: QueryOptions): Promise<MemoryRecord[]> {
106
+ async query(options) {
165
107
  if (!this.collection) throw new Error("Collection not initialized");
166
-
167
108
  const result = await this.collection.get({
168
109
  limit: options.limit || 100,
169
- include: ["documents", "metadatas", "embeddings"],
110
+ include: ["documents", "metadatas", "embeddings"]
170
111
  });
171
-
172
- return (result.ids || []).map((id: string, i: number) => {
112
+ return (result.ids || []).map((id, i) => {
173
113
  const meta = result.metadatas?.[i] || {};
174
114
  return {
175
115
  id,
@@ -179,28 +119,26 @@ export class ChromaAdapter implements StorageAdapter {
179
119
  scope: meta.scope ?? "global",
180
120
  importance: meta.importance ?? 0.5,
181
121
  category: meta.category ?? "other",
182
- metadata: meta.metadata ?? "{}",
122
+ metadata: meta.metadata ?? "{}"
183
123
  };
184
124
  });
185
125
  }
186
-
187
- async count(): Promise<number> {
126
+ async count() {
188
127
  if (!this.collection) return 0;
189
128
  return await this.collection.count();
190
129
  }
191
-
192
- async ensureFullTextIndex(): Promise<void> {
193
- // Chroma handles text search natively via queryTexts
130
+ async ensureFullTextIndex() {
194
131
  }
195
-
196
- hasFullTextSearch(): boolean {
197
- return true; // Chroma supports document-level text search
132
+ hasFullTextSearch() {
133
+ return true;
198
134
  }
199
-
200
- async close(): Promise<void> {
135
+ async close() {
201
136
  this.collection = null;
202
137
  this.client = null;
203
138
  }
204
139
  }
205
-
206
140
  registerAdapter("chroma", (config) => new ChromaAdapter(config));
141
+ export {
142
+ ChromaAdapter
143
+ };
144
+ //# sourceMappingURL=chroma.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/adapters/chroma.ts"],
4
+ "sourcesContent": ["// SPDX-License-Identifier: MIT\n/**\n * Chroma Storage Adapter for Mnemo\n *\n * Requirements:\n * npm install chromadb\n *\n * Config:\n * storage: \"chroma\"\n * storageConfig: { url: \"http://localhost:8000\" } or { path: \"./chroma-data\" }\n */\n\nimport type {\n StorageAdapter,\n MemoryRecord,\n SearchResult,\n QueryOptions,\n} from \"../storage-adapter.js\";\nimport { registerAdapter } from \"../storage-adapter.js\";\n\nconst COLLECTION = \"mnemo_memories\";\n\nexport class ChromaAdapter implements StorageAdapter {\n readonly name = \"chroma\";\n\n private client: any = null;\n private collection: any = null;\n private config: Record<string, unknown>;\n\n constructor(config?: Record<string, unknown>) {\n this.config = config || {};\n }\n\n async connect(dbPath: string): Promise<void> {\n const chroma = await import(\"chromadb\");\n\n if (this.config.url) {\n // Remote Chroma server\n this.client = new chroma.ChromaClient({ path: this.config.url as string });\n } else {\n // Persistent local (Chroma supports persistent storage)\n this.client = new chroma.ChromaClient({ path: dbPath || this.config.path as string });\n }\n }\n\n async ensureTable(vectorDimensions: number): Promise<void> {\n this.collection = await this.client.getOrCreateCollection({\n name: COLLECTION,\n metadata: { \"hnsw:space\": \"cosine\" },\n });\n }\n\n async add(records: MemoryRecord[]): Promise<void> {\n if (!this.collection) throw new Error(\"Collection not initialized\");\n\n await this.collection.upsert({\n ids: records.map((r) => r.id),\n embeddings: records.map((r) => r.vector),\n documents: records.map((r) => r.text),\n metadatas: records.map((r) => ({\n timestamp: r.timestamp,\n scope: r.scope,\n importance: r.importance,\n category: r.category,\n metadata: r.metadata,\n })),\n });\n }\n\n async update(id: string, record: MemoryRecord): Promise<void> {\n await this.add([record]);\n }\n\n async delete(filter: string): Promise<void> {\n if (!this.collection) throw new Error(\"Collection not initialized\");\n\n const idMatch = filter.match(/id\\s*=\\s*'([^']+)'/);\n if (idMatch) {\n await this.collection.delete({ ids: [idMatch[1]] });\n }\n }\n\n async vectorSearch(\n vector: number[],\n limit: number,\n minScore = 0,\n scopeFilter?: string[],\n ): Promise<SearchResult[]> {\n if (!this.collection) throw new Error(\"Collection not initialized\");\n\n const where = scopeFilter?.length\n ? { scope: { $in: scopeFilter } }\n : undefined;\n\n const results = await this.collection.query({\n queryEmbeddings: [vector],\n nResults: limit,\n ...(where ? { where } : {}),\n });\n\n if (!results.ids?.[0]) return [];\n\n return results.ids[0].map((id: string, i: number) => {\n // Chroma returns distances, convert to similarity\n const distance = results.distances?.[0]?.[i] ?? 1;\n const score = 1 - distance; // cosine distance \u2192 similarity\n const meta = results.metadatas?.[0]?.[i] || {};\n\n return {\n record: {\n id,\n text: results.documents?.[0]?.[i] ?? \"\",\n vector: [], // Chroma doesn't return vectors by default\n timestamp: meta.timestamp ?? 0,\n scope: meta.scope ?? \"global\",\n importance: meta.importance ?? 0.5,\n category: meta.category ?? \"other\",\n metadata: meta.metadata ?? \"{}\",\n },\n score,\n };\n }).filter((r: SearchResult) => r.score >= minScore);\n }\n\n async fullTextSearch(\n query: string,\n limit: number,\n scopeFilter?: string[],\n ): Promise<SearchResult[]> {\n if (!this.collection) return [];\n\n const where = scopeFilter?.length\n ? { scope: { $in: scopeFilter } }\n : undefined;\n\n // Chroma supports document search via where_document\n const results = await this.collection.query({\n queryTexts: [query],\n nResults: limit,\n ...(where ? { where } : {}),\n });\n\n if (!results.ids?.[0]) return [];\n\n return results.ids[0].map((id: string, i: number) => {\n const distance = results.distances?.[0]?.[i] ?? 1;\n const meta = results.metadatas?.[0]?.[i] || {};\n return {\n record: {\n id,\n text: results.documents?.[0]?.[i] ?? \"\",\n vector: [],\n timestamp: meta.timestamp ?? 0,\n scope: meta.scope ?? \"global\",\n importance: meta.importance ?? 0.5,\n category: meta.category ?? \"other\",\n metadata: meta.metadata ?? \"{}\",\n },\n score: 1 - distance,\n };\n });\n }\n\n async query(options: QueryOptions): Promise<MemoryRecord[]> {\n if (!this.collection) throw new Error(\"Collection not initialized\");\n\n const result = await this.collection.get({\n limit: options.limit || 100,\n include: [\"documents\", \"metadatas\", \"embeddings\"],\n });\n\n return (result.ids || []).map((id: string, i: number) => {\n const meta = result.metadatas?.[i] || {};\n return {\n id,\n text: result.documents?.[i] ?? \"\",\n vector: result.embeddings?.[i] ?? [],\n timestamp: meta.timestamp ?? 0,\n scope: meta.scope ?? \"global\",\n importance: meta.importance ?? 0.5,\n category: meta.category ?? \"other\",\n metadata: meta.metadata ?? \"{}\",\n };\n });\n }\n\n async count(): Promise<number> {\n if (!this.collection) return 0;\n return await this.collection.count();\n }\n\n async ensureFullTextIndex(): Promise<void> {\n // Chroma handles text search natively via queryTexts\n }\n\n hasFullTextSearch(): boolean {\n return true; // Chroma supports document-level text search\n }\n\n async close(): Promise<void> {\n this.collection = null;\n this.client = null;\n }\n}\n\nregisterAdapter(\"chroma\", (config) => new ChromaAdapter(config));\n"],
5
+ "mappings": "AAkBA,SAAS,uBAAuB;AAEhC,MAAM,aAAa;AAEZ,MAAM,cAAwC;AAAA,EAC1C,OAAO;AAAA,EAER,SAAc;AAAA,EACd,aAAkB;AAAA,EAClB;AAAA,EAER,YAAY,QAAkC;AAC5C,SAAK,SAAS,UAAU,CAAC;AAAA,EAC3B;AAAA,EAEA,MAAM,QAAQ,QAA+B;AAC3C,UAAM,SAAS,MAAM,OAAO,UAAU;AAEtC,QAAI,KAAK,OAAO,KAAK;AAEnB,WAAK,SAAS,IAAI,OAAO,aAAa,EAAE,MAAM,KAAK,OAAO,IAAc,CAAC;AAAA,IAC3E,OAAO;AAEL,WAAK,SAAS,IAAI,OAAO,aAAa,EAAE,MAAM,UAAU,KAAK,OAAO,KAAe,CAAC;AAAA,IACtF;AAAA,EACF;AAAA,EAEA,MAAM,YAAY,kBAAyC;AACzD,SAAK,aAAa,MAAM,KAAK,OAAO,sBAAsB;AAAA,MACxD,MAAM;AAAA,MACN,UAAU,EAAE,cAAc,SAAS;AAAA,IACrC,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,IAAI,SAAwC;AAChD,QAAI,CAAC,KAAK,WAAY,OAAM,IAAI,MAAM,4BAA4B;AAElE,UAAM,KAAK,WAAW,OAAO;AAAA,MAC3B,KAAK,QAAQ,IAAI,CAAC,MAAM,EAAE,EAAE;AAAA,MAC5B,YAAY,QAAQ,IAAI,CAAC,MAAM,EAAE,MAAM;AAAA,MACvC,WAAW,QAAQ,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,MACpC,WAAW,QAAQ,IAAI,CAAC,OAAO;AAAA,QAC7B,WAAW,EAAE;AAAA,QACb,OAAO,EAAE;AAAA,QACT,YAAY,EAAE;AAAA,QACd,UAAU,EAAE;AAAA,QACZ,UAAU,EAAE;AAAA,MACd,EAAE;AAAA,IACJ,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,OAAO,IAAY,QAAqC;AAC5D,UAAM,KAAK,IAAI,CAAC,MAAM,CAAC;AAAA,EACzB;AAAA,EAEA,MAAM,OAAO,QAA+B;AAC1C,QAAI,CAAC,KAAK,WAAY,OAAM,IAAI,MAAM,4BAA4B;AAElE,UAAM,UAAU,OAAO,MAAM,oBAAoB;AACjD,QAAI,SAAS;AACX,YAAM,KAAK,WAAW,OAAO,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;AAAA,IACpD;AAAA,EACF;AAAA,EAEA,MAAM,aACJ,QACA,OACA,WAAW,GACX,aACyB;AACzB,QAAI,CAAC,KAAK,WAAY,OAAM,IAAI,MAAM,4BAA4B;AAElE,UAAM,QAAQ,aAAa,SACvB,EAAE,OAAO,EAAE,KAAK,YAAY,EAAE,IAC9B;AAEJ,UAAM,UAAU,MAAM,KAAK,WAAW,MAAM;AAAA,MAC1C,iBAAiB,CAAC,MAAM;AAAA,MACxB,UAAU;AAAA,MACV,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;AAAA,IAC3B,CAAC;AAED,QAAI,CAAC,QAAQ,MAAM,CAAC,EAAG,QAAO,CAAC;AAE/B,WAAO,QAAQ,IAAI,CAAC,EAAE,IAAI,CAAC,IAAY,MAAc;AAEnD,YAAM,WAAW,QAAQ,YAAY,CAAC,IAAI,CAAC,KAAK;AAChD,YAAM,QAAQ,IAAI;AAClB,YAAM,OAAO,QAAQ,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC;AAE7C,aAAO;AAAA,QACL,QAAQ;AAAA,UACN;AAAA,UACA,MAAM,QAAQ,YAAY,CAAC,IAAI,CAAC,KAAK;AAAA,UACrC,QAAQ,CAAC;AAAA;AAAA,UACT,WAAW,KAAK,aAAa;AAAA,UAC7B,OAAO,KAAK,SAAS;AAAA,UACrB,YAAY,KAAK,cAAc;AAAA,UAC/B,UAAU,KAAK,YAAY;AAAA,UAC3B,UAAU,KAAK,YAAY;AAAA,QAC7B;AAAA,QACA;AAAA,MACF;AAAA,IACF,CAAC,EAAE,OAAO,CAAC,MAAoB,EAAE,SAAS,QAAQ;AAAA,EACpD;AAAA,EAEA,MAAM,eACJ,OACA,OACA,aACyB;AACzB,QAAI,CAAC,KAAK,WAAY,QAAO,CAAC;AAE9B,UAAM,QAAQ,aAAa,SACvB,EAAE,OAAO,EAAE,KAAK,YAAY,EAAE,IAC9B;AAGJ,UAAM,UAAU,MAAM,KAAK,WAAW,MAAM;AAAA,MAC1C,YAAY,CAAC,KAAK;AAAA,MAClB,UAAU;AAAA,MACV,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;AAAA,IAC3B,CAAC;AAED,QAAI,CAAC,QAAQ,MAAM,CAAC,EAAG,QAAO,CAAC;AAE/B,WAAO,QAAQ,IAAI,CAAC,EAAE,IAAI,CAAC,IAAY,MAAc;AACnD,YAAM,WAAW,QAAQ,YAAY,CAAC,IAAI,CAAC,KAAK;AAChD,YAAM,OAAO,QAAQ,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC;AAC7C,aAAO;AAAA,QACL,QAAQ;AAAA,UACN;AAAA,UACA,MAAM,QAAQ,YAAY,CAAC,IAAI,CAAC,KAAK;AAAA,UACrC,QAAQ,CAAC;AAAA,UACT,WAAW,KAAK,aAAa;AAAA,UAC7B,OAAO,KAAK,SAAS;AAAA,UACrB,YAAY,KAAK,cAAc;AAAA,UAC/B,UAAU,KAAK,YAAY;AAAA,UAC3B,UAAU,KAAK,YAAY;AAAA,QAC7B;AAAA,QACA,OAAO,IAAI;AAAA,MACb;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,MAAM,SAAgD;AAC1D,QAAI,CAAC,KAAK,WAAY,OAAM,IAAI,MAAM,4BAA4B;AAElE,UAAM,SAAS,MAAM,KAAK,WAAW,IAAI;AAAA,MACvC,OAAO,QAAQ,SAAS;AAAA,MACxB,SAAS,CAAC,aAAa,aAAa,YAAY;AAAA,IAClD,CAAC;AAED,YAAQ,OAAO,OAAO,CAAC,GAAG,IAAI,CAAC,IAAY,MAAc;AACvD,YAAM,OAAO,OAAO,YAAY,CAAC,KAAK,CAAC;AACvC,aAAO;AAAA,QACL;AAAA,QACA,MAAM,OAAO,YAAY,CAAC,KAAK;AAAA,QAC/B,QAAQ,OAAO,aAAa,CAAC,KAAK,CAAC;AAAA,QACnC,WAAW,KAAK,aAAa;AAAA,QAC7B,OAAO,KAAK,SAAS;AAAA,QACrB,YAAY,KAAK,cAAc;AAAA,QAC/B,UAAU,KAAK,YAAY;AAAA,QAC3B,UAAU,KAAK,YAAY;AAAA,MAC7B;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,QAAyB;AAC7B,QAAI,CAAC,KAAK,WAAY,QAAO;AAC7B,WAAO,MAAM,KAAK,WAAW,MAAM;AAAA,EACrC;AAAA,EAEA,MAAM,sBAAqC;AAAA,EAE3C;AAAA,EAEA,oBAA6B;AAC3B,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,QAAuB;AAC3B,SAAK,aAAa;AAClB,SAAK,SAAS;AAAA,EAChB;AACF;AAEA,gBAAgB,UAAU,CAAC,WAAW,IAAI,cAAc,MAAM,CAAC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * LanceDB Storage Adapter — Default backend for Mnemo.
3
+ *
4
+ * Implements StorageAdapter using @lancedb/lancedb.
5
+ * This is the reference implementation; other backends should
6
+ * produce equivalent behavior.
7
+ */
8
+ import type { StorageAdapter, MemoryRecord, SearchResult, QueryOptions } from "../storage-adapter.js";
9
+ export declare class LanceDBAdapter implements StorageAdapter {
10
+ readonly name = "lancedb";
11
+ private db;
12
+ private table;
13
+ private ftsReady;
14
+ private vectorDim;
15
+ connect(dbPath: string): Promise<void>;
16
+ ensureTable(vectorDimensions: number): Promise<void>;
17
+ add(records: MemoryRecord[]): Promise<void>;
18
+ update(id: string, record: MemoryRecord): Promise<void>;
19
+ delete(filter: string): Promise<void>;
20
+ vectorSearch(vector: number[], limit: number, minScore?: number, scopeFilter?: string[]): Promise<SearchResult[]>;
21
+ fullTextSearch(queryText: string, limit: number, scopeFilter?: string[]): Promise<SearchResult[]>;
22
+ query(options: QueryOptions): Promise<MemoryRecord[]>;
23
+ count(filter?: string): Promise<number>;
24
+ ensureFullTextIndex(): Promise<void>;
25
+ hasFullTextSearch(): boolean;
26
+ close(): Promise<void>;
27
+ private toRecord;
28
+ }
29
+ //# sourceMappingURL=lancedb.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lancedb.d.ts","sourceRoot":"","sources":["../../../src/adapters/lancedb.ts"],"names":[],"mappings":"AACA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,YAAY,EACb,MAAM,uBAAuB,CAAC;AAqB/B,qBAAa,cAAe,YAAW,cAAc;IACnD,QAAQ,CAAC,IAAI,aAAa;IAE1B,OAAO,CAAC,EAAE,CAAa;IACvB,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAAK;IAEhB,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKtC,WAAW,CAAC,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA8CpD,GAAG,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAK3C,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAMvD,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKrC,YAAY,CAChB,MAAM,EAAE,MAAM,EAAE,EAChB,KAAK,EAAE,MAAM,EACb,QAAQ,SAAI,EACZ,WAAW,CAAC,EAAE,MAAM,EAAE,GACrB,OAAO,CAAC,YAAY,EAAE,CAAC;IAsBpB,cAAc,CAClB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,MAAM,EAAE,GACrB,OAAO,CAAC,YAAY,EAAE,CAAC;IAoBpB,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAmBrD,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAQvC,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAqB1C,iBAAiB,IAAI,OAAO;IAItB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAO5B,OAAO,CAAC,QAAQ;CAajB"}