@ulrichc1/sparn 1.2.1 → 1.4.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.
- package/PRIVACY.md +1 -1
- package/README.md +136 -642
- package/SECURITY.md +1 -1
- package/dist/cli/dashboard.cjs +3977 -0
- package/dist/cli/dashboard.cjs.map +1 -0
- package/dist/cli/dashboard.d.cts +17 -0
- package/dist/cli/dashboard.d.ts +17 -0
- package/dist/cli/dashboard.js +3932 -0
- package/dist/cli/dashboard.js.map +1 -0
- package/dist/cli/index.cjs +3855 -486
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +3812 -459
- package/dist/cli/index.js.map +1 -1
- package/dist/daemon/index.cjs +411 -99
- package/dist/daemon/index.cjs.map +1 -1
- package/dist/daemon/index.js +423 -103
- package/dist/daemon/index.js.map +1 -1
- package/dist/hooks/post-tool-result.cjs +129 -225
- package/dist/hooks/post-tool-result.cjs.map +1 -1
- package/dist/hooks/post-tool-result.js +129 -225
- package/dist/hooks/post-tool-result.js.map +1 -1
- package/dist/hooks/pre-prompt.cjs +206 -242
- package/dist/hooks/pre-prompt.cjs.map +1 -1
- package/dist/hooks/pre-prompt.js +192 -243
- package/dist/hooks/pre-prompt.js.map +1 -1
- package/dist/hooks/stop-docs-refresh.cjs +123 -0
- package/dist/hooks/stop-docs-refresh.cjs.map +1 -0
- package/dist/hooks/stop-docs-refresh.d.cts +1 -0
- package/dist/hooks/stop-docs-refresh.d.ts +1 -0
- package/dist/hooks/stop-docs-refresh.js +126 -0
- package/dist/hooks/stop-docs-refresh.js.map +1 -0
- package/dist/index.cjs +1756 -339
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +540 -41
- package/dist/index.d.ts +540 -41
- package/dist/index.js +1739 -331
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.cjs +306 -73
- package/dist/mcp/index.cjs.map +1 -1
- package/dist/mcp/index.js +310 -73
- package/dist/mcp/index.js.map +1 -1
- package/package.json +10 -3
package/dist/mcp/index.cjs
CHANGED
|
@@ -6,6 +6,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
9
16
|
var __copyProps = (to, from, except, desc) => {
|
|
10
17
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
18
|
for (let key of __getOwnPropNames(from))
|
|
@@ -23,12 +30,61 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
30
|
mod
|
|
24
31
|
));
|
|
25
32
|
|
|
33
|
+
// node_modules/tsup/assets/cjs_shims.js
|
|
34
|
+
var init_cjs_shims = __esm({
|
|
35
|
+
"node_modules/tsup/assets/cjs_shims.js"() {
|
|
36
|
+
"use strict";
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// src/utils/tokenizer.ts
|
|
41
|
+
var tokenizer_exports = {};
|
|
42
|
+
__export(tokenizer_exports, {
|
|
43
|
+
countTokensPrecise: () => countTokensPrecise,
|
|
44
|
+
estimateTokens: () => estimateTokens,
|
|
45
|
+
setPreciseTokenCounting: () => setPreciseTokenCounting
|
|
46
|
+
});
|
|
47
|
+
function setPreciseTokenCounting(enabled) {
|
|
48
|
+
usePrecise = enabled;
|
|
49
|
+
}
|
|
50
|
+
function countTokensPrecise(text) {
|
|
51
|
+
if (!text || text.length === 0) {
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
return (0, import_gpt_tokenizer.encode)(text).length;
|
|
55
|
+
}
|
|
56
|
+
function estimateTokens(text) {
|
|
57
|
+
if (!text || text.length === 0) {
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
if (usePrecise) {
|
|
61
|
+
return (0, import_gpt_tokenizer.encode)(text).length;
|
|
62
|
+
}
|
|
63
|
+
const words = text.split(/\s+/).filter((w) => w.length > 0);
|
|
64
|
+
const wordCount = words.length;
|
|
65
|
+
const charCount = text.length;
|
|
66
|
+
const charEstimate = Math.ceil(charCount / 4);
|
|
67
|
+
const wordEstimate = Math.ceil(wordCount * 0.75);
|
|
68
|
+
return Math.max(wordEstimate, charEstimate);
|
|
69
|
+
}
|
|
70
|
+
var import_gpt_tokenizer, usePrecise;
|
|
71
|
+
var init_tokenizer = __esm({
|
|
72
|
+
"src/utils/tokenizer.ts"() {
|
|
73
|
+
"use strict";
|
|
74
|
+
init_cjs_shims();
|
|
75
|
+
import_gpt_tokenizer = require("gpt-tokenizer");
|
|
76
|
+
usePrecise = false;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
26
80
|
// src/mcp/index.ts
|
|
81
|
+
init_cjs_shims();
|
|
27
82
|
var import_node_fs2 = require("fs");
|
|
28
83
|
var import_node_path = require("path");
|
|
29
84
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
30
85
|
|
|
31
86
|
// src/core/kv-memory.ts
|
|
87
|
+
init_cjs_shims();
|
|
32
88
|
var import_node_fs = require("fs");
|
|
33
89
|
var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
|
|
34
90
|
function createBackup(dbPath) {
|
|
@@ -50,6 +106,7 @@ async function createKVMemory(dbPath) {
|
|
|
50
106
|
const integrityCheck = db.pragma("quick_check", { simple: true });
|
|
51
107
|
if (integrityCheck !== "ok") {
|
|
52
108
|
console.error("\u26A0 Database corruption detected!");
|
|
109
|
+
db.close();
|
|
53
110
|
if ((0, import_node_fs.existsSync)(dbPath)) {
|
|
54
111
|
const backupPath = createBackup(dbPath);
|
|
55
112
|
if (backupPath) {
|
|
@@ -57,7 +114,6 @@ async function createKVMemory(dbPath) {
|
|
|
57
114
|
}
|
|
58
115
|
}
|
|
59
116
|
console.log("Attempting database recovery...");
|
|
60
|
-
db.close();
|
|
61
117
|
db = new import_better_sqlite3.default(dbPath);
|
|
62
118
|
}
|
|
63
119
|
} catch (error) {
|
|
@@ -69,6 +125,7 @@ async function createKVMemory(dbPath) {
|
|
|
69
125
|
db = new import_better_sqlite3.default(dbPath);
|
|
70
126
|
}
|
|
71
127
|
db.pragma("journal_mode = WAL");
|
|
128
|
+
db.pragma("foreign_keys = ON");
|
|
72
129
|
db.exec(`
|
|
73
130
|
CREATE TABLE IF NOT EXISTS entries_index (
|
|
74
131
|
id TEXT PRIMARY KEY NOT NULL,
|
|
@@ -108,6 +165,36 @@ async function createKVMemory(dbPath) {
|
|
|
108
165
|
CREATE INDEX IF NOT EXISTS idx_entries_timestamp ON entries_index(timestamp DESC);
|
|
109
166
|
CREATE INDEX IF NOT EXISTS idx_stats_timestamp ON optimization_stats(timestamp DESC);
|
|
110
167
|
`);
|
|
168
|
+
db.exec(`
|
|
169
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entries_fts USING fts5(id, content, tokenize='porter');
|
|
170
|
+
`);
|
|
171
|
+
db.exec(`
|
|
172
|
+
CREATE TRIGGER IF NOT EXISTS entries_fts_insert
|
|
173
|
+
AFTER INSERT ON entries_value
|
|
174
|
+
BEGIN
|
|
175
|
+
INSERT OR REPLACE INTO entries_fts(id, content) VALUES (NEW.id, NEW.content);
|
|
176
|
+
END;
|
|
177
|
+
`);
|
|
178
|
+
db.exec(`
|
|
179
|
+
CREATE TRIGGER IF NOT EXISTS entries_fts_delete
|
|
180
|
+
AFTER DELETE ON entries_value
|
|
181
|
+
BEGIN
|
|
182
|
+
DELETE FROM entries_fts WHERE id = OLD.id;
|
|
183
|
+
END;
|
|
184
|
+
`);
|
|
185
|
+
db.exec(`
|
|
186
|
+
CREATE TRIGGER IF NOT EXISTS entries_fts_update
|
|
187
|
+
AFTER UPDATE ON entries_value
|
|
188
|
+
BEGIN
|
|
189
|
+
DELETE FROM entries_fts WHERE id = OLD.id;
|
|
190
|
+
INSERT INTO entries_fts(id, content) VALUES (NEW.id, NEW.content);
|
|
191
|
+
END;
|
|
192
|
+
`);
|
|
193
|
+
db.exec(`
|
|
194
|
+
INSERT OR IGNORE INTO entries_fts(id, content)
|
|
195
|
+
SELECT id, content FROM entries_value
|
|
196
|
+
WHERE id NOT IN (SELECT id FROM entries_fts);
|
|
197
|
+
`);
|
|
111
198
|
const putIndexStmt = db.prepare(`
|
|
112
199
|
INSERT OR REPLACE INTO entries_index
|
|
113
200
|
(id, hash, timestamp, score, ttl, state, accessCount, isBTSP)
|
|
@@ -196,14 +283,20 @@ async function createKVMemory(dbPath) {
|
|
|
196
283
|
sql += " AND i.isBTSP = ?";
|
|
197
284
|
params.push(filters.isBTSP ? 1 : 0);
|
|
198
285
|
}
|
|
286
|
+
if (filters.tags && filters.tags.length > 0) {
|
|
287
|
+
for (const tag of filters.tags) {
|
|
288
|
+
sql += " AND v.tags LIKE ?";
|
|
289
|
+
params.push(`%"${tag}"%`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
199
292
|
sql += " ORDER BY i.score DESC";
|
|
200
293
|
if (filters.limit) {
|
|
201
294
|
sql += " LIMIT ?";
|
|
202
295
|
params.push(filters.limit);
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
296
|
+
if (filters.offset) {
|
|
297
|
+
sql += " OFFSET ?";
|
|
298
|
+
params.push(filters.offset);
|
|
299
|
+
}
|
|
207
300
|
}
|
|
208
301
|
const stmt = db.prepare(sql);
|
|
209
302
|
const rows = stmt.all(...params);
|
|
@@ -238,7 +331,22 @@ async function createKVMemory(dbPath) {
|
|
|
238
331
|
},
|
|
239
332
|
async compact() {
|
|
240
333
|
const before = db.prepare("SELECT COUNT(*) as count FROM entries_index").get();
|
|
241
|
-
|
|
334
|
+
const now = Date.now();
|
|
335
|
+
db.prepare("DELETE FROM entries_index WHERE isBTSP = 0 AND (timestamp + ttl * 1000) < ?").run(
|
|
336
|
+
now
|
|
337
|
+
);
|
|
338
|
+
db.exec("DELETE FROM entries_index WHERE isBTSP = 0 AND ttl <= 0");
|
|
339
|
+
const candidates = db.prepare("SELECT id, timestamp, ttl FROM entries_index WHERE isBTSP = 0").all();
|
|
340
|
+
for (const row of candidates) {
|
|
341
|
+
const ageSeconds = Math.max(0, (now - row.timestamp) / 1e3);
|
|
342
|
+
const ttlSeconds = row.ttl;
|
|
343
|
+
if (ttlSeconds <= 0) continue;
|
|
344
|
+
const decay = 1 - Math.exp(-ageSeconds / ttlSeconds);
|
|
345
|
+
if (decay >= 0.95) {
|
|
346
|
+
db.prepare("DELETE FROM entries_index WHERE id = ?").run(row.id);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
db.exec("DELETE FROM entries_value WHERE id NOT IN (SELECT id FROM entries_index)");
|
|
242
350
|
db.exec("VACUUM");
|
|
243
351
|
const after = db.prepare("SELECT COUNT(*) as count FROM entries_index").get();
|
|
244
352
|
return before.count - after.count;
|
|
@@ -258,6 +366,9 @@ async function createKVMemory(dbPath) {
|
|
|
258
366
|
stats.entries_pruned,
|
|
259
367
|
stats.duration_ms
|
|
260
368
|
);
|
|
369
|
+
db.prepare(
|
|
370
|
+
"DELETE FROM optimization_stats WHERE id NOT IN (SELECT id FROM optimization_stats ORDER BY timestamp DESC LIMIT 1000)"
|
|
371
|
+
).run();
|
|
261
372
|
},
|
|
262
373
|
async getOptimizationStats() {
|
|
263
374
|
const stmt = db.prepare(`
|
|
@@ -270,28 +381,70 @@ async function createKVMemory(dbPath) {
|
|
|
270
381
|
},
|
|
271
382
|
async clearOptimizationStats() {
|
|
272
383
|
db.exec("DELETE FROM optimization_stats");
|
|
384
|
+
},
|
|
385
|
+
async searchFTS(query, limit = 10) {
|
|
386
|
+
if (!query || query.trim().length === 0) return [];
|
|
387
|
+
const sanitized = query.replace(/[{}()[\]"':*^~]/g, " ").trim();
|
|
388
|
+
if (sanitized.length === 0) return [];
|
|
389
|
+
const stmt = db.prepare(`
|
|
390
|
+
SELECT
|
|
391
|
+
f.id, f.content, rank,
|
|
392
|
+
i.hash, i.timestamp, i.score, i.ttl, i.state, i.accessCount, i.isBTSP,
|
|
393
|
+
v.tags, v.metadata
|
|
394
|
+
FROM entries_fts f
|
|
395
|
+
JOIN entries_index i ON f.id = i.id
|
|
396
|
+
JOIN entries_value v ON f.id = v.id
|
|
397
|
+
WHERE entries_fts MATCH ?
|
|
398
|
+
ORDER BY rank
|
|
399
|
+
LIMIT ?
|
|
400
|
+
`);
|
|
401
|
+
try {
|
|
402
|
+
const rows = stmt.all(sanitized, limit);
|
|
403
|
+
return rows.map((r) => ({
|
|
404
|
+
entry: {
|
|
405
|
+
id: r.id,
|
|
406
|
+
content: r.content,
|
|
407
|
+
hash: r.hash,
|
|
408
|
+
timestamp: r.timestamp,
|
|
409
|
+
score: r.score,
|
|
410
|
+
ttl: r.ttl,
|
|
411
|
+
state: r.state,
|
|
412
|
+
accessCount: r.accessCount,
|
|
413
|
+
tags: r.tags ? JSON.parse(r.tags) : [],
|
|
414
|
+
metadata: r.metadata ? JSON.parse(r.metadata) : {},
|
|
415
|
+
isBTSP: r.isBTSP === 1
|
|
416
|
+
},
|
|
417
|
+
rank: r.rank
|
|
418
|
+
}));
|
|
419
|
+
} catch {
|
|
420
|
+
return [];
|
|
421
|
+
}
|
|
273
422
|
}
|
|
274
423
|
};
|
|
275
424
|
}
|
|
276
425
|
|
|
277
426
|
// src/mcp/server.ts
|
|
427
|
+
init_cjs_shims();
|
|
278
428
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
279
429
|
var import_zod = require("zod");
|
|
280
430
|
|
|
281
431
|
// src/adapters/generic.ts
|
|
432
|
+
init_cjs_shims();
|
|
282
433
|
var import_node_crypto3 = require("crypto");
|
|
283
434
|
|
|
284
435
|
// src/core/btsp-embedder.ts
|
|
436
|
+
init_cjs_shims();
|
|
285
437
|
var import_node_crypto2 = require("crypto");
|
|
286
438
|
|
|
287
439
|
// src/utils/hash.ts
|
|
440
|
+
init_cjs_shims();
|
|
288
441
|
var import_node_crypto = require("crypto");
|
|
289
442
|
function hashContent(content) {
|
|
290
443
|
return (0, import_node_crypto.createHash)("sha256").update(content, "utf8").digest("hex");
|
|
291
444
|
}
|
|
292
445
|
|
|
293
446
|
// src/core/btsp-embedder.ts
|
|
294
|
-
function createBTSPEmbedder() {
|
|
447
|
+
function createBTSPEmbedder(config) {
|
|
295
448
|
const BTSP_PATTERNS = [
|
|
296
449
|
// Error patterns
|
|
297
450
|
/\b(error|exception|failure|fatal|critical|panic)\b/i,
|
|
@@ -310,6 +463,14 @@ function createBTSPEmbedder() {
|
|
|
310
463
|
/^=======/m,
|
|
311
464
|
/^>>>>>>> /m
|
|
312
465
|
];
|
|
466
|
+
if (config?.customPatterns) {
|
|
467
|
+
for (const pattern of config.customPatterns) {
|
|
468
|
+
try {
|
|
469
|
+
BTSP_PATTERNS.push(new RegExp(pattern));
|
|
470
|
+
} catch {
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
313
474
|
function detectBTSP(content) {
|
|
314
475
|
return BTSP_PATTERNS.some((pattern) => pattern.test(content));
|
|
315
476
|
}
|
|
@@ -338,13 +499,14 @@ function createBTSPEmbedder() {
|
|
|
338
499
|
}
|
|
339
500
|
|
|
340
501
|
// src/core/confidence-states.ts
|
|
502
|
+
init_cjs_shims();
|
|
341
503
|
function createConfidenceStates(config) {
|
|
342
504
|
const { activeThreshold, readyThreshold } = config;
|
|
343
505
|
function calculateState(entry) {
|
|
344
506
|
if (entry.isBTSP) {
|
|
345
507
|
return "active";
|
|
346
508
|
}
|
|
347
|
-
if (entry.score
|
|
509
|
+
if (entry.score >= activeThreshold) {
|
|
348
510
|
return "active";
|
|
349
511
|
}
|
|
350
512
|
if (entry.score >= readyThreshold) {
|
|
@@ -380,8 +542,11 @@ function createConfidenceStates(config) {
|
|
|
380
542
|
}
|
|
381
543
|
|
|
382
544
|
// src/core/engram-scorer.ts
|
|
545
|
+
init_cjs_shims();
|
|
383
546
|
function createEngramScorer(config) {
|
|
384
547
|
const { defaultTTL } = config;
|
|
548
|
+
const recencyWindowMs = (config.recencyBoostMinutes ?? 30) * 60 * 1e3;
|
|
549
|
+
const recencyMultiplier = config.recencyBoostMultiplier ?? 1.3;
|
|
385
550
|
function calculateDecay(ageInSeconds, ttlInSeconds) {
|
|
386
551
|
if (ttlInSeconds === 0) return 1;
|
|
387
552
|
if (ageInSeconds <= 0) return 0;
|
|
@@ -401,6 +566,13 @@ function createEngramScorer(config) {
|
|
|
401
566
|
if (entry.isBTSP) {
|
|
402
567
|
score = Math.max(score, 0.9);
|
|
403
568
|
}
|
|
569
|
+
if (!entry.isBTSP && recencyWindowMs > 0) {
|
|
570
|
+
const ageMs = currentTime - entry.timestamp;
|
|
571
|
+
if (ageMs >= 0 && ageMs < recencyWindowMs) {
|
|
572
|
+
const boostFactor = 1 + (recencyMultiplier - 1) * (1 - ageMs / recencyWindowMs);
|
|
573
|
+
score = score * boostFactor;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
404
576
|
return Math.max(0, Math.min(1, score));
|
|
405
577
|
}
|
|
406
578
|
function refreshTTL(entry) {
|
|
@@ -418,49 +590,53 @@ function createEngramScorer(config) {
|
|
|
418
590
|
};
|
|
419
591
|
}
|
|
420
592
|
|
|
421
|
-
// src/
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
593
|
+
// src/core/sparse-pruner.ts
|
|
594
|
+
init_cjs_shims();
|
|
595
|
+
|
|
596
|
+
// src/utils/tfidf.ts
|
|
597
|
+
init_cjs_shims();
|
|
598
|
+
function tokenize(text) {
|
|
599
|
+
return text.toLowerCase().split(/\s+/).filter((word) => word.length > 0);
|
|
600
|
+
}
|
|
601
|
+
function calculateTF(term, tokens) {
|
|
602
|
+
const count = tokens.filter((t) => t === term).length;
|
|
603
|
+
return Math.sqrt(count);
|
|
604
|
+
}
|
|
605
|
+
function createTFIDFIndex(entries) {
|
|
606
|
+
const documentFrequency = /* @__PURE__ */ new Map();
|
|
607
|
+
for (const entry of entries) {
|
|
608
|
+
const tokens = tokenize(entry.content);
|
|
609
|
+
const uniqueTerms = new Set(tokens);
|
|
610
|
+
for (const term of uniqueTerms) {
|
|
611
|
+
documentFrequency.set(term, (documentFrequency.get(term) || 0) + 1);
|
|
612
|
+
}
|
|
425
613
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
614
|
+
return {
|
|
615
|
+
documentFrequency,
|
|
616
|
+
totalDocuments: entries.length
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
function scoreTFIDF(entry, index) {
|
|
620
|
+
const tokens = tokenize(entry.content);
|
|
621
|
+
if (tokens.length === 0) return 0;
|
|
622
|
+
const uniqueTerms = new Set(tokens);
|
|
623
|
+
let totalScore = 0;
|
|
624
|
+
for (const term of uniqueTerms) {
|
|
625
|
+
const tf = calculateTF(term, tokens);
|
|
626
|
+
const docsWithTerm = index.documentFrequency.get(term) || 0;
|
|
627
|
+
if (docsWithTerm === 0) continue;
|
|
628
|
+
const idf = Math.log(index.totalDocuments / docsWithTerm);
|
|
629
|
+
totalScore += tf * idf;
|
|
630
|
+
}
|
|
631
|
+
return totalScore / tokens.length;
|
|
432
632
|
}
|
|
433
633
|
|
|
434
634
|
// src/core/sparse-pruner.ts
|
|
635
|
+
init_tokenizer();
|
|
435
636
|
function createSparsePruner(config) {
|
|
436
637
|
const { threshold } = config;
|
|
437
|
-
function tokenize(text) {
|
|
438
|
-
return text.toLowerCase().split(/\s+/).filter((word) => word.length > 0);
|
|
439
|
-
}
|
|
440
|
-
function calculateTF(term, tokens) {
|
|
441
|
-
const count = tokens.filter((t) => t === term).length;
|
|
442
|
-
return Math.sqrt(count);
|
|
443
|
-
}
|
|
444
|
-
function calculateIDF(term, allEntries) {
|
|
445
|
-
const totalDocs = allEntries.length;
|
|
446
|
-
const docsWithTerm = allEntries.filter((entry) => {
|
|
447
|
-
const tokens = tokenize(entry.content);
|
|
448
|
-
return tokens.includes(term);
|
|
449
|
-
}).length;
|
|
450
|
-
if (docsWithTerm === 0) return 0;
|
|
451
|
-
return Math.log(totalDocs / docsWithTerm);
|
|
452
|
-
}
|
|
453
638
|
function scoreEntry(entry, allEntries) {
|
|
454
|
-
|
|
455
|
-
if (tokens.length === 0) return 0;
|
|
456
|
-
const uniqueTerms = [...new Set(tokens)];
|
|
457
|
-
let totalScore = 0;
|
|
458
|
-
for (const term of uniqueTerms) {
|
|
459
|
-
const tf = calculateTF(term, tokens);
|
|
460
|
-
const idf = calculateIDF(term, allEntries);
|
|
461
|
-
totalScore += tf * idf;
|
|
462
|
-
}
|
|
463
|
-
return totalScore / tokens.length;
|
|
639
|
+
return scoreTFIDF(entry, createTFIDFIndex(allEntries));
|
|
464
640
|
}
|
|
465
641
|
function prune(entries) {
|
|
466
642
|
if (entries.length === 0) {
|
|
@@ -472,9 +648,10 @@ function createSparsePruner(config) {
|
|
|
472
648
|
};
|
|
473
649
|
}
|
|
474
650
|
const originalTokens = entries.reduce((sum, e) => sum + estimateTokens(e.content), 0);
|
|
651
|
+
const tfidfIndex = createTFIDFIndex(entries);
|
|
475
652
|
const scored = entries.map((entry) => ({
|
|
476
653
|
entry,
|
|
477
|
-
score:
|
|
654
|
+
score: scoreTFIDF(entry, tfidfIndex)
|
|
478
655
|
}));
|
|
479
656
|
scored.sort((a, b) => b.score - a.score);
|
|
480
657
|
const keepCount = Math.max(1, Math.ceil(entries.length * (threshold / 100)));
|
|
@@ -495,29 +672,33 @@ function createSparsePruner(config) {
|
|
|
495
672
|
}
|
|
496
673
|
|
|
497
674
|
// src/adapters/generic.ts
|
|
675
|
+
init_tokenizer();
|
|
498
676
|
function createGenericAdapter(memory, config) {
|
|
499
677
|
const pruner = createSparsePruner(config.pruning);
|
|
500
678
|
const scorer = createEngramScorer(config.decay);
|
|
501
679
|
const states = createConfidenceStates(config.states);
|
|
502
|
-
const btsp = createBTSPEmbedder();
|
|
680
|
+
const btsp = createBTSPEmbedder({ customPatterns: config.btspPatterns });
|
|
503
681
|
async function optimize(context, options = {}) {
|
|
504
682
|
const startTime = Date.now();
|
|
505
683
|
const lines = context.split("\n").filter((line) => line.trim().length > 0);
|
|
506
|
-
const
|
|
507
|
-
|
|
508
|
-
content
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
684
|
+
const now = Date.now();
|
|
685
|
+
const entries = lines.map((content, index) => {
|
|
686
|
+
const isBTSP = btsp.detectBTSP(content);
|
|
687
|
+
return {
|
|
688
|
+
id: (0, import_node_crypto3.randomUUID)(),
|
|
689
|
+
content,
|
|
690
|
+
hash: hashContent(content),
|
|
691
|
+
timestamp: now + index,
|
|
692
|
+
// Unique timestamps preserve ordering
|
|
693
|
+
score: isBTSP ? 1 : 0.5,
|
|
694
|
+
ttl: config.decay.defaultTTL * 3600,
|
|
695
|
+
state: "ready",
|
|
696
|
+
accessCount: 0,
|
|
697
|
+
tags: [],
|
|
698
|
+
metadata: {},
|
|
699
|
+
isBTSP
|
|
700
|
+
};
|
|
701
|
+
});
|
|
521
702
|
const tokensBefore = entries.reduce((sum, e) => sum + estimateTokens(e.content), 0);
|
|
522
703
|
const scoredEntries = entries.map((entry) => ({
|
|
523
704
|
...entry,
|
|
@@ -570,6 +751,7 @@ function createGenericAdapter(memory, config) {
|
|
|
570
751
|
}
|
|
571
752
|
|
|
572
753
|
// src/core/sleep-compressor.ts
|
|
754
|
+
init_cjs_shims();
|
|
573
755
|
function createSleepCompressor() {
|
|
574
756
|
const scorer = createEngramScorer({ defaultTTL: 24, decayThreshold: 0.95 });
|
|
575
757
|
function consolidate(entries) {
|
|
@@ -660,13 +842,15 @@ function createSleepCompressor() {
|
|
|
660
842
|
function cosineSimilarity(text1, text2) {
|
|
661
843
|
const words1 = tokenize(text1);
|
|
662
844
|
const words2 = tokenize(text2);
|
|
663
|
-
const vocab = /* @__PURE__ */ new Set([...words1, ...words2]);
|
|
664
845
|
const vec1 = {};
|
|
665
846
|
const vec2 = {};
|
|
666
|
-
for (const word of
|
|
667
|
-
vec1[word] =
|
|
668
|
-
|
|
847
|
+
for (const word of words1) {
|
|
848
|
+
vec1[word] = (vec1[word] ?? 0) + 1;
|
|
849
|
+
}
|
|
850
|
+
for (const word of words2) {
|
|
851
|
+
vec2[word] = (vec2[word] ?? 0) + 1;
|
|
669
852
|
}
|
|
853
|
+
const vocab = /* @__PURE__ */ new Set([...words1, ...words2]);
|
|
670
854
|
let dotProduct = 0;
|
|
671
855
|
let mag1 = 0;
|
|
672
856
|
let mag2 = 0;
|
|
@@ -682,9 +866,6 @@ function createSleepCompressor() {
|
|
|
682
866
|
if (mag1 === 0 || mag2 === 0) return 0;
|
|
683
867
|
return dotProduct / (mag1 * mag2);
|
|
684
868
|
}
|
|
685
|
-
function tokenize(text) {
|
|
686
|
-
return text.toLowerCase().split(/\s+/).filter((word) => word.length > 0);
|
|
687
|
-
}
|
|
688
869
|
return {
|
|
689
870
|
consolidate,
|
|
690
871
|
findDuplicates,
|
|
@@ -693,6 +874,7 @@ function createSleepCompressor() {
|
|
|
693
874
|
}
|
|
694
875
|
|
|
695
876
|
// src/types/config.ts
|
|
877
|
+
init_cjs_shims();
|
|
696
878
|
var DEFAULT_CONFIG = {
|
|
697
879
|
pruning: {
|
|
698
880
|
threshold: 5,
|
|
@@ -714,8 +896,8 @@ var DEFAULT_CONFIG = {
|
|
|
714
896
|
},
|
|
715
897
|
autoConsolidate: null,
|
|
716
898
|
realtime: {
|
|
717
|
-
tokenBudget:
|
|
718
|
-
autoOptimizeThreshold:
|
|
899
|
+
tokenBudget: 4e4,
|
|
900
|
+
autoOptimizeThreshold: 6e4,
|
|
719
901
|
watchPatterns: ["**/*.jsonl"],
|
|
720
902
|
pidFile: ".sparn/daemon.pid",
|
|
721
903
|
logFile: ".sparn/daemon.log",
|
|
@@ -731,11 +913,12 @@ function createSparnMcpServer(options) {
|
|
|
731
913
|
const { memory, config = DEFAULT_CONFIG } = options;
|
|
732
914
|
const server = new import_mcp.McpServer({
|
|
733
915
|
name: "sparn",
|
|
734
|
-
version: "1.
|
|
916
|
+
version: "1.4.0"
|
|
735
917
|
});
|
|
736
918
|
registerOptimizeTool(server, memory, config);
|
|
737
919
|
registerStatsTool(server, memory);
|
|
738
920
|
registerConsolidateTool(server, memory);
|
|
921
|
+
registerSearchTool(server, memory);
|
|
739
922
|
return server;
|
|
740
923
|
}
|
|
741
924
|
function registerOptimizeTool(server, memory, config) {
|
|
@@ -743,7 +926,7 @@ function registerOptimizeTool(server, memory, config) {
|
|
|
743
926
|
"sparn_optimize",
|
|
744
927
|
{
|
|
745
928
|
title: "Sparn Optimize",
|
|
746
|
-
description: "Optimize context using
|
|
929
|
+
description: "Optimize context using multi-stage pruning. Applies critical event detection, relevance scoring, entry classification, and sparse pruning to reduce token usage while preserving important information.",
|
|
747
930
|
inputSchema: {
|
|
748
931
|
context: import_zod.z.string().describe("The context text to optimize"),
|
|
749
932
|
dryRun: import_zod.z.boolean().optional().default(false).describe("If true, do not persist changes to the memory store"),
|
|
@@ -873,12 +1056,58 @@ function registerStatsTool(server, memory) {
|
|
|
873
1056
|
}
|
|
874
1057
|
);
|
|
875
1058
|
}
|
|
1059
|
+
function registerSearchTool(server, memory) {
|
|
1060
|
+
server.registerTool(
|
|
1061
|
+
"sparn_search",
|
|
1062
|
+
{
|
|
1063
|
+
title: "Sparn Search",
|
|
1064
|
+
description: "Search memory entries using full-text search. Returns matching entries with relevance ranking, score, and state information.",
|
|
1065
|
+
inputSchema: {
|
|
1066
|
+
query: import_zod.z.string().describe("Search query text"),
|
|
1067
|
+
limit: import_zod.z.number().int().min(1).max(100).optional().default(10).describe("Maximum number of results (1-100, default 10)")
|
|
1068
|
+
}
|
|
1069
|
+
},
|
|
1070
|
+
async ({ query, limit }) => {
|
|
1071
|
+
try {
|
|
1072
|
+
const results = await memory.searchFTS(query, limit);
|
|
1073
|
+
const response = results.map((r) => ({
|
|
1074
|
+
id: r.entry.id,
|
|
1075
|
+
content: r.entry.content.length > 500 ? `${r.entry.content.slice(0, 500)}...` : r.entry.content,
|
|
1076
|
+
score: r.entry.score,
|
|
1077
|
+
state: r.entry.state,
|
|
1078
|
+
rank: r.rank,
|
|
1079
|
+
tags: r.entry.tags,
|
|
1080
|
+
isBTSP: r.entry.isBTSP
|
|
1081
|
+
}));
|
|
1082
|
+
return {
|
|
1083
|
+
content: [
|
|
1084
|
+
{
|
|
1085
|
+
type: "text",
|
|
1086
|
+
text: JSON.stringify({ results: response, total: response.length }, null, 2)
|
|
1087
|
+
}
|
|
1088
|
+
]
|
|
1089
|
+
};
|
|
1090
|
+
} catch (error) {
|
|
1091
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1092
|
+
return {
|
|
1093
|
+
content: [
|
|
1094
|
+
{
|
|
1095
|
+
type: "text",
|
|
1096
|
+
text: JSON.stringify({ error: message })
|
|
1097
|
+
}
|
|
1098
|
+
],
|
|
1099
|
+
isError: true
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
876
1105
|
function registerConsolidateTool(server, memory) {
|
|
877
1106
|
server.registerTool(
|
|
878
1107
|
"sparn_consolidate",
|
|
879
1108
|
{
|
|
880
1109
|
title: "Sparn Consolidate",
|
|
881
|
-
description: "Run memory consolidation
|
|
1110
|
+
description: "Run memory consolidation. Removes decayed entries and merges duplicates to reclaim space."
|
|
882
1111
|
},
|
|
883
1112
|
async () => {
|
|
884
1113
|
try {
|
|
@@ -934,6 +1163,10 @@ function registerConsolidateTool(server, memory) {
|
|
|
934
1163
|
|
|
935
1164
|
// src/mcp/index.ts
|
|
936
1165
|
async function main() {
|
|
1166
|
+
if (process.env["SPARN_PRECISE_TOKENS"] === "true") {
|
|
1167
|
+
const { setPreciseTokenCounting: setPreciseTokenCounting2 } = await Promise.resolve().then(() => (init_tokenizer(), tokenizer_exports));
|
|
1168
|
+
setPreciseTokenCounting2(true);
|
|
1169
|
+
}
|
|
937
1170
|
const dbPath = (0, import_node_path.resolve)(process.env["SPARN_DB_PATH"] ?? ".sparn/memory.db");
|
|
938
1171
|
(0, import_node_fs2.mkdirSync)((0, import_node_path.dirname)(dbPath), { recursive: true });
|
|
939
1172
|
const memory = await createKVMemory(dbPath);
|