metame-cli 1.4.34 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +136 -94
  2. package/index.js +312 -57
  3. package/package.json +8 -4
  4. package/scripts/agent-layer.js +320 -0
  5. package/scripts/daemon-admin-commands.js +328 -28
  6. package/scripts/daemon-agent-commands.js +145 -6
  7. package/scripts/daemon-agent-tools.js +163 -7
  8. package/scripts/daemon-bridges.js +110 -20
  9. package/scripts/daemon-checkpoints.js +36 -7
  10. package/scripts/daemon-claude-engine.js +849 -358
  11. package/scripts/daemon-command-router.js +31 -10
  12. package/scripts/daemon-default.yaml +28 -4
  13. package/scripts/daemon-engine-runtime.js +328 -0
  14. package/scripts/daemon-exec-commands.js +15 -7
  15. package/scripts/daemon-notify.js +37 -1
  16. package/scripts/daemon-ops-commands.js +8 -6
  17. package/scripts/daemon-runtime-lifecycle.js +129 -5
  18. package/scripts/daemon-session-commands.js +60 -25
  19. package/scripts/daemon-session-store.js +121 -13
  20. package/scripts/daemon-task-scheduler.js +129 -49
  21. package/scripts/daemon-user-acl.js +35 -9
  22. package/scripts/daemon.js +268 -33
  23. package/scripts/distill.js +327 -18
  24. package/scripts/docs/agent-guide.md +12 -0
  25. package/scripts/docs/maintenance-manual.md +155 -0
  26. package/scripts/docs/pointer-map.md +110 -0
  27. package/scripts/feishu-adapter.js +42 -13
  28. package/scripts/hooks/stop-session-capture.js +243 -0
  29. package/scripts/memory-extract.js +105 -6
  30. package/scripts/memory-nightly-reflect.js +199 -11
  31. package/scripts/memory.js +134 -3
  32. package/scripts/mentor-engine.js +405 -0
  33. package/scripts/platform.js +24 -0
  34. package/scripts/providers.js +182 -22
  35. package/scripts/schema.js +12 -0
  36. package/scripts/session-analytics.js +245 -12
  37. package/scripts/skill-changelog.js +245 -0
  38. package/scripts/skill-evolution.js +288 -5
  39. package/scripts/telegram-adapter.js +12 -8
  40. package/scripts/usage-classifier.js +1 -1
  41. package/scripts/daemon-admin-commands.test.js +0 -333
  42. package/scripts/daemon-task-envelope.test.js +0 -59
  43. package/scripts/daemon-task-scheduler.test.js +0 -106
  44. package/scripts/reliability-core.test.js +0 -280
  45. package/scripts/skill-evolution.test.js +0 -113
  46. package/scripts/task-board.test.js +0 -83
  47. package/scripts/test_daemon.js +0 -1407
  48. package/scripts/utils.test.js +0 -192
@@ -26,15 +26,17 @@ const REFLECT_LOG_FILE = path.join(METAME_DIR, 'memory_reflect_log.jsonl');
26
26
  const MEMORY_DIR = path.join(HOME, '.metame', 'memory');
27
27
  const DECISIONS_DIR = path.join(MEMORY_DIR, 'decisions');
28
28
  const LESSONS_DIR = path.join(MEMORY_DIR, 'lessons');
29
+ const CAPSULES_DIR = path.join(MEMORY_DIR, 'capsules');
29
30
 
30
31
  // Hot zone thresholds
31
32
  const MIN_SEARCH_COUNT = 3;
32
33
  const WINDOW_DAYS = 7;
33
34
  const MAX_FACTS = 20;
34
35
  const LOCK_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
36
+ const EXCLUDED_RELATIONS = ['project_milestone', 'synthesized_insight', 'knowledge_capsule', 'bug_lesson'];
35
37
 
36
38
  // Ensure output directories exist at startup
37
- [MEMORY_DIR, DECISIONS_DIR, LESSONS_DIR].forEach(d => fs.mkdirSync(d, { recursive: true }));
39
+ [MEMORY_DIR, DECISIONS_DIR, LESSONS_DIR, CAPSULES_DIR].forEach(d => fs.mkdirSync(d, { recursive: true }));
38
40
 
