@ekkos/mcp-server 2.0.3 → 2.0.4

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.
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Local Pattern Cache for ekkOS MCP Server
3
+ *
4
+ * Provides <2ms local-first search via SQLite FTS5 (with BM25 relevance ranking)
5
+ * with automatic fallback to the original JSON + in-memory Map approach when
6
+ * better-sqlite3 is unavailable (e.g. missing native build tools).
7
+ *
8
+ * SQLite backend (preferred):
9
+ * - DB at ~/.ekkos/cache/patterns.db
10
+ * - patterns table + patterns_fts virtual table (FTS5, BM25 ranking)
11
+ * - directives table
12
+ *
13
+ * JSON fallback (when SQLite unavailable):
14
+ * - Disk file at ~/.ekkos/cache/patterns.json
15
+ * - In-memory Map + array for sub-millisecond reads
16
+ *
17
+ * Public API is identical between both backends — callers in index.ts need
18
+ * no changes.
19
+ */
20
+ export interface CachedPattern {
21
+ id: string;
22
+ title: string;
23
+ similarity?: number;
24
+ success_rate: number | null;
25
+ applied_count: number;
26
+ tags?: string[];
27
+ content?: string;
28
+ updated_at?: string;
29
+ }
30
+ export interface CachedDirective {
31
+ id: string;
32
+ type: string;
33
+ rule: string;
34
+ context?: string;
35
+ }
36
+ /**
37
+ * Initialise cache from persistent storage on startup.
38
+ * SQLite: the DB file is already open — just read counts and restore warmed_at.
39
+ * JSON fallback: deserialise patterns.json into the in-memory Map.
40
+ */
41
+ export declare function loadCacheFromDisk(): {
42
+ patterns: number;
43
+ directives: number;
44
+ };
45
+ /**
46
+ * Warm cache from the /api/v1/warmup bundle endpoint (non-blocking).
47
+ *
48
+ * A single HTTP GET replaces the previous multi-step ekkOS_Search approach,
49
+ * returning top patterns + directives + project patterns in one response.
50
+ *
51
+ * With SQLite backend, data is written into patterns / directives tables and
52
+ * FTS index is updated automatically via triggers.
53
+ *
54
+ * With JSON fallback, data is written to the in-memory Map and then flushed
55
+ * to disk as patterns.json.
56
+ */
57
+ export declare function warmCache(_callGateway: (endpoint: string, method: string, params?: any) => Promise<any>, userId: string, options?: {
58
+ apiKey?: string;
59
+ baseUrl?: string;
60
+ projectId?: string;
61
+ }): Promise<void>;
62
+ /**
63
+ * Search local cache by query string.
64
+ *
65
+ * SQLite backend: FTS5 MATCH with bm25() ranking — proper relevance scoring,
66
+ * sub-2ms on typical pattern sets.
67
+ *
68
+ * JSON fallback: simple term-frequency scoring on title + tags + content.
69
+ */
70
+ export declare function searchLocal(query: string, limit?: number): CachedPattern[];
71
+ /**
72
+ * Return all cached active directives.
73
+ */
74
+ export declare function getDirectives(): CachedDirective[];
75
+ export declare function isCacheStale(): boolean;
76
+ export declare function hasCacheData(): boolean;
77
+ /**
78
+ * Mark cache as stale — next search will trigger a background re-warm.
79
+ * Does NOT delete SQLite data (that would remove offline fallback data).
80
+ */
81
+ export declare function invalidateCache(): void;
82
+ export declare function startPeriodicRefresh(callGateway: (endpoint: string, method: string, params?: any) => Promise<any>, userId: string): void;
83
+ export declare function getCacheStats(): {
84
+ patterns: number;
85
+ directives: number;
86
+ age_ms: number;
87
+ stale: boolean;
88
+ backend: 'sqlite' | 'json';
89
+ };
90
+ //# sourceMappingURL=local-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-cache.d.ts","sourceRoot":"","sources":["../src/local-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAiBH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA8LD;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CA2C5E;AAyBD;;;;;;;;;;;GAWG;AACH,wBAAsB,SAAS,CAC7B,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,EAC9E,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAClE,OAAO,CAAC,IAAI,CAAC,CAsKf;AAID;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,MAAU,GAAG,aAAa,EAAE,CAgE7E;AAID;;GAEG;AACH,wBAAgB,aAAa,IAAI,eAAe,EAAE,CAWjD;AAID,wBAAgB,YAAY,IAAI,OAAO,CAGtC;AAID,wBAAgB,YAAY,IAAI,OAAO,CAUtC;AAID;;;GAGG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAStC;AAID,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,EAC7E,MAAM,EAAE,MAAM,GACb,IAAI,CAMN;AAID,wBAAgB,aAAa,IAAI;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,QAAQ,GAAG,MAAM,CAAA;CAAE,CAuBpI"}
@@ -0,0 +1,525 @@
1
+ /**
2
+ * Local Pattern Cache for ekkOS MCP Server
3
+ *
4
+ * Provides <2ms local-first search via SQLite FTS5 (with BM25 relevance ranking)
5
+ * with automatic fallback to the original JSON + in-memory Map approach when
6
+ * better-sqlite3 is unavailable (e.g. missing native build tools).
7
+ *
8
+ * SQLite backend (preferred):
9
+ * - DB at ~/.ekkos/cache/patterns.db
10
+ * - patterns table + patterns_fts virtual table (FTS5, BM25 ranking)
11
+ * - directives table
12
+ *
13
+ * JSON fallback (when SQLite unavailable):
14
+ * - Disk file at ~/.ekkos/cache/patterns.json
15
+ * - In-memory Map + array for sub-millisecond reads
16
+ *
17
+ * Public API is identical between both backends — callers in index.ts need
18
+ * no changes.
19
+ */
20
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
21
+ import { join } from 'path';
22
+ import { homedir } from 'os';
23
+ import { createRequire } from 'module';
24
+ // ─── Paths ────────────────────────────────────────────────────────────────────
25
+ const CACHE_DIR = join(homedir(), '.ekkos', 'cache');
26
+ const CACHE_FILE = join(CACHE_DIR, 'patterns.json'); // JSON fallback
27
+ const SQLITE_DB_FILE = join(CACHE_DIR, 'patterns.db'); // SQLite primary
28
+ const CACHE_MAX_AGE_MS = 5 * 60 * 1000; // 5 minutes
29
+ const REFRESH_INTERVAL_MS = 5 * 60 * 1000;
30
+ let db = null;
31
+ try {
32
+ const _require = createRequire(import.meta.url);
33
+ const BetterSqlite3 = _require('better-sqlite3');
34
+ if (!existsSync(CACHE_DIR)) {
35
+ mkdirSync(CACHE_DIR, { recursive: true });
36
+ }
37
+ db = new BetterSqlite3(SQLITE_DB_FILE);
38
+ // WAL mode: concurrent reads, fast writes
39
+ db.pragma('journal_mode = WAL');
40
+ db.pragma('synchronous = NORMAL');
41
+ // Canonical storage table
42
+ db.exec(`
43
+ CREATE TABLE IF NOT EXISTS patterns (
44
+ rowid INTEGER PRIMARY KEY AUTOINCREMENT,
45
+ pattern_id TEXT UNIQUE NOT NULL,
46
+ title TEXT NOT NULL DEFAULT '',
47
+ problem TEXT NOT NULL DEFAULT '',
48
+ solution TEXT NOT NULL DEFAULT '',
49
+ tags TEXT NOT NULL DEFAULT '',
50
+ success_rate REAL,
51
+ layer TEXT NOT NULL DEFAULT '',
52
+ updated_at TEXT,
53
+ full_json TEXT NOT NULL
54
+ );
55
+
56
+ CREATE VIRTUAL TABLE IF NOT EXISTS patterns_fts USING fts5(
57
+ pattern_id UNINDEXED,
58
+ title,
59
+ problem,
60
+ solution,
61
+ tags,
62
+ content='patterns',
63
+ content_rowid='rowid'
64
+ );
65
+
66
+ -- Keep FTS in sync via triggers
67
+ CREATE TRIGGER IF NOT EXISTS patterns_ai
68
+ AFTER INSERT ON patterns BEGIN
69
+ INSERT INTO patterns_fts(rowid, pattern_id, title, problem, solution, tags)
70
+ VALUES (new.rowid, new.pattern_id, new.title, new.problem, new.solution, new.tags);
71
+ END;
72
+
73
+ CREATE TRIGGER IF NOT EXISTS patterns_ad
74
+ AFTER DELETE ON patterns BEGIN
75
+ INSERT INTO patterns_fts(patterns_fts, rowid, pattern_id, title, problem, solution, tags)
76
+ VALUES ('delete', old.rowid, old.pattern_id, old.title, old.problem, old.solution, old.tags);
77
+ END;
78
+
79
+ CREATE TRIGGER IF NOT EXISTS patterns_au
80
+ AFTER UPDATE ON patterns BEGIN
81
+ INSERT INTO patterns_fts(patterns_fts, rowid, pattern_id, title, problem, solution, tags)
82
+ VALUES ('delete', old.rowid, old.pattern_id, old.title, old.problem, old.solution, old.tags);
83
+ INSERT INTO patterns_fts(rowid, pattern_id, title, problem, solution, tags)
84
+ VALUES (new.rowid, new.pattern_id, new.title, new.problem, new.solution, new.tags);
85
+ END;
86
+
87
+ CREATE TABLE IF NOT EXISTS directives (
88
+ id TEXT PRIMARY KEY,
89
+ type TEXT NOT NULL DEFAULT 'PREFER',
90
+ rule TEXT NOT NULL DEFAULT '',
91
+ scope TEXT,
92
+ is_active INTEGER NOT NULL DEFAULT 1,
93
+ full_json TEXT NOT NULL
94
+ );
95
+
96
+ CREATE TABLE IF NOT EXISTS cache_meta (
97
+ key TEXT PRIMARY KEY,
98
+ value TEXT NOT NULL
99
+ );
100
+ `);
101
+ console.error('[ekkOS:cache] SQLite FTS5 backend initialised at', SQLITE_DB_FILE);
102
+ }
103
+ catch (err) {
104
+ db = null;
105
+ console.error('[ekkOS:cache] SQLite unavailable — using JSON fallback:', err.message);
106
+ }
107
+ // ─── FTS5 query sanitiser ─────────────────────────────────────────────────────
108
+ // FTS5 treats special characters (", *, :, ^, etc.) as operators. We quote
109
+ // each whitespace-separated token individually so arbitrary user queries don't
110
+ // cause a parse error.
111
+ function sanitizeFTS5Query(query) {
112
+ return query
113
+ .trim()
114
+ .split(/\s+/)
115
+ .filter(t => t.length > 0)
116
+ .map(t => `"${t.replace(/"/g, '""')}"`) // escape internal quotes
117
+ .join(' '); // implicit AND between tokens
118
+ }
119
+ // ─── SQLite helper: upsert a single pattern ───────────────────────────────────
120
+ function sqliteUpsertPattern(p) {
121
+ if (!db)
122
+ return;
123
+ const tagsStr = (p.tags || []).join(' ');
124
+ // split content into problem/solution heuristically:
125
+ // store the full content in both fields so FTS matches either way
126
+ const content = p.content || '';
127
+ const stmt = db.prepare(`
128
+ INSERT INTO patterns (pattern_id, title, problem, solution, tags, success_rate, updated_at, full_json)
129
+ VALUES (@pattern_id, @title, @problem, @solution, @tags, @success_rate, @updated_at, @full_json)
130
+ ON CONFLICT(pattern_id) DO UPDATE SET
131
+ title = excluded.title,
132
+ problem = excluded.problem,
133
+ solution = excluded.solution,
134
+ tags = excluded.tags,
135
+ success_rate = excluded.success_rate,
136
+ updated_at = excluded.updated_at,
137
+ full_json = excluded.full_json
138
+ `);
139
+ stmt.run({
140
+ pattern_id: p.id,
141
+ title: p.title || '',
142
+ problem: content,
143
+ solution: content,
144
+ tags: tagsStr,
145
+ success_rate: p.success_rate ?? null,
146
+ updated_at: p.updated_at || null,
147
+ full_json: JSON.stringify(p),
148
+ });
149
+ }
150
+ // ─── SQLite helper: set cache_meta ────────────────────────────────────────────
151
+ function sqliteSetMeta(key, value) {
152
+ if (!db)
153
+ return;
154
+ db.prepare(`
155
+ INSERT INTO cache_meta (key, value) VALUES (?, ?)
156
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value
157
+ `).run(key, value);
158
+ }
159
+ function sqliteGetMeta(key) {
160
+ if (!db)
161
+ return null;
162
+ const row = db.prepare('SELECT value FROM cache_meta WHERE key = ?').get(key);
163
+ return row ? row.value : null;
164
+ }
165
+ // ─── JSON fallback state ──────────────────────────────────────────────────────
166
+ const patternMap = new Map();
167
+ const directiveList = [];
168
+ let cacheWarmedAt = 0;
169
+ let isWarming = false;
170
+ // ─── loadCacheFromDisk ────────────────────────────────────────────────────────
171
+ /**
172
+ * Initialise cache from persistent storage on startup.
173
+ * SQLite: the DB file is already open — just read counts and restore warmed_at.
174
+ * JSON fallback: deserialise patterns.json into the in-memory Map.
175
+ */
176
+ export function loadCacheFromDisk() {
177
+ if (db) {
178
+ // SQLite path
179
+ try {
180
+ const patCount = db.prepare('SELECT COUNT(*) as n FROM patterns').get().n;
181
+ const dirCount = db.prepare('SELECT COUNT(*) as n FROM directives WHERE is_active = 1').get().n;
182
+ const warmedAtStr = sqliteGetMeta('warmed_at');
183
+ if (warmedAtStr) {
184
+ cacheWarmedAt = new Date(warmedAtStr).getTime();
185
+ }
186
+ return { patterns: patCount, directives: dirCount };
187
+ }
188
+ catch (err) {
189
+ console.error('[ekkOS:cache] SQLite loadCacheFromDisk error:', err);
190
+ return { patterns: 0, directives: 0 };
191
+ }
192
+ }
193
+ // JSON fallback
194
+ try {
195
+ if (!existsSync(CACHE_FILE)) {
196
+ return { patterns: 0, directives: 0 };
197
+ }
198
+ const raw = readFileSync(CACHE_FILE, 'utf-8');
199
+ const data = JSON.parse(raw);
200
+ patternMap.clear();
201
+ directiveList.length = 0;
202
+ for (const p of data.patterns) {
203
+ patternMap.set(p.id, p);
204
+ }
205
+ for (const d of data.directives) {
206
+ directiveList.push(d);
207
+ }
208
+ cacheWarmedAt = new Date(data.warmed_at).getTime();
209
+ return { patterns: patternMap.size, directives: directiveList.length };
210
+ }
211
+ catch {
212
+ return { patterns: 0, directives: 0 };
213
+ }
214
+ }
215
+ // ─── saveCacheToDisk (JSON fallback only) ─────────────────────────────────────
216
+ function saveCacheToDisk(userId) {
217
+ try {
218
+ if (!existsSync(CACHE_DIR)) {
219
+ mkdirSync(CACHE_DIR, { recursive: true });
220
+ }
221
+ const data = {
222
+ patterns: Array.from(patternMap.values()),
223
+ directives: [...directiveList],
224
+ warmed_at: new Date().toISOString(),
225
+ user_id: userId,
226
+ };
227
+ writeFileSync(CACHE_FILE, JSON.stringify(data));
228
+ }
229
+ catch (err) {
230
+ console.error('[ekkOS:cache] Failed to save cache to disk:', err);
231
+ }
232
+ }
233
+ // ─── warmCache ────────────────────────────────────────────────────────────────
234
+ /**
235
+ * Warm cache from the /api/v1/warmup bundle endpoint (non-blocking).
236
+ *
237
+ * A single HTTP GET replaces the previous multi-step ekkOS_Search approach,
238
+ * returning top patterns + directives + project patterns in one response.
239
+ *
240
+ * With SQLite backend, data is written into patterns / directives tables and
241
+ * FTS index is updated automatically via triggers.
242
+ *
243
+ * With JSON fallback, data is written to the in-memory Map and then flushed
244
+ * to disk as patterns.json.
245
+ */
246
+ export async function warmCache(_callGateway, userId, options) {
247
+ if (isWarming)
248
+ return;
249
+ isWarming = true;
250
+ try {
251
+ const apiKey = options?.apiKey || process.env.EKKOS_API_KEY || '';
252
+ const baseUrl = (options?.baseUrl || process.env.EKKOS_MCP_URL || 'https://mcp.ekkos.dev/api/v1/mcp')
253
+ .replace(/\/api\/v1\/mcp.*$/, '');
254
+ const params = new URLSearchParams({ user_id: userId });
255
+ if (options?.projectId) {
256
+ params.set('project_id', options.projectId);
257
+ }
258
+ const controller = new AbortController();
259
+ const timeout = setTimeout(() => controller.abort(), 10_000);
260
+ let response;
261
+ try {
262
+ response = await fetch(`${baseUrl}/api/v1/warmup?${params.toString()}`, {
263
+ method: 'GET',
264
+ headers: {
265
+ 'Authorization': `Bearer ${apiKey}`,
266
+ 'Content-Type': 'application/json',
267
+ },
268
+ signal: controller.signal,
269
+ });
270
+ }
271
+ finally {
272
+ clearTimeout(timeout);
273
+ }
274
+ if (!response.ok) {
275
+ throw new Error(`Warmup HTTP ${response.status}: ${response.statusText}`);
276
+ }
277
+ const bundle = await response.json();
278
+ const allPatterns = [
279
+ ...(Array.isArray(bundle.patterns) ? bundle.patterns : []),
280
+ ...(Array.isArray(bundle.project_patterns) ? bundle.project_patterns : []),
281
+ ];
282
+ if (db) {
283
+ // ── SQLite path ──────────────────────────────────────────────────────
284
+ // Upsert all patterns
285
+ for (const p of allPatterns) {
286
+ const cached = {
287
+ id: p.pattern_id,
288
+ title: p.title || '',
289
+ success_rate: p.success_rate ?? null,
290
+ applied_count: p.applied_count || 0,
291
+ tags: p.tags,
292
+ content: p.solution || p.content || '',
293
+ updated_at: p.updated_at,
294
+ };
295
+ sqliteUpsertPattern(cached);
296
+ }
297
+ // Replace directives (delete active, re-insert)
298
+ if (Array.isArray(bundle.directives)) {
299
+ db.exec('DELETE FROM directives');
300
+ const insertDir = db.prepare(`
301
+ INSERT INTO directives (id, type, rule, scope, is_active, full_json)
302
+ VALUES (@id, @type, @rule, @scope, 1, @full_json)
303
+ ON CONFLICT(id) DO UPDATE SET
304
+ type = excluded.type,
305
+ rule = excluded.rule,
306
+ scope = excluded.scope,
307
+ is_active = 1,
308
+ full_json = excluded.full_json
309
+ `);
310
+ for (const d of bundle.directives) {
311
+ insertDir.run({
312
+ id: d.id,
313
+ type: d.type || 'PREFER',
314
+ rule: d.rule || '',
315
+ scope: d.context || null,
316
+ full_json: JSON.stringify(d),
317
+ });
318
+ }
319
+ }
320
+ sqliteSetMeta('warmed_at', new Date().toISOString());
321
+ cacheWarmedAt = Date.now();
322
+ const patCount = db.prepare('SELECT COUNT(*) as n FROM patterns').get().n;
323
+ const dirCount = db.prepare('SELECT COUNT(*) as n FROM directives WHERE is_active = 1').get().n;
324
+ console.error(`[ekkOS:cache] SQLite warmed via /api/v1/warmup: ${patCount} patterns, ${dirCount} directives` +
325
+ (bundle.meta?.project_pattern_count ? `, ${bundle.meta.project_pattern_count} project patterns` : ''));
326
+ }
327
+ else {
328
+ // ── JSON fallback path ───────────────────────────────────────────────
329
+ for (const p of allPatterns) {
330
+ patternMap.set(p.pattern_id, {
331
+ id: p.pattern_id,
332
+ title: p.title || '',
333
+ success_rate: p.success_rate ?? null,
334
+ applied_count: p.applied_count || 0,
335
+ tags: p.tags,
336
+ content: p.solution || p.content || '',
337
+ updated_at: p.updated_at,
338
+ });
339
+ }
340
+ if (Array.isArray(bundle.directives)) {
341
+ directiveList.length = 0;
342
+ for (const d of bundle.directives) {
343
+ directiveList.push({
344
+ id: d.id,
345
+ type: d.type || 'PREFER',
346
+ rule: d.rule || '',
347
+ context: d.context,
348
+ });
349
+ }
350
+ }
351
+ cacheWarmedAt = Date.now();
352
+ saveCacheToDisk(userId);
353
+ console.error(`[ekkOS:cache] JSON warmed via /api/v1/warmup: ${patternMap.size} patterns, ${directiveList.length} directives` +
354
+ (bundle.meta?.project_pattern_count ? `, ${bundle.meta.project_pattern_count} project patterns` : ''));
355
+ }
356
+ }
357
+ catch (err) {
358
+ console.error('[ekkOS:cache] Warm failed (will use stale cache):', err);
359
+ }
360
+ finally {
361
+ isWarming = false;
362
+ }
363
+ }
364
+ // ─── searchLocal ──────────────────────────────────────────────────────────────
365
+ /**
366
+ * Search local cache by query string.
367
+ *
368
+ * SQLite backend: FTS5 MATCH with bm25() ranking — proper relevance scoring,
369
+ * sub-2ms on typical pattern sets.
370
+ *
371
+ * JSON fallback: simple term-frequency scoring on title + tags + content.
372
+ */
373
+ export function searchLocal(query, limit = 5) {
374
+ if (db) {
375
+ // ── SQLite / FTS5 path ───────────────────────────────────────────────
376
+ try {
377
+ const ftsQuery = sanitizeFTS5Query(query);
378
+ if (!ftsQuery)
379
+ return [];
380
+ const rows = db.prepare(`
381
+ SELECT p.full_json, bm25(patterns_fts) AS rank
382
+ FROM patterns_fts f
383
+ JOIN patterns p ON f.rowid = p.rowid
384
+ WHERE patterns_fts MATCH ?
385
+ ORDER BY rank
386
+ LIMIT ?
387
+ `).all(ftsQuery, limit);
388
+ return rows.map(r => JSON.parse(r.full_json));
389
+ }
390
+ catch (err) {
391
+ // FTS parse error or other SQLite issue — fall through to JSON fallback
392
+ console.error('[ekkOS:cache] FTS5 search error, falling back to Map search:', err);
393
+ // Load patterns from SQLite into patternMap for fallback
394
+ if (patternMap.size === 0) {
395
+ try {
396
+ const all = db.prepare('SELECT full_json FROM patterns').all();
397
+ for (const row of all) {
398
+ const p = JSON.parse(row.full_json);
399
+ patternMap.set(p.id, p);
400
+ }
401
+ }
402
+ catch {
403
+ return [];
404
+ }
405
+ }
406
+ }
407
+ }
408
+ // ── JSON / Map fallback path ─────────────────────────────────────────────
409
+ if (patternMap.size === 0)
410
+ return [];
411
+ const queryLower = query.toLowerCase();
412
+ const queryTerms = queryLower.split(/\s+/).filter(t => t.length > 2);
413
+ const scored = [];
414
+ for (const pattern of patternMap.values()) {
415
+ let score = 0;
416
+ const titleLower = (pattern.title || '').toLowerCase();
417
+ const contentLower = (pattern.content || '').toLowerCase();
418
+ const tagsLower = (pattern.tags || []).join(' ').toLowerCase();
419
+ if (titleLower.includes(queryLower))
420
+ score += 10;
421
+ for (const term of queryTerms) {
422
+ if (titleLower.includes(term))
423
+ score += 3;
424
+ if (contentLower.includes(term))
425
+ score += 1;
426
+ if (tagsLower.includes(term))
427
+ score += 2;
428
+ }
429
+ if (pattern.success_rate !== null && pattern.success_rate > 0.7)
430
+ score += 1;
431
+ if (pattern.applied_count > 5)
432
+ score += 0.5;
433
+ if (score > 0)
434
+ scored.push({ pattern, score });
435
+ }
436
+ scored.sort((a, b) => b.score - a.score);
437
+ return scored.slice(0, limit).map(s => s.pattern);
438
+ }
439
+ // ─── getDirectives ────────────────────────────────────────────────────────────
440
+ /**
441
+ * Return all cached active directives.
442
+ */
443
+ export function getDirectives() {
444
+ if (db) {
445
+ try {
446
+ const rows = db.prepare('SELECT full_json FROM directives WHERE is_active = 1').all();
447
+ return rows.map(r => JSON.parse(r.full_json));
448
+ }
449
+ catch (err) {
450
+ console.error('[ekkOS:cache] getDirectives SQLite error:', err);
451
+ return [];
452
+ }
453
+ }
454
+ return [...directiveList];
455
+ }
456
+ // ─── isCacheStale ─────────────────────────────────────────────────────────────
457
+ export function isCacheStale() {
458
+ if (cacheWarmedAt === 0)
459
+ return true;
460
+ return (Date.now() - cacheWarmedAt) > CACHE_MAX_AGE_MS;
461
+ }
462
+ // ─── hasCacheData ─────────────────────────────────────────────────────────────
463
+ export function hasCacheData() {
464
+ if (db) {
465
+ try {
466
+ const patCount = db.prepare('SELECT COUNT(*) as n FROM patterns').get().n;
467
+ return patCount > 0;
468
+ }
469
+ catch {
470
+ return false;
471
+ }
472
+ }
473
+ return patternMap.size > 0 || directiveList.length > 0;
474
+ }
475
+ // ─── invalidateCache ─────────────────────────────────────────────────────────
476
+ /**
477
+ * Mark cache as stale — next search will trigger a background re-warm.
478
+ * Does NOT delete SQLite data (that would remove offline fallback data).
479
+ */
480
+ export function invalidateCache() {
481
+ cacheWarmedAt = 0;
482
+ if (db) {
483
+ try {
484
+ sqliteSetMeta('warmed_at', '1970-01-01T00:00:00.000Z');
485
+ }
486
+ catch {
487
+ // best-effort
488
+ }
489
+ }
490
+ }
491
+ // ─── startPeriodicRefresh ─────────────────────────────────────────────────────
492
+ export function startPeriodicRefresh(callGateway, userId) {
493
+ setInterval(() => {
494
+ if (isCacheStale()) {
495
+ warmCache(callGateway, userId).catch(() => { });
496
+ }
497
+ }, REFRESH_INTERVAL_MS);
498
+ }
499
+ // ─── getCacheStats ────────────────────────────────────────────────────────────
500
+ export function getCacheStats() {
501
+ if (db) {
502
+ try {
503
+ const patCount = db.prepare('SELECT COUNT(*) as n FROM patterns').get().n;
504
+ const dirCount = db.prepare('SELECT COUNT(*) as n FROM directives WHERE is_active = 1').get().n;
505
+ return {
506
+ patterns: patCount,
507
+ directives: dirCount,
508
+ age_ms: cacheWarmedAt > 0 ? Date.now() - cacheWarmedAt : -1,
509
+ stale: isCacheStale(),
510
+ backend: 'sqlite',
511
+ };
512
+ }
513
+ catch {
514
+ // fall through
515
+ }
516
+ }
517
+ return {
518
+ patterns: patternMap.size,
519
+ directives: directiveList.length,
520
+ age_ms: cacheWarmedAt > 0 ? Date.now() - cacheWarmedAt : -1,
521
+ stale: isCacheStale(),
522
+ backend: 'json',
523
+ };
524
+ }
525
+ //# sourceMappingURL=local-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-cache.js","sourceRoot":"","sources":["../src/local-cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Offline Write Queue for ekkOS MCP Server
3
+ *
4
+ * When the cloud gateway is unreachable, queues write operations
5
+ * (Forge, Track, Outcome, Directive) to a local JSONL file.
6
+ * Flushes the queue when connectivity is restored.
7
+ */
8
+ /**
9
+ * Check if a tool is a write operation that can be queued
10
+ */
11
+ export declare function isWriteTool(toolName: string): boolean;
12
+ /**
13
+ * Queue a write operation for later execution
14
+ */
15
+ export declare function queueWrite(toolName: string, args: Record<string, unknown>): void;
16
+ /**
17
+ * Flush all pending writes to the cloud gateway
18
+ * Returns the number of successfully flushed writes
19
+ */
20
+ export declare function flushQueue(callGateway: (endpoint: string, method: string, params?: any) => Promise<any>): Promise<number>;
21
+ /**
22
+ * Get count of pending writes
23
+ */
24
+ export declare function getPendingCount(): number;
25
+ //# sourceMappingURL=offline-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"offline-queue.d.ts","sourceRoot":"","sources":["../src/offline-queue.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA6BH;;GAEG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAErD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAiBhF;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,GAC5E,OAAO,CAAC,MAAM,CAAC,CA6CjB;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,MAAM,CASxC"}