@memrosetta/core 0.2.19 → 0.2.20
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/dist/index.d.ts +20 -3
- package/dist/index.js +175 -51
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
|
-
import { Memory, MemoryInput, RelationType, MemoryRelation, SearchResult, SearchFilters, SearchQuery, SearchResponse, IMemoryEngine, CompressResult, MaintenanceResult, MemoryTier, TierConfig } from '@memrosetta/types';
|
|
2
|
+
import { Memory, MemoryState, MemoryInput, RelationType, MemoryRelation, SearchResult, SearchFilters, SearchQuery, SearchResponse, IMemoryEngine, CompressResult, MaintenanceResult, MemoryTier, MemoryQuality, TierConfig } from '@memrosetta/types';
|
|
3
3
|
import { Embedder, ContradictionDetector } from '@memrosetta/embeddings';
|
|
4
4
|
|
|
5
5
|
interface SchemaOptions {
|
|
@@ -12,6 +12,13 @@ declare function generateMemoryId(): string;
|
|
|
12
12
|
declare function nowIso(): string;
|
|
13
13
|
declare function keywordsToString(keywords: readonly string[] | undefined): string | null;
|
|
14
14
|
declare function stringToKeywords(str: string | null): readonly string[];
|
|
15
|
+
/**
|
|
16
|
+
* Derive the logical state of a memory from its existing fields.
|
|
17
|
+
* - invalidated: invalidatedAt is set (takes precedence)
|
|
18
|
+
* - superseded: isLatest is false
|
|
19
|
+
* - current: isLatest is true and not invalidated
|
|
20
|
+
*/
|
|
21
|
+
declare function deriveMemoryState(memory: Memory): MemoryState;
|
|
15
22
|
|
|
16
23
|
interface MemoryRow {
|
|
17
24
|
readonly id: number;
|
|
@@ -204,7 +211,7 @@ declare function updateAccessTracking(db: Database.Database, memoryIds: readonly
|
|
|
204
211
|
*
|
|
205
212
|
* Results are weighted by activation score and access tracking is updated.
|
|
206
213
|
*/
|
|
207
|
-
declare function searchMemories(db: Database.Database, query: SearchQuery, queryVec?: Float32Array, useVecTable?: boolean): SearchResponse;
|
|
214
|
+
declare function searchMemories(db: Database.Database, query: SearchQuery, queryVec?: Float32Array, useVecTable?: boolean, skipAccessTracking?: boolean): SearchResponse;
|
|
208
215
|
|
|
209
216
|
interface SqliteEngineOptions {
|
|
210
217
|
readonly dbPath: string;
|
|
@@ -235,6 +242,7 @@ declare class SqliteMemoryEngine implements IMemoryEngine {
|
|
|
235
242
|
compress(userId: string): Promise<CompressResult>;
|
|
236
243
|
maintain(userId: string): Promise<MaintenanceResult>;
|
|
237
244
|
setTier(memoryId: string, tier: MemoryTier): Promise<void>;
|
|
245
|
+
quality(userId: string): Promise<MemoryQuality>;
|
|
238
246
|
/**
|
|
239
247
|
* Check the newly stored memory against existing similar memories
|
|
240
248
|
* for contradictions using the NLI model.
|
|
@@ -254,6 +262,15 @@ declare class SqliteMemoryEngine implements IMemoryEngine {
|
|
|
254
262
|
* Only runs for single store() calls, not storeBatch() (too slow for bulk).
|
|
255
263
|
*/
|
|
256
264
|
private checkDuplicates;
|
|
265
|
+
/**
|
|
266
|
+
* Auto-create 'extends' relations when a new memory shares keywords
|
|
267
|
+
* with existing memories. Builds graph density for future graph-based retrieval.
|
|
268
|
+
*
|
|
269
|
+
* Only runs for individual store() calls, not storeBatch().
|
|
270
|
+
* Requires at least 2 overlapping keywords to create a relation.
|
|
271
|
+
* Graceful: errors are silently swallowed.
|
|
272
|
+
*/
|
|
273
|
+
private autoRelate;
|
|
257
274
|
private ensureInitialized;
|
|
258
275
|
}
|
|
259
276
|
declare function createEngine(options: SqliteEngineOptions): SqliteMemoryEngine;
|
|
@@ -311,4 +328,4 @@ declare function determineTier(memory: {
|
|
|
311
328
|
*/
|
|
312
329
|
declare function estimateTokens(content: string): number;
|
|
313
330
|
|
|
314
|
-
export { DEFAULT_TIER_CONFIG, type MemoryRow, type PreparedStatements, type RelationStatements, type SchemaOptions, type SearchSqlResult, type SqliteEngineOptions, SqliteMemoryEngine, type VectorSearchResult, applyKeywordBoost, applyThreeFactorReranking, bruteForceVectorSearch, buildFtsQuery, buildSearchSql, computeActivation, computeEbbinghaus, createEngine, createPreparedStatements, createRelation, createRelationStatements, deduplicateResults, determineTier, ensureSchema, estimateTokens, extractQueryTokens, ftsSearch, generateMemoryId, getRelationsByMemory, keywordsToString, normalizeScores, nowIso, rowToMemory, rrfMerge, rrfMergeWeighted, searchMemories, serializeEmbedding, storeBatchAsync, storeBatchInTransaction, storeMemory, storeMemoryAsync, stringToKeywords, updateAccessTracking, vectorSearch };
|
|
331
|
+
export { DEFAULT_TIER_CONFIG, type MemoryRow, type PreparedStatements, type RelationStatements, type SchemaOptions, type SearchSqlResult, type SqliteEngineOptions, SqliteMemoryEngine, type VectorSearchResult, applyKeywordBoost, applyThreeFactorReranking, bruteForceVectorSearch, buildFtsQuery, buildSearchSql, computeActivation, computeEbbinghaus, createEngine, createPreparedStatements, createRelation, createRelationStatements, deduplicateResults, deriveMemoryState, determineTier, ensureSchema, estimateTokens, extractQueryTokens, ftsSearch, generateMemoryId, getRelationsByMemory, keywordsToString, normalizeScores, nowIso, rowToMemory, rrfMerge, rrfMergeWeighted, searchMemories, serializeEmbedding, storeBatchAsync, storeBatchInTransaction, storeMemory, storeMemoryAsync, stringToKeywords, updateAccessTracking, vectorSearch };
|
package/dist/index.js
CHANGED
|
@@ -183,6 +183,11 @@ function stringToKeywords(str) {
|
|
|
183
183
|
if (!str || str.trim() === "") return [];
|
|
184
184
|
return str.split(" ").filter((s) => s.length > 0);
|
|
185
185
|
}
|
|
186
|
+
function deriveMemoryState(memory) {
|
|
187
|
+
if (memory.invalidatedAt) return "invalidated";
|
|
188
|
+
if (!memory.isLatest) return "superseded";
|
|
189
|
+
return "current";
|
|
190
|
+
}
|
|
186
191
|
|
|
187
192
|
// src/mapper.ts
|
|
188
193
|
function rowToMemory(row) {
|
|
@@ -608,9 +613,29 @@ function buildSearchSql(query) {
|
|
|
608
613
|
whereClauses.push("m.confidence >= ?");
|
|
609
614
|
params.push(filters.minConfidence);
|
|
610
615
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
616
|
+
if (filters?.states && filters.states.length > 0) {
|
|
617
|
+
const stateConditions = [];
|
|
618
|
+
if (filters.states.includes("current")) {
|
|
619
|
+
stateConditions.push("(m.is_latest = 1 AND m.invalidated_at IS NULL)");
|
|
620
|
+
}
|
|
621
|
+
if (filters.states.includes("superseded")) {
|
|
622
|
+
stateConditions.push("(m.is_latest = 0)");
|
|
623
|
+
}
|
|
624
|
+
if (filters.states.includes("invalidated")) {
|
|
625
|
+
stateConditions.push("(m.invalidated_at IS NOT NULL)");
|
|
626
|
+
}
|
|
627
|
+
if (stateConditions.length > 0) {
|
|
628
|
+
whereClauses.push(`(${stateConditions.join(" OR ")})`);
|
|
629
|
+
}
|
|
630
|
+
} else {
|
|
631
|
+
const onlyLatest = filters?.onlyLatest ?? true;
|
|
632
|
+
if (onlyLatest) {
|
|
633
|
+
whereClauses.push("m.is_latest = 1");
|
|
634
|
+
}
|
|
635
|
+
const excludeInvalidated = filters?.excludeInvalidated ?? true;
|
|
636
|
+
if (excludeInvalidated) {
|
|
637
|
+
whereClauses.push("m.invalidated_at IS NULL");
|
|
638
|
+
}
|
|
614
639
|
}
|
|
615
640
|
if (filters?.eventDateRange?.start) {
|
|
616
641
|
whereClauses.push("m.event_date_start >= ?");
|
|
@@ -620,10 +645,6 @@ function buildSearchSql(query) {
|
|
|
620
645
|
whereClauses.push("m.event_date_end <= ?");
|
|
621
646
|
params.push(filters.eventDateRange.end);
|
|
622
647
|
}
|
|
623
|
-
const excludeInvalidated = filters?.excludeInvalidated ?? true;
|
|
624
|
-
if (excludeInvalidated) {
|
|
625
|
-
whereClauses.push("m.invalidated_at IS NULL");
|
|
626
|
-
}
|
|
627
648
|
const limit = query.limit ?? DEFAULT_LIMIT;
|
|
628
649
|
params.push(limit);
|
|
629
650
|
const sql = [
|
|
@@ -676,9 +697,29 @@ function ftsSearch(db, query) {
|
|
|
676
697
|
function bruteForceVectorSearch(db, queryVec, userId, limit, filters) {
|
|
677
698
|
const whereClauses = ["user_id = ?", "embedding IS NOT NULL"];
|
|
678
699
|
const params = [userId];
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
700
|
+
if (filters?.states && filters.states.length > 0) {
|
|
701
|
+
const stateConditions = [];
|
|
702
|
+
if (filters.states.includes("current")) {
|
|
703
|
+
stateConditions.push("(is_latest = 1 AND invalidated_at IS NULL)");
|
|
704
|
+
}
|
|
705
|
+
if (filters.states.includes("superseded")) {
|
|
706
|
+
stateConditions.push("(is_latest = 0)");
|
|
707
|
+
}
|
|
708
|
+
if (filters.states.includes("invalidated")) {
|
|
709
|
+
stateConditions.push("(invalidated_at IS NOT NULL)");
|
|
710
|
+
}
|
|
711
|
+
if (stateConditions.length > 0) {
|
|
712
|
+
whereClauses.push(`(${stateConditions.join(" OR ")})`);
|
|
713
|
+
}
|
|
714
|
+
} else {
|
|
715
|
+
const onlyLatest = filters?.onlyLatest ?? true;
|
|
716
|
+
if (onlyLatest) {
|
|
717
|
+
whereClauses.push("is_latest = 1");
|
|
718
|
+
}
|
|
719
|
+
const excludeInvalidated = filters?.excludeInvalidated ?? true;
|
|
720
|
+
if (excludeInvalidated) {
|
|
721
|
+
whereClauses.push("invalidated_at IS NULL");
|
|
722
|
+
}
|
|
682
723
|
}
|
|
683
724
|
if (filters?.memoryTypes && filters.memoryTypes.length > 0) {
|
|
684
725
|
const mtPlaceholders = filters.memoryTypes.map(() => "?").join(",");
|
|
@@ -707,10 +748,6 @@ function bruteForceVectorSearch(db, queryVec, userId, limit, filters) {
|
|
|
707
748
|
whereClauses.push("event_date_end <= ?");
|
|
708
749
|
params.push(filters.eventDateRange.end);
|
|
709
750
|
}
|
|
710
|
-
const excludeInvalidated = filters?.excludeInvalidated ?? true;
|
|
711
|
-
if (excludeInvalidated) {
|
|
712
|
-
whereClauses.push("invalidated_at IS NULL");
|
|
713
|
-
}
|
|
714
751
|
const sql = `SELECT * FROM memories WHERE ${whereClauses.join(" AND ")}`;
|
|
715
752
|
const rows = db.prepare(sql).all(...params);
|
|
716
753
|
if (rows.length === 0) {
|
|
@@ -748,9 +785,29 @@ function vectorSearch(db, queryVec, userId, limit, filters, useVecTable = true)
|
|
|
748
785
|
const placeholders = rowids.map(() => "?").join(",");
|
|
749
786
|
let sql = `SELECT * FROM memories WHERE id IN (${placeholders}) AND user_id = ?`;
|
|
750
787
|
const params = [...rowids, userId];
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
788
|
+
if (filters?.states && filters.states.length > 0) {
|
|
789
|
+
const stateConditions = [];
|
|
790
|
+
if (filters.states.includes("current")) {
|
|
791
|
+
stateConditions.push("(is_latest = 1 AND invalidated_at IS NULL)");
|
|
792
|
+
}
|
|
793
|
+
if (filters.states.includes("superseded")) {
|
|
794
|
+
stateConditions.push("(is_latest = 0)");
|
|
795
|
+
}
|
|
796
|
+
if (filters.states.includes("invalidated")) {
|
|
797
|
+
stateConditions.push("(invalidated_at IS NOT NULL)");
|
|
798
|
+
}
|
|
799
|
+
if (stateConditions.length > 0) {
|
|
800
|
+
sql += ` AND (${stateConditions.join(" OR ")})`;
|
|
801
|
+
}
|
|
802
|
+
} else {
|
|
803
|
+
const onlyLatest = filters?.onlyLatest ?? true;
|
|
804
|
+
if (onlyLatest) {
|
|
805
|
+
sql += " AND is_latest = 1";
|
|
806
|
+
}
|
|
807
|
+
const excludeInvalidated = filters?.excludeInvalidated ?? true;
|
|
808
|
+
if (excludeInvalidated) {
|
|
809
|
+
sql += " AND invalidated_at IS NULL";
|
|
810
|
+
}
|
|
754
811
|
}
|
|
755
812
|
if (filters?.memoryTypes && filters.memoryTypes.length > 0) {
|
|
756
813
|
const mtPlaceholders = filters.memoryTypes.map(() => "?").join(",");
|
|
@@ -779,10 +836,6 @@ function vectorSearch(db, queryVec, userId, limit, filters, useVecTable = true)
|
|
|
779
836
|
sql += " AND event_date_end <= ?";
|
|
780
837
|
params.push(filters.eventDateRange.end);
|
|
781
838
|
}
|
|
782
|
-
const excludeInvalidated = filters?.excludeInvalidated ?? true;
|
|
783
|
-
if (excludeInvalidated) {
|
|
784
|
-
sql += " AND invalidated_at IS NULL";
|
|
785
|
-
}
|
|
786
839
|
const rows = db.prepare(sql).all(...params);
|
|
787
840
|
return rows.map((row) => ({
|
|
788
841
|
memory: rowToMemory(row),
|
|
@@ -950,7 +1003,7 @@ function updateAccessTracking(db, memoryIds) {
|
|
|
950
1003
|
});
|
|
951
1004
|
updateAll(memoryIds);
|
|
952
1005
|
}
|
|
953
|
-
function searchMemories(db, query, queryVec, useVecTable = true) {
|
|
1006
|
+
function searchMemories(db, query, queryVec, useVecTable = true, skipAccessTracking = false) {
|
|
954
1007
|
const startTime = performance.now();
|
|
955
1008
|
const queryTokens = extractQueryTokens(query.query);
|
|
956
1009
|
const ftsResults = ftsSearch(db, query);
|
|
@@ -959,7 +1012,9 @@ function searchMemories(db, query, queryVec, useVecTable = true) {
|
|
|
959
1012
|
const weighted = applyThreeFactorReranking(ftsResults);
|
|
960
1013
|
const boosted = applyKeywordBoost(weighted, queryTokens);
|
|
961
1014
|
finalResults = deduplicateResults(boosted);
|
|
962
|
-
|
|
1015
|
+
if (!skipAccessTracking) {
|
|
1016
|
+
updateAccessTracking(db, finalResults.map((r) => r.memory.memoryId));
|
|
1017
|
+
}
|
|
963
1018
|
return {
|
|
964
1019
|
results: finalResults,
|
|
965
1020
|
totalCount: finalResults.length,
|
|
@@ -1088,19 +1143,6 @@ function estimateTokens(content) {
|
|
|
1088
1143
|
}
|
|
1089
1144
|
|
|
1090
1145
|
// src/engine.ts
|
|
1091
|
-
function cosineSim(a, b) {
|
|
1092
|
-
let dot = 0;
|
|
1093
|
-
let normA = 0;
|
|
1094
|
-
let normB = 0;
|
|
1095
|
-
const len = Math.min(a.length, b.length);
|
|
1096
|
-
for (let i = 0; i < len; i++) {
|
|
1097
|
-
dot += a[i] * b[i];
|
|
1098
|
-
normA += a[i] * a[i];
|
|
1099
|
-
normB += b[i] * b[i];
|
|
1100
|
-
}
|
|
1101
|
-
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
1102
|
-
return denom === 0 ? 0 : dot / denom;
|
|
1103
|
-
}
|
|
1104
1146
|
var SqliteMemoryEngine = class {
|
|
1105
1147
|
db = null;
|
|
1106
1148
|
stmts = null;
|
|
@@ -1156,6 +1198,7 @@ var SqliteMemoryEngine = class {
|
|
|
1156
1198
|
await this.checkContradictions(memory);
|
|
1157
1199
|
}
|
|
1158
1200
|
await this.checkDuplicates(memory);
|
|
1201
|
+
await this.autoRelate(memory);
|
|
1159
1202
|
return memory;
|
|
1160
1203
|
}
|
|
1161
1204
|
async storeBatch(inputs) {
|
|
@@ -1424,6 +1467,41 @@ var SqliteMemoryEngine = class {
|
|
|
1424
1467
|
this.ensureInitialized();
|
|
1425
1468
|
this.db.prepare("UPDATE memories SET tier = ? WHERE memory_id = ?").run(tier, memoryId);
|
|
1426
1469
|
}
|
|
1470
|
+
async quality(userId) {
|
|
1471
|
+
this.ensureInitialized();
|
|
1472
|
+
const db = this.db;
|
|
1473
|
+
const total = db.prepare("SELECT COUNT(*) as c FROM memories WHERE user_id = ?").get(userId).c;
|
|
1474
|
+
const fresh = db.prepare(
|
|
1475
|
+
"SELECT COUNT(*) as c FROM memories WHERE user_id = ? AND is_latest = 1 AND invalidated_at IS NULL"
|
|
1476
|
+
).get(userId).c;
|
|
1477
|
+
const invalidated = db.prepare(
|
|
1478
|
+
"SELECT COUNT(*) as c FROM memories WHERE user_id = ? AND invalidated_at IS NOT NULL"
|
|
1479
|
+
).get(userId).c;
|
|
1480
|
+
const superseded = db.prepare(
|
|
1481
|
+
"SELECT COUNT(*) as c FROM memories WHERE user_id = ? AND is_latest = 0"
|
|
1482
|
+
).get(userId).c;
|
|
1483
|
+
const withRelations = db.prepare(`
|
|
1484
|
+
SELECT COUNT(DISTINCT mid) as c FROM (
|
|
1485
|
+
SELECT src_memory_id as mid FROM memory_relations
|
|
1486
|
+
WHERE src_memory_id IN (SELECT memory_id FROM memories WHERE user_id = ?)
|
|
1487
|
+
UNION
|
|
1488
|
+
SELECT dst_memory_id as mid FROM memory_relations
|
|
1489
|
+
WHERE dst_memory_id IN (SELECT memory_id FROM memories WHERE user_id = ?)
|
|
1490
|
+
)
|
|
1491
|
+
`).get(userId, userId).c;
|
|
1492
|
+
const avgRow = db.prepare(
|
|
1493
|
+
"SELECT AVG(activation_score) as avg FROM memories WHERE user_id = ? AND is_latest = 1"
|
|
1494
|
+
).get(userId);
|
|
1495
|
+
const avgActivation = avgRow.avg ?? 0;
|
|
1496
|
+
return {
|
|
1497
|
+
total,
|
|
1498
|
+
fresh,
|
|
1499
|
+
invalidated,
|
|
1500
|
+
superseded,
|
|
1501
|
+
withRelations,
|
|
1502
|
+
avgActivation
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1427
1505
|
/**
|
|
1428
1506
|
* Check the newly stored memory against existing similar memories
|
|
1429
1507
|
* for contradictions using the NLI model.
|
|
@@ -1449,7 +1527,9 @@ var SqliteMemoryEngine = class {
|
|
|
1449
1527
|
filters: { onlyLatest: true }
|
|
1450
1528
|
},
|
|
1451
1529
|
queryVec,
|
|
1452
|
-
this.vectorEnabled
|
|
1530
|
+
this.vectorEnabled,
|
|
1531
|
+
true
|
|
1532
|
+
// skipAccessTracking
|
|
1453
1533
|
);
|
|
1454
1534
|
for (const result of similar.results) {
|
|
1455
1535
|
if (result.memory.memoryId === newMemory.memoryId) continue;
|
|
@@ -1483,27 +1563,21 @@ var SqliteMemoryEngine = class {
|
|
|
1483
1563
|
if (!this.options.embedder) return;
|
|
1484
1564
|
try {
|
|
1485
1565
|
const queryVec = await this.options.embedder.embed(newMemory.content);
|
|
1486
|
-
const
|
|
1566
|
+
const candidates = bruteForceVectorSearch(
|
|
1487
1567
|
this.db,
|
|
1488
|
-
{
|
|
1489
|
-
userId: newMemory.userId,
|
|
1490
|
-
query: newMemory.content,
|
|
1491
|
-
limit: 5,
|
|
1492
|
-
filters: { onlyLatest: true }
|
|
1493
|
-
},
|
|
1494
1568
|
queryVec,
|
|
1495
|
-
|
|
1569
|
+
newMemory.userId,
|
|
1570
|
+
10,
|
|
1571
|
+
{ onlyLatest: true }
|
|
1496
1572
|
);
|
|
1497
|
-
for (const
|
|
1498
|
-
if (
|
|
1499
|
-
|
|
1500
|
-
const embB = new Float32Array(result.memory.embedding);
|
|
1501
|
-
const similarity = cosineSim(queryVec, embB);
|
|
1573
|
+
for (const candidate of candidates) {
|
|
1574
|
+
if (candidate.memory.memoryId === newMemory.memoryId) continue;
|
|
1575
|
+
const similarity = 1 - candidate.distance;
|
|
1502
1576
|
if (similarity > 0.95) {
|
|
1503
1577
|
try {
|
|
1504
1578
|
await this.relate(
|
|
1505
1579
|
newMemory.memoryId,
|
|
1506
|
-
|
|
1580
|
+
candidate.memory.memoryId,
|
|
1507
1581
|
"updates",
|
|
1508
1582
|
`Auto-detected duplicate: cosine similarity ${similarity.toFixed(3)}`
|
|
1509
1583
|
);
|
|
@@ -1514,6 +1588,55 @@ var SqliteMemoryEngine = class {
|
|
|
1514
1588
|
} catch {
|
|
1515
1589
|
}
|
|
1516
1590
|
}
|
|
1591
|
+
/**
|
|
1592
|
+
* Auto-create 'extends' relations when a new memory shares keywords
|
|
1593
|
+
* with existing memories. Builds graph density for future graph-based retrieval.
|
|
1594
|
+
*
|
|
1595
|
+
* Only runs for individual store() calls, not storeBatch().
|
|
1596
|
+
* Requires at least 2 overlapping keywords to create a relation.
|
|
1597
|
+
* Graceful: errors are silently swallowed.
|
|
1598
|
+
*/
|
|
1599
|
+
async autoRelate(newMemory) {
|
|
1600
|
+
if (!newMemory.keywords || newMemory.keywords.length === 0) return;
|
|
1601
|
+
try {
|
|
1602
|
+
const keywordList = newMemory.keywords;
|
|
1603
|
+
const existing = this.db.prepare(`
|
|
1604
|
+
SELECT memory_id, keywords FROM memories
|
|
1605
|
+
WHERE user_id = ? AND is_latest = 1 AND memory_id != ?
|
|
1606
|
+
AND invalidated_at IS NULL
|
|
1607
|
+
ORDER BY learned_at DESC LIMIT 10
|
|
1608
|
+
`).all(newMemory.userId, newMemory.memoryId);
|
|
1609
|
+
for (const row of existing) {
|
|
1610
|
+
if (!row.keywords) continue;
|
|
1611
|
+
const existingKeywords = row.keywords.split(" ").map((k) => k.trim().toLowerCase());
|
|
1612
|
+
const overlap = keywordList.filter((k) => existingKeywords.includes(k.toLowerCase()));
|
|
1613
|
+
if (overlap.length >= 3) {
|
|
1614
|
+
const existingRelation = this.db.prepare(
|
|
1615
|
+
`SELECT 1 FROM memory_relations
|
|
1616
|
+
WHERE (src_memory_id = ? AND dst_memory_id = ?)
|
|
1617
|
+
OR (src_memory_id = ? AND dst_memory_id = ?)
|
|
1618
|
+
LIMIT 1`
|
|
1619
|
+
).get(
|
|
1620
|
+
newMemory.memoryId,
|
|
1621
|
+
row.memory_id,
|
|
1622
|
+
row.memory_id,
|
|
1623
|
+
newMemory.memoryId
|
|
1624
|
+
);
|
|
1625
|
+
if (existingRelation) continue;
|
|
1626
|
+
try {
|
|
1627
|
+
await this.relate(
|
|
1628
|
+
newMemory.memoryId,
|
|
1629
|
+
row.memory_id,
|
|
1630
|
+
"extends",
|
|
1631
|
+
`Auto: ${overlap.length} shared keywords (${overlap.join(", ")})`
|
|
1632
|
+
);
|
|
1633
|
+
} catch {
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
} catch {
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1517
1640
|
ensureInitialized() {
|
|
1518
1641
|
if (!this.db || !this.stmts || !this.relStmts) {
|
|
1519
1642
|
throw new Error("Engine not initialized. Call initialize() first.");
|
|
@@ -1538,6 +1661,7 @@ export {
|
|
|
1538
1661
|
createRelation,
|
|
1539
1662
|
createRelationStatements,
|
|
1540
1663
|
deduplicateResults,
|
|
1664
|
+
deriveMemoryState,
|
|
1541
1665
|
determineTier,
|
|
1542
1666
|
ensureSchema,
|
|
1543
1667
|
estimateTokens,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memrosetta/core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.20",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
"better-sqlite3": "^11.0.0",
|
|
21
21
|
"nanoid": "^5.0.0",
|
|
22
22
|
"sqlite-vec": "^0.1.7",
|
|
23
|
-
"@memrosetta/types": "0.2.
|
|
23
|
+
"@memrosetta/types": "0.2.20"
|
|
24
24
|
},
|
|
25
25
|
"optionalDependencies": {
|
|
26
|
-
"@memrosetta/embeddings": "0.2.
|
|
26
|
+
"@memrosetta/embeddings": "0.2.20"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@types/better-sqlite3": "^7.6.0",
|