39
41
  /**
40
42
  * Load callHaiku + buildDistillEnv from deployed path, fallback to scripts dir.
@@ -107,18 +109,19 @@ function writeReflectLog(record) {
107
109
  * Returns array of plain objects.
108
110
  */
109
111
  function queryHotFacts(db) {
112
+ const relationPlaceholders = EXCLUDED_RELATIONS.map(() => '?').join(', ');
110
113
  const stmt = db.prepare(`
111
- SELECT entity, relation, value, confidence, search_count, created_at
114
+ SELECT id, entity, relation, value, confidence, search_count, created_at
112
115
  FROM facts
113
116
  WHERE search_count >= ${MIN_SEARCH_COUNT}
114
117
  AND created_at >= datetime('now', '-${WINDOW_DAYS} days')
115
118
  AND superseded_by IS NULL
116
119
  AND (conflict_status IS NULL OR conflict_status = 'OK')
117
- AND relation != 'project_milestone'
120
+ AND relation NOT IN (${relationPlaceholders})
118
121
  ORDER BY search_count DESC, created_at DESC
119
122
  LIMIT ${MAX_FACTS}
120
123
  `);
121
- return stmt.all();
124
+ return stmt.all(...EXCLUDED_RELATIONS);
122
125
  }
123
126
 
124
127
  /**
@@ -142,6 +145,101 @@ ${sections}
142
145
  fs.writeFileSync(filePath, content, 'utf8');
143
146
  }
144
147
 
148
+ function sanitizeSlug(input, fallback = 'capsule') {
149
+ const v = String(input || '')
150
+ .trim()
151
+ .toLowerCase()
152
+ .replace(/[^a-z0-9\u4e00-\u9fff_-]+/g, '-')
153
+ .replace(/-+/g, '-')
154
+ .replace(/^-|-$/g, '');
155
+ if (!v) return fallback;
156
+ return v.slice(0, 50);
157
+ }
158
+
159
+ function stripMd(text) {
160
+ return String(text || '').replace(/[#*_`>\[\]\(\)]/g, ' ').replace(/\s+/g, ' ').trim();
161
+ }
162
+
163
+ function buildSynthesizedFacts(today, decisions, lessons) {
164
+ const all = []
165
+ .concat(Array.isArray(decisions) ? decisions : [])
166
+ .concat(Array.isArray(lessons) ? lessons : []);
167
+ const out = [];
168
+ for (const item of all) {
169
+ const title = String(item && item.title ? item.title : '').trim();
170
+ const content = String(item && item.content ? item.content : '').trim();
171
+ if (!title || !content) continue;
172
+ const value = stripMd(`${title}: ${content}`).slice(0, 280);
173
+ if (value.length < 20) continue;
174
+ out.push({
175
+ entity: `nightly.reflect.${today}`,
176
+ relation: 'synthesized_insight',
177
+ value,
178
+ confidence: 'high',
179
+ tags: ['nightly', 'reflection'],
180
+ });
181
+ }
182
+ return out;
183
+ }
184
+
185
+ function entityPrefix(entity) {
186
+ const src = String(entity || '').trim();
187
+ if (!src) return '';
188
+ const parts = src.split('.').map(s => s.trim()).filter(Boolean);
189
+ if (parts.length === 0) return '';
190
+ if (parts.length === 1) return parts[0];
191
+ return `${parts[0]}.${parts[1]}`;
192
+ }
193
+
194
+ function collectCapsuleGroups(facts, minGroupSize = 3) {
195
+ const groups = new Map();
196
+ for (const fact of Array.isArray(facts) ? facts : []) {
197
+ const prefix = entityPrefix(fact && fact.entity);
198
+ if (!prefix) continue;
199
+ if (!groups.has(prefix)) groups.set(prefix, []);
200
+ groups.get(prefix).push(fact);
201
+ }
202
+ return [...groups.entries()]
203
+ .map(([prefix, items]) => ({ prefix, items }))
204
+ .filter(g => g.items.length >= minGroupSize)
205
+ .sort((a, b) => b.items.length - a.items.length);
206
+ }
207
+
208
+ function parseJsonFromLlm(raw) {
209
+ const text = String(raw || '');
210
+ const cleaned = text.replace(/```json\n?/g, '').replace(/```\n?/g, '').trim();
211
+ if (!cleaned) return null;
212
+ try { return JSON.parse(cleaned); } catch { return null; }
213
+ }
214
+
215
+ function writeCapsuleFile(filePath, capsule, facts, today, prefix) {
216
+ const related = Array.isArray(capsule.related_concepts) ? capsule.related_concepts.slice(0, 8) : [];
217
+ const supporting = Array.isArray(capsule.supporting_facts) ? capsule.supporting_facts.slice(0, 8) : [];
218
+ const content = `---
219
+ date: ${today}
220
+ source: nightly-reflect
221
+ type: knowledge-capsule
222
+ entity_prefix: ${prefix}
223
+ facts_analyzed: ${Array.isArray(facts) ? facts.length : 0}
224
+ ---
225
+
226
+ # ${capsule.title}
227
+
228
+ ## 核心结论
229
+ ${capsule.core_conclusion}
230
+
231
+ ## 适用场景
232
+ ${capsule.applicable_scenarios}
233
+
234
+ ## 关联概念
235
+ ${related.length > 0 ? related.map(x => `- ${x}`).join('\n') : '- (none)'}
236
+
237
+ ## 支撑事实
238
+ ${supporting.length > 0 ? supporting.map(x => `- ${x}`).join('\n') : '- (none)'}
239
+ `;
240
+ fs.writeFileSync(filePath, content, 'utf8');
241
+ }
242
+
145
243
  /**
146
244
  * Main nightly reflect run.
147
245
  */
