@pi-unipi/memory 2.3.0 → 2.4.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.
- package/package.json +3 -3
- package/storage.ts +0 -18
- package/tools.ts +0 -1
- package/search.ts +0 -134
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/memory",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.1",
|
|
4
4
|
"description": "Persistent cross-session memory with MemPalace backend (auto-installed) and SQLite fallback for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"better-sqlite3": "^12.9.0",
|
|
44
44
|
"sqlite-vec": "^0.1.9",
|
|
45
45
|
"js-yaml": "^4.1.0",
|
|
46
|
-
"@pi-unipi/core": "2.
|
|
47
|
-
"@pi-unipi/info-screen": "2.
|
|
46
|
+
"@pi-unipi/core": "2.4.1",
|
|
47
|
+
"@pi-unipi/info-screen": "2.4.1"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"@earendil-works/pi-coding-agent": "^0.80.0",
|
package/storage.ts
CHANGED
|
@@ -454,24 +454,6 @@ export class MemoryStorage {
|
|
|
454
454
|
}
|
|
455
455
|
}
|
|
456
456
|
|
|
457
|
-
/**
|
|
458
|
-
* Remove corrupted database files (db, wal, shm).
|
|
459
|
-
*/
|
|
460
|
-
private removeCorruptedDb(): void {
|
|
461
|
-
const dbPath = path.join(this.scopeDir, MEMORY_DB_NAME);
|
|
462
|
-
const files = [dbPath, `${dbPath}-wal`, `${dbPath}-shm`];
|
|
463
|
-
for (const file of files) {
|
|
464
|
-
try {
|
|
465
|
-
if (fs.existsSync(file)) {
|
|
466
|
-
fs.unlinkSync(file);
|
|
467
|
-
// Removed console.warn — corrupted file cleanup is silent.
|
|
468
|
-
}
|
|
469
|
-
} catch {
|
|
470
|
-
// Ignore removal errors
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
457
|
/**
|
|
476
458
|
* Check if database is healthy.
|
|
477
459
|
*/
|
package/tools.ts
CHANGED
package/search.ts
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @unipi/memory — Hybrid search algorithm
|
|
3
|
-
*
|
|
4
|
-
* Combines vector similarity search with fuzzy text matching
|
|
5
|
-
* for best recall across semantic and exact matches.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { MemoryStorage, MemoryRecord, SearchResult } from "./storage.js";
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Perform hybrid search combining vector + fuzzy.
|
|
12
|
-
*/
|
|
13
|
-
export function hybridSearch(
|
|
14
|
-
storage: MemoryStorage,
|
|
15
|
-
query: string,
|
|
16
|
-
limit = 10,
|
|
17
|
-
embedding?: Float32Array | null
|
|
18
|
-
): SearchResult[] {
|
|
19
|
-
// Delegate to storage's search method which already implements hybrid
|
|
20
|
-
return storage.search(query, limit, embedding);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Calculate fuzzy match score between text and query.
|
|
25
|
-
* Returns 0-1 score (1 = perfect match).
|
|
26
|
-
*/
|
|
27
|
-
export function fuzzyMatch(text: string, query: string): number {
|
|
28
|
-
const lowerText = text.toLowerCase();
|
|
29
|
-
const lowerQuery = query.toLowerCase();
|
|
30
|
-
|
|
31
|
-
// Exact match
|
|
32
|
-
if (lowerText === lowerQuery) return 1.0;
|
|
33
|
-
|
|
34
|
-
// Starts with
|
|
35
|
-
if (lowerText.startsWith(lowerQuery)) return 0.9;
|
|
36
|
-
|
|
37
|
-
// Contains
|
|
38
|
-
if (lowerText.includes(lowerQuery)) return 0.7;
|
|
39
|
-
|
|
40
|
-
// Word boundary match
|
|
41
|
-
const words = lowerQuery.split(/\s+/);
|
|
42
|
-
let matchedWords = 0;
|
|
43
|
-
for (const word of words) {
|
|
44
|
-
if (lowerText.includes(word)) {
|
|
45
|
-
matchedWords++;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
if (matchedWords > 0) {
|
|
49
|
-
return 0.3 + (matchedWords / words.length) * 0.4;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Subsequence match
|
|
53
|
-
let textIdx = 0;
|
|
54
|
-
let queryIdx = 0;
|
|
55
|
-
let subsequenceMatches = 0;
|
|
56
|
-
|
|
57
|
-
while (textIdx < lowerText.length && queryIdx < lowerQuery.length) {
|
|
58
|
-
if (lowerText[textIdx] === lowerQuery[queryIdx]) {
|
|
59
|
-
subsequenceMatches++;
|
|
60
|
-
queryIdx++;
|
|
61
|
-
}
|
|
62
|
-
textIdx++;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (queryIdx === lowerQuery.length) {
|
|
66
|
-
// All query chars found in order
|
|
67
|
-
return 0.2 + (subsequenceMatches / lowerQuery.length) * 0.2;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return 0;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Extract a snippet around the query match.
|
|
75
|
-
*/
|
|
76
|
-
export function extractSnippet(
|
|
77
|
-
content: string,
|
|
78
|
-
query: string,
|
|
79
|
-
chars = 150
|
|
80
|
-
): string {
|
|
81
|
-
const lowerContent = content.toLowerCase();
|
|
82
|
-
const lowerQuery = query.toLowerCase();
|
|
83
|
-
|
|
84
|
-
// Find best match position
|
|
85
|
-
let bestIdx = -1;
|
|
86
|
-
let bestScore = 0;
|
|
87
|
-
|
|
88
|
-
const words = lowerQuery.split(/\s+/);
|
|
89
|
-
for (const word of words) {
|
|
90
|
-
const idx = lowerContent.indexOf(word);
|
|
91
|
-
if (idx !== -1 && (bestIdx === -1 || idx < bestIdx)) {
|
|
92
|
-
bestIdx = idx;
|
|
93
|
-
bestScore = 0.8;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (bestIdx === -1) {
|
|
98
|
-
// No match, return beginning
|
|
99
|
-
return content.slice(0, chars) + (content.length > chars ? "..." : "");
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const start = Math.max(0, bestIdx - chars / 3);
|
|
103
|
-
const end = Math.min(content.length, bestIdx + chars * 2 / 3);
|
|
104
|
-
let snippet = content.slice(start, end);
|
|
105
|
-
|
|
106
|
-
if (start > 0) snippet = "..." + snippet;
|
|
107
|
-
if (end < content.length) snippet = snippet + "...";
|
|
108
|
-
|
|
109
|
-
return snippet;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Merge and deduplicate search results from multiple sources.
|
|
114
|
-
*/
|
|
115
|
-
export function mergeResults(
|
|
116
|
-
...resultSets: SearchResult[][]
|
|
117
|
-
): SearchResult[] {
|
|
118
|
-
const merged = new Map<string, SearchResult>();
|
|
119
|
-
|
|
120
|
-
for (const results of resultSets) {
|
|
121
|
-
for (const result of results) {
|
|
122
|
-
const existing = merged.get(result.record.id);
|
|
123
|
-
if (existing) {
|
|
124
|
-
// Boost score if found in multiple sources
|
|
125
|
-
existing.score = Math.min(existing.score + result.score * 0.2, 1);
|
|
126
|
-
} else {
|
|
127
|
-
merged.set(result.record.id, { ...result });
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return Array.from(merged.values())
|
|
133
|
-
.sort((a, b) => b.score - a.score);
|
|
134
|
-
}
|