@ulrichc1/sparn 1.0.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/dist/index.js ADDED
@@ -0,0 +1,900 @@
1
+ // src/adapters/claude-code.ts
2
+ import { randomUUID as randomUUID2 } from "crypto";
3
+
4
+ // src/core/btsp-embedder.ts
5
+ import { randomUUID } from "crypto";
6
+
7
+ // src/utils/hash.ts
8
+ import { createHash } from "crypto";
9
+ function hashContent(content) {
10
+ return createHash("sha256").update(content, "utf8").digest("hex");
11
+ }
12
+
13
+ // src/core/btsp-embedder.ts
14
+ function createBTSPEmbedder() {
15
+ const BTSP_PATTERNS = [
16
+ // Error patterns
17
+ /\b(error|exception|failure|fatal|critical|panic)\b/i,
18
+ /\b(TypeError|ReferenceError|SyntaxError|RangeError|URIError)\b/,
19
+ /\bENOENT|EACCES|ECONNREFUSED|ETIMEDOUT\b/,
20
+ // Stack trace patterns
21
+ /^\s+at\s+.*\(.*:\d+:\d+\)/m,
22
+ // JavaScript stack trace
23
+ /^\s+at\s+.*\.[a-zA-Z]+:\d+/m,
24
+ // Python/Ruby stack trace
25
+ // Git diff new files
26
+ /^new file mode \d+$/m,
27
+ /^--- \/dev\/null$/m,
28
+ // Merge conflict markers
29
+ /^<<<<<<< /m,
30
+ /^=======/m,
31
+ /^>>>>>>> /m
32
+ ];
33
+ function detectBTSP(content) {
34
+ return BTSP_PATTERNS.some((pattern) => pattern.test(content));
35
+ }
36
+ function createBTSPEntry(content, tags = [], metadata = {}) {
37
+ return {
38
+ id: randomUUID(),
39
+ content,
40
+ hash: hashContent(content),
41
+ timestamp: Date.now(),
42
+ score: 1,
43
+ // Maximum initial score
44
+ ttl: 365 * 24 * 3600,
45
+ // 1 year in seconds (long retention)
46
+ state: "active",
47
+ // Always active
48
+ accessCount: 0,
49
+ tags: [...tags, "btsp"],
50
+ metadata,
51
+ isBTSP: true
52
+ };
53
+ }
54
+ return {
55
+ detectBTSP,
56
+ createBTSPEntry
57
+ };
58
+ }
59
+
60
+ // src/core/confidence-states.ts
61
+ function createConfidenceStates(config) {
62
+ const { activeThreshold, readyThreshold } = config;
63
+ function calculateState(entry) {
64
+ if (entry.isBTSP) {
65
+ return "active";
66
+ }
67
+ if (entry.score > activeThreshold) {
68
+ return "active";
69
+ }
70
+ if (entry.score >= readyThreshold) {
71
+ return "ready";
72
+ }
73
+ return "silent";
74
+ }
75
+ function transition(entry) {
76
+ const newState = calculateState(entry);
77
+ return {
78
+ ...entry,
79
+ state: newState
80
+ };
81
+ }
82
+ function getDistribution(entries) {
83
+ const distribution = {
84
+ silent: 0,
85
+ ready: 0,
86
+ active: 0,
87
+ total: entries.length
88
+ };
89
+ for (const entry of entries) {
90
+ const state = calculateState(entry);
91
+ distribution[state]++;
92
+ }
93
+ return distribution;
94
+ }
95
+ return {
96
+ calculateState,
97
+ transition,
98
+ getDistribution
99
+ };
100
+ }
101
+
102
+ // src/core/engram-scorer.ts
103
+ function createEngramScorer(config) {
104
+ const { defaultTTL } = config;
105
+ function calculateDecay(ageInSeconds, ttlInSeconds) {
106
+ if (ttlInSeconds === 0) return 1;
107
+ if (ageInSeconds <= 0) return 0;
108
+ const ratio = ageInSeconds / ttlInSeconds;
109
+ const decay = 1 - Math.exp(-ratio);
110
+ return Math.max(0, Math.min(1, decay));
111
+ }
112
+ function calculateScore(entry, currentTime = Date.now()) {
113
+ const ageInMilliseconds = currentTime - entry.timestamp;
114
+ const ageInSeconds = Math.max(0, ageInMilliseconds / 1e3);
115
+ const decay = calculateDecay(ageInSeconds, entry.ttl);
116
+ let score = entry.score * (1 - decay);
117
+ if (entry.accessCount > 0) {
118
+ const accessBonus = Math.log(entry.accessCount + 1) * 0.1;
119
+ score = Math.min(1, score + accessBonus);
120
+ }
121
+ if (entry.isBTSP) {
122
+ score = Math.max(score, 0.9);
123
+ }
124
+ return Math.max(0, Math.min(1, score));
125
+ }
126
+ function refreshTTL(entry) {
127
+ return {
128
+ ...entry,
129
+ ttl: defaultTTL * 3600,
130
+ // Convert hours to seconds
131
+ timestamp: Date.now()
132
+ };
133
+ }
134
+ return {
135
+ calculateScore,
136
+ refreshTTL,
137
+ calculateDecay
138
+ };
139
+ }
140
+
141
+ // src/utils/tokenizer.ts
142
+ function estimateTokens(text) {
143
+ if (!text || text.length === 0) {
144
+ return 0;
145
+ }
146
+ const words = text.split(/\s+/).filter((w) => w.length > 0);
147
+ const wordCount = words.length;
148
+ const charCount = text.length;
149
+ const charEstimate = Math.ceil(charCount / 4);
150
+ const wordEstimate = Math.ceil(wordCount * 0.75);
151
+ return Math.max(wordEstimate, charEstimate);
152
+ }
153
+
154
+ // src/core/sparse-pruner.ts
155
+ function createSparsePruner(config) {
156
+ const { threshold } = config;
157
+ function tokenize(text) {
158
+ return text.toLowerCase().split(/\s+/).filter((word) => word.length > 0);
159
+ }
160
+ function calculateTF(term, tokens) {
161
+ const count = tokens.filter((t) => t === term).length;
162
+ return Math.sqrt(count);
163
+ }
164
+ function calculateIDF(term, allEntries) {
165
+ const totalDocs = allEntries.length;
166
+ const docsWithTerm = allEntries.filter((entry) => {
167
+ const tokens = tokenize(entry.content);
168
+ return tokens.includes(term);
169
+ }).length;
170
+ if (docsWithTerm === 0) return 0;
171
+ return Math.log(totalDocs / docsWithTerm);
172
+ }
173
+ function scoreEntry(entry, allEntries) {
174
+ const tokens = tokenize(entry.content);
175
+ if (tokens.length === 0) return 0;
176
+ const uniqueTerms = [...new Set(tokens)];
177
+ let totalScore = 0;
178
+ for (const term of uniqueTerms) {
179
+ const tf = calculateTF(term, tokens);
180
+ const idf = calculateIDF(term, allEntries);
181
+ totalScore += tf * idf;
182
+ }
183
+ return totalScore / tokens.length;
184
+ }
185
+ function prune(entries) {
186
+ if (entries.length === 0) {
187
+ return {
188
+ kept: [],
189
+ removed: [],
190
+ originalTokens: 0,
191
+ prunedTokens: 0
192
+ };
193
+ }
194
+ const originalTokens = entries.reduce((sum, e) => sum + estimateTokens(e.content), 0);
195
+ const scored = entries.map((entry) => ({
196
+ entry,
197
+ score: scoreEntry(entry, entries)
198
+ }));
199
+ scored.sort((a, b) => b.score - a.score);
200
+ const keepCount = Math.max(1, Math.ceil(entries.length * (threshold / 100)));
201
+ const kept = scored.slice(0, keepCount).map((s) => s.entry);
202
+ const removed = scored.slice(keepCount).map((s) => s.entry);
203
+ const prunedTokens = kept.reduce((sum, e) => sum + estimateTokens(e.content), 0);
204
+ return {
205
+ kept,
206
+ removed,
207
+ originalTokens,
208
+ prunedTokens
209
+ };
210
+ }
211
+ return {
212
+ prune,
213
+ scoreEntry
214
+ };
215
+ }
216
+
217
+ // src/adapters/claude-code.ts
218
+ var CLAUDE_CODE_PROFILE = {
219
+ // More aggressive pruning for tool results (they can be verbose)
220
+ toolResultThreshold: 3,
221
+ // Keep top 3% of tool results
222
+ // Preserve conversation turns more aggressively
223
+ conversationBoost: 1.5,
224
+ // 50% boost for User/Assistant exchanges
225
+ // Prioritize recent context (Claude Code sessions are typically focused)
226
+ recentContextWindow: 10 * 60,
227
+ // Last 10 minutes gets priority
228
+ // BTSP patterns specific to Claude Code
229
+ btspPatterns: [
230
+ // Error patterns
231
+ /\b(error|exception|failure|fatal|critical|panic)\b/i,
232
+ /^\s+at\s+.*\(.*:\d+:\d+\)/m,
233
+ // Stack traces
234
+ /^Error:/m,
235
+ // Git conflict markers
236
+ /^<<<<<<< /m,
237
+ /^=======/m,
238
+ /^>>>>>>> /m,
239
+ // Tool use patterns (important for context)
240
+ /<function_calls>/,
241
+ /<invoke>/,
242
+ /<tool_use>/,
243
+ // File operation results (often critical)
244
+ /ENOENT|EACCES|EISDIR|EEXIST/
245
+ ]
246
+ };
247
+ function createClaudeCodeAdapter(memory, config) {
248
+ const pruner = createSparsePruner({
249
+ threshold: config.pruning.threshold
250
+ });
251
+ const scorer = createEngramScorer(config.decay);
252
+ const states = createConfidenceStates(config.states);
253
+ const btsp = createBTSPEmbedder();
254
+ async function optimize(context, options = {}) {
255
+ const startTime = Date.now();
256
+ const entries = parseClaudeCodeContext(context);
257
+ const entriesWithBTSP = entries.map((entry) => {
258
+ const isBTSP = CLAUDE_CODE_PROFILE.btspPatterns.some(
259
+ (pattern) => pattern.test(entry.content)
260
+ );
261
+ if (isBTSP) {
262
+ const btspEntry = btsp.createBTSPEntry(entry.content, [...entry.tags, "claude-code"], {
263
+ originalTimestamp: entry.timestamp
264
+ });
265
+ return {
266
+ ...btspEntry,
267
+ timestamp: entry.timestamp
268
+ };
269
+ }
270
+ return entry;
271
+ });
272
+ const boostedEntries = entriesWithBTSP.map((entry) => {
273
+ const isConversationTurn = entry.content.trim().startsWith("User:") || entry.content.trim().startsWith("Assistant:");
274
+ if (isConversationTurn) {
275
+ return {
276
+ ...entry,
277
+ score: entry.score * CLAUDE_CODE_PROFILE.conversationBoost
278
+ };
279
+ }
280
+ return entry;
281
+ });
282
+ const scoredEntries = boostedEntries.map((entry) => {
283
+ const decayScore = scorer.calculateScore(entry);
284
+ return {
285
+ ...entry,
286
+ score: decayScore
287
+ };
288
+ });
289
+ const entriesWithStates = scoredEntries.map((entry) => {
290
+ const state = states.calculateState(entry);
291
+ return {
292
+ ...entry,
293
+ state
294
+ };
295
+ });
296
+ const pruneResult = pruner.prune(entriesWithStates);
297
+ if (!options.dryRun) {
298
+ for (const entry of pruneResult.kept) {
299
+ await memory.put(entry);
300
+ }
301
+ await memory.recordOptimization({
302
+ timestamp: Date.now(),
303
+ tokens_before: pruneResult.originalTokens,
304
+ tokens_after: pruneResult.prunedTokens,
305
+ entries_pruned: pruneResult.removed.length,
306
+ duration_ms: Date.now() - startTime
307
+ });
308
+ }
309
+ const optimizedContext = pruneResult.kept.map((entry) => entry.content).join("\n");
310
+ const stateDistribution = states.getDistribution(pruneResult.kept);
311
+ const result = {
312
+ optimizedContext,
313
+ tokensBefore: pruneResult.originalTokens,
314
+ tokensAfter: pruneResult.prunedTokens,
315
+ reduction: pruneResult.originalTokens > 0 ? (pruneResult.originalTokens - pruneResult.prunedTokens) / pruneResult.originalTokens : 0,
316
+ entriesProcessed: entries.length,
317
+ entriesKept: pruneResult.kept.length,
318
+ durationMs: Date.now() - startTime,
319
+ stateDistribution
320
+ };
321
+ if (options.verbose) {
322
+ result.details = pruneResult.kept.map((entry) => ({
323
+ id: entry.id,
324
+ score: entry.score,
325
+ state: entry.state || "unknown",
326
+ isBTSP: entry.tags.includes("btsp"),
327
+ tokens: estimateTokens(entry.content)
328
+ }));
329
+ }
330
+ return result;
331
+ }
332
+ return {
333
+ optimize
334
+ };
335
+ }
336
+ function parseClaudeCodeContext(context) {
337
+ const entries = [];
338
+ const now = Date.now();
339
+ const lines = context.split("\n");
340
+ let currentBlock = [];
341
+ let blockType = "other";
342
+ for (const line of lines) {
343
+ const trimmed = line.trim();
344
+ if (trimmed.startsWith("User:") || trimmed.startsWith("Assistant:")) {
345
+ if (currentBlock.length > 0) {
346
+ entries.push(createEntry(currentBlock.join("\n"), blockType, now));
347
+ currentBlock = [];
348
+ }
349
+ blockType = "conversation";
350
+ currentBlock.push(line);
351
+ } else if (trimmed.includes("<function_calls>") || trimmed.includes("<invoke>") || trimmed.includes("<tool_use>")) {
352
+ if (currentBlock.length > 0) {
353
+ entries.push(createEntry(currentBlock.join("\n"), blockType, now));
354
+ currentBlock = [];
355
+ }
356
+ blockType = "tool";
357
+ currentBlock.push(line);
358
+ } else if (trimmed.includes("<function_results>") || trimmed.includes("</function_results>")) {
359
+ if (currentBlock.length > 0 && blockType !== "result") {
360
+ entries.push(createEntry(currentBlock.join("\n"), blockType, now));
361
+ currentBlock = [];
362
+ }
363
+ blockType = "result";
364
+ currentBlock.push(line);
365
+ } else if (currentBlock.length > 0) {
366
+ currentBlock.push(line);
367
+ } else if (trimmed.length > 0) {
368
+ currentBlock.push(line);
369
+ blockType = "other";
370
+ }
371
+ }
372
+ if (currentBlock.length > 0) {
373
+ entries.push(createEntry(currentBlock.join("\n"), blockType, now));
374
+ }
375
+ return entries.filter((e) => e.content.trim().length > 0);
376
+ }
377
+ function createEntry(content, type, baseTime) {
378
+ const tags = [type];
379
+ let initialScore = 0.5;
380
+ if (type === "conversation") initialScore = 0.8;
381
+ if (type === "tool") initialScore = 0.7;
382
+ if (type === "result") initialScore = 0.4;
383
+ return {
384
+ id: randomUUID2(),
385
+ content,
386
+ hash: hashContent(content),
387
+ timestamp: baseTime,
388
+ score: initialScore,
389
+ state: initialScore > 0.7 ? "active" : initialScore > 0.3 ? "ready" : "silent",
390
+ ttl: 24 * 3600,
391
+ // 24 hours default
392
+ accessCount: 0,
393
+ tags,
394
+ metadata: { type },
395
+ isBTSP: false
396
+ };
397
+ }
398
+
399
+ // src/adapters/generic.ts
400
+ import { randomUUID as randomUUID3 } from "crypto";
401
+ function createGenericAdapter(memory, config) {
402
+ const pruner = createSparsePruner(config.pruning);
403
+ const scorer = createEngramScorer(config.decay);
404
+ const states = createConfidenceStates(config.states);
405
+ const btsp = createBTSPEmbedder();
406
+ async function optimize(context, options = {}) {
407
+ const startTime = Date.now();
408
+ const lines = context.split("\n").filter((line) => line.trim().length > 0);
409
+ const entries = lines.map((content) => ({
410
+ id: randomUUID3(),
411
+ content,
412
+ hash: hashContent(content),
413
+ timestamp: Date.now(),
414
+ score: btsp.detectBTSP(content) ? 1 : 0.5,
415
+ // BTSP gets high initial score
416
+ ttl: config.decay.defaultTTL * 3600,
417
+ // Convert hours to seconds
418
+ state: "ready",
419
+ accessCount: 0,
420
+ tags: [],
421
+ metadata: {},
422
+ isBTSP: btsp.detectBTSP(content)
423
+ }));
424
+ const tokensBefore = entries.reduce((sum, e) => sum + estimateTokens(e.content), 0);
425
+ const scoredEntries = entries.map((entry) => ({
426
+ ...entry,
427
+ score: scorer.calculateScore(entry)
428
+ }));
429
+ const statedEntries = scoredEntries.map((entry) => states.transition(entry));
430
+ const pruneResult = pruner.prune(statedEntries);
431
+ const optimizedEntries = pruneResult.kept.filter(
432
+ (e) => e.state === "active" || e.state === "ready"
433
+ );
434
+ const tokensAfter = optimizedEntries.reduce((sum, e) => sum + estimateTokens(e.content), 0);
435
+ const optimizedContext = optimizedEntries.map((e) => e.content).join("\n");
436
+ if (!options.dryRun) {
437
+ for (const entry of optimizedEntries) {
438
+ await memory.put(entry);
439
+ }
440
+ await memory.recordOptimization({
441
+ timestamp: Date.now(),
442
+ tokens_before: tokensBefore,
443
+ tokens_after: tokensAfter,
444
+ entries_pruned: entries.length - optimizedEntries.length,
445
+ duration_ms: Date.now() - startTime
446
+ });
447
+ }
448
+ const distribution = states.getDistribution(optimizedEntries);
449
+ const result = {
450
+ optimizedContext,
451
+ tokensBefore,
452
+ tokensAfter,
453
+ reduction: tokensBefore > 0 ? (tokensBefore - tokensAfter) / tokensBefore : 0,
454
+ entriesProcessed: entries.length,
455
+ entriesKept: optimizedEntries.length,
456
+ stateDistribution: distribution,
457
+ durationMs: Date.now() - startTime
458
+ };
459
+ if (options.verbose) {
460
+ result.details = optimizedEntries.map((e) => ({
461
+ id: e.id,
462
+ score: e.score,
463
+ state: e.state,
464
+ isBTSP: e.isBTSP,
465
+ tokens: estimateTokens(e.content)
466
+ }));
467
+ }
468
+ return result;
469
+ }
470
+ return {
471
+ optimize
472
+ };
473
+ }
474
+
475
+ // src/core/kv-memory.ts
476
+ import { copyFileSync, existsSync } from "fs";
477
+ import Database from "better-sqlite3";
478
+ function createBackup(dbPath) {
479
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
480
+ const backupPath = `${dbPath}.backup-${timestamp}`;
481
+ try {
482
+ copyFileSync(dbPath, backupPath);
483
+ console.log(`\u2713 Database backed up to: ${backupPath}`);
484
+ return backupPath;
485
+ } catch (error) {
486
+ console.error(`Warning: Could not create backup: ${error}`);
487
+ return "";
488
+ }
489
+ }
490
+ async function createKVMemory(dbPath) {
491
+ let db;
492
+ try {
493
+ db = new Database(dbPath);
494
+ const integrityCheck = db.pragma("quick_check", { simple: true });
495
+ if (integrityCheck !== "ok") {
496
+ console.error("\u26A0 Database corruption detected!");
497
+ if (existsSync(dbPath)) {
498
+ const backupPath = createBackup(dbPath);
499
+ if (backupPath) {
500
+ console.log(`Backup created at: ${backupPath}`);
501
+ }
502
+ }
503
+ console.log("Attempting database recovery...");
504
+ db.close();
505
+ db = new Database(dbPath);
506
+ }
507
+ } catch (error) {
508
+ console.error("\u26A0 Database error detected:", error);
509
+ if (existsSync(dbPath)) {
510
+ createBackup(dbPath);
511
+ console.log("Creating new database...");
512
+ }
513
+ db = new Database(dbPath);
514
+ }
515
+ db.pragma("journal_mode = WAL");
516
+ db.exec(`
517
+ CREATE TABLE IF NOT EXISTS entries_index (
518
+ id TEXT PRIMARY KEY NOT NULL,
519
+ hash TEXT UNIQUE NOT NULL,
520
+ timestamp INTEGER NOT NULL,
521
+ score REAL NOT NULL DEFAULT 0.0 CHECK(score >= 0.0 AND score <= 1.0),
522
+ ttl INTEGER NOT NULL CHECK(ttl >= 0),
523
+ state TEXT NOT NULL CHECK(state IN ('silent', 'ready', 'active')),
524
+ accessCount INTEGER NOT NULL DEFAULT 0 CHECK(accessCount >= 0),
525
+ isBTSP INTEGER NOT NULL DEFAULT 0 CHECK(isBTSP IN (0, 1)),
526
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
527
+ );
528
+ `);
529
+ db.exec(`
530
+ CREATE TABLE IF NOT EXISTS entries_value (
531
+ id TEXT PRIMARY KEY NOT NULL,
532
+ content TEXT NOT NULL,
533
+ tags TEXT,
534
+ metadata TEXT,
535
+ FOREIGN KEY (id) REFERENCES entries_index(id) ON DELETE CASCADE
536
+ );
537
+ `);
538
+ db.exec(`
539
+ CREATE TABLE IF NOT EXISTS optimization_stats (
540
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
541
+ timestamp INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
542
+ tokens_before INTEGER NOT NULL,
543
+ tokens_after INTEGER NOT NULL,
544
+ entries_pruned INTEGER NOT NULL,
545
+ duration_ms INTEGER NOT NULL
546
+ );
547
+ `);
548
+ db.exec(`
549
+ CREATE INDEX IF NOT EXISTS idx_entries_state ON entries_index(state);
550
+ CREATE INDEX IF NOT EXISTS idx_entries_score ON entries_index(score DESC);
551
+ CREATE INDEX IF NOT EXISTS idx_entries_hash ON entries_index(hash);
552
+ CREATE INDEX IF NOT EXISTS idx_entries_timestamp ON entries_index(timestamp DESC);
553
+ CREATE INDEX IF NOT EXISTS idx_stats_timestamp ON optimization_stats(timestamp DESC);
554
+ `);
555
+ const putIndexStmt = db.prepare(`
556
+ INSERT OR REPLACE INTO entries_index
557
+ (id, hash, timestamp, score, ttl, state, accessCount, isBTSP)
558
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
559
+ `);
560
+ const putValueStmt = db.prepare(`
561
+ INSERT OR REPLACE INTO entries_value
562
+ (id, content, tags, metadata)
563
+ VALUES (?, ?, ?, ?)
564
+ `);
565
+ const getStmt = db.prepare(`
566
+ SELECT
567
+ i.id, i.hash, i.timestamp, i.score, i.ttl, i.state, i.accessCount, i.isBTSP,
568
+ v.content, v.tags, v.metadata
569
+ FROM entries_index i
570
+ JOIN entries_value v ON i.id = v.id
571
+ WHERE i.id = ?
572
+ `);
573
+ const deleteIndexStmt = db.prepare("DELETE FROM entries_index WHERE id = ?");
574
+ const deleteValueStmt = db.prepare("DELETE FROM entries_value WHERE id = ?");
575
+ return {
576
+ async put(entry) {
577
+ const transaction = db.transaction(() => {
578
+ putIndexStmt.run(
579
+ entry.id,
580
+ entry.hash,
581
+ entry.timestamp,
582
+ entry.score,
583
+ entry.ttl,
584
+ entry.state,
585
+ entry.accessCount,
586
+ entry.isBTSP ? 1 : 0
587
+ );
588
+ putValueStmt.run(
589
+ entry.id,
590
+ entry.content,
591
+ JSON.stringify(entry.tags),
592
+ JSON.stringify(entry.metadata)
593
+ );
594
+ });
595
+ transaction();
596
+ },
597
+ async get(id) {
598
+ const row = getStmt.get(id);
599
+ if (!row) {
600
+ return null;
601
+ }
602
+ const r = row;
603
+ return {
604
+ id: r.id,
605
+ content: r.content,
606
+ hash: r.hash,
607
+ timestamp: r.timestamp,
608
+ score: r.score,
609
+ ttl: r.ttl,
610
+ state: r.state,
611
+ accessCount: r.accessCount,
612
+ tags: r.tags ? JSON.parse(r.tags) : [],
613
+ metadata: r.metadata ? JSON.parse(r.metadata) : {},
614
+ isBTSP: r.isBTSP === 1
615
+ };
616
+ },
617
+ async query(filters) {
618
+ let sql = `
619
+ SELECT
620
+ i.id, i.hash, i.timestamp, i.score, i.ttl, i.state, i.accessCount, i.isBTSP,
621
+ v.content, v.tags, v.metadata
622
+ FROM entries_index i
623
+ JOIN entries_value v ON i.id = v.id
624
+ WHERE 1=1
625
+ `;
626
+ const params = [];
627
+ if (filters.state) {
628
+ sql += " AND i.state = ?";
629
+ params.push(filters.state);
630
+ }
631
+ if (filters.minScore !== void 0) {
632
+ sql += " AND i.score >= ?";
633
+ params.push(filters.minScore);
634
+ }
635
+ if (filters.maxScore !== void 0) {
636
+ sql += " AND i.score <= ?";
637
+ params.push(filters.maxScore);
638
+ }
639
+ if (filters.isBTSP !== void 0) {
640
+ sql += " AND i.isBTSP = ?";
641
+ params.push(filters.isBTSP ? 1 : 0);
642
+ }
643
+ sql += " ORDER BY i.score DESC";
644
+ if (filters.limit) {
645
+ sql += " LIMIT ?";
646
+ params.push(filters.limit);
647
+ }
648
+ if (filters.offset) {
649
+ sql += " OFFSET ?";
650
+ params.push(filters.offset);
651
+ }
652
+ const stmt = db.prepare(sql);
653
+ const rows = stmt.all(...params);
654
+ return rows.map((row) => {
655
+ const r = row;
656
+ return {
657
+ id: r.id,
658
+ content: r.content,
659
+ hash: r.hash,
660
+ timestamp: r.timestamp,
661
+ score: r.score,
662
+ ttl: r.ttl,
663
+ state: r.state,
664
+ accessCount: r.accessCount,
665
+ tags: r.tags ? JSON.parse(r.tags) : [],
666
+ metadata: r.metadata ? JSON.parse(r.metadata) : {},
667
+ isBTSP: r.isBTSP === 1
668
+ };
669
+ });
670
+ },
671
+ async delete(id) {
672
+ const transaction = db.transaction(() => {
673
+ deleteIndexStmt.run(id);
674
+ deleteValueStmt.run(id);
675
+ });
676
+ transaction();
677
+ },
678
+ async list() {
679
+ const stmt = db.prepare("SELECT id FROM entries_index");
680
+ const rows = stmt.all();
681
+ return rows.map((r) => r.id);
682
+ },
683
+ async compact() {
684
+ const before = db.prepare("SELECT COUNT(*) as count FROM entries_index").get();
685
+ db.exec("DELETE FROM entries_index WHERE ttl <= 0");
686
+ db.exec("VACUUM");
687
+ const after = db.prepare("SELECT COUNT(*) as count FROM entries_index").get();
688
+ return before.count - after.count;
689
+ },
690
+ async close() {
691
+ db.close();
692
+ },
693
+ async recordOptimization(stats) {
694
+ const stmt = db.prepare(`
695
+ INSERT INTO optimization_stats (timestamp, tokens_before, tokens_after, entries_pruned, duration_ms)
696
+ VALUES (?, ?, ?, ?, ?)
697
+ `);
698
+ stmt.run(
699
+ stats.timestamp,
700
+ stats.tokens_before,
701
+ stats.tokens_after,
702
+ stats.entries_pruned,
703
+ stats.duration_ms
704
+ );
705
+ },
706
+ async getOptimizationStats() {
707
+ const stmt = db.prepare(`
708
+ SELECT id, timestamp, tokens_before, tokens_after, entries_pruned, duration_ms
709
+ FROM optimization_stats
710
+ ORDER BY timestamp DESC
711
+ `);
712
+ const rows = stmt.all();
713
+ return rows;
714
+ },
715
+ async clearOptimizationStats() {
716
+ db.exec("DELETE FROM optimization_stats");
717
+ }
718
+ };
719
+ }
720
+
721
+ // src/core/sleep-compressor.ts
722
+ function createSleepCompressor() {
723
+ const scorer = createEngramScorer({ defaultTTL: 24, decayThreshold: 0.95 });
724
+ function consolidate(entries) {
725
+ const startTime = Date.now();
726
+ const originalCount = entries.length;
727
+ const now = Date.now();
728
+ const nonDecayed = entries.filter((entry) => {
729
+ const ageInSeconds = (now - entry.timestamp) / 1e3;
730
+ const decay = scorer.calculateDecay(ageInSeconds, entry.ttl);
731
+ return decay < 0.95;
732
+ });
733
+ const decayedRemoved = originalCount - nonDecayed.length;
734
+ const duplicateGroups = findDuplicates(nonDecayed);
735
+ const merged = mergeDuplicates(duplicateGroups);
736
+ const duplicateIds = new Set(duplicateGroups.flatMap((g) => g.entries.map((e) => e.id)));
737
+ const nonDuplicates = nonDecayed.filter((e) => !duplicateIds.has(e.id));
738
+ const kept = [...merged, ...nonDuplicates];
739
+ const removed = entries.filter((e) => !kept.some((k) => k.id === e.id));
740
+ const duplicatesRemoved = duplicateGroups.reduce((sum, g) => sum + (g.entries.length - 1), 0);
741
+ return {
742
+ kept,
743
+ removed,
744
+ entriesBefore: originalCount,
745
+ entriesAfter: kept.length,
746
+ decayedRemoved,
747
+ duplicatesRemoved,
748
+ compressionRatio: originalCount > 0 ? kept.length / originalCount : 0,
749
+ durationMs: Date.now() - startTime
750
+ };
751
+ }
752
+ function findDuplicates(entries) {
753
+ const groups = [];
754
+ const processed = /* @__PURE__ */ new Set();
755
+ for (let i = 0; i < entries.length; i++) {
756
+ const entry = entries[i];
757
+ if (!entry || processed.has(entry.id)) continue;
758
+ const duplicates = entries.filter((e, idx) => idx !== i && e.hash === entry.hash);
759
+ if (duplicates.length > 0) {
760
+ const group = {
761
+ entries: [entry, ...duplicates],
762
+ similarity: 1
763
+ // Exact match
764
+ };
765
+ groups.push(group);
766
+ processed.add(entry.id);
767
+ for (const dup of duplicates) {
768
+ processed.add(dup.id);
769
+ }
770
+ }
771
+ }
772
+ for (let i = 0; i < entries.length; i++) {
773
+ const entryI = entries[i];
774
+ if (!entryI || processed.has(entryI.id)) continue;
775
+ for (let j = i + 1; j < entries.length; j++) {
776
+ const entryJ = entries[j];
777
+ if (!entryJ || processed.has(entryJ.id)) continue;
778
+ const similarity = cosineSimilarity(entryI.content, entryJ.content);
779
+ if (similarity >= 0.85) {
780
+ const group = {
781
+ entries: [entryI, entryJ],
782
+ similarity
783
+ };
784
+ groups.push(group);
785
+ processed.add(entryI.id);
786
+ processed.add(entryJ.id);
787
+ break;
788
+ }
789
+ }
790
+ }
791
+ return groups;
792
+ }
793
+ function mergeDuplicates(groups) {
794
+ const merged = [];
795
+ for (const group of groups) {
796
+ const sorted = [...group.entries].sort((a, b) => b.score - a.score);
797
+ const best = sorted[0];
798
+ if (!best) continue;
799
+ const totalAccessCount = group.entries.reduce((sum, e) => sum + e.accessCount, 0);
800
+ const allTags = new Set(group.entries.flatMap((e) => e.tags));
801
+ merged.push({
802
+ ...best,
803
+ accessCount: totalAccessCount,
804
+ tags: Array.from(allTags)
805
+ });
806
+ }
807
+ return merged;
808
+ }
809
+ function cosineSimilarity(text1, text2) {
810
+ const words1 = tokenize(text1);
811
+ const words2 = tokenize(text2);
812
+ const vocab = /* @__PURE__ */ new Set([...words1, ...words2]);
813
+ const vec1 = {};
814
+ const vec2 = {};
815
+ for (const word of vocab) {
816
+ vec1[word] = words1.filter((w) => w === word).length;
817
+ vec2[word] = words2.filter((w) => w === word).length;
818
+ }
819
+ let dotProduct = 0;
820
+ let mag1 = 0;
821
+ let mag2 = 0;
822
+ for (const word of vocab) {
823
+ const count1 = vec1[word] ?? 0;
824
+ const count2 = vec2[word] ?? 0;
825
+ dotProduct += count1 * count2;
826
+ mag1 += count1 * count1;
827
+ mag2 += count2 * count2;
828
+ }
829
+ mag1 = Math.sqrt(mag1);
830
+ mag2 = Math.sqrt(mag2);
831
+ if (mag1 === 0 || mag2 === 0) return 0;
832
+ return dotProduct / (mag1 * mag2);
833
+ }
834
+ function tokenize(text) {
835
+ return text.toLowerCase().split(/\s+/).filter((word) => word.length > 0);
836
+ }
837
+ return {
838
+ consolidate,
839
+ findDuplicates,
840
+ mergeDuplicates
841
+ };
842
+ }
843
+
844
+ // src/types/config.ts
845
+ var DEFAULT_CONFIG = {
846
+ pruning: {
847
+ threshold: 5,
848
+ aggressiveness: 50
849
+ },
850
+ decay: {
851
+ defaultTTL: 24,
852
+ decayThreshold: 0.95
853
+ },
854
+ states: {
855
+ activeThreshold: 0.7,
856
+ readyThreshold: 0.3
857
+ },
858
+ agent: "generic",
859
+ ui: {
860
+ colors: true,
861
+ sounds: false,
862
+ verbose: false
863
+ },
864
+ autoConsolidate: null
865
+ };
866
+
867
+ // src/utils/logger.ts
868
+ function createLogger(verbose = false) {
869
+ return {
870
+ debug(message, ...args) {
871
+ if (verbose) {
872
+ console.debug(`[DEBUG] ${message}`, ...args);
873
+ }
874
+ },
875
+ info(message, ...args) {
876
+ console.info(`[INFO] ${message}`, ...args);
877
+ },
878
+ warn(message, ...args) {
879
+ console.warn(`[WARN] ${message}`, ...args);
880
+ },
881
+ error(message, ...args) {
882
+ console.error(`[ERROR] ${message}`, ...args);
883
+ }
884
+ };
885
+ }
886
+ export {
887
+ DEFAULT_CONFIG,
888
+ createBTSPEmbedder,
889
+ createClaudeCodeAdapter,
890
+ createConfidenceStates,
891
+ createEngramScorer,
892
+ createGenericAdapter,
893
+ createKVMemory,
894
+ createLogger,
895
+ createSleepCompressor,
896
+ createSparsePruner,
897
+ estimateTokens,
898
+ hashContent
899
+ };
900
+ //# sourceMappingURL=index.js.map