@@ -238,12 +336,9 @@ Rules:
238
336
  }
239
337
 
240
338
  // Parse Haiku response
241
- let parsed;
242
- try {
243
- const cleaned = raw.replace(/```json\n?/g, '').replace(/```\n?/g, '').trim();
244
- parsed = JSON.parse(cleaned);
245
- } catch (e) {
246
- console.log(`[NIGHTLY-REFLECT] Failed to parse Haiku output: ${e.message}`);
339
+ const parsed = parseJsonFromLlm(raw);
340
+ if (!parsed || typeof parsed !== 'object') {
341
+ console.log('[NIGHTLY-REFLECT] Failed to parse Haiku output.');
247
342
  writeReflectLog({ status: 'error', reason: 'parse_failed', facts_found: hotFacts.length });
248
343
  return;
249
344
  }
@@ -265,12 +360,92 @@ Rules:
265
360
  console.log(`[NIGHTLY-REFLECT] Lessons written: ${lessonFile}`);
266
361
  }
267
362
 
363
+ let synthesizedSaved = 0;
364
+ let capsulesWritten = 0;
365
+ let capsuleFactsSaved = 0;
366
+ let memory = null;
367
+ try {
368
+ try { memory = require('./memory'); } catch { /* optional */ }
369
+
370
+ // 3B: write distilled insights back into memory.db for closed-loop retrieval.
371
+ if (memory && typeof memory.saveFacts === 'function') {
372
+ const synthesizedFacts = buildSynthesizedFacts(today, decisions, lessons);
373
+ if (synthesizedFacts.length > 0) {
374
+ const writeRes = memory.saveFacts(`nightly-reflect-${today}`, '*', synthesizedFacts, { scope: '*' });
375
+ synthesizedSaved = Number(writeRes && writeRes.saved) || 0;
376
+ }
377
+ }
378
+
379
+ // 3C: knowledge capsule aggregation by entity prefix.
380
+ const capsuleGroups = collectCapsuleGroups(hotFacts, 3).slice(0, 3);
381
+ for (const group of capsuleGroups) {
382
+ const groupFacts = group.items.map(f => ({
383
+ entity: f.entity,
384
+ relation: f.relation,
385
+ value: f.value,
386
+ search_count: f.search_count,
387
+ }));
388
+ const capsulePrompt = `你是知识胶囊生成器。请将同一主题下的事实聚合成结构化胶囊。
389
+
390
+ entity_prefix: ${group.prefix}
391
+ facts(json): ${JSON.stringify(groupFacts, null, 2).slice(0, 5000)}
392
+
393
+ 输出 JSON:
394
+ {
395
+ "title":"标题",
396
+ "core_conclusion":"一句核心结论",
397
+ "applicable_scenarios":"适用场景(1-2句)",
398
+ "related_concepts":["概念1","概念2"],
399
+ "supporting_facts":["支撑点1","支撑点2"]
400
+ }
401
+
402
+ 规则:
403
+ - 只基于输入事实,不虚构
404
+ - 每个字段简洁具体
405
+ - 仅输出 JSON`;
406
+
407
+ let capsule = null;
408
+ try {
409
+ const rawCapsule = await Promise.race([
410
+ callHaiku(capsulePrompt, distillEnv, 60000),
411
+ new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 65000)),
412
+ ]);
413
+ capsule = parseJsonFromLlm(rawCapsule);
414
+ } catch { /* non-fatal */ }
415
+ if (!capsule || !capsule.title || !capsule.core_conclusion || !capsule.applicable_scenarios) continue;
416
+
417
+ const capsuleSlug = sanitizeSlug(group.prefix.replace(/\./g, '-'), 'capsule');
418
+ const capsuleFile = path.join(CAPSULES_DIR, `${capsuleSlug}-${today}.md`);
419
+ writeCapsuleFile(capsuleFile, capsule, group.items, today, group.prefix);
420
+ capsulesWritten++;
421
+
422
+ if (memory && typeof memory.saveFacts === 'function') {
423
+ const capsuleValue = stripMd(`${capsule.title}: ${capsule.core_conclusion}`).slice(0, 280);
424
+ if (capsuleValue.length >= 20) {
425
+ const saveCapsule = memory.saveFacts(`capsule-${today}-${capsuleSlug}`, '*', [{
426
+ entity: `capsule.${group.prefix.replace(/\./g, '_')}`,
427
+ relation: 'knowledge_capsule',
428
+ value: capsuleValue,
429
+ confidence: 'high',
430
+ tags: ['capsule'],
431
+ }], { scope: '*' });
432
+ capsuleFactsSaved += Number(saveCapsule && saveCapsule.saved) || 0;
433
+ }
434
+ }
435
+ }
436
+ } finally {
437
+ try { if (memory && typeof memory.close === 'function') memory.close(); } catch { /* non-fatal */ }
438
+ }
439
+
268
440
  // Write audit log
