mindlore 0.5.8 → 0.6.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.
Files changed (68) hide show
  1. package/README.md +14 -2
  2. package/agents/mindlore-assistant.md +26 -0
  3. package/agents/mindlore-librarian.md +27 -0
  4. package/agents/mindlore-researcher.md +28 -0
  5. package/dist/scripts/lib/backfill.d.ts.map +1 -1
  6. package/dist/scripts/lib/backfill.js +5 -18
  7. package/dist/scripts/lib/backfill.js.map +1 -1
  8. package/dist/scripts/lib/constants.d.ts.map +1 -1
  9. package/dist/scripts/lib/constants.js +2 -0
  10. package/dist/scripts/lib/constants.js.map +1 -1
  11. package/dist/scripts/lib/contradiction.d.ts.map +1 -1
  12. package/dist/scripts/lib/contradiction.js +11 -20
  13. package/dist/scripts/lib/contradiction.js.map +1 -1
  14. package/dist/scripts/lib/embedding.js +4 -4
  15. package/dist/scripts/lib/embedding.js.map +1 -1
  16. package/dist/scripts/maintain-cleanup.d.ts +13 -0
  17. package/dist/scripts/maintain-cleanup.d.ts.map +1 -0
  18. package/dist/scripts/maintain-cleanup.js +93 -0
  19. package/dist/scripts/maintain-cleanup.js.map +1 -0
  20. package/dist/scripts/mindlore-fts5-index.js +3 -3
  21. package/dist/scripts/mindlore-fts5-index.js.map +1 -1
  22. package/dist/tests/embedding-hf-integration.test.d.ts +2 -0
  23. package/dist/tests/embedding-hf-integration.test.d.ts.map +1 -0
  24. package/dist/tests/embedding-hf-integration.test.js +52 -0
  25. package/dist/tests/embedding-hf-integration.test.js.map +1 -0
  26. package/dist/tests/embedding.test.d.ts +1 -1
  27. package/dist/tests/embedding.test.js +2 -2
  28. package/dist/tests/embedding.test.js.map +1 -1
  29. package/dist/tests/fts5.test.js +54 -0
  30. package/dist/tests/fts5.test.js.map +1 -1
  31. package/dist/tests/helpers/db.d.ts +16 -0
  32. package/dist/tests/helpers/db.d.ts.map +1 -1
  33. package/dist/tests/helpers/db.js +5 -1
  34. package/dist/tests/helpers/db.js.map +1 -1
  35. package/dist/tests/maintain-cleanup.test.d.ts +2 -0
  36. package/dist/tests/maintain-cleanup.test.d.ts.map +1 -0
  37. package/dist/tests/maintain-cleanup.test.js +65 -0
  38. package/dist/tests/maintain-cleanup.test.js.map +1 -0
  39. package/dist/tests/quality-populate.test.js +10 -0
  40. package/dist/tests/quality-populate.test.js.map +1 -1
  41. package/dist/tests/sqlite-vec-v12.test.d.ts +2 -0
  42. package/dist/tests/sqlite-vec-v12.test.d.ts.map +1 -0
  43. package/dist/tests/sqlite-vec-v12.test.js +72 -0
  44. package/dist/tests/sqlite-vec-v12.test.js.map +1 -0
  45. package/dist/tests/telemetry.test.d.ts +2 -0
  46. package/dist/tests/telemetry.test.d.ts.map +1 -0
  47. package/dist/tests/telemetry.test.js +55 -0
  48. package/dist/tests/telemetry.test.js.map +1 -0
  49. package/hooks/lib/mindlore-common.cjs +76 -9
  50. package/hooks/mindlore-cwd-changed.cjs +2 -2
  51. package/hooks/mindlore-decision-detector.cjs +5 -2
  52. package/hooks/mindlore-dont-repeat.cjs +2 -2
  53. package/hooks/mindlore-fts5-sync.cjs +7 -4
  54. package/hooks/mindlore-index.cjs +8 -5
  55. package/hooks/mindlore-model-router.cjs +2 -2
  56. package/hooks/mindlore-post-compact.cjs +5 -2
  57. package/hooks/mindlore-post-read.cjs +2 -2
  58. package/hooks/mindlore-pre-compact.cjs +5 -2
  59. package/hooks/mindlore-read-guard.cjs +2 -2
  60. package/hooks/mindlore-research-guard.cjs +2 -2
  61. package/hooks/mindlore-search.cjs +5 -2
  62. package/hooks/mindlore-session-end.cjs +17 -8
  63. package/hooks/mindlore-session-focus.cjs +5 -2
  64. package/package.json +6 -4
  65. package/plugin.json +1 -1
  66. package/skills/mindlore-ingest/SKILL.md +27 -6
  67. package/templates/SCHEMA.md +106 -7
  68. package/templates/config.json +4 -1
