@prometheus-ai/memory 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/README.md +107 -0
  2. package/dist/types/cli.d.ts +35 -0
  3. package/dist/types/config.d.ts +77 -0
  4. package/dist/types/core/aaak.d.ts +55 -0
  5. package/dist/types/core/annotations.d.ts +75 -0
  6. package/dist/types/core/banks.d.ts +33 -0
  7. package/dist/types/core/beam/consolidate.d.ts +32 -0
  8. package/dist/types/core/beam/helpers.d.ts +76 -0
  9. package/dist/types/core/beam/index.d.ts +59 -0
  10. package/dist/types/core/beam/recall.d.ts +32 -0
  11. package/dist/types/core/beam/schema.d.ts +2 -0
  12. package/dist/types/core/beam/store.d.ts +35 -0
  13. package/dist/types/core/beam/types.d.ts +233 -0
  14. package/dist/types/core/binary-vectors.d.ts +54 -0
  15. package/dist/types/core/chat-normalize.d.ts +13 -0
  16. package/dist/types/core/content-sanitizer.d.ts +18 -0
  17. package/dist/types/core/cost-log.d.ts +13 -0
  18. package/dist/types/core/embeddings.d.ts +44 -0
  19. package/dist/types/core/entities.d.ts +7 -0
  20. package/dist/types/core/episodic-graph.d.ts +89 -0
  21. package/dist/types/core/extraction/client.d.ts +31 -0
  22. package/dist/types/core/extraction/diagnostics.d.ts +51 -0
  23. package/dist/types/core/extraction/prompts.d.ts +2 -0
  24. package/dist/types/core/extraction.d.ts +6 -0
  25. package/dist/types/core/index.d.ts +4 -0
  26. package/dist/types/core/llm-backends.d.ts +21 -0
  27. package/dist/types/core/local-llm.d.ts +15 -0
  28. package/dist/types/core/memory.d.ts +160 -0
  29. package/dist/types/core/migrations/e6-triplestore-split.d.ts +17 -0
  30. package/dist/types/core/migrations/index.d.ts +1 -0
  31. package/dist/types/core/mmr.d.ts +8 -0
  32. package/dist/types/core/orchestrator.d.ts +20 -0
  33. package/dist/types/core/patterns.d.ts +61 -0
  34. package/dist/types/core/plugins.d.ts +109 -0
  35. package/dist/types/core/polyphonic-recall.d.ts +66 -0
  36. package/dist/types/core/query-cache.d.ts +46 -0
  37. package/dist/types/core/query-intent.d.ts +20 -0
  38. package/dist/types/core/recall-diagnostics.d.ts +48 -0
  39. package/dist/types/core/runtime-options.d.ts +68 -0
  40. package/dist/types/core/shmr.d.ts +56 -0
  41. package/dist/types/core/streaming.d.ts +136 -0
  42. package/dist/types/core/synonyms.d.ts +46 -0
  43. package/dist/types/core/temporal-parser.d.ts +16 -0
  44. package/dist/types/core/token-counter.d.ts +8 -0
  45. package/dist/types/core/triples.d.ts +63 -0
  46. package/dist/types/core/typed-memory.d.ts +39 -0
  47. package/dist/types/core/vector-math.d.ts +1 -0
  48. package/dist/types/core/veracity-consolidation.d.ts +60 -0
  49. package/dist/types/core/weibull.d.ts +96 -0
  50. package/dist/types/db.d.ts +16 -0
  51. package/dist/types/diagnose.d.ts +24 -0
  52. package/dist/types/dr/index.d.ts +1 -0
  53. package/dist/types/dr/recovery.d.ts +68 -0
  54. package/dist/types/index.d.ts +5 -0
  55. package/dist/types/mcp-server.d.ts +40 -0
  56. package/dist/types/mcp-tools.d.ts +484 -0
  57. package/dist/types/migrations/e6-triplestore-split.d.ts +1 -0
  58. package/dist/types/migrations/index.d.ts +1 -0
  59. package/dist/types/types.d.ts +145 -0
  60. package/dist/types/util/datetime.d.ts +8 -0
  61. package/dist/types/util/env.d.ts +10 -0
  62. package/dist/types/util/ids.d.ts +3 -0
  63. package/dist/types/util/lru.d.ts +12 -0
  64. package/dist/types/util/regex.d.ts +10 -0
  65. package/package.json +85 -0
  66. package/src/cli.ts +398 -0
  67. package/src/config.ts +326 -0
  68. package/src/core/aaak.ts +142 -0
  69. package/src/core/annotations.ts +457 -0
  70. package/src/core/banks.ts +133 -0
  71. package/src/core/beam/consolidate.ts +965 -0
  72. package/src/core/beam/helpers.ts +977 -0
  73. package/src/core/beam/index.ts +353 -0
  74. package/src/core/beam/recall.ts +1100 -0
  75. package/src/core/beam/schema.ts +423 -0
  76. package/src/core/beam/store.ts +829 -0
  77. package/src/core/beam/types.ts +268 -0
  78. package/src/core/binary-vectors.ts +317 -0
  79. package/src/core/chat-normalize.ts +160 -0
  80. package/src/core/content-sanitizer.ts +136 -0
  81. package/src/core/cost-log.ts +103 -0
  82. package/src/core/embeddings.ts +423 -0
  83. package/src/core/entities.ts +259 -0
  84. package/src/core/episodic-graph.ts +708 -0
  85. package/src/core/extraction/client.ts +162 -0
  86. package/src/core/extraction/diagnostics.ts +193 -0
  87. package/src/core/extraction/prompts.ts +31 -0
  88. package/src/core/extraction.ts +335 -0
  89. package/src/core/index.ts +30 -0
  90. package/src/core/llm-backends.ts +51 -0
  91. package/src/core/local-llm.ts +436 -0
  92. package/src/core/memory.ts +630 -0
  93. package/src/core/migrations/e6-triplestore-split.ts +211 -0
  94. package/src/core/migrations/index.ts +1 -0
  95. package/src/core/mmr.ts +71 -0
  96. package/src/core/orchestrator.ts +62 -0
  97. package/src/core/patterns.ts +484 -0
  98. package/src/core/plugins.ts +375 -0
  99. package/src/core/polyphonic-recall.ts +563 -0
  100. package/src/core/query-cache.ts +354 -0
  101. package/src/core/query-intent.ts +139 -0
  102. package/src/core/recall-diagnostics.ts +157 -0
  103. package/src/core/runtime-options.ts +119 -0
  104. package/src/core/shmr.ts +460 -0
  105. package/src/core/streaming.ts +419 -0
  106. package/src/core/synonyms.ts +197 -0
  107. package/src/core/temporal-parser.ts +363 -0
  108. package/src/core/token-counter.ts +30 -0
  109. package/src/core/triples.ts +454 -0
  110. package/src/core/typed-memory.ts +407 -0
  111. package/src/core/vector-math.ts +23 -0
  112. package/src/core/veracity-consolidation.ts +477 -0
  113. package/src/core/weibull.ts +124 -0
  114. package/src/db.ts +128 -0
  115. package/src/diagnose.ts +174 -0
  116. package/src/dr/index.ts +1 -0
  117. package/src/dr/recovery.ts +405 -0
  118. package/src/index.ts +33 -0
  119. package/src/mcp-server.ts +155 -0
  120. package/src/mcp-tools.ts +970 -0
  121. package/src/migrations/e6-triplestore-split.ts +1 -0
  122. package/src/migrations/index.ts +1 -0
  123. package/src/types.ts +157 -0
  124. package/src/util/datetime.ts +69 -0
  125. package/src/util/env.ts +65 -0
  126. package/src/util/ids.ts +19 -0
  127. package/src/util/lru.ts +48 -0
  128. package/src/util/regex.ts +165 -0