269
441
  writeReflectLog({
270
442
  status: 'success',
271
443
  facts_analyzed: hotFacts.length,
272
444
  decisions_written: decisions.length,
273
445
  lessons_written: lessons.length,
446
+ synthesized_insights_saved: synthesizedSaved,
447
+ capsules_written: capsulesWritten,
448
+ capsule_facts_saved: capsuleFactsSaved,
274
449
  decision_file: decisions.length > 0 ? decisionFile : null,
275
450
  lesson_file: lessons.length > 0 ? lessonFile : null,
276
451
  });
@@ -290,10 +465,23 @@ Rules:
290
465
  if (require.main === module) {
291
466
  run().then(() => {
292
467
  console.log('✅ nightly-reflect complete');
468
+ // Report estimated token usage for daemon budget tracking
469
+ // ~5k tokens per reflection + capsule generation
470
+ console.log('__TOKENS__:5000');
293
471
  }).catch(e => {
294
472
  console.error(`[NIGHTLY-REFLECT] Fatal: ${e.message}`);
295
473
  process.exit(1);
296
474
  });
297
475
  }
298
476
 
299
- module.exports = { run };
477
+ module.exports = {
478
+ run,
479
+ _private: {
480
+ queryHotFacts,
481
+ buildSynthesizedFacts,
482
+ collectCapsuleGroups,
483
+ entityPrefix,
484
+ parseJsonFromLlm,
485
+ EXCLUDED_RELATIONS,
486
+ },
487
+ };
package/scripts/memory.js CHANGED
@@ -122,6 +122,17 @@ function getDb() {
122
122
  )
123
123
  `);
124
124
 
125
+ // Optional concept label side-table (non-invasive, no ALTER on facts schema)
126
+ _db.exec(`
127
+ CREATE TABLE IF NOT EXISTS fact_labels (
128
+ fact_id TEXT NOT NULL REFERENCES facts(id) ON DELETE CASCADE,
129
+ label TEXT NOT NULL,
130
+ domain TEXT,
131
+ created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
132
+ PRIMARY KEY (fact_id, label)
133
+ )
134
+ `);
135
+
125
136
  // FTS5 index for facts (separate from sessions_fts, zero compatibility risk)
126
137
  try {
127
138
  _db.exec(`