@@ -62,6 +62,16 @@ function getProjectName() {
62
62
  return path.basename(process.cwd());
63
63
  }
64
64
 
65
+ function resolveProject(ftsProject, filePath, cwdFallback) {
66
+ if (ftsProject) return ftsProject;
67
+ const normalized = filePath.replace(/\\/g, '/');
68
+ const sessionMatch = normalized.match(/raw\/sessions\/([^/]+)\//);
69
+ if (sessionMatch) return sessionMatch[1];
70
+ const diaryMatch = normalized.match(/diary\/([^/]+)\//);
71
+ if (diaryMatch) return diaryMatch[1];
72
+ return cwdFallback;
73
+ }
74
+
65
75
  function getLatestDelta(diaryDir) {
66
76
  if (!fs.existsSync(diaryDir)) return null;
67
77
 
@@ -131,7 +141,8 @@ function extractFtsMetadata(meta, body, filePath, baseDir) {
131
141
  }
132
142
  const quality = meta.quality !== undefined && meta.quality !== null ? meta.quality : null;
133
143
  const dateCaptured = meta.date_captured || meta.date || null;
134
- return { slug, description, type, category, title, tags, quality, dateCaptured };
144
+ const project = meta.project || null;
145
+ return { slug, description, type, category, title, tags, quality, dateCaptured, project };
135
146
  }
136
147
 
137
148
  /**
@@ -629,6 +640,65 @@ const extractSkeleton = (() => {
629
640
  }
630
641
  })();
631
642
 
643
+ const TELEMETRY_KEEP_LINES = 200;
644
+
645
+ function _rotateFile(filePath, maxBytes, keepLines) {
646
+ try {
647
+ const stat = fs.statSync(filePath);
648
+ if (stat.size > maxBytes) {
649
+ const lines = fs.readFileSync(filePath, 'utf8').trim().split('\n');
650
+ fs.writeFileSync(filePath, lines.slice(-keepLines).join('\n') + '\n');
651
+ }
652
+ } catch { /* file may not exist yet */ }
653
+ }
654
+
655
+ let _telDirEnsured = false;
656
+
657
+ function _writeTelemetry(hookName, duration_ms, ok) {
658
+ try {
659
+ if (!_telDirEnsured) {
660
+ fs.mkdirSync(GLOBAL_MINDLORE_DIR, { recursive: true });
661
+ _telDirEnsured = true;
662
+ }
663
+ const telPath = path.join(GLOBAL_MINDLORE_DIR, 'telemetry.jsonl');
664
+ const line = JSON.stringify({ ts: new Date().toISOString(), hook: hookName, duration_ms, ok }) + '\n';
665
+ _rotateFile(telPath, HOOK_LOG_MAX_BYTES, TELEMETRY_KEEP_LINES);
666
+ fs.appendFileSync(telPath, line);
667
+ } catch { /* silent — telemetry must never crash hook */ }
668
+ }
669
+
670
+ async function withTelemetry(hookName, fn) {
671
+ const start = Date.now();
672
+ let ok = true;
673
+ let result;
674
+ let thrown;
675
+ try {
676
+ result = await fn();
677
+ } catch (err) {
678
+ ok = false;
679
+ thrown = err;
680
+ }
681
+ _writeTelemetry(hookName, Date.now() - start, ok);
682
+ if (thrown) throw thrown;
683
+ return result;
684
+ }
685
+
686
+ function withTelemetrySync(hookName, fn) {
687
+ const start = Date.now();
688
+ let ok = true;
689
+ let result;
690
+ let thrown;
691
+ try {
692
+ result = fn();
693
+ } catch (err) {
694
+ ok = false;
695
+ thrown = err;
696
+ }
697
+ _writeTelemetry(hookName, Date.now() - start, ok);
698
+ if (thrown) throw thrown;
699
+ return result;
700
+ }
701
+
632
702
  module.exports = {
633
703
  MINDLORE_DIR,
634
704
  GLOBAL_MINDLORE_DIR,
@@ -653,6 +723,7 @@ module.exports = {
653
723
  readConfig,
654
724
  detectSchemaVersion,
655
725
  getProjectName,
726
+ resolveProject,
656
727
  DEFAULT_MODELS,
657
728
  // Episodes (v0.4.1)
658
729
  EPISODE_KINDS_CJS,
@@ -689,6 +760,9 @@ module.exports = {
689
760
  isDaemonRunning,
690
761
  getDaemonPortFile,
691
762
  getDaemonPidFile,
763
+ // Telemetry (v0.6.0)
764
+ withTelemetry,
765
+ withTelemetrySync,
692
766
  };
693
767
 
694
768
  function isDaemonRunning(pidFile) {
@@ -789,14 +863,7 @@ function hookLog(hook, level, message) {
789
863
  msg: message,
790
864
  pid: process.pid,
791
865
  });
792
- // Rotate if file exceeds threshold (at most once per hook — each runs as separate process)
793
- try {
794
- const stat = fs.statSync(logFile);
795
- if (stat.size > HOOK_LOG_MAX_BYTES) {
796
- const lines = fs.readFileSync(logFile, 'utf8').trim().split('\n');
797
- fs.writeFileSync(logFile, lines.slice(-HOOK_LOG_KEEP_LINES).join('\n') + '\n');
798
- }
799
- } catch (_rotateErr) { /* file may not exist yet */ }
866
+ _rotateFile(logFile, HOOK_LOG_MAX_BYTES, HOOK_LOG_KEEP_LINES);
800
867
  fs.appendFileSync(logFile, entry + '\n');
801
868
  } catch (_err) {
802
869
  // Best effort — never crash a hook for logging
@@ -15,7 +15,7 @@
15
15
 
16
16
  const fs = require('fs');
17
17
  const path = require('path');
18
- const { findMindloreDir, globalDir, hookLog } = require('./lib/mindlore-common.cjs');
18
+ const { findMindloreDir, globalDir, hookLog, withTelemetry } = require('./lib/mindlore-common.cjs');
19
19
 
20
20
  function main() {
21
21
  const cwd = process.cwd();
@@ -54,4 +54,4 @@ function main() {
54
54
  }
55
55
  }
56
56
 
57
- try { main(); } catch (err) { hookLog('cwd-changed', 'error', err?.message ?? String(err)); }
57
+ withTelemetry('mindlore-cwd-changed', main).catch(err => { hookLog('cwd-changed', 'error', err?.message ?? String(err)); });
@@ -9,7 +9,7 @@
9
9
  * Does NOT block (exit 0) — advisory only.
10
10
  */
11
11
 
12
- const { findMindloreDir, readHookStdin, hookLog } = require('./lib/mindlore-common.cjs');
12
+ const { findMindloreDir, readHookStdin, hookLog, withTelemetry } = require('./lib/mindlore-common.cjs');
13
13
 
14
14
  const SIGNALS_TR = [
15
15
  'karar verdik', 'karar verildi', 'kararlastirdik', 'kararlaştırdık',
@@ -48,4 +48,7 @@ function main() {
48
48
  }
49
49
  }
50
50
 
51
- try { main(); } catch (err) { hookLog('decision-detector', 'error', err?.message ?? String(err)); }
51
+ withTelemetry('mindlore-decision-detector', main).catch(err => {
52
+ hookLog('mindlore-decision-detector', 'error', err?.message ?? String(err));
53
+ process.exit(0);
54
+ });
@@ -18,7 +18,7 @@
18
18
  const fs = require('fs');
19
19
  const path = require('path');
20
20
  const os = require('os');
21
- const { findMindloreDir, getProjectName, hookLog } = require('./lib/mindlore-common.cjs');
21
+ const { findMindloreDir, getProjectName, hookLog, withTelemetrySync } = require('./lib/mindlore-common.cjs');
22
22
 
23
23
  /**
24
24
  * File-persisted pattern cache — survives across process invocations.
@@ -219,4 +219,4 @@ function main() {
219
219
  });
220
220
  }
221
221
 
222
- try { main(); } catch (err) { hookLog('dont-repeat', 'error', err?.message ?? String(err)); }
222
+ try { withTelemetrySync('mindlore-dont-repeat', main); } catch (err) { hookLog('dont-repeat', 'error', err?.message ?? String(err)); }
@@ -13,7 +13,7 @@
13
13
 
14
14
  const fs = require('fs');
15
15
  const path = require('path');
16
- const { MINDLORE_DIR, DB_NAME, sha256, openDatabase, getAllMdFiles, parseFrontmatter, extractFtsMetadata, insertFtsRow, readHookStdin, getActiveMindloreDir, getProjectName, hookLog } = require('./lib/mindlore-common.cjs');
16
+ const { MINDLORE_DIR, DB_NAME, sha256, openDatabase, getAllMdFiles, parseFrontmatter, extractFtsMetadata, insertFtsRow, readHookStdin, getActiveMindloreDir, getProjectName, resolveProject, hookLog, withTelemetry } = require('./lib/mindlore-common.cjs');
17
17
 
18
18
  function main() {
19
19
  const filePath = readHookStdin(['path', 'file_path']);
@@ -60,9 +60,9 @@ function main() {
60
60
  if (existing && existing.content_hash === hash) continue;
61
61
 
62
62
  const { meta, body } = parseFrontmatter(content);
63
- const { slug, description, type, category, title, tags, quality, dateCaptured } = extractFtsMetadata(meta, body, file, baseDir);
63
+ const { slug, description, type, category, title, tags, quality, dateCaptured, project: ftsProject } = extractFtsMetadata(meta, body, file, baseDir);
64
64
  deleteFts.run(file);
65
- insertFtsRow(db, { path: file, slug, description, type, category, title, content: body, tags, quality, dateCaptured, project });
65
+ insertFtsRow(db, { path: file, slug, description, type, category, title, content: body, tags, quality, dateCaptured, project: resolveProject(ftsProject, file, project) });
66
66
  upsertHash.run(file, hash, now);
67
67
  }
68
68
  });
@@ -75,4 +75,7 @@ function main() {
75
75
  // process.stdout.write kaldırıldı (kimse görmüyor)
76
76
  }
77
77
 
78
- try { main(); } catch (err) { hookLog('fts5-sync', 'error', err?.message ?? String(err)); }
78
+ withTelemetry('mindlore-fts5-sync', main).catch(err => {
79
+ hookLog('mindlore-fts5-sync', 'error', err?.message ?? String(err));
80
+ process.exit(0);
81
+ });
@@ -10,7 +10,7 @@
10
10
 
11
11
  const fs = require('fs');
12
12
  const path = require('path');
13
- const { MINDLORE_DIR, DB_NAME, SKIP_FILES, sha256, openDatabase, parseFrontmatter, extractFtsMetadata, insertFtsRow, readHookStdin, getProjectName, globalDir, hookLog } = require('./lib/mindlore-common.cjs');
13
+ const { MINDLORE_DIR, DB_NAME, SKIP_FILES, sha256, openDatabase, parseFrontmatter, extractFtsMetadata, insertFtsRow, readHookStdin, getProjectName, resolveProject, globalDir, hookLog, withTelemetry } = require('./lib/mindlore-common.cjs');
14
14
 
15
15
  function main() {
16
16
  const filePath = readHookStdin(['path', 'file_path']);
@@ -77,12 +77,12 @@ function main() {
77
77
 
78
78
  // Parse frontmatter for rich FTS5 columns
79
79
  const { meta, body } = parseFrontmatter(content);
80
- const { slug, description, type, category, title, tags, quality, dateCaptured } = extractFtsMetadata(meta, body, filePath, baseDir);
80
+ const { slug, description, type, category, title, tags, quality, dateCaptured, project: ftsProject } = extractFtsMetadata(meta, body, filePath, baseDir);
81
81
 
82
82
  // Update FTS5 + hash atomically
83
83
  const updateIndex = db.transaction(() => {
84
84
  db.prepare('DELETE FROM mindlore_fts WHERE path = ?').run(filePath);
85
- insertFtsRow(db, { path: filePath, slug, description, type, category, title, content: body, tags, quality, dateCaptured, project: getProjectName() });
85
+ insertFtsRow(db, { path: filePath, slug, description, type, category, title, content: body, tags, quality, dateCaptured, project: resolveProject(ftsProject, filePath, getProjectName()) });
86
86
  db.prepare(
87
87
  `INSERT INTO file_hashes (path, content_hash, last_indexed)
88
88
  VALUES (?, ?, ?)
@@ -197,7 +197,7 @@ function catchUpScan(baseDir, dbPath) {
197
197
 
198
198
  const update = db.transaction(() => {
199
199
  db.prepare('DELETE FROM mindlore_fts WHERE path = ?').run(filePath);
200
- insertFtsRow(db, { path: filePath, ...ftsData, project: getProjectName() });
200
+ insertFtsRow(db, { path: filePath, ...ftsData, project: resolveProject(ftsData.project, filePath, getProjectName()) });
201
201
  db.prepare(
202
202
  `INSERT INTO file_hashes (path, content_hash, last_indexed)
203
203
  VALUES (?, ?, ?)
@@ -218,4 +218,7 @@ function catchUpScan(baseDir, dbPath) {
218
218
  }
219
219
  }
220
220
 
221
- main();
221
+ withTelemetry('mindlore-index', main).catch(err => {
222
+ hookLog('mindlore-index', 'error', err?.message ?? String(err));
223
+ process.exit(0);
224
+ });
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  const fs = require('fs');
9
- const { findMindloreDir, readConfig, DEFAULT_MODELS, hookLog } = require('./lib/mindlore-common.cjs');
9
+ const { findMindloreDir, readConfig, DEFAULT_MODELS, hookLog, withTelemetrySync } = require('./lib/mindlore-common.cjs');
10
10
 
11
11
  const SKILL_KEYS = Object.keys(DEFAULT_MODELS).filter((k) => k !== 'default');
12
12
  const MARKER_REGEX = new RegExp(`\\[mindlore:(${SKILL_KEYS.join('|')})\\]`);
@@ -51,4 +51,4 @@ function main() {
51
51
  process.stdout.write(JSON.stringify(output));
52
52
  }
53
53
 
54
- try { main(); } catch (err) { hookLog('model-router', 'error', err?.message ?? String(err)); }
54
+ try { withTelemetrySync('mindlore-model-router', main); } catch (err) { hookLog('model-router', 'error', err?.message ?? String(err)); }
@@ -14,7 +14,7 @@
14
14
 
15
15
  const fs = require('fs');
16
16
  const path = require('path');
17
- const { findMindloreDir, getLatestDelta, hookLog } = require('./lib/mindlore-common.cjs');
17
+ const { findMindloreDir, getLatestDelta, hookLog, withTelemetry } = require('./lib/mindlore-common.cjs');
18
18
 
19
19
  function main() {
20
20
  const baseDir = findMindloreDir();
@@ -43,4 +43,7 @@ function main() {
43
43
  }
44
44
  }
45
45
 
46
- try { main(); } catch (err) { hookLog('post-compact', 'error', err?.message ?? String(err)); }
46
+ withTelemetry('mindlore-post-compact', main).catch(err => {
47
+ hookLog('mindlore-post-compact', 'error', err?.message ?? String(err));
48
+ process.exit(0);
49
+ });
@@ -13,7 +13,7 @@
13
13
 
14
14
  const fs = require('fs');
15
15
  const path = require('path');
16
- const { findMindloreDir, getProjectName, hookLog } = require('./lib/mindlore-common.cjs');
16
+ const { findMindloreDir, getProjectName, hookLog, withTelemetry } = require('./lib/mindlore-common.cjs');
17
17
 
18
18
  const CODE_EXTS = new Set(['.ts', '.tsx', '.js', '.jsx', '.py', '.rs', '.go', '.java', '.c', '.cpp', '.h', '.css', '.scss', '.sql', '.sh', '.yaml', '.yml', '.json', '.toml', '.xml', '.cjs', '.mjs']);
19
19
  const PROSE_EXTS = new Set(['.md', '.txt', '.rst', '.adoc']);
@@ -103,4 +103,4 @@ function main() {
103
103
  });
104
104
  }
105
105
 
106
- try { main(); } catch (err) { hookLog('post-read', 'error', err?.message ?? String(err)); }
106
+ withTelemetry('mindlore-post-read', main).catch(err => { hookLog('post-read', 'error', err?.message ?? String(err)); });
@@ -11,7 +11,7 @@
11
11
 
12
12
  const fs = require('fs');
13
13
  const path = require('path');
14
- const { findMindloreDir, hookLog } = require('./lib/mindlore-common.cjs');
14
+ const { findMindloreDir, hookLog, withTelemetry } = require('./lib/mindlore-common.cjs');
15
15
 
16
16
  function main() {
17
17
  const baseDir = findMindloreDir();
@@ -63,4 +63,7 @@ function main() {
63
63
  process.stdout.write('[Mindlore: pre-compact FTS5 flush complete]\n');
64
64
  }
65
65
 
66
- try { main(); } catch (err) { hookLog('pre-compact', 'error', err?.message ?? String(err)); }
66
+ withTelemetry('mindlore-pre-compact', main).catch(err => {
67
+ hookLog('mindlore-pre-compact', 'error', err?.message ?? String(err));
68
+ process.exit(0);
69
+ });
@@ -14,7 +14,7 @@
14
14
 
15
15
  const fs = require('fs');
16
16
  const path = require('path');
17
- const { findMindloreDir, readHookStdin, getProjectName, hookLog, extractSkeleton } = require('./lib/mindlore-common.cjs');
17
+ const { findMindloreDir, readHookStdin, getProjectName, hookLog, extractSkeleton, withTelemetrySync } = require('./lib/mindlore-common.cjs');
18
18
 
19
19
  function main() {
20
20
  const baseDir = findMindloreDir();
@@ -102,4 +102,4 @@ function main() {
102
102
  }
103
103
  }
104
104
 
105
- try { main(); } catch (err) { hookLog('read-guard', 'error', err?.message ?? String(err)); }
105
+ try { withTelemetrySync('mindlore-read-guard', main); } catch (err) { hookLog('read-guard', 'error', err?.message ?? String(err)); }
@@ -14,7 +14,7 @@
14
14
 
15
15
  const fs = require('fs');
16
16
  const path = require('path');
17
- const { getAllDbs, requireDatabase, extractKeywords, sanitizeKeyword, hookLog } = require('./lib/mindlore-common.cjs');
17
+ const { getAllDbs, requireDatabase, extractKeywords, sanitizeKeyword, hookLog, withTelemetrySync } = require('./lib/mindlore-common.cjs');
18
18
 
19
19
  // Keywords that signal a research/web-search intent in agent prompts
20
20
  // Note: entries with dots/stars are regex patterns, rest are literals
@@ -173,4 +173,4 @@ function main() {
173
173
  process.stdout.write(JSON.stringify(output));
174
174
  }
175
175
 
176
- try { main(); } catch (err) { hookLog('research-guard', 'error', err?.message ?? String(err)); }
176
+ try { withTelemetrySync('mindlore-research-guard', main); } catch (err) { hookLog('research-guard', 'error', err?.message ?? String(err)); }
@@ -10,7 +10,7 @@
10
10
 
11
11
  const fs = require('fs');
12
12
  const path = require('path');
13
- const { getAllDbs, openDatabase, extractHeadings, readHookStdin, extractKeywords, sanitizeKeyword, readConfig, loadSqliteVecCjs, hasVecTableCjs, hookLog, incrementRecallCount, getDaemonPortFile } = require('./lib/mindlore-common.cjs');
13
+ const { getAllDbs, openDatabase, extractHeadings, readHookStdin, extractKeywords, sanitizeKeyword, readConfig, loadSqliteVecCjs, hasVecTableCjs, hookLog, incrementRecallCount, getDaemonPortFile, withTelemetry } = require('./lib/mindlore-common.cjs');
14
14
 
15
15
  const { execFileSync } = require('child_process');
16
16
 
@@ -307,4 +307,7 @@ function main() {
307
307
  }
308
308
  }
309
309
 
310
- try { main(); } catch (err) { hookLog('search', 'error', err?.message ?? String(err)); }
310
+ withTelemetry('mindlore-search', main).catch(err => {
311
+ hookLog('mindlore-search', 'error', err?.message ?? String(err));
312
+ process.exit(0);
313
+ });
@@ -13,7 +13,7 @@ const fs = require('fs');
13
13
  const path = require('path');
14
14
  const os = require('os');
15
15
  const { execSync, execFileSync, spawn } = require('child_process');
16
- const { findMindloreDir, globalDir, getProjectName, openDatabase, ensureEpisodesTable, hasEpisodesTable, insertBareEpisode, insertFtsRow, hookLog, SHARED_EXPORT_DIRS, resolveWin32Bin } = require('./lib/mindlore-common.cjs');
16
+ const { findMindloreDir, globalDir, getProjectName, openDatabase, ensureEpisodesTable, hasEpisodesTable, insertBareEpisode, insertFtsRow, hookLog, SHARED_EXPORT_DIRS, resolveWin32Bin, withTelemetry } = require('./lib/mindlore-common.cjs');
17
17
 
18
18
  const EXPORT_DIRS = SHARED_EXPORT_DIRS;
19
19
 
@@ -369,19 +369,25 @@ function writeEpisodeFile(baseDir, project, commits, changedFiles, reads) {
369
369
  fs.writeFileSync(filePath, lines.join('\n'), 'utf8');
370
370
  }
371
371
 
372
+ let _obsidianHelpersCache = undefined; // undefined = not yet attempted
372
373
  /**
373
374
  * Load obsidian-helpers from compiled dist (single source of truth for wikilink conversion).
374
375
  * Returns null if helpers not available (e.g. dev environment without build).
376
+ * Result is cached — require() runs at most once per process.
375
377
  */
376
- function loadObsidianHelpers() {
378
+ function getObsidianHelpers() {
379
+ if (_obsidianHelpersCache !== undefined) return _obsidianHelpersCache;
377
380
  try {
378
- // Resolve from package root (hooks/ is sibling to dist/)
379
381
  const hookDir = __dirname;
380
382
  const pkgRoot = path.dirname(hookDir);
381
383
  const helpersPath = path.join(pkgRoot, 'dist', 'scripts', 'lib', 'obsidian-helpers.js');
382
- if (!fs.existsSync(helpersPath)) return null;
383
- return require(helpersPath);
384
- } catch (_err) {
384
+ _obsidianHelpersCache = require(helpersPath);
385
+ return _obsidianHelpersCache;
386
+ } catch (err) {
387
+ if (process.env.MINDLORE_DEBUG === '1') {
388
+ process.stderr.write(`[mindlore] obsidian-helpers not available: ${err.message}\n`);
389
+ }
390
+ _obsidianHelpersCache = null;
385
391
  return null;
386
392
  }
387
393
  }
@@ -419,7 +425,7 @@ function syncObsidian(baseDir) {
419
425
  if (!vaultPath || typeof vaultPath !== 'string') return;
420
426
  if (!fs.existsSync(vaultPath)) return;
421
427
 
422
- const helpers = loadObsidianHelpers();
428
+ const helpers = getObsidianHelpers();
423
429
  // Fallback regex if helpers unavailable (strips path prefixes like the canonical version)
424
430
  const convertFn = helpers?.convertToWikilinks
425
431
  ?? ((c) => c.replace(/\[([^\]]+)\]\((?:\.\.?\/)?(?:[\w-]+\/)*([^/)]+)\.md\)/g, '[[$2]]'));
@@ -503,5 +509,8 @@ function syncGlobalRepo() {
503
509
  }
504
510
 
505
511
  if (!process.argv.includes('--worker')) {
506
- main();
512
+ withTelemetry('mindlore-session-end', main).catch(err => {
513
+ hookLog('mindlore-session-end', 'error', err?.message ?? String(err));
514
+ process.exit(0);
515
+ });
507
516
  }
@@ -10,7 +10,7 @@
10
10
 
11
11
  const fs = require('fs');
12
12
  const path = require('path');
13
- const { findMindloreDir, readConfig, openDatabase, hasEpisodesTable, querySupersededChains, formatSupersededChains, hookLog, getProjectName, parseFrontmatter, isDaemonRunning, getDaemonPidFile } = require('./lib/mindlore-common.cjs');
13
+ const { findMindloreDir, readConfig, openDatabase, hasEpisodesTable, querySupersededChains, formatSupersededChains, hookLog, getProjectName, parseFrontmatter, isDaemonRunning, getDaemonPidFile, withTelemetry } = require('./lib/mindlore-common.cjs');
14
14
 
15
15
  function main() {
16
16
  const baseDir = findMindloreDir();
@@ -166,4 +166,7 @@ function main() {
166
166
  }
167
167
  }
168
168
 
169
- main();
169
+ withTelemetry('mindlore-session-focus', main).catch(err => {
170
+ hookLog('mindlore-session-focus', 'error', err?.message ?? String(err));
171
+ process.exit(0);
172
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mindlore",
3
- "version": "0.5.8",
3
+ "version": "0.6.0",
4
4
  "description": "AI-native knowledge system for Claude Code",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -19,7 +19,8 @@
19
19
  "search": "node dist/scripts/mindlore-fts5-search.js",
20
20
  "quality": "node dist/scripts/quality-populate.js",
21
21
  "fetch-raw": "node dist/scripts/fetch-raw.js",
22
- "cc-sync": "node dist/scripts/cc-memory-bulk-sync.js"
22
+ "cc-sync": "node dist/scripts/cc-memory-bulk-sync.js",
23
+ "cleanup": "node dist/scripts/maintain-cleanup.js"
23
24
  },
24
25
  "keywords": [
25
26
  "claude-code",
@@ -45,7 +46,7 @@
45
46
  "node": ">=20.0.0"
46
47
  },
47
48
  "dependencies": {
48
- "better-sqlite3": "^11.10.0",
49
+ "better-sqlite3": "^12.9.0",
49
50
  "zod": "^4.3.6"
50
51
  },
51
52
  "devDependencies": {
@@ -54,7 +55,7 @@
54
55
  "@types/node": "^25.6.0",
55
56
  "@typescript-eslint/eslint-plugin": "^8.58.1",
56
57
  "@typescript-eslint/parser": "^8.58.1",
57
- "@xenova/transformers": "^2.17.2",
58
+ "@huggingface/transformers": "^4.2.0",
58
59
  "eslint": "^10.2.0",
59
60
  "globals": "^17.5.0",
60
61
  "jest": "^30.3.0",
@@ -63,6 +64,7 @@
63
64
  "typescript": "^6.0.2"
64
65
  },
65
66
  "files": [
67
+ "agents/",
66
68
  "dist/",
67
69
  "hooks/",
68
70
  "skills/",
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mindlore",
3
3
  "description": "AI-native knowledge system for Claude Code. Persistent, searchable, evolving knowledge base with FTS5.",
4
- "version": "0.5.8",
4
+ "version": "0.6.0",
5
5
  "skills": [
6
6
  {
7
7
  "name": "mindlore-ingest",
@@ -21,16 +21,36 @@ Add a new knowledge source to the `.mindlore/` knowledge base.
21
21
 
22
22
  ## Scope
23
23
 
24
- Determine target directory using `getActiveMindloreDir()` logic:
25
- - If CWD has `.mindlore/` write to project scope
26
- - Otherwise write to global `~/.mindlore/`
27
- - `--global` flag: force write to `~/.mindlore/` even if project scope exists
28
- - Never hardcode `.mindlore/` path — always resolve dynamically
24
+ Tüm ingest işlemleri global `~/.mindlore/` dizinine yazılır.
25
+ Proje ayrımı frontmatter'daki `project` alanı ile yapılır.
26
+ `raw/` altına proje bazlı klasörleme yapılmaz (session sync hariç).
27
+ Frontmatter'a `project: {CWD project adı}` otomatik eklenir.
29
28
 
30
29
  ## Trigger
31
30
 
32
31
  User shares a URL, text, file, or says "kaynak ekle", "source ingest", "bu linki kaydet", "knowledge ingest".
33
32
 
33
+ ## Multi-URL Support
34
+
35
+ Birden fazla URL verildiğinde (boşluk veya virgülle ayrılmış):
36
+
37
+ 1. URL'leri parse et — boşluk veya virgülle split
38
+ 2. Her URL için sırayla mevcut URL Mode pipeline'ını çalıştır:
39
+ - Fetch → raw/ yazımı → sources/ dönüşümü → INDEX.md güncelle → log → FTS5 sync
40
+ 3. Her URL için ayrı `skill-memory` dedup kontrolü yap
41
+ 4. Her URL sonrası 7-point quality gate uygula
42
+ 5. Hata olan URL'yi raporla ama sonrakine devam et (fail-forward)
43
+ 6. Tüm URL'ler bittikten sonra toplu rapor döndür:
44
+
45
+ ```json
46
+ { "processed": 3, "failed": 0, "skipped": 1, "sources": ["slug1", "slug2", "slug3"] }
47
+ ```
48
+
49
+ Örnek kullanım:
50
+ ```
51
+ /mindlore-ingest https://docs.aws.amazon.com/lambda/latest/dg/best-practices.html https://docs.aws.amazon.com/dynamodb/latest/developerguide/best-practices.html
52
+ ```
53
+
34
54
  ## Modes
35
55
 
36
56
  ### URL Mode (v0.5.2 — Zero-Token Pipeline)
@@ -60,6 +80,7 @@ If URL already in the list, warn user: "This URL was ingested recently. Re-inges
60
80
  3. **Write sources/ summary from truncated content:**
61
81
  - Extract: title, description (first paragraph), key topics
62
82
  - Generate frontmatter: slug, type: source, source_url, date_captured, tags, quality
83
+ - `project: {path.basename(process.cwd()) ile CWD'den alınan proje adı}`
63
84
  - `source_type` is auto-detected from URL pattern (see Source Summary Format)
64
85
  - Write to `$MINDLORE_DIR/sources/{slug}.md`
65
86
 
@@ -148,7 +169,7 @@ After every ingest, verify all 7 checkpoints before reporting success:
148
169
  ```
149
170
  Eğer score > 0.7 olan sonuç varsa KULLANICIYA SOR: "Bu içerik '${slug}' ile benzer görünüyor. Yine de eklensin mi?"
150
171
  Kullanıcı onaylarsa devam et, yoksa atla.
151
- 1. **raw/ file exists** — immutable capture written with frontmatter (slug, type, source_url)
172
+ 1. **raw/ file exists** — immutable capture written with frontmatter (slug, type, source_url, project)
152
173
  2. **sources/ summary exists** — processed summary with full frontmatter (slug, type, title, tags, quality, description)
153
174
  3. **INDEX.md updated** — stats line incremented, Recent section has new entry
154
175
  4. **Domain updated** — if relevant domain exists, new finding added (max 1 domain per ingest)