@@ -0,0 +1,419 @@
1
+ import type { Database, SQLQueryBindings } from "bun:sqlite";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+
5
+ export const ALLOWED_DELTA_TABLES = new Set(["working_memory", "episodic_memory"] as const);
6
+ export type DeltaTable = "working_memory" | "episodic_memory";
7
+
8
+ const QUALIFIED_TABLE_NAMES: Record<DeltaTable, string> = {
9
+ working_memory: '"main"."working_memory"',
10
+ episodic_memory: '"main"."episodic_memory"',
11
+ };
12
+ const DELTA_UPDATABLE_COLUMNS = new Set([
13
+ "content",
14
+ "importance",
15
+ "metadata_json",
16
+ "veracity",
17
+ "memory_type",
18
+ "binary_vector",
19
+ "source",
20
+ "summary_of",
21
+ ]);
22
+ const DELTA_INSERTABLE_COLUMNS = new Set([
23
+ "id",
24
+ "content",
25
+ "importance",
26
+ "metadata_json",
27
+ "veracity",
28
+ "memory_type",
29
+ "binary_vector",
30
+ "source",
31
+ "summary_of",
32
+ "timestamp",
33
+ ]);
34
+
35
+ export enum EventType {
36
+ MEMORY_ADDED = "MEMORY_ADDED",
37
+ MEMORY_RECALLED = "MEMORY_RECALLED",
38
+ MEMORY_INVALIDATED = "MEMORY_INVALIDATED",
39
+ MEMORY_CONSOLIDATED = "MEMORY_CONSOLIDATED",
40
+ MEMORY_UPDATED = "MEMORY_UPDATED",
41
+ }
42
+
43
+ export interface MemoryEventInit {
44
+ readonly eventType?: string;
45
+ readonly event_type?: string;
46
+ readonly memoryId?: string;
47
+ readonly memory_id?: string;
48
+ readonly timestamp?: string;
49
+ readonly sessionId?: string | null;
50
+ readonly session_id?: string | null;
51
+ readonly content?: string | null;
52
+ readonly source?: string | null;
53
+ readonly importance?: number | null;
54
+ readonly metadata?: Record<string, unknown> | null;
55
+ readonly delta?: Record<string, unknown> | null;
56
+ }
57
+
58
+ export type MemoryEventDict = {
59
+ event_type: string;
60
+ memory_id: string;
61
+ timestamp: string;
62
+ session_id?: string | null;
63
+ content?: string | null;
64
+ source?: string | null;
65
+ importance?: number | null;
66
+ metadata?: Record<string, unknown> | null;
67
+ delta?: Record<string, unknown> | null;
68
+ };
69
+
70
+ function normalizeEventType(value: string | undefined): EventType {
71
+ if (value === undefined) throw new TypeError("event_type is required");
72
+ switch (value) {
73
+ case EventType.MEMORY_ADDED:
74
+ case EventType.MEMORY_RECALLED:
75
+ case EventType.MEMORY_INVALIDATED:
76
+ case EventType.MEMORY_CONSOLIDATED:
77
+ case EventType.MEMORY_UPDATED:
78
+ return value;
79
+ default: {
80
+ const mapped = EventType[value as keyof typeof EventType];
81
+ if (mapped !== undefined) return mapped;
82
+ throw new RangeError(`Unknown event type: ${value}`);
83
+ }
84
+ }
85
+ }
86
+
87
+ function isSqlQueryBinding(value: unknown): value is SQLQueryBindings {
88
+ return (
89
+ value === null ||
90
+ typeof value === "string" ||
91
+ typeof value === "number" ||
92
+ typeof value === "bigint" ||
93
+ typeof value === "boolean" ||
94
+ (ArrayBuffer.isView(value) && !(value instanceof DataView))
95
+ );
96
+ }
97
+
98
+ export class MemoryEvent {
99
+ readonly eventType: EventType;
100
+ readonly memoryId: string;
101
+ readonly timestamp: string;
102
+ readonly sessionId: string | null;
103
+ readonly content: string | null;
104
+ readonly source: string | null;
105
+ readonly importance: number | null;
106
+ readonly metadata: Record<string, unknown> | null;
107
+ readonly delta: Record<string, unknown> | null;
108
+
109
+ constructor(init: MemoryEventInit) {
110
+ this.eventType = normalizeEventType(init.eventType ?? init.event_type);
111
+ this.memoryId = init.memoryId ?? init.memory_id ?? "";
112
+ if (this.memoryId.length === 0) throw new TypeError("memory_id is required");
113
+ this.timestamp = init.timestamp ?? new Date().toISOString();
114
+ this.sessionId = init.sessionId ?? init.session_id ?? null;
115
+ this.content = init.content ?? null;
116
+ this.source = init.source ?? null;
117
+ this.importance = init.importance ?? null;
118
+ this.metadata = init.metadata ?? null;
119
+ this.delta = init.delta ?? null;
120
+ }
121
+ toDict(): MemoryEventDict {
122
+ const out: MemoryEventDict = {
123
+ event_type: this.eventType,
124
+ memory_id: this.memoryId,
125
+ timestamp: this.timestamp,
126
+ };
127
+ if (this.sessionId !== null) out.session_id = this.sessionId;
128
+ if (this.content !== null) out.content = this.content;
129
+ if (this.source !== null) out.source = this.source;
130
+ if (this.importance !== null) out.importance = this.importance;
131
+ if (this.metadata !== null) out.metadata = this.metadata;
132
+ if (this.delta !== null) out.delta = this.delta;
133
+ return out;
134
+ }
135
+ toJSON(): string {
136
+ return JSON.stringify(this.toDict());
137
+ }
138
+ static fromDict(data: MemoryEventDict | MemoryEventInit): MemoryEvent {
139
+ const eventType = normalizeEventType(("eventType" in data ? data.eventType : undefined) ?? data.event_type);
140
+ return new MemoryEvent({ ...data, eventType });
141
+ }
142
+ }
143
+
144
+ export type MemoryEventHandler = (event: MemoryEvent) => void;
145
+
146
+ type EventWaiter = (result: IteratorResult<MemoryEvent>) => void;
147
+
148
+ export class StreamIterator implements AsyncIterable<MemoryEvent>, AsyncIterator<MemoryEvent> {
149
+ private readonly queue: MemoryEvent[] = [];
150
+ private readonly waiters: EventWaiter[] = [];
151
+ private closed = false;
152
+ constructor(
153
+ private readonly stream: MemoryStream,
154
+ private readonly eventTypes: readonly EventType[] | null = null,
155
+ ) {}
156
+ push(event: MemoryEvent): void {
157
+ if (this.closed || (this.eventTypes !== null && !this.eventTypes.includes(event.eventType))) return;
158
+ const waiter = this.waiters.shift();
159
+ if (waiter !== undefined) waiter({ value: event, done: false });
160
+ else this.queue.push(event);
161
+ }
162
+ next(): Promise<IteratorResult<MemoryEvent>> {
163
+ const value = this.queue.shift();
164
+ if (value !== undefined) return Promise.resolve({ value, done: false });
165
+ if (this.closed) return Promise.resolve({ value: undefined, done: true });
166
+ const { promise, resolve } = Promise.withResolvers<IteratorResult<MemoryEvent>>();
167
+ this.waiters.push(resolve);
168
+ return promise;
169
+ }
170
+ return(): Promise<IteratorResult<MemoryEvent>> {
171
+ this.closed = true;
172
+ this.stream.removeIterator(this);
173
+ while (this.waiters.length > 0) this.waiters.shift()?.({ value: undefined, done: true });
174
+ return Promise.resolve({ value: undefined, done: true });
175
+ }
176
+ [Symbol.asyncIterator](): AsyncIterator<MemoryEvent> {
177
+ return this;
178
+ }
179
+ }
180
+
181
+ export class MemoryStream {
182
+ private readonly callbacks = new Map<EventType, MemoryEventHandler[]>();
183
+ private readonly anyCallbacks: MemoryEventHandler[] = [];
184
+ private readonly buffer: MemoryEvent[] = [];
185
+ private readonly iterators = new Set<StreamIterator>();
186
+ constructor(private readonly maxBuffer = 1000) {
187
+ for (const eventType of Object.values(EventType)) this.callbacks.set(eventType, []);
188
+ }
189
+ on(eventType: EventType, callback: MemoryEventHandler): void {
190
+ this.callbacks.get(eventType)?.push(callback);
191
+ }
192
+ onAny(callback: MemoryEventHandler): void {
193
+ this.anyCallbacks.push(callback);
194
+ }
195
+ off(eventType: EventType, callback: MemoryEventHandler): void {
196
+ const callbacks = this.callbacks.get(eventType);
197
+ if (callbacks === undefined) return;
198
+ const index = callbacks.indexOf(callback);
199
+ if (index >= 0) callbacks.splice(index, 1);
200
+ }
201
+ offAny(callback: MemoryEventHandler): void {
202
+ const index = this.anyCallbacks.indexOf(callback);
203
+ if (index >= 0) this.anyCallbacks.splice(index, 1);
204
+ }
205
+ emit(event: MemoryEvent): void {
206
+ this.buffer.push(event);
207
+ if (this.buffer.length > this.maxBuffer) this.buffer.splice(0, this.buffer.length - this.maxBuffer);
208
+ for (const callback of this.callbacks.get(event.eventType) ?? []) {
209
+ try {
210
+ callback(event);
211
+ } catch {}
212
+ }
213
+ for (const callback of this.anyCallbacks) {
214
+ try {
215
+ callback(event);
216
+ } catch {}
217
+ }
218
+ for (const iterator of this.iterators) iterator.push(event);
219
+ }
220
+ listen(eventTypes: readonly EventType[] | null = null): StreamIterator {
221
+ const iterator = new StreamIterator(this, eventTypes);
222
+ this.iterators.add(iterator);
223
+ return iterator;
224
+ }
225
+ removeIterator(iterator: StreamIterator): void {
226
+ this.iterators.delete(iterator);
227
+ }
228
+ getBuffer(eventTypes: readonly EventType[] | null = null, since: string | null = null): MemoryEvent[] {
229
+ let events = this.buffer.slice();
230
+ if (eventTypes !== null) events = events.filter(event => eventTypes.includes(event.eventType));
231
+ if (since !== null) events = events.filter(event => event.timestamp >= since);
232
+ return events;
233
+ }
234
+ clearBuffer(): void {
235
+ this.buffer.length = 0;
236
+ }
237
+ }
238
+
239
+ export interface SyncCheckpointInit {
240
+ readonly peerId?: string;
241
+ readonly peer_id?: string;
242
+ readonly lastSyncAt?: string;
243
+ readonly last_sync_at?: string;
244
+ readonly lastRowid?: number;
245
+ readonly last_rowid?: number;
246
+ readonly checksum?: string;
247
+ }
248
+ export class SyncCheckpoint {
249
+ readonly peerId: string;
250
+ readonly lastSyncAt: string;
251
+ readonly lastRowid: number;
252
+ readonly checksum: string | null;
253
+ constructor(init: SyncCheckpointInit) {
254
+ this.peerId = init.peerId ?? init.peer_id ?? "";
255
+ this.lastSyncAt = init.lastSyncAt ?? init.last_sync_at ?? new Date().toISOString();
256
+ this.lastRowid = init.lastRowid ?? init.last_rowid ?? 0;
257
+ this.checksum = init.checksum ?? null;
258
+ }
259
+ toDict(): Record<string, unknown> {
260
+ return {
261
+ peer_id: this.peerId,
262
+ last_sync_at: this.lastSyncAt,
263
+ last_rowid: this.lastRowid,
264
+ checksum: this.checksum,
265
+ };
266
+ }
267
+ toJson(): string {
268
+ return JSON.stringify(this.toDict());
269
+ }
270
+ static fromJSON(text: string): SyncCheckpoint {
271
+ return new SyncCheckpoint(JSON.parse(text) as SyncCheckpointInit);
272
+ }
273
+ }
274
+
275
+ type MemoryHost = {
276
+ readonly conn?: Database;
277
+ readonly db?: Database;
278
+ readonly dbPath?: string;
279
+ readonly db_path?: string;
280
+ };
281
+ function databaseOf(host: MemoryHost): Database {
282
+ const db = host.conn ?? host.db;
283
+ if (db === undefined) throw new TypeError("DeltaSync requires a memory object with conn or db");
284
+ return db;
285
+ }
286
+ function assertDeltaTable(table: unknown): asserts table is DeltaTable {
287
+ if (typeof table !== "string" || !ALLOWED_DELTA_TABLES.has(table as DeltaTable))
288
+ throw new RangeError(`Delta table ${String(table)} is not in the allowlist`);
289
+ }
290
+ function checkpointRoot(host: MemoryHost): string {
291
+ const path = host.dbPath ?? host.db_path;
292
+ return path === undefined || path === ":memory:"
293
+ ? join(process.cwd(), ".prometheus-memory-sync")
294
+ : join(path, "..", "sync_checkpoints");
295
+ }
296
+
297
+ export class DeltaSync {
298
+ readonly checkpointDir: string;
299
+ private readonly db: Database;
300
+ constructor(
301
+ readonly mnemopi: MemoryHost,
302
+ checkpointDir?: string,
303
+ ) {
304
+ this.db = databaseOf(mnemopi);
305
+ this.checkpointDir = checkpointDir ?? checkpointRoot(mnemopi);
306
+ mkdirSync(this.checkpointDir, { recursive: true });
307
+ }
308
+ private checkpointPath(peerId: string, table: DeltaTable): string {
309
+ return join(this.checkpointDir, `${peerId}.${table}.json`);
310
+ }
311
+ private legacyCheckpointPath(peerId: string): string {
312
+ return join(this.checkpointDir, `${peerId}.json`);
313
+ }
314
+ getCheckpoint(peerId: string, table: DeltaTable = "working_memory"): SyncCheckpoint | null {
315
+ assertDeltaTable(table);
316
+ const path = this.checkpointPath(peerId, table);
317
+ if (existsSync(path)) return SyncCheckpoint.fromJSON(readFileSync(path, "utf8"));
318
+ if (table === "working_memory") {
319
+ const legacyPath = this.legacyCheckpointPath(peerId);
320
+ if (existsSync(legacyPath)) return SyncCheckpoint.fromJSON(readFileSync(legacyPath, "utf8"));
321
+ }
322
+ return null;
323
+ }
324
+ saveCheckpoint(checkpoint: SyncCheckpoint, table: DeltaTable = "working_memory"): void {
325
+ assertDeltaTable(table);
326
+ writeFileSync(this.checkpointPath(checkpoint.peerId, table), checkpoint.toJson());
327
+ }
328
+ setCheckpoint(peerId: string, checkpoint: SyncCheckpoint, table: DeltaTable = "working_memory"): void {
329
+ assertDeltaTable(table);
330
+ const peerCheckpoint =
331
+ checkpoint.peerId === peerId ? checkpoint : new SyncCheckpoint({ ...checkpoint.toDict(), peerId });
332
+ this.saveCheckpoint(peerCheckpoint, table);
333
+ }
334
+ computeDelta(peerId: string, table: DeltaTable = "working_memory"): Record<string, unknown>[] {
335
+ assertDeltaTable(table);
336
+ const checkpoint = this.getCheckpoint(peerId, table);
337
+ const minRowid = checkpoint?.lastRowid ?? 0;
338
+ return this.db
339
+ .query(`SELECT rowid, * FROM ${QUALIFIED_TABLE_NAMES[table]} WHERE rowid > ? ORDER BY rowid ASC`)
340
+ .all(minRowid) as Record<string, unknown>[];
341
+ }
342
+ applyDelta(
343
+ peerId: string,
344
+ delta: readonly Record<string, unknown>[],
345
+ table: DeltaTable = "working_memory",
346
+ ): { inserted: number; updated: number; skipped: number; filtered_keys: number } {
347
+ assertDeltaTable(table);
348
+ let inserted = 0,
349
+ updated = 0,
350
+ skipped = 0,
351
+ filteredKeys = 0,
352
+ maxRowid = 0;
353
+ const qname = QUALIFIED_TABLE_NAMES[table];
354
+ for (const row of delta) {
355
+ const id = row.id;
356
+ if (typeof id !== "string" || id.length === 0) {
357
+ skipped++;
358
+ continue;
359
+ }
360
+ const remoteRowid = typeof row.rowid === "number" ? row.rowid : 0;
361
+ if (remoteRowid > maxRowid) maxRowid = remoteRowid;
362
+ const exists = this.db.query(`SELECT 1 FROM ${qname} WHERE id = ?`).get(id) !== null;
363
+ if (exists) {
364
+ const entries: [string, SQLQueryBindings][] = [];
365
+ for (const key in row) {
366
+ const value = row[key];
367
+ if (DELTA_UPDATABLE_COLUMNS.has(key) && isSqlQueryBinding(value)) {
368
+ entries.push([key, value]);
369
+ } else if (key !== "id") {
370
+ filteredKeys++;
371
+ }
372
+ }
373
+ if (entries.length === 0) {
374
+ skipped++;
375
+ continue;
376
+ }
377
+ const setSql = entries.map(([key]) => `${key} = ?`).join(", ");
378
+ const params: SQLQueryBindings[] = [...entries.map(([, value]) => value), id];
379
+ this.db.run(`UPDATE ${qname} SET ${setSql} WHERE id = ?`, params);
380
+ updated++;
381
+ } else {
382
+ const entries: [string, SQLQueryBindings][] = [];
383
+ for (const key in row) {
384
+ const value = row[key];
385
+ if (DELTA_INSERTABLE_COLUMNS.has(key) && isSqlQueryBinding(value)) {
386
+ entries.push([key, value]);
387
+ } else if (key !== "id") {
388
+ filteredKeys++;
389
+ }
390
+ }
391
+ if (!entries.some(([key]) => key === "content")) {
392
+ skipped++;
393
+ continue;
394
+ }
395
+ const columns = entries.map(([key]) => key);
396
+ const placeholders = columns.map(() => "?").join(", ");
397
+ const params: SQLQueryBindings[] = entries.map(([, value]) => value);
398
+ this.db.run(`INSERT INTO ${qname} (${columns.join(", ")}) VALUES (${placeholders})`, params);
399
+ inserted++;
400
+ }
401
+ }
402
+ this.saveCheckpoint(
403
+ new SyncCheckpoint({ peerId, lastRowid: maxRowid, lastSyncAt: new Date().toISOString() }),
404
+ table,
405
+ );
406
+ return { inserted, updated, skipped, filtered_keys: filteredKeys };
407
+ }
408
+ syncTo(peerId: string, table: DeltaTable = "working_memory"): { delta: Record<string, unknown>[]; count: number } {
409
+ const delta = this.computeDelta(peerId, table);
410
+ return { delta, count: delta.length };
411
+ }
412
+ syncFrom(
413
+ peerId: string,
414
+ delta: readonly Record<string, unknown>[],
415
+ table: DeltaTable = "working_memory",
416
+ ): { stats: { inserted: number; updated: number; skipped: number; filtered_keys: number } } {
417
+ return { stats: this.applyDelta(peerId, delta, table) };
418
+ }
419
+ }
@@ -0,0 +1,197 @@
1
+ export const SYNONYM_GROUPS = {
2
+ database: ["db", "datastore", "data_store"],
3
+ password: ["pass", "pwd", "passwd", "credential", "secret", "token"],
4
+ config: ["configuration", "settings", "cfg", "setup"],
5
+ error: ["bug", "issue", "fault", "failure", "crash", "exception", "traceback"],
6
+ fix: ["repair", "resolve", "solve", "patch", "correct", "address"],
7
+ deploy: ["deployment", "release", "ship", "push", "rollout"],
8
+ server: ["host", "machine", "vm", "instance", "node", "vps"],
9
+ api: ["endpoint", "interface", "service"],
10
+ key: ["token", "credential", "secret", "api_key"],
11
+ user: ["account", "profile", "identity", "person"],
12
+ model: ["llm", "ai", "provider", "gpt", "claude", "gemini"],
13
+ speed: ["fast", "quick", "performance", "latency", "throughput"],
14
+ memory: ["recall", "remember", "storage", "retention"],
15
+ search: ["find", "lookup", "query", "retrieve", "locate"],
16
+ file: ["document", "doc", "text", "note"],
17
+ code: ["script", "program", "source", "implementation"],
18
+ test: ["verify", "check", "validate", "probe", "examine"],
19
+ backup: ["snapshot", "copy", "save", "archive"],
20
+ install: ["setup", "configure", "bootstrap", "init"],
21
+ update: ["upgrade", "refresh", "renew", "sync"],
22
+ delete: ["remove", "destroy", "purge", "clean", "wipe", "erase"],
23
+ list: ["show", "display", "enumerate", "catalog"],
24
+ time: ["date", "when", "timestamp", "schedule"],
25
+ url: ["link", "address", "uri", "path"],
26
+ health: ["status", "check", "pulse", "alive", "up"],
27
+ service: ["daemon", "process", "systemd", "worker"],
28
+ port: ["socket", "bind", "listen"],
29
+ network: ["internet", "connection", "connectivity", "dns"],
30
+ ssh: ["terminal", "shell", "remote", "connect"],
31
+ git: ["commit", "push", "pull", "repo", "repository", "branch"],
32
+ log: ["output", "stdout", "stderr", "trace", "debug"],
33
+ cron: ["schedule", "job", "task", "timer", "periodic"],
34
+ email: ["mail", "message", "inbox", "smtp"],
35
+ image: ["picture", "photo", "screenshot", "graphic"],
36
+ browser: ["web", "page", "site", "navigate", "chrome"],
37
+ monitor: ["watch", "observe", "track", "survey"],
38
+ alert: ["notify", "notification", "warning", "ping"],
39
+ migrate: ["transfer", "move", "relocate", "port"],
40
+ compare: ["diff", "versus", "vs", "contrast"],
41
+ save: ["store", "persist", "preserve", "keep"],
42
+ } as const;
43
+
44
+ export const STOP_WORDS = new Set<string>([
45
+ "a",
46
+ "an",
47
+ "the",
48
+ "is",
49
+ "are",
50
+ "was",
51
+ "were",
52
+ "be",
53
+ "been",
54
+ "have",
55
+ "has",
56
+ "had",
57
+ "do",
58
+ "does",
59
+ "did",
60
+ "will",
61
+ "would",
62
+ "could",
63
+ "should",
64
+ "may",
65
+ "might",
66
+ "can",
67
+ "shall",
68
+ "must",
69
+ "i",
70
+ "you",
71
+ "he",
72
+ "she",
73
+ "it",
74
+ "we",
75
+ "they",
76
+ "me",
77
+ "him",
78
+ "her",
79
+ "us",
80
+ "them",
81
+ "my",
82
+ "your",
83
+ "his",
84
+ "its",
85
+ "our",
86
+ "their",
87
+ "mine",
88
+ "yours",
89
+ "hers",
90
+ "ours",
91
+ "theirs",
92
+ "what",
93
+ "which",
94
+ "who",
95
+ "whom",
96
+ "where",
97
+ "when",
98
+ "why",
99
+ "how",
100
+ "this",
101
+ "that",
102
+ "these",
103
+ "those",
104
+ "of",
105
+ "in",
106
+ "to",
107
+ "for",
108
+ "on",
109
+ "with",
110
+ "at",
111
+ "by",
112
+ "from",
113
+ "as",
114
+ "into",
115
+ "through",
116
+ "during",
117
+ "before",
118
+ "after",
119
+ "above",
120
+ "below",
121
+ "between",
122
+ "under",
123
+ "and",
124
+ "but",
125
+ "or",
126
+ "nor",
127
+ "not",
128
+ "so",
129
+ "than",
130
+ "too",
131
+ "very",
132
+ "just",
133
+ "about",
134
+ "also",
135
+ "really",
136
+ "actually",
137
+ "basically",
138
+ "simply",
139
+ "if",
140
+ "then",
141
+ "else",
142
+ "while",
143
+ "because",
144
+ "though",
145
+ "although",
146
+ ]);
147
+
148
+ type Canonical = keyof typeof SYNONYM_GROUPS;
149
+
150
+ const WORD_TO_CANONICAL = buildReverseMap();
151
+
152
+ function buildReverseMap(): ReadonlyMap<string, Canonical> {
153
+ const reverse = new Map<string, Canonical>();
154
+ for (const canonical in SYNONYM_GROUPS) {
155
+ const key = canonical as Canonical;
156
+ reverse.set(key, key);
157
+ for (const synonym of SYNONYM_GROUPS[key]) reverse.set(synonym, key);
158
+ }
159
+ return reverse;
160
+ }
161
+
162
+ export function normalizeQuery(query: string): string {
163
+ const canonicalWords = new Set<string>();
164
+ for (const rawWord of query.toLowerCase().split(/\s+/)) {
165
+ if (rawWord.length === 0 || STOP_WORDS.has(rawWord)) continue;
166
+ canonicalWords.add(WORD_TO_CANONICAL.get(rawWord) ?? rawWord);
167
+ }
168
+ return Array.from(canonicalWords).sort().join(" ");
169
+ }
170
+ export function expandQuery(query: string): string {
171
+ const words = query.toLowerCase().split(/\s+/);
172
+ const expandedParts: string[] = [];
173
+ for (const word of words) {
174
+ if (word.length === 0) continue;
175
+ if (STOP_WORDS.has(word)) {
176
+ expandedParts.push(word);
177
+ continue;
178
+ }
179
+ const canonical = WORD_TO_CANONICAL.get(word);
180
+ if (canonical !== undefined) {
181
+ const group = SYNONYM_GROUPS[canonical];
182
+ let expanded = `(${canonical}`;
183
+ for (const synonym of group) expanded += `|${synonym}`;
184
+ expanded += ")";
185
+ expandedParts.push(expanded);
186
+ } else {
187
+ expandedParts.push(word);
188
+ }
189
+ }
190
+ return expandedParts.join(" ");
191
+ }
192
+ export function getSynonyms(word: string): string[] {
193
+ const lowered = word.toLowerCase();
194
+ const canonical = WORD_TO_CANONICAL.get(lowered);
195
+ if (canonical === undefined) return [lowered];
196
+ return [canonical, ...SYNONYM_GROUPS[canonical]];
197
+ }