@@ -159,6 +170,8 @@ function getDb() {
159
170
  try { _db.exec('CREATE INDEX IF NOT EXISTS idx_facts_entity ON facts(entity)'); } catch {}
160
171
  try { _db.exec('CREATE INDEX IF NOT EXISTS idx_facts_entity_relation ON facts(entity, relation)'); } catch {}
161
172
  try { _db.exec('CREATE INDEX IF NOT EXISTS idx_facts_project ON facts(project)'); } catch {}
173
+ try { _db.exec('CREATE INDEX IF NOT EXISTS idx_fact_labels_label ON fact_labels(label)'); } catch {}
174
+ try { _db.exec('CREATE INDEX IF NOT EXISTS idx_fact_labels_domain ON fact_labels(domain)'); } catch {}
162
175
 
163
176
  // Backward-compatible migration for old DBs without `scope`
164
177
  try { _db.exec('ALTER TABLE facts ADD COLUMN scope TEXT DEFAULT NULL'); } catch {}
@@ -369,7 +382,43 @@ function saveFacts(sessionId, project, facts, { scope = null } = {}) {
369
382
 
370
383
  if (conflicts > 0) log('WARN', `[MEMORY] ${conflicts} conflict(s) detected`);
371
384
 
372
- return { saved, skipped, superseded, conflicts };
385
+ return { saved, skipped, superseded, conflicts, savedFacts };
386
+ }
387
+
388
+ /**
389
+ * Save concept labels for facts (side-table).
390
+ *
391
+ * @param {Array<{fact_id:string,label:string,domain?:string}>} rows
392
+ * @returns {{ saved: number, skipped: number }}
393
+ */
394
+ function saveFactLabels(rows) {
395
+ if (!Array.isArray(rows) || rows.length === 0) return { saved: 0, skipped: 0 };
396
+ const db = getDb();
397
+ const upsert = db.prepare(`
398
+ INSERT INTO fact_labels (fact_id, label, domain)
399
+ VALUES (?, ?, ?)
400
+ ON CONFLICT(fact_id, label) DO UPDATE SET
401
+ domain = COALESCE(excluded.domain, fact_labels.domain)
402
+ `);
403
+
404
+ let saved = 0;
405
+ let skipped = 0;
406
+ for (const row of rows) {
407
+ const factId = String(row && row.fact_id ? row.fact_id : '').trim();
408
+ const label = String(row && row.label ? row.label : '').trim();
409
+ const domainRaw = row && row.domain != null ? String(row.domain).trim() : '';
410
+ const domain = domainRaw || null;
411
+ if (!factId || !label) { skipped++; continue; }
412
+ if (label.length > 60) { skipped++; continue; }
413
+ if (domain && domain.length > 60) { skipped++; continue; }
414
+ try {
415
+ upsert.run(factId, label, domain);
416
+ saved++;
417
+ } catch {
418
+ skipped++;
419
+ }
420
+ }
421
+ return { saved, skipped };
373
422
  }
374
423
 
375
424
  /**
@@ -613,12 +662,46 @@ function searchFacts(query, { limit = 5, project = null, scope = null } = {}) {
613
662
  }
614
663
  const ftsResults = db.prepare(sql).all(...params);
615
664
  if (ftsResults.length > 0) {
665
+ // Supplement with fact_labels matches (concepts written by memory-extract).
666
+ const ftsIds = new Set(ftsResults.map(r => r.id));
667
+ const remaining = limit - ftsResults.length;
668
+ if (remaining > 0) {
669
+ try {
670
+ const labelLike = '%' + query.trim() + '%';
671
+ let labelSql = `
672
+ SELECT DISTINCT f.id, f.entity, f.relation, f.value, f.confidence, f.project, f.scope, f.tags, f.created_at
673
+ FROM fact_labels fl JOIN facts f ON f.id = fl.fact_id
674
+ WHERE fl.label LIKE ?
675
+ AND f.superseded_by IS NULL
676
+ AND (f.conflict_status IS NULL OR f.conflict_status NOT IN ('ARCHIVED', 'CONFLICT'))`;
677
+ const labelParams = [labelLike];
678
+ if (scope && project) {
679
+ labelSql += ` AND ((f.scope = ? OR f.scope = '*') OR (f.scope IS NULL AND (f.project = ? OR f.project = '*')))`;
680
+ labelParams.push(scope, project);
681
+ } else if (scope) {
682
+ labelSql += ` AND (f.scope = ? OR f.scope = '*')`;
683
+ labelParams.push(scope);
684
+ } else if (project) {
685
+ labelSql += ` AND (f.project = ? OR f.project = '*')`;
686
+ labelParams.push(project);
687
+ }
688
+ labelSql += ` LIMIT ?`;
689
+ labelParams.push(remaining + ftsResults.length);
690
+ const labelRows = db.prepare(labelSql).all(...labelParams);
691
+ for (const row of labelRows) {
692
+ if (!ftsIds.has(row.id) && ftsResults.length < limit) {
693
+ ftsIds.add(row.id);
694
+ ftsResults.push(row);
695
+ }
696
+ }
697
+ } catch { /* fact_labels table may not exist yet */ }
698
+ }
616
699
  _trackSearch(ftsResults.map(r => r.id));
