@ulrichc1/sparn 1.2.2 → 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 +3853 -484
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +3810 -457
- 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 +115 -266
- package/dist/hooks/post-tool-result.cjs.map +1 -1
- package/dist/hooks/post-tool-result.js +115 -266
- package/dist/hooks/post-tool-result.js.map +1 -1
- package/dist/hooks/pre-prompt.cjs +197 -268
- package/dist/hooks/pre-prompt.cjs.map +1 -1
- package/dist/hooks/pre-prompt.js +182 -268
- 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 +1754 -337
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +539 -40
- package/dist/index.d.ts +539 -40
- package/dist/index.js +1737 -329
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.cjs +304 -71
- package/dist/mcp/index.cjs.map +1 -1
- package/dist/mcp/index.js +308 -71
- package/dist/mcp/index.js.map +1 -1
- package/package.json +10 -3
package/dist/daemon/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/daemon/index.ts
|
|
2
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
2
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync3, readFileSync as readFileSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
3
3
|
|
|
4
4
|
// src/core/kv-memory.ts
|
|
5
5
|
import { copyFileSync, existsSync } from "fs";
|
|
@@ -23,6 +23,7 @@ async function createKVMemory(dbPath) {
|
|
|
23
23
|
const integrityCheck = db.pragma("quick_check", { simple: true });
|
|
24
24
|
if (integrityCheck !== "ok") {
|
|
25
25
|
console.error("\u26A0 Database corruption detected!");
|
|
26
|
+
db.close();
|
|
26
27
|
if (existsSync(dbPath)) {
|
|
27
28
|
const backupPath = createBackup(dbPath);
|
|
28
29
|
if (backupPath) {
|
|
@@ -30,7 +31,6 @@ async function createKVMemory(dbPath) {
|
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
33
|
console.log("Attempting database recovery...");
|
|
33
|
-
db.close();
|
|
34
34
|
db = new Database(dbPath);
|
|
35
35
|
}
|
|
36
36
|
} catch (error) {
|
|
@@ -42,6 +42,7 @@ async function createKVMemory(dbPath) {
|
|
|
42
42
|
db = new Database(dbPath);
|
|
43
43
|
}
|
|
44
44
|
db.pragma("journal_mode = WAL");
|
|
45
|
+
db.pragma("foreign_keys = ON");
|
|
45
46
|
db.exec(`
|
|
46
47
|
CREATE TABLE IF NOT EXISTS entries_index (
|
|
47
48
|
id TEXT PRIMARY KEY NOT NULL,
|
|
@@ -81,6 +82,36 @@ async function createKVMemory(dbPath) {
|
|
|
81
82
|
CREATE INDEX IF NOT EXISTS idx_entries_timestamp ON entries_index(timestamp DESC);
|
|
82
83
|
CREATE INDEX IF NOT EXISTS idx_stats_timestamp ON optimization_stats(timestamp DESC);
|
|
83
84
|
`);
|
|
85
|
+
db.exec(`
|
|
86
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entries_fts USING fts5(id, content, tokenize='porter');
|
|
87
|
+
`);
|
|
88
|
+
db.exec(`
|
|
89
|
+
CREATE TRIGGER IF NOT EXISTS entries_fts_insert
|
|
90
|
+
AFTER INSERT ON entries_value
|
|
91
|
+
BEGIN
|
|
92
|
+
INSERT OR REPLACE INTO entries_fts(id, content) VALUES (NEW.id, NEW.content);
|
|
93
|
+
END;
|
|
94
|
+
`);
|
|
95
|
+
db.exec(`
|
|
96
|
+
CREATE TRIGGER IF NOT EXISTS entries_fts_delete
|
|
97
|
+
AFTER DELETE ON entries_value
|
|
98
|
+
BEGIN
|
|
99
|
+
DELETE FROM entries_fts WHERE id = OLD.id;
|
|
100
|
+
END;
|
|
101
|
+
`);
|
|
102
|
+
db.exec(`
|
|
103
|
+
CREATE TRIGGER IF NOT EXISTS entries_fts_update
|
|
104
|
+
AFTER UPDATE ON entries_value
|
|
105
|
+
BEGIN
|
|
106
|
+
DELETE FROM entries_fts WHERE id = OLD.id;
|
|
107
|
+
INSERT INTO entries_fts(id, content) VALUES (NEW.id, NEW.content);
|
|
108
|
+
END;
|
|
109
|
+
`);
|
|
110
|
+
db.exec(`
|
|
111
|
+
INSERT OR IGNORE INTO entries_fts(id, content)
|
|
112
|
+
SELECT id, content FROM entries_value
|
|
113
|
+
WHERE id NOT IN (SELECT id FROM entries_fts);
|
|
114
|
+
`);
|
|
84
115
|
const putIndexStmt = db.prepare(`
|
|
85
116
|
INSERT OR REPLACE INTO entries_index
|
|
86
117
|
(id, hash, timestamp, score, ttl, state, accessCount, isBTSP)
|
|
@@ -169,14 +200,20 @@ async function createKVMemory(dbPath) {
|
|
|
169
200
|
sql += " AND i.isBTSP = ?";
|
|
170
201
|
params.push(filters.isBTSP ? 1 : 0);
|
|
171
202
|
}
|
|
203
|
+
if (filters.tags && filters.tags.length > 0) {
|
|
204
|
+
for (const tag of filters.tags) {
|
|
205
|
+
sql += " AND v.tags LIKE ?";
|
|
206
|
+
params.push(`%"${tag}"%`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
172
209
|
sql += " ORDER BY i.score DESC";
|
|
173
210
|
if (filters.limit) {
|
|
174
211
|
sql += " LIMIT ?";
|
|
175
212
|
params.push(filters.limit);
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
213
|
+
if (filters.offset) {
|
|
214
|
+
sql += " OFFSET ?";
|
|
215
|
+
params.push(filters.offset);
|
|
216
|
+
}
|
|
180
217
|
}
|
|
181
218
|
const stmt = db.prepare(sql);
|
|
182
219
|
const rows = stmt.all(...params);
|
|
@@ -211,7 +248,22 @@ async function createKVMemory(dbPath) {
|
|
|
211
248
|
},
|
|
212
249
|
async compact() {
|
|
213
250
|
const before = db.prepare("SELECT COUNT(*) as count FROM entries_index").get();
|
|
214
|
-
|
|
251
|
+
const now = Date.now();
|
|
252
|
+
db.prepare("DELETE FROM entries_index WHERE isBTSP = 0 AND (timestamp + ttl * 1000) < ?").run(
|
|
253
|
+
now
|
|
254
|
+
);
|
|
255
|
+
db.exec("DELETE FROM entries_index WHERE isBTSP = 0 AND ttl <= 0");
|
|
256
|
+
const candidates = db.prepare("SELECT id, timestamp, ttl FROM entries_index WHERE isBTSP = 0").all();
|
|
257
|
+
for (const row of candidates) {
|
|
258
|
+
const ageSeconds = Math.max(0, (now - row.timestamp) / 1e3);
|
|
259
|
+
const ttlSeconds = row.ttl;
|
|
260
|
+
if (ttlSeconds <= 0) continue;
|
|
261
|
+
const decay = 1 - Math.exp(-ageSeconds / ttlSeconds);
|
|
262
|
+
if (decay >= 0.95) {
|
|
263
|
+
db.prepare("DELETE FROM entries_index WHERE id = ?").run(row.id);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
db.exec("DELETE FROM entries_value WHERE id NOT IN (SELECT id FROM entries_index)");
|
|
215
267
|
db.exec("VACUUM");
|
|
216
268
|
const after = db.prepare("SELECT COUNT(*) as count FROM entries_index").get();
|
|
217
269
|
return before.count - after.count;
|
|
@@ -231,6 +283,9 @@ async function createKVMemory(dbPath) {
|
|
|
231
283
|
stats.entries_pruned,
|
|
232
284
|
stats.duration_ms
|
|
233
285
|
);
|
|
286
|
+
db.prepare(
|
|
287
|
+
"DELETE FROM optimization_stats WHERE id NOT IN (SELECT id FROM optimization_stats ORDER BY timestamp DESC LIMIT 1000)"
|
|
288
|
+
).run();
|
|
234
289
|
},
|
|
235
290
|
async getOptimizationStats() {
|
|
236
291
|
const stmt = db.prepare(`
|
|
@@ -243,16 +298,114 @@ async function createKVMemory(dbPath) {
|
|
|
243
298
|
},
|
|
244
299
|
async clearOptimizationStats() {
|
|
245
300
|
db.exec("DELETE FROM optimization_stats");
|
|
301
|
+
},
|
|
302
|
+
async searchFTS(query, limit = 10) {
|
|
303
|
+
if (!query || query.trim().length === 0) return [];
|
|
304
|
+
const sanitized = query.replace(/[{}()[\]"':*^~]/g, " ").trim();
|
|
305
|
+
if (sanitized.length === 0) return [];
|
|
306
|
+
const stmt = db.prepare(`
|
|
307
|
+
SELECT
|
|
308
|
+
f.id, f.content, rank,
|
|
309
|
+
i.hash, i.timestamp, i.score, i.ttl, i.state, i.accessCount, i.isBTSP,
|
|
310
|
+
v.tags, v.metadata
|
|
311
|
+
FROM entries_fts f
|
|
312
|
+
JOIN entries_index i ON f.id = i.id
|
|
313
|
+
JOIN entries_value v ON f.id = v.id
|
|
314
|
+
WHERE entries_fts MATCH ?
|
|
315
|
+
ORDER BY rank
|
|
316
|
+
LIMIT ?
|
|
317
|
+
`);
|
|
318
|
+
try {
|
|
319
|
+
const rows = stmt.all(sanitized, limit);
|
|
320
|
+
return rows.map((r) => ({
|
|
321
|
+
entry: {
|
|
322
|
+
id: r.id,
|
|
323
|
+
content: r.content,
|
|
324
|
+
hash: r.hash,
|
|
325
|
+
timestamp: r.timestamp,
|
|
326
|
+
score: r.score,
|
|
327
|
+
ttl: r.ttl,
|
|
328
|
+
state: r.state,
|
|
329
|
+
accessCount: r.accessCount,
|
|
330
|
+
tags: r.tags ? JSON.parse(r.tags) : [],
|
|
331
|
+
metadata: r.metadata ? JSON.parse(r.metadata) : {},
|
|
332
|
+
isBTSP: r.isBTSP === 1
|
|
333
|
+
},
|
|
334
|
+
rank: r.rank
|
|
335
|
+
}));
|
|
336
|
+
} catch {
|
|
337
|
+
return [];
|
|
338
|
+
}
|
|
246
339
|
}
|
|
247
340
|
};
|
|
248
341
|
}
|
|
249
342
|
|
|
343
|
+
// src/utils/tokenizer.ts
|
|
344
|
+
import { encode } from "gpt-tokenizer";
|
|
345
|
+
var usePrecise = false;
|
|
346
|
+
function setPreciseTokenCounting(enabled) {
|
|
347
|
+
usePrecise = enabled;
|
|
348
|
+
}
|
|
349
|
+
function estimateTokens(text) {
|
|
350
|
+
if (!text || text.length === 0) {
|
|
351
|
+
return 0;
|
|
352
|
+
}
|
|
353
|
+
if (usePrecise) {
|
|
354
|
+
return encode(text).length;
|
|
355
|
+
}
|
|
356
|
+
const words = text.split(/\s+/).filter((w) => w.length > 0);
|
|
357
|
+
const wordCount = words.length;
|
|
358
|
+
const charCount = text.length;
|
|
359
|
+
const charEstimate = Math.ceil(charCount / 4);
|
|
360
|
+
const wordEstimate = Math.ceil(wordCount * 0.75);
|
|
361
|
+
return Math.max(wordEstimate, charEstimate);
|
|
362
|
+
}
|
|
363
|
+
|
|
250
364
|
// src/daemon/consolidation-scheduler.ts
|
|
251
365
|
import { appendFileSync } from "fs";
|
|
252
366
|
|
|
367
|
+
// src/utils/tfidf.ts
|
|
368
|
+
function tokenize(text) {
|
|
369
|
+
return text.toLowerCase().split(/\s+/).filter((word) => word.length > 0);
|
|
370
|
+
}
|
|
371
|
+
function calculateTF(term, tokens) {
|
|
372
|
+
const count = tokens.filter((t) => t === term).length;
|
|
373
|
+
return Math.sqrt(count);
|
|
374
|
+
}
|
|
375
|
+
function createTFIDFIndex(entries) {
|
|
376
|
+
const documentFrequency = /* @__PURE__ */ new Map();
|
|
377
|
+
for (const entry of entries) {
|
|
378
|
+
const tokens = tokenize(entry.content);
|
|
379
|
+
const uniqueTerms = new Set(tokens);
|
|
380
|
+
for (const term of uniqueTerms) {
|
|
381
|
+
documentFrequency.set(term, (documentFrequency.get(term) || 0) + 1);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
documentFrequency,
|
|
386
|
+
totalDocuments: entries.length
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
function scoreTFIDF(entry, index) {
|
|
390
|
+
const tokens = tokenize(entry.content);
|
|
391
|
+
if (tokens.length === 0) return 0;
|
|
392
|
+
const uniqueTerms = new Set(tokens);
|
|
393
|
+
let totalScore = 0;
|
|
394
|
+
for (const term of uniqueTerms) {
|
|
395
|
+
const tf = calculateTF(term, tokens);
|
|
396
|
+
const docsWithTerm = index.documentFrequency.get(term) || 0;
|
|
397
|
+
if (docsWithTerm === 0) continue;
|
|
398
|
+
const idf = Math.log(index.totalDocuments / docsWithTerm);
|
|
399
|
+
totalScore += tf * idf;
|
|
400
|
+
}
|
|
401
|
+
return totalScore / tokens.length;
|
|
402
|
+
}
|
|
403
|
+
|
|
253
404
|
// src/core/engram-scorer.ts
|
|
254
405
|
function createEngramScorer(config2) {
|
|
255
406
|
const { defaultTTL } = config2;
|
|
407
|
+
const recencyWindowMs = (config2.recencyBoostMinutes ?? 30) * 60 * 1e3;
|
|
408
|
+
const recencyMultiplier = config2.recencyBoostMultiplier ?? 1.3;
|
|
256
409
|
function calculateDecay(ageInSeconds, ttlInSeconds) {
|
|
257
410
|
if (ttlInSeconds === 0) return 1;
|
|
258
411
|
if (ageInSeconds <= 0) return 0;
|
|
@@ -272,6 +425,13 @@ function createEngramScorer(config2) {
|
|
|
272
425
|
if (entry.isBTSP) {
|
|
273
426
|
score = Math.max(score, 0.9);
|
|
274
427
|
}
|
|
428
|
+
if (!entry.isBTSP && recencyWindowMs > 0) {
|
|
429
|
+
const ageMs = currentTime - entry.timestamp;
|
|
430
|
+
if (ageMs >= 0 && ageMs < recencyWindowMs) {
|
|
431
|
+
const boostFactor = 1 + (recencyMultiplier - 1) * (1 - ageMs / recencyWindowMs);
|
|
432
|
+
score = score * boostFactor;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
275
435
|
return Math.max(0, Math.min(1, score));
|
|
276
436
|
}
|
|
277
437
|
function refreshTTL(entry) {
|
|
@@ -380,13 +540,15 @@ function createSleepCompressor() {
|
|
|
380
540
|
function cosineSimilarity(text1, text2) {
|
|
381
541
|
const words1 = tokenize(text1);
|
|
382
542
|
const words2 = tokenize(text2);
|
|
383
|
-
const vocab = /* @__PURE__ */ new Set([...words1, ...words2]);
|
|
384
543
|
const vec1 = {};
|
|
385
544
|
const vec2 = {};
|
|
386
|
-
for (const word of
|
|
387
|
-
vec1[word] =
|
|
388
|
-
|
|
545
|
+
for (const word of words1) {
|
|
546
|
+
vec1[word] = (vec1[word] ?? 0) + 1;
|
|
547
|
+
}
|
|
548
|
+
for (const word of words2) {
|
|
549
|
+
vec2[word] = (vec2[word] ?? 0) + 1;
|
|
389
550
|
}
|
|
551
|
+
const vocab = /* @__PURE__ */ new Set([...words1, ...words2]);
|
|
390
552
|
let dotProduct = 0;
|
|
391
553
|
let mag1 = 0;
|
|
392
554
|
let mag2 = 0;
|
|
@@ -402,9 +564,6 @@ function createSleepCompressor() {
|
|
|
402
564
|
if (mag1 === 0 || mag2 === 0) return 0;
|
|
403
565
|
return dotProduct / (mag1 * mag2);
|
|
404
566
|
}
|
|
405
|
-
function tokenize(text) {
|
|
406
|
-
return text.toLowerCase().split(/\s+/).filter((word) => word.length > 0);
|
|
407
|
-
}
|
|
408
567
|
return {
|
|
409
568
|
consolidate,
|
|
410
569
|
findDuplicates,
|
|
@@ -476,11 +635,10 @@ function createMetricsCollector() {
|
|
|
476
635
|
...metric
|
|
477
636
|
};
|
|
478
637
|
}
|
|
479
|
-
function calculatePercentile(
|
|
480
|
-
if (
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
return sorted[index] || 0;
|
|
638
|
+
function calculatePercentile(sortedValues, percentile) {
|
|
639
|
+
if (sortedValues.length === 0) return 0;
|
|
640
|
+
const index = Math.ceil(percentile / 100 * sortedValues.length) - 1;
|
|
641
|
+
return sortedValues[index] || 0;
|
|
484
642
|
}
|
|
485
643
|
function getSnapshot() {
|
|
486
644
|
const totalRuns = optimizations.length;
|
|
@@ -491,7 +649,7 @@ function createMetricsCollector() {
|
|
|
491
649
|
);
|
|
492
650
|
const totalTokensBefore = optimizations.reduce((sum, m) => sum + m.tokensBefore, 0);
|
|
493
651
|
const averageReduction = totalTokensBefore > 0 ? totalTokensSaved / totalTokensBefore : 0;
|
|
494
|
-
const
|
|
652
|
+
const sortedDurations = optimizations.map((m) => m.duration).sort((a, b) => a - b);
|
|
495
653
|
const totalCacheQueries = cacheHits + cacheMisses;
|
|
496
654
|
const hitRate = totalCacheQueries > 0 ? cacheHits / totalCacheQueries : 0;
|
|
497
655
|
return {
|
|
@@ -501,9 +659,9 @@ function createMetricsCollector() {
|
|
|
501
659
|
totalDuration,
|
|
502
660
|
totalTokensSaved,
|
|
503
661
|
averageReduction,
|
|
504
|
-
p50Latency: calculatePercentile(
|
|
505
|
-
p95Latency: calculatePercentile(
|
|
506
|
-
p99Latency: calculatePercentile(
|
|
662
|
+
p50Latency: calculatePercentile(sortedDurations, 50),
|
|
663
|
+
p95Latency: calculatePercentile(sortedDurations, 95),
|
|
664
|
+
p99Latency: calculatePercentile(sortedDurations, 99)
|
|
507
665
|
},
|
|
508
666
|
cache: {
|
|
509
667
|
hitRate,
|
|
@@ -559,6 +717,7 @@ function createConsolidationScheduler(options) {
|
|
|
559
717
|
let lastRun = null;
|
|
560
718
|
let lastResult = null;
|
|
561
719
|
let nextRun = null;
|
|
720
|
+
let isRunning = false;
|
|
562
721
|
function log2(message) {
|
|
563
722
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
564
723
|
const logMessage = `[${timestamp}] [Consolidation] ${message}
|
|
@@ -572,6 +731,11 @@ function createConsolidationScheduler(options) {
|
|
|
572
731
|
}
|
|
573
732
|
}
|
|
574
733
|
async function runConsolidation() {
|
|
734
|
+
if (isRunning) {
|
|
735
|
+
log2("Consolidation already in progress, skipping");
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
isRunning = true;
|
|
575
739
|
const startTime = Date.now();
|
|
576
740
|
log2("Starting scheduled consolidation");
|
|
577
741
|
try {
|
|
@@ -624,6 +788,7 @@ function createConsolidationScheduler(options) {
|
|
|
624
788
|
if (intervalHours !== null && intervalHours > 0) {
|
|
625
789
|
nextRun = Date.now() + intervalHours * 60 * 60 * 1e3;
|
|
626
790
|
}
|
|
791
|
+
isRunning = false;
|
|
627
792
|
}
|
|
628
793
|
function start() {
|
|
629
794
|
if (intervalHours === null || intervalHours <= 0) {
|
|
@@ -669,7 +834,15 @@ function createConsolidationScheduler(options) {
|
|
|
669
834
|
}
|
|
670
835
|
|
|
671
836
|
// src/daemon/session-watcher.ts
|
|
672
|
-
import {
|
|
837
|
+
import {
|
|
838
|
+
existsSync as existsSync2,
|
|
839
|
+
mkdirSync,
|
|
840
|
+
readdirSync,
|
|
841
|
+
readFileSync,
|
|
842
|
+
statSync as statSync2,
|
|
843
|
+
watch,
|
|
844
|
+
writeFileSync
|
|
845
|
+
} from "fs";
|
|
673
846
|
import { homedir } from "os";
|
|
674
847
|
import { dirname, join } from "path";
|
|
675
848
|
|
|
@@ -684,6 +857,11 @@ function hashContent(content) {
|
|
|
684
857
|
|
|
685
858
|
// src/utils/context-parser.ts
|
|
686
859
|
function parseClaudeCodeContext(context) {
|
|
860
|
+
const firstNonEmpty = context.split("\n").find((line) => line.trim().length > 0);
|
|
861
|
+
if (firstNonEmpty?.trim().startsWith("{")) {
|
|
862
|
+
const jsonlEntries = parseJSONLContext(context);
|
|
863
|
+
if (jsonlEntries.length > 0) return jsonlEntries;
|
|
864
|
+
}
|
|
687
865
|
const entries = [];
|
|
688
866
|
const now = Date.now();
|
|
689
867
|
const lines = context.split("\n");
|
|
@@ -736,7 +914,7 @@ function createEntry(content, type, baseTime) {
|
|
|
736
914
|
hash: hashContent(content),
|
|
737
915
|
timestamp: baseTime,
|
|
738
916
|
score: initialScore,
|
|
739
|
-
state: initialScore
|
|
917
|
+
state: initialScore >= 0.7 ? "active" : initialScore >= 0.3 ? "ready" : "silent",
|
|
740
918
|
ttl: 24 * 3600,
|
|
741
919
|
// 24 hours default
|
|
742
920
|
accessCount: 0,
|
|
@@ -745,52 +923,63 @@ function createEntry(content, type, baseTime) {
|
|
|
745
923
|
isBTSP: false
|
|
746
924
|
};
|
|
747
925
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
return
|
|
926
|
+
function parseJSONLLine(line) {
|
|
927
|
+
const trimmed = line.trim();
|
|
928
|
+
if (trimmed.length === 0) return null;
|
|
929
|
+
try {
|
|
930
|
+
return JSON.parse(trimmed);
|
|
931
|
+
} catch {
|
|
932
|
+
return null;
|
|
753
933
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
934
|
+
}
|
|
935
|
+
function extractContent(content) {
|
|
936
|
+
if (typeof content === "string") return content;
|
|
937
|
+
if (Array.isArray(content)) {
|
|
938
|
+
return content.map((block) => {
|
|
939
|
+
if (block.type === "text" && block.text) return block.text;
|
|
940
|
+
if (block.type === "tool_use" && block.name) return `[tool_use: ${block.name}]`;
|
|
941
|
+
if (block.type === "tool_result") {
|
|
942
|
+
if (typeof block.content === "string") return block.content;
|
|
943
|
+
if (Array.isArray(block.content)) {
|
|
944
|
+
return block.content.filter((c) => c.type === "text" && c.text).map((c) => c.text).join("\n");
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
return "";
|
|
948
|
+
}).filter((s) => s.length > 0).join("\n");
|
|
949
|
+
}
|
|
950
|
+
return "";
|
|
951
|
+
}
|
|
952
|
+
function classifyJSONLMessage(msg) {
|
|
953
|
+
if (Array.isArray(msg.content)) {
|
|
954
|
+
const hasToolUse = msg.content.some((b) => b.type === "tool_use");
|
|
955
|
+
const hasToolResult = msg.content.some((b) => b.type === "tool_result");
|
|
956
|
+
if (hasToolUse) return "tool";
|
|
957
|
+
if (hasToolResult) return "result";
|
|
958
|
+
}
|
|
959
|
+
if (msg.type === "tool_use" || msg.tool_use) return "tool";
|
|
960
|
+
if (msg.type === "tool_result" || msg.tool_result) return "result";
|
|
961
|
+
if (msg.role === "user" || msg.role === "assistant") return "conversation";
|
|
962
|
+
return "other";
|
|
963
|
+
}
|
|
964
|
+
function parseJSONLContext(context) {
|
|
965
|
+
const entries = [];
|
|
966
|
+
const now = Date.now();
|
|
967
|
+
const lines = context.split("\n");
|
|
968
|
+
for (const line of lines) {
|
|
969
|
+
const msg = parseJSONLLine(line);
|
|
970
|
+
if (!msg) continue;
|
|
971
|
+
const content = extractContent(msg.content);
|
|
972
|
+
if (!content || content.trim().length === 0) continue;
|
|
973
|
+
const blockType = classifyJSONLMessage(msg);
|
|
974
|
+
entries.push(createEntry(content, blockType, now));
|
|
975
|
+
}
|
|
976
|
+
return entries;
|
|
760
977
|
}
|
|
761
978
|
|
|
762
979
|
// src/core/budget-pruner.ts
|
|
763
980
|
function createBudgetPruner(config2) {
|
|
764
981
|
const { tokenBudget, decay } = config2;
|
|
765
982
|
const engramScorer = createEngramScorer(decay);
|
|
766
|
-
function tokenize(text) {
|
|
767
|
-
return text.toLowerCase().split(/\s+/).filter((word) => word.length > 0);
|
|
768
|
-
}
|
|
769
|
-
function calculateTF(term, tokens) {
|
|
770
|
-
const count = tokens.filter((t) => t === term).length;
|
|
771
|
-
return Math.sqrt(count);
|
|
772
|
-
}
|
|
773
|
-
function calculateIDF(term, allEntries) {
|
|
774
|
-
const totalDocs = allEntries.length;
|
|
775
|
-
const docsWithTerm = allEntries.filter((entry) => {
|
|
776
|
-
const tokens = tokenize(entry.content);
|
|
777
|
-
return tokens.includes(term);
|
|
778
|
-
}).length;
|
|
779
|
-
if (docsWithTerm === 0) return 0;
|
|
780
|
-
return Math.log(totalDocs / docsWithTerm);
|
|
781
|
-
}
|
|
782
|
-
function calculateTFIDF(entry, allEntries) {
|
|
783
|
-
const tokens = tokenize(entry.content);
|
|
784
|
-
if (tokens.length === 0) return 0;
|
|
785
|
-
const uniqueTerms = [...new Set(tokens)];
|
|
786
|
-
let totalScore = 0;
|
|
787
|
-
for (const term of uniqueTerms) {
|
|
788
|
-
const tf = calculateTF(term, tokens);
|
|
789
|
-
const idf = calculateIDF(term, allEntries);
|
|
790
|
-
totalScore += tf * idf;
|
|
791
|
-
}
|
|
792
|
-
return totalScore / tokens.length;
|
|
793
|
-
}
|
|
794
983
|
function getStateMultiplier(entry) {
|
|
795
984
|
if (entry.isBTSP) return 2;
|
|
796
985
|
switch (entry.state) {
|
|
@@ -804,8 +993,8 @@ function createBudgetPruner(config2) {
|
|
|
804
993
|
return 1;
|
|
805
994
|
}
|
|
806
995
|
}
|
|
807
|
-
function priorityScore(entry, allEntries) {
|
|
808
|
-
const tfidf =
|
|
996
|
+
function priorityScore(entry, allEntries, index) {
|
|
997
|
+
const tfidf = index ? scoreTFIDF(entry, index) : scoreTFIDF(entry, createTFIDFIndex(allEntries));
|
|
809
998
|
const currentScore = engramScorer.calculateScore(entry);
|
|
810
999
|
const engramDecay = 1 - currentScore;
|
|
811
1000
|
const stateMultiplier = getStateMultiplier(entry);
|
|
@@ -824,15 +1013,33 @@ function createBudgetPruner(config2) {
|
|
|
824
1013
|
const originalTokens = entries.reduce((sum, e) => sum + estimateTokens(e.content), 0);
|
|
825
1014
|
const btspEntries = entries.filter((e) => e.isBTSP);
|
|
826
1015
|
const regularEntries = entries.filter((e) => !e.isBTSP);
|
|
827
|
-
|
|
1016
|
+
let includedBtsp = [];
|
|
1017
|
+
let btspTokens = 0;
|
|
1018
|
+
const sortedBtsp = [...btspEntries].sort((a, b) => b.timestamp - a.timestamp);
|
|
1019
|
+
for (const entry of sortedBtsp) {
|
|
1020
|
+
const tokens = estimateTokens(entry.content);
|
|
1021
|
+
if (btspTokens + tokens <= budget * 0.8) {
|
|
1022
|
+
includedBtsp.push(entry);
|
|
1023
|
+
btspTokens += tokens;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
if (includedBtsp.length === 0 && sortedBtsp.length > 0) {
|
|
1027
|
+
const firstBtsp = sortedBtsp[0];
|
|
1028
|
+
if (firstBtsp) {
|
|
1029
|
+
includedBtsp = [firstBtsp];
|
|
1030
|
+
btspTokens = estimateTokens(firstBtsp.content);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
const excludedBtsp = btspEntries.filter((e) => !includedBtsp.includes(e));
|
|
1034
|
+
const tfidfIndex = createTFIDFIndex(entries);
|
|
828
1035
|
const scored = regularEntries.map((entry) => ({
|
|
829
1036
|
entry,
|
|
830
|
-
score: priorityScore(entry, entries),
|
|
1037
|
+
score: priorityScore(entry, entries, tfidfIndex),
|
|
831
1038
|
tokens: estimateTokens(entry.content)
|
|
832
1039
|
}));
|
|
833
1040
|
scored.sort((a, b) => b.score - a.score);
|
|
834
|
-
const kept = [...
|
|
835
|
-
const removed = [];
|
|
1041
|
+
const kept = [...includedBtsp];
|
|
1042
|
+
const removed = [...excludedBtsp];
|
|
836
1043
|
let currentTokens = btspTokens;
|
|
837
1044
|
for (const item of scored) {
|
|
838
1045
|
if (currentTokens + item.tokens <= budget) {
|
|
@@ -868,9 +1075,6 @@ function createIncrementalOptimizer(config2) {
|
|
|
868
1075
|
updateCount: 0,
|
|
869
1076
|
lastFullOptimization: Date.now()
|
|
870
1077
|
};
|
|
871
|
-
function tokenize(text) {
|
|
872
|
-
return text.toLowerCase().split(/\s+/).filter((word) => word.length > 0);
|
|
873
|
-
}
|
|
874
1078
|
function updateDocumentFrequency(entries, remove = false) {
|
|
875
1079
|
for (const entry of entries) {
|
|
876
1080
|
const tokens = tokenize(entry.content);
|
|
@@ -893,7 +1097,19 @@ function createIncrementalOptimizer(config2) {
|
|
|
893
1097
|
if (!cached) return null;
|
|
894
1098
|
return cached.entry;
|
|
895
1099
|
}
|
|
1100
|
+
const MAX_CACHE_SIZE = 1e4;
|
|
896
1101
|
function cacheEntry(entry, score) {
|
|
1102
|
+
if (state.entryCache.size >= MAX_CACHE_SIZE) {
|
|
1103
|
+
const entries = Array.from(state.entryCache.entries());
|
|
1104
|
+
entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
|
|
1105
|
+
const toRemove = Math.floor(MAX_CACHE_SIZE * 0.2);
|
|
1106
|
+
for (let i = 0; i < toRemove && i < entries.length; i++) {
|
|
1107
|
+
const entry2 = entries[i];
|
|
1108
|
+
if (entry2) {
|
|
1109
|
+
state.entryCache.delete(entry2[0]);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
897
1113
|
state.entryCache.set(entry.hash, {
|
|
898
1114
|
entry,
|
|
899
1115
|
score,
|
|
@@ -1020,13 +1236,40 @@ function createIncrementalOptimizer(config2) {
|
|
|
1020
1236
|
lastFullOptimization: state.lastFullOptimization
|
|
1021
1237
|
};
|
|
1022
1238
|
}
|
|
1239
|
+
function serializeState() {
|
|
1240
|
+
const s = getState();
|
|
1241
|
+
return JSON.stringify({
|
|
1242
|
+
entryCache: Array.from(s.entryCache.entries()),
|
|
1243
|
+
documentFrequency: Array.from(s.documentFrequency.entries()),
|
|
1244
|
+
totalDocuments: s.totalDocuments,
|
|
1245
|
+
updateCount: s.updateCount,
|
|
1246
|
+
lastFullOptimization: s.lastFullOptimization
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
function deserializeState(json) {
|
|
1250
|
+
try {
|
|
1251
|
+
const parsed = JSON.parse(json);
|
|
1252
|
+
restoreState({
|
|
1253
|
+
entryCache: new Map(parsed.entryCache),
|
|
1254
|
+
documentFrequency: new Map(parsed.documentFrequency),
|
|
1255
|
+
totalDocuments: parsed.totalDocuments,
|
|
1256
|
+
updateCount: parsed.updateCount,
|
|
1257
|
+
lastFullOptimization: parsed.lastFullOptimization
|
|
1258
|
+
});
|
|
1259
|
+
return true;
|
|
1260
|
+
} catch {
|
|
1261
|
+
return false;
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1023
1264
|
return {
|
|
1024
1265
|
optimizeIncremental,
|
|
1025
1266
|
optimizeFull,
|
|
1026
1267
|
getState,
|
|
1027
1268
|
restoreState,
|
|
1028
1269
|
reset,
|
|
1029
|
-
getStats
|
|
1270
|
+
getStats,
|
|
1271
|
+
serializeState,
|
|
1272
|
+
deserializeState
|
|
1030
1273
|
};
|
|
1031
1274
|
}
|
|
1032
1275
|
|
|
@@ -1051,9 +1294,22 @@ function createContextPipeline(config2) {
|
|
|
1051
1294
|
currentEntries = result.kept;
|
|
1052
1295
|
budgetUtilization = result.budgetUtilization;
|
|
1053
1296
|
if (currentEntries.length > windowSize) {
|
|
1054
|
-
const
|
|
1055
|
-
const
|
|
1056
|
-
const
|
|
1297
|
+
const timestamps = currentEntries.map((e) => e.timestamp);
|
|
1298
|
+
const minTs = Math.min(...timestamps);
|
|
1299
|
+
const maxTs = Math.max(...timestamps);
|
|
1300
|
+
const tsRange = maxTs - minTs || 1;
|
|
1301
|
+
const scored = currentEntries.map((entry) => {
|
|
1302
|
+
if (entry.isBTSP) return { entry, hybridScore: 2 };
|
|
1303
|
+
const ageNormalized = (entry.timestamp - minTs) / tsRange;
|
|
1304
|
+
const hybridScore = ageNormalized * 0.4 + entry.score * 0.6;
|
|
1305
|
+
return { entry, hybridScore };
|
|
1306
|
+
});
|
|
1307
|
+
scored.sort((a, b) => {
|
|
1308
|
+
if (b.hybridScore !== a.hybridScore) return b.hybridScore - a.hybridScore;
|
|
1309
|
+
return b.entry.timestamp - a.entry.timestamp;
|
|
1310
|
+
});
|
|
1311
|
+
const toKeep = scored.slice(0, windowSize).map((s) => s.entry);
|
|
1312
|
+
const toRemove = scored.slice(windowSize);
|
|
1057
1313
|
currentEntries = toKeep;
|
|
1058
1314
|
evictedEntries += toRemove.length;
|
|
1059
1315
|
}
|
|
@@ -1089,7 +1345,15 @@ function createContextPipeline(config2) {
|
|
|
1089
1345
|
budgetUtilization = 0;
|
|
1090
1346
|
optimizer.reset();
|
|
1091
1347
|
}
|
|
1348
|
+
function serializeOptimizerState() {
|
|
1349
|
+
return optimizer.serializeState();
|
|
1350
|
+
}
|
|
1351
|
+
function deserializeOptimizerState(json) {
|
|
1352
|
+
return optimizer.deserializeState(json);
|
|
1353
|
+
}
|
|
1092
1354
|
return {
|
|
1355
|
+
serializeOptimizerState,
|
|
1356
|
+
deserializeOptimizerState,
|
|
1093
1357
|
ingest,
|
|
1094
1358
|
getContext,
|
|
1095
1359
|
getEntries,
|
|
@@ -1099,7 +1363,7 @@ function createContextPipeline(config2) {
|
|
|
1099
1363
|
}
|
|
1100
1364
|
|
|
1101
1365
|
// src/daemon/file-tracker.ts
|
|
1102
|
-
import {
|
|
1366
|
+
import { closeSync, openSync, readSync, statSync } from "fs";
|
|
1103
1367
|
function createFileTracker() {
|
|
1104
1368
|
const positions = /* @__PURE__ */ new Map();
|
|
1105
1369
|
function readNewLines(filePath) {
|
|
@@ -1125,9 +1389,14 @@ function createFileTracker() {
|
|
|
1125
1389
|
}
|
|
1126
1390
|
return [];
|
|
1127
1391
|
}
|
|
1128
|
-
const
|
|
1129
|
-
const
|
|
1130
|
-
fd
|
|
1392
|
+
const bytesToRead = currentSize - pos.position;
|
|
1393
|
+
const buffer = Buffer.alloc(bytesToRead);
|
|
1394
|
+
const fd = openSync(filePath, "r");
|
|
1395
|
+
try {
|
|
1396
|
+
readSync(fd, buffer, 0, bytesToRead, pos.position);
|
|
1397
|
+
} finally {
|
|
1398
|
+
closeSync(fd);
|
|
1399
|
+
}
|
|
1131
1400
|
const newContent = (pos.partialLine + buffer.toString("utf-8")).split("\n");
|
|
1132
1401
|
const partialLine = newContent.pop() || "";
|
|
1133
1402
|
pos.position = currentSize;
|
|
@@ -1186,6 +1455,7 @@ function createSessionWatcher(config2) {
|
|
|
1186
1455
|
fullOptimizationInterval: 50
|
|
1187
1456
|
// Full re-optimization every 50 incremental updates
|
|
1188
1457
|
});
|
|
1458
|
+
loadState(sessionId, pipeline);
|
|
1189
1459
|
pipelines.set(sessionId, pipeline);
|
|
1190
1460
|
}
|
|
1191
1461
|
return pipeline;
|
|
@@ -1236,7 +1506,7 @@ function createSessionWatcher(config2) {
|
|
|
1236
1506
|
} else if (entry.endsWith(".jsonl")) {
|
|
1237
1507
|
const matches = realtime.watchPatterns.some((pattern) => {
|
|
1238
1508
|
const regex = new RegExp(
|
|
1239
|
-
pattern.replace(
|
|
1509
|
+
pattern.replace(/\./g, "\\.").replace(/\*\*/g, ".*").replace(/(?<!\.)(\*)/g, "[^/\\\\]*")
|
|
1240
1510
|
);
|
|
1241
1511
|
return regex.test(fullPath);
|
|
1242
1512
|
});
|
|
@@ -1265,34 +1535,69 @@ function createSessionWatcher(config2) {
|
|
|
1265
1535
|
async function start() {
|
|
1266
1536
|
const projectsDir = getProjectsDir();
|
|
1267
1537
|
const jsonlFiles = findJsonlFiles(projectsDir);
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1538
|
+
try {
|
|
1539
|
+
const projectsWatcher = watch(projectsDir, { recursive: true }, (_eventType, filename) => {
|
|
1540
|
+
if (filename?.endsWith(".jsonl")) {
|
|
1541
|
+
const fullPath = join(projectsDir, filename);
|
|
1542
|
+
handleFileChange(fullPath);
|
|
1543
|
+
}
|
|
1544
|
+
});
|
|
1545
|
+
watchers.push(projectsWatcher);
|
|
1546
|
+
} catch {
|
|
1547
|
+
const watchedDirs = /* @__PURE__ */ new Set();
|
|
1548
|
+
for (const file of jsonlFiles) {
|
|
1549
|
+
const dir = dirname(file);
|
|
1550
|
+
if (!watchedDirs.has(dir)) {
|
|
1551
|
+
const watcher2 = watch(dir, { recursive: false }, (_eventType, filename) => {
|
|
1552
|
+
if (filename?.endsWith(".jsonl")) {
|
|
1553
|
+
const fullPath = join(dir, filename);
|
|
1554
|
+
handleFileChange(fullPath);
|
|
1555
|
+
}
|
|
1556
|
+
});
|
|
1557
|
+
watchers.push(watcher2);
|
|
1558
|
+
watchedDirs.add(dir);
|
|
1559
|
+
}
|
|
1280
1560
|
}
|
|
1281
1561
|
}
|
|
1282
|
-
const projectsWatcher = watch(projectsDir, { recursive: true }, (_eventType, filename) => {
|
|
1283
|
-
if (filename?.endsWith(".jsonl")) {
|
|
1284
|
-
const fullPath = join(projectsDir, filename);
|
|
1285
|
-
handleFileChange(fullPath);
|
|
1286
|
-
}
|
|
1287
|
-
});
|
|
1288
|
-
watchers.push(projectsWatcher);
|
|
1289
1562
|
getMetrics().updateDaemon({
|
|
1290
1563
|
startTime: Date.now(),
|
|
1291
1564
|
sessionsWatched: jsonlFiles.length,
|
|
1292
1565
|
memoryUsage: process.memoryUsage().heapUsed
|
|
1293
1566
|
});
|
|
1294
1567
|
}
|
|
1568
|
+
function getStatePath() {
|
|
1569
|
+
return join(homedir(), ".sparn", "optimizer-state.json");
|
|
1570
|
+
}
|
|
1571
|
+
function saveState() {
|
|
1572
|
+
try {
|
|
1573
|
+
const stateMap = {};
|
|
1574
|
+
for (const [sessionId, pipeline] of pipelines.entries()) {
|
|
1575
|
+
stateMap[sessionId] = pipeline.serializeOptimizerState();
|
|
1576
|
+
}
|
|
1577
|
+
const statePath = getStatePath();
|
|
1578
|
+
const dir = dirname(statePath);
|
|
1579
|
+
if (!existsSync2(dir)) {
|
|
1580
|
+
mkdirSync(dir, { recursive: true });
|
|
1581
|
+
}
|
|
1582
|
+
writeFileSync(statePath, JSON.stringify(stateMap), "utf-8");
|
|
1583
|
+
} catch {
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
function loadState(sessionId, pipeline) {
|
|
1587
|
+
try {
|
|
1588
|
+
const statePath = getStatePath();
|
|
1589
|
+
if (!existsSync2(statePath)) return;
|
|
1590
|
+
const raw = readFileSync(statePath, "utf-8");
|
|
1591
|
+
const stateMap = JSON.parse(raw);
|
|
1592
|
+
const sessionState = stateMap[sessionId];
|
|
1593
|
+
if (sessionState) {
|
|
1594
|
+
pipeline.deserializeOptimizerState(sessionState);
|
|
1595
|
+
}
|
|
1596
|
+
} catch {
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1295
1599
|
function stop() {
|
|
1600
|
+
saveState();
|
|
1296
1601
|
for (const watcher2 of watchers) {
|
|
1297
1602
|
watcher2.close();
|
|
1298
1603
|
}
|
|
@@ -1340,11 +1645,22 @@ function createSessionWatcher(config2) {
|
|
|
1340
1645
|
var configJson = process.env["SPARN_CONFIG"];
|
|
1341
1646
|
var pidFile = process.env["SPARN_PID_FILE"];
|
|
1342
1647
|
var logFile = process.env["SPARN_LOG_FILE"];
|
|
1648
|
+
var configFilePath = process.env["SPARN_CONFIG_FILE"];
|
|
1649
|
+
if ((!configJson || !pidFile || !logFile) && configFilePath && existsSync3(configFilePath)) {
|
|
1650
|
+
const fileConfig = JSON.parse(readFileSync2(configFilePath, "utf-8"));
|
|
1651
|
+
configJson = configJson || JSON.stringify(fileConfig.config);
|
|
1652
|
+
pidFile = pidFile || fileConfig.pidFile;
|
|
1653
|
+
logFile = logFile || fileConfig.logFile;
|
|
1654
|
+
}
|
|
1343
1655
|
if (!configJson || !pidFile || !logFile) {
|
|
1344
1656
|
console.error("Daemon: Missing required environment variables");
|
|
1345
1657
|
process.exit(1);
|
|
1346
1658
|
}
|
|
1347
1659
|
var config = JSON.parse(configJson);
|
|
1660
|
+
if (config.realtime?.preciseTokenCounting) {
|
|
1661
|
+
setPreciseTokenCounting(true);
|
|
1662
|
+
}
|
|
1663
|
+
writeFileSync2(pidFile, String(process.pid), "utf-8");
|
|
1348
1664
|
function log(message) {
|
|
1349
1665
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1350
1666
|
const logMessage = `[${timestamp}] ${message}
|
|
@@ -1358,7 +1674,7 @@ function log(message) {
|
|
|
1358
1674
|
}
|
|
1359
1675
|
function cleanup() {
|
|
1360
1676
|
log("Daemon shutting down");
|
|
1361
|
-
if (pidFile &&
|
|
1677
|
+
if (pidFile && existsSync3(pidFile)) {
|
|
1362
1678
|
try {
|
|
1363
1679
|
unlinkSync(pidFile);
|
|
1364
1680
|
} catch {
|
|
@@ -1423,5 +1739,9 @@ watcher.start().then(async () => {
|
|
|
1423
1739
|
cleanup();
|
|
1424
1740
|
});
|
|
1425
1741
|
setInterval(() => {
|
|
1426
|
-
},
|
|
1742
|
+
}, 3e4);
|
|
1743
|
+
try {
|
|
1744
|
+
process.stdin.resume();
|
|
1745
|
+
} catch {
|
|
1746
|
+
}
|
|
1427
1747
|
//# sourceMappingURL=index.js.map
|