617
700
  return ftsResults;
618
701
  }
619
702
  } catch { /* FTS error, fall through */ }
620
703
 
621
- // LIKE fallback
704
+ // LIKE fallback (also check fact_labels)
622
705
  const like = '%' + query.trim() + '%';
623
706
  const likeSql = scope && project
624
707
  ? `SELECT id, entity, relation, value, confidence, project, scope, tags, created_at
@@ -651,6 +734,39 @@ function searchFacts(query, { limit = 5, project = null, scope = null } = {}) {
651
734
  : project
652
735
  ? db.prepare(likeSql).all(like, like, like, project, limit)
653
736
  : db.prepare(likeSql).all(like, like, like, limit);
737
+ // Supplement LIKE results with fact_labels matches.
738
+ if (likeResults.length < limit) {
739
+ try {
740
+ const labelLike = '%' + query.trim() + '%';
741
+ const likeIds = new Set(likeResults.map(r => r.id));
742
+ let labelSql2 = `
743
+ SELECT DISTINCT f.id, f.entity, f.relation, f.value, f.confidence, f.project, f.scope, f.tags, f.created_at
744
+ FROM fact_labels fl JOIN facts f ON f.id = fl.fact_id
745
+ WHERE fl.label LIKE ?
746
+ AND f.superseded_by IS NULL
747
+ AND (f.conflict_status IS NULL OR f.conflict_status NOT IN ('ARCHIVED', 'CONFLICT'))`;
748
+ const labelParams2 = [labelLike];
749
+ if (scope && project) {
750
+ labelSql2 += ` AND ((f.scope = ? OR f.scope = '*') OR (f.scope IS NULL AND (f.project = ? OR f.project = '*')))`;
751
+ labelParams2.push(scope, project);
752
+ } else if (scope) {
753
+ labelSql2 += ` AND (f.scope = ? OR f.scope = '*')`;
754
+ labelParams2.push(scope);
755
+ } else if (project) {
756
+ labelSql2 += ` AND (f.project = ? OR f.project = '*')`;
757
+ labelParams2.push(project);
758
+ }
759
+ labelSql2 += ` LIMIT ?`;
760
+ labelParams2.push(limit);
761
+ const labelRows = db.prepare(labelSql2).all(...labelParams2);
762
+ for (const row of labelRows) {
763
+ if (!likeIds.has(row.id) && likeResults.length < limit) {
764
+ likeIds.add(row.id);
765
+ likeResults.push(row);
766
+ }
767
+ }
768
+ } catch { /* fact_labels table may not exist yet */ }
769
+ }
654
770
  if (likeResults.length > 0) _trackSearch(likeResults.map(r => r.id));
655
771
  return likeResults;
656
772
  }
@@ -839,4 +955,19 @@ function forceClose() {
839
955
  if (_db) { _db.close(); _db = null; }
840
956
  }
841
957
 
842
- module.exports = { saveSession, saveFacts, searchFacts, searchFactsAsync, searchSessions, recentSessions, getSession, stats, acquire, release, close, forceClose, DB_PATH };
958
+ module.exports = {
959
+ saveSession,
960
+ saveFacts,
961
+ saveFactLabels,
962
+ searchFacts,
963
+ searchFactsAsync,
964
+ searchSessions,
965
+ recentSessions,
966
+ getSession,
967
+ stats,
968
+ acquire,
969
+ release,
970
+ close,
971
+ forceClose,
972
+ DB_PATH,
973
+ };