mindlore 0.6.0 → 0.6.2

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 (127) hide show
  1. package/README.md +20 -13
  2. package/SCHEMA.md +3 -2
  3. package/dist/scripts/cc-session-sync.d.ts +1 -0
  4. package/dist/scripts/cc-session-sync.d.ts.map +1 -1
  5. package/dist/scripts/cc-session-sync.js +55 -0
  6. package/dist/scripts/cc-session-sync.js.map +1 -1
  7. package/dist/scripts/fetch-raw.js +62 -2
  8. package/dist/scripts/fetch-raw.js.map +1 -1
  9. package/dist/scripts/init.js +92 -47
  10. package/dist/scripts/init.js.map +1 -1
  11. package/dist/scripts/lib/constants.d.ts +4 -0
  12. package/dist/scripts/lib/constants.d.ts.map +1 -1
  13. package/dist/scripts/lib/constants.js +11 -1
  14. package/dist/scripts/lib/constants.js.map +1 -1
  15. package/dist/scripts/lib/daemon.d.ts +1 -0
  16. package/dist/scripts/lib/daemon.d.ts.map +1 -1
  17. package/dist/scripts/lib/daemon.js +1 -0
  18. package/dist/scripts/lib/daemon.js.map +1 -1
  19. package/dist/scripts/lib/decay.d.ts.map +1 -1
  20. package/dist/scripts/lib/decay.js +3 -0
  21. package/dist/scripts/lib/decay.js.map +1 -1
  22. package/dist/scripts/lib/episodes.d.ts +1 -1
  23. package/dist/scripts/lib/episodes.d.ts.map +1 -1
  24. package/dist/scripts/lib/episodes.js +1 -1
  25. package/dist/scripts/lib/episodes.js.map +1 -1
  26. package/dist/scripts/lib/hybrid-search.d.ts +1 -0
  27. package/dist/scripts/lib/hybrid-search.d.ts.map +1 -1
  28. package/dist/scripts/lib/hybrid-search.js +16 -1
  29. package/dist/scripts/lib/hybrid-search.js.map +1 -1
  30. package/dist/scripts/lib/migrations-v061.d.ts +3 -0
  31. package/dist/scripts/lib/migrations-v061.d.ts.map +1 -0
  32. package/dist/scripts/lib/migrations-v061.js +58 -0
  33. package/dist/scripts/lib/migrations-v061.js.map +1 -0
  34. package/dist/scripts/lib/migrations-v062.d.ts +3 -0
  35. package/dist/scripts/lib/migrations-v062.d.ts.map +1 -0
  36. package/dist/scripts/lib/migrations-v062.js +35 -0
  37. package/dist/scripts/lib/migrations-v062.js.map +1 -0
  38. package/dist/scripts/lib/session-payload.d.ts.map +1 -1
  39. package/dist/scripts/lib/session-payload.js +12 -0
  40. package/dist/scripts/lib/session-payload.js.map +1 -1
  41. package/dist/scripts/lib/triage.d.ts +18 -0
  42. package/dist/scripts/lib/triage.d.ts.map +1 -0
  43. package/dist/scripts/lib/triage.js +81 -0
  44. package/dist/scripts/lib/triage.js.map +1 -0
  45. package/dist/scripts/mindlore-daemon.js +1 -0
  46. package/dist/scripts/mindlore-daemon.js.map +1 -1
  47. package/dist/scripts/mindlore-doctor.d.ts +18 -0
  48. package/dist/scripts/mindlore-doctor.d.ts.map +1 -0
  49. package/dist/scripts/mindlore-doctor.js +243 -0
  50. package/dist/scripts/mindlore-doctor.js.map +1 -0
  51. package/dist/scripts/mindlore-fts5-index.js +16 -3
  52. package/dist/scripts/mindlore-fts5-index.js.map +1 -1
  53. package/dist/scripts/mindlore-fts5-search.js +12 -10
  54. package/dist/scripts/mindlore-fts5-search.js.map +1 -1
  55. package/dist/scripts/mindlore-health-check.d.ts +10 -0
  56. package/dist/scripts/mindlore-health-check.d.ts.map +1 -1
  57. package/dist/scripts/mindlore-health-check.js +26 -1
  58. package/dist/scripts/mindlore-health-check.js.map +1 -1
  59. package/dist/scripts/mindlore-perf.d.ts +22 -0
  60. package/dist/scripts/mindlore-perf.d.ts.map +1 -0
  61. package/dist/scripts/mindlore-perf.js +130 -0
  62. package/dist/scripts/mindlore-perf.js.map +1 -0
  63. package/dist/tests/compaction-snapshot.test.d.ts +2 -0
  64. package/dist/tests/compaction-snapshot.test.d.ts.map +1 -0
  65. package/dist/tests/compaction-snapshot.test.js +55 -0
  66. package/dist/tests/compaction-snapshot.test.js.map +1 -0
  67. package/dist/tests/decay.test.js +29 -0
  68. package/dist/tests/decay.test.js.map +1 -1
  69. package/dist/tests/diary.test.js +3 -3
  70. package/dist/tests/diary.test.js.map +1 -1
  71. package/dist/tests/doctor.test.d.ts +2 -0
  72. package/dist/tests/doctor.test.d.ts.map +1 -0
  73. package/dist/tests/doctor.test.js +82 -0
  74. package/dist/tests/doctor.test.js.map +1 -0
  75. package/dist/tests/fetch-raw.test.js +12 -5
  76. package/dist/tests/fetch-raw.test.js.map +1 -1
  77. package/dist/tests/fts5.test.js +28 -0
  78. package/dist/tests/fts5.test.js.map +1 -1
  79. package/dist/tests/health-check-memory.test.js +27 -0
  80. package/dist/tests/health-check-memory.test.js.map +1 -1
  81. package/dist/tests/helpers/db.d.ts.map +1 -1
  82. package/dist/tests/helpers/db.js +3 -1
  83. package/dist/tests/helpers/db.js.map +1 -1
  84. package/dist/tests/hook-smoke.test.js +27 -0
  85. package/dist/tests/hook-smoke.test.js.map +1 -1
  86. package/dist/tests/hybrid-search.test.js +25 -0
  87. package/dist/tests/hybrid-search.test.js.map +1 -1
  88. package/dist/tests/init.test.js +20 -0
  89. package/dist/tests/init.test.js.map +1 -1
  90. package/dist/tests/migrations-v061.test.d.ts +2 -0
  91. package/dist/tests/migrations-v061.test.d.ts.map +1 -0
  92. package/dist/tests/migrations-v061.test.js +70 -0
  93. package/dist/tests/migrations-v061.test.js.map +1 -0
  94. package/dist/tests/migrations-v062.test.d.ts +2 -0
  95. package/dist/tests/migrations-v062.test.d.ts.map +1 -0
  96. package/dist/tests/migrations-v062.test.js +61 -0
  97. package/dist/tests/migrations-v062.test.js.map +1 -0
  98. package/dist/tests/nomination.test.js +1 -1
  99. package/dist/tests/savings.test.d.ts +2 -0
  100. package/dist/tests/savings.test.d.ts.map +1 -0
  101. package/dist/tests/savings.test.js +87 -0
  102. package/dist/tests/savings.test.js.map +1 -0
  103. package/dist/tests/session-focus.test.js +15 -0
  104. package/dist/tests/session-focus.test.js.map +1 -1
  105. package/dist/tests/session-summary.test.d.ts +2 -0
  106. package/dist/tests/session-summary.test.d.ts.map +1 -0
  107. package/dist/tests/session-summary.test.js +102 -0
  108. package/dist/tests/session-summary.test.js.map +1 -0
  109. package/dist/tests/telemetry-perf.test.d.ts +2 -0
  110. package/dist/tests/telemetry-perf.test.d.ts.map +1 -0
  111. package/dist/tests/telemetry-perf.test.js +65 -0
  112. package/dist/tests/telemetry-perf.test.js.map +1 -0
  113. package/dist/tests/triage.test.d.ts +2 -0
  114. package/dist/tests/triage.test.d.ts.map +1 -0
  115. package/dist/tests/triage.test.js +69 -0
  116. package/dist/tests/triage.test.js.map +1 -0
  117. package/hooks/lib/mindlore-common.cjs +54 -6
  118. package/hooks/mindlore-fts5-sync.cjs +10 -4
  119. package/hooks/mindlore-post-compact.cjs +23 -3
  120. package/hooks/mindlore-pre-compact.cjs +78 -3
  121. package/hooks/mindlore-search.cjs +20 -11
  122. package/hooks/mindlore-session-end.cjs +32 -19
  123. package/hooks/mindlore-session-focus.cjs +107 -72
  124. package/package.json +7 -4
  125. package/plugin.json +1 -1
  126. package/skills/mindlore-maintain/SKILL.md +1 -0
  127. package/templates/config.json +1 -1
@@ -154,6 +154,23 @@ const SQL_FTS_CREATE =
154
154
  const SQL_FTS_INSERT =
155
155
  'INSERT INTO mindlore_fts (path, slug, description, type, category, title, content, tags, quality, date_captured, project) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
156
156
 
157
+ const SQL_FTS_SESSIONS_CREATE =
158
+ "CREATE VIRTUAL TABLE IF NOT EXISTS mindlore_fts_sessions USING fts5(path, slug, description, type, category, title, content, tags, quality, date_captured, project)";
159
+
160
+ const SQL_FTS_SESSIONS_INSERT =
161
+ 'INSERT INTO mindlore_fts_sessions (path, slug, description, type, category, title, content, tags, quality, date_captured, project) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
162
+
163
+ const SESSION_CATEGORIES = ['cc-subagent', 'cc-session'];
164
+
165
+ function isSessionCategory(category) {
166
+ return SESSION_CATEGORIES.includes(category);
167
+ }
168
+
169
+ const VERSION_RE = /v(\d+)\.(\d+)(?:\.(\d+))?/g;
170
+ function fixVersionTokens(query) {
171
+ return query.replace(VERSION_RE, (_m, a, b, c) => c ? `"v${a} ${b} ${c}"` : `"v${a} ${b}"`);
172
+ }
173
+
157
174
  /**
158
175
  * Insert a row into FTS5 using an object parameter (replaces positional args).
159
176
  */
@@ -330,7 +347,7 @@ CREATE TABLE IF NOT EXISTS episodes (
330
347
  // ~625 tokens context budget for multi-session inject (~4 chars/token)
331
348
  const MULTI_SESSION_TOKEN_CAP_CHARS = 2500;
332
349
 
333
- const EPISODE_KINDS_CJS = ['session', 'decision', 'event', 'preference', 'learning', 'friction', 'discovery', 'nomination'];
350
+ const EPISODE_KINDS_CJS = ['session', 'decision', 'event', 'preference', 'learning', 'friction', 'discovery', 'nomination', 'session-summary'];
334
351
 
335
352
  /**
336
353
  * Valid episode statuses. CO-EVOLUTION: mirrors EPISODE_STATUSES in scripts/lib/episodes.ts
@@ -623,7 +640,7 @@ function resolveWin32Bin(name) {
623
640
  if (process.platform === 'win32') {
624
641
  try {
625
642
  return require('child_process')
626
- .execSync(`where ${name}`, { encoding: 'utf8', timeout: 3000 })
643
+ .execSync(`where ${name}`, { encoding: 'utf8', timeout: 3000, windowsHide: true })
627
644
  .trim().split('\n')[0].trim();
628
645
  } catch (_e) { /* fall through */ }
629
646
  }
@@ -654,14 +671,20 @@ function _rotateFile(filePath, maxBytes, keepLines) {
654
671
 
655
672
  let _telDirEnsured = false;
656
673
 
657
- function _writeTelemetry(hookName, duration_ms, ok) {
674
+ function _writeTelemetry({ hookName, duration_ms, ok, extra }) {
658
675
  try {
659
676
  if (!_telDirEnsured) {
660
677
  fs.mkdirSync(GLOBAL_MINDLORE_DIR, { recursive: true });
661
678
  _telDirEnsured = true;
662
679
  }
663
680
  const telPath = path.join(GLOBAL_MINDLORE_DIR, 'telemetry.jsonl');
664
- const line = JSON.stringify({ ts: new Date().toISOString(), hook: hookName, duration_ms, ok }) + '\n';
681
+ const entry = { ts: new Date().toISOString(), hook: hookName, duration_ms, ok };
682
+ if (extra && typeof extra === 'object') {
683
+ for (const key of ['inject_tokens', 'source_tokens', 'injected_tokens', 'full_read_tokens']) {
684
+ if (typeof extra[key] === 'number') entry[key] = extra[key];
685
+ }
686
+ }
687
+ const line = JSON.stringify(entry) + '\n';
665
688
  _rotateFile(telPath, HOOK_LOG_MAX_BYTES, TELEMETRY_KEEP_LINES);
666
689
  fs.appendFileSync(telPath, line);
667
690
  } catch { /* silent — telemetry must never crash hook */ }
@@ -678,7 +701,8 @@ async function withTelemetry(hookName, fn) {
678
701
  ok = false;
679
702
  thrown = err;
680
703
  }
681
- _writeTelemetry(hookName, Date.now() - start, ok);
704
+ const extra = (result && typeof result === 'object') ? result : undefined;
705
+ _writeTelemetry({ hookName, duration_ms: Date.now() - start, ok, extra });
682
706
  if (thrown) throw thrown;
683
707
  return result;
684
708
  }
@@ -694,11 +718,28 @@ function withTelemetrySync(hookName, fn) {
694
718
  ok = false;
695
719
  thrown = err;
696
720
  }
697
- _writeTelemetry(hookName, Date.now() - start, ok);
721
+ const extra = (result && typeof result === 'object') ? result : undefined;
722
+ _writeTelemetry({ hookName, duration_ms: Date.now() - start, ok, extra });
698
723
  if (thrown) throw thrown;
699
724
  return result;
700
725
  }
701
726
 
727
+ function withTimeoutDb(db, sql, params = [], { timeoutMs = 3000, mode = 'all' } = {}) {
728
+ if (!db) return mode === 'get' ? undefined : [];
729
+ try {
730
+ db.pragma(`busy_timeout = ${timeoutMs}`);
731
+ const stmt = db.prepare(sql);
732
+ if (mode === 'get') {
733
+ return params.length > 0 ? stmt.get(...params) : stmt.get();
734
+ }
735
+ return params.length > 0 ? stmt.all(...params) : stmt.all();
736
+ } catch (err) {
737
+ hookLog('timeout', 'warn', `DB query timeout/error: ${err.message}`);
738
+ _writeTelemetry({ hookName: 'db_timeout', duration_ms: 0, ok: false });
739
+ return mode === 'get' ? undefined : [];
740
+ }
741
+ }
742
+
702
743
  module.exports = {
703
744
  MINDLORE_DIR,
704
745
  GLOBAL_MINDLORE_DIR,
@@ -715,6 +756,11 @@ module.exports = {
715
756
  readHookStdin,
716
757
  SQL_FTS_CREATE,
717
758
  SQL_FTS_INSERT,
759
+ SQL_FTS_SESSIONS_CREATE,
760
+ SQL_FTS_SESSIONS_INSERT,
761
+ SESSION_CATEGORIES,
762
+ isSessionCategory,
763
+ fixVersionTokens,
718
764
  insertFtsRow,
719
765
  extractHeadings,
720
766
  requireDatabase,
@@ -763,6 +809,8 @@ module.exports = {
763
809
  // Telemetry (v0.6.0)
764
810
  withTelemetry,
765
811
  withTelemetrySync,
812
+ // DB timeout wrapper (v0.6.1)
813
+ withTimeoutDb,
766
814
  };
767
815
 
768
816
  function isDaemonRunning(pidFile) {
@@ -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, resolveProject, hookLog, withTelemetry } = require('./lib/mindlore-common.cjs');
16
+ const { MINDLORE_DIR, DB_NAME, sha256, openDatabase, getAllMdFiles, parseFrontmatter, extractFtsMetadata, insertFtsRow, readHookStdin, getActiveMindloreDir, getProjectName, resolveProject, hookLog, withTelemetry, SQL_FTS_SESSIONS_INSERT, isSessionCategory } = require('./lib/mindlore-common.cjs');
17
17
 
18
18
  function main() {
19
19
  const filePath = readHookStdin(['path', 'file_path']);
@@ -39,6 +39,8 @@ function main() {
39
39
 
40
40
  const getHash = db.prepare('SELECT content_hash FROM file_hashes WHERE path = ?');
41
41
  const deleteFts = db.prepare('DELETE FROM mindlore_fts WHERE path = ?');
42
+ const deleteFtsSessions = db.prepare('DELETE FROM mindlore_fts_sessions WHERE path = ?');
43
+ const insertFtsSessions = db.prepare(SQL_FTS_SESSIONS_INSERT);
42
44
  const upsertHash = db.prepare(`
43
45
  INSERT INTO file_hashes (path, content_hash, last_indexed)
44
46
  VALUES (?, ?, ?)
@@ -61,8 +63,14 @@ function main() {
61
63
 
62
64
  const { meta, body } = parseFrontmatter(content);
63
65
  const { slug, description, type, category, title, tags, quality, dateCaptured, project: ftsProject } = extractFtsMetadata(meta, body, file, baseDir);
66
+ const resolvedProject = resolveProject(ftsProject, file, project);
64
67
  deleteFts.run(file);
65
- insertFtsRow(db, { path: file, slug, description, type, category, title, content: body, tags, quality, dateCaptured, project: resolveProject(ftsProject, file, project) });
68
+ deleteFtsSessions.run(file);
69
+ if (isSessionCategory(category)) {
70
+ insertFtsSessions.run(file, slug, description, type, category, title, body, tags, quality ?? null, dateCaptured ?? null, resolvedProject);
71
+ } else {
72
+ insertFtsRow(db, { path: file, slug, description, type, category, title, content: body, tags, quality, dateCaptured, project: resolvedProject });
73
+ }
66
74
  upsertHash.run(file, hash, now);
67
75
  }
68
76
  });
@@ -71,8 +79,6 @@ function main() {
71
79
  db.close();
72
80
  }
73
81
 
74
- // FileChanged event stdout'u yutulur — log gerekiyorsa dosyaya yaz
75
- // process.stdout.write kaldırıldı (kimse görmüyor)
76
82
  }
77
83
 
78
84
  withTelemetry('mindlore-fts5-sync', main).catch(err => {
@@ -14,7 +14,7 @@
14
14
 
15
15
  const fs = require('fs');
16
16
  const path = require('path');
17
- const { findMindloreDir, getLatestDelta, hookLog, withTelemetry } = require('./lib/mindlore-common.cjs');
17
+ const { findMindloreDir, getLatestDelta, readConfig, hookLog, withTelemetry } = require('./lib/mindlore-common.cjs');
18
18
 
19
19
  function main() {
20
20
  const baseDir = findMindloreDir();
@@ -29,8 +29,8 @@ function main() {
29
29
  output.push(`[Mindlore INDEX (post-compact)]\n${content}`);
30
30
  }
31
31
 
32
- // Re-inject latest delta
33
32
  const diaryDir = path.join(baseDir, 'diary');
33
+
34
34
  const latestDelta = getLatestDelta(diaryDir);
35
35
  if (latestDelta) {
36
36
  const deltaContent = fs.readFileSync(latestDelta, 'utf8').trim();
@@ -38,8 +38,28 @@ function main() {
38
38
  output.push(`[Mindlore Delta (post-compact): ${deltaName}]\n${deltaContent}`);
39
39
  }
40
40
 
41
+ try {
42
+ const snapshots = fs.readdirSync(diaryDir)
43
+ .filter(f => f.startsWith('compaction-snapshot-'))
44
+ .sort();
45
+ if (snapshots.length > 0) {
46
+ const latestSnapshot = snapshots[snapshots.length - 1];
47
+ const snapshotContent = fs.readFileSync(
48
+ path.join(diaryDir, latestSnapshot), 'utf8'
49
+ ).trim();
50
+ output.push(`[Mindlore Compaction Resume]\n${snapshotContent}`);
51
+ }
52
+ } catch (_err) { /* snapshot best-effort */ }
53
+
41
54
  if (output.length > 0) {
42
- process.stdout.write(output.join('\n\n') + '\n');
55
+ const config = readConfig(baseDir);
56
+ const budgetConfig = config?.tokenBudget ?? {};
57
+ const maxInjectChars = (budgetConfig.sessionInject || 2000) * 4;
58
+ let joined = output.join('\n\n');
59
+ if (joined.length > maxInjectChars) {
60
+ joined = joined.slice(0, maxInjectChars) + '\n[...truncated by token budget]';
61
+ }
62
+ process.stdout.write(joined + '\n');
43
63
  }
44
64
  }
45
65
 
@@ -11,7 +11,7 @@
11
11
 
12
12
  const fs = require('fs');
13
13
  const path = require('path');
14
- const { findMindloreDir, hookLog, withTelemetry } = require('./lib/mindlore-common.cjs');
14
+ const { findMindloreDir, openDatabase, hookLog, withTelemetry } = require('./lib/mindlore-common.cjs');
15
15
 
16
16
  function main() {
17
17
  const baseDir = findMindloreDir();
@@ -25,6 +25,7 @@ function main() {
25
25
  spawnSync('node', [indexScript, baseDir], {
26
26
  timeout: 10000,
27
27
  stdio: 'pipe',
28
+ windowsHide: true,
28
29
  });
29
30
  } catch (_err) {
30
31
  // Non-fatal — index might fail if better-sqlite3 not available
@@ -33,11 +34,10 @@ function main() {
33
34
 
34
35
  const now = new Date();
35
36
  const iso = now.toISOString();
37
+ const ts = iso.replace(/[:.]/g, '-');
36
38
 
37
- // Write pre-compact episode
38
39
  const episodesDir = path.join(baseDir, 'episodes');
39
40
  try {
40
- const ts = iso.replace(/[:.]/g, '-');
41
41
  const episodePath = path.join(episodesDir, `pre-compact-${ts}.md`);
42
42
  const content = [
43
43
  '---',
@@ -60,6 +60,81 @@ function main() {
60
60
  fs.appendFileSync(logPath, entry, 'utf8');
61
61
  } catch (_err) { /* log file may not exist */ }
62
62
 
63
+ // Build compaction snapshot (#17)
64
+ const diaryDir = path.join(baseDir, 'diary');
65
+ try {
66
+ const sections = [];
67
+
68
+ try {
69
+ const dbPath = path.join(baseDir, 'mindlore.db');
70
+ const db = openDatabase(dbPath, { readonly: true });
71
+ if (db) {
72
+ try {
73
+ const episodes = db.prepare(
74
+ "SELECT kind, summary FROM episodes WHERE created_at > datetime('now', '-4 hours') ORDER BY created_at DESC LIMIT 20"
75
+ ).all();
76
+ if (episodes.length > 0) {
77
+ const grouped = {};
78
+ for (const ep of episodes) {
79
+ const kind = ep.kind || 'other';
80
+ if (!grouped[kind]) grouped[kind] = [];
81
+ grouped[kind].push(ep.summary);
82
+ }
83
+ sections.push('## Session Episodes');
84
+ for (const [kind, items] of Object.entries(grouped)) {
85
+ sections.push(`### ${kind}`);
86
+ for (const item of items) sections.push(`- ${item}`);
87
+ }
88
+ }
89
+ } finally {
90
+ db.close();
91
+ }
92
+ }
93
+ } catch (_err) { /* graceful skip */ }
94
+
95
+ try {
96
+ const { execSync } = require('child_process');
97
+ const diffStat = execSync('git diff --stat HEAD 2>/dev/null || echo ""', {
98
+ encoding: 'utf8', timeout: 2000, windowsHide: true,
99
+ }).trim();
100
+ if (diffStat) {
101
+ sections.push('## Changed Files (uncommitted)', '```', diffStat, '```');
102
+ }
103
+ } catch (_err) { /* no git */ }
104
+
105
+ try {
106
+ const plansDir = path.join(process.cwd(), '.claude', 'plans');
107
+ if (fs.existsSync(plansDir)) {
108
+ const plans = fs.readdirSync(plansDir).filter(f => f.endsWith('.md'));
109
+ if (plans.length > 0) {
110
+ const latestPlan = plans.sort().pop();
111
+ sections.push(`## Active Plan: ${latestPlan}`);
112
+ }
113
+ }
114
+ } catch (_err) { /* skip */ }
115
+
116
+ if (sections.length > 0) {
117
+ const snapshotContent = [
118
+ '---',
119
+ 'type: compaction-snapshot',
120
+ `date: ${iso.slice(0, 10)}`,
121
+ `project: ${path.basename(process.cwd())}`,
122
+ '---',
123
+ '',
124
+ ...sections,
125
+ ].join('\n');
126
+ fs.writeFileSync(path.join(diaryDir, `compaction-snapshot-${ts}.md`), snapshotContent);
127
+ }
128
+
129
+ const snapshots = fs.readdirSync(diaryDir)
130
+ .filter(f => f.startsWith('compaction-snapshot-'))
131
+ .sort();
132
+ while (snapshots.length > 5) {
133
+ const oldest = snapshots.shift();
134
+ if (oldest) fs.unlinkSync(path.join(diaryDir, oldest));
135
+ }
136
+ } catch (_err) { /* snapshot is best-effort */ }
137
+
63
138
  process.stdout.write('[Mindlore: pre-compact FTS5 flush complete]\n');
64
139
  }
65
140
 
@@ -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, withTelemetry } = require('./lib/mindlore-common.cjs');
13
+ const { getAllDbs, openDatabase, extractHeadings, readHookStdin, extractKeywords, sanitizeKeyword, readConfig, loadSqliteVecCjs, hasVecTableCjs, hookLog, incrementRecallCount, getDaemonPortFile, withTelemetry, fixVersionTokens } = require('./lib/mindlore-common.cjs');
14
14
 
15
15
  const { execFileSync } = require('child_process');
16
16
 
@@ -33,7 +33,7 @@ function requestEmbeddingSync(query) {
33
33
  const clientScript = path.join(__dirname, '..', 'scripts', 'lib', 'daemon-client.js');
34
34
  if (!fs.existsSync(clientScript)) return null;
35
35
  const result = execFileSync(process.execPath, [clientScript, portFile, query, '300'], {
36
- timeout: 500, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe']
36
+ timeout: 500, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true,
37
37
  });
38
38
  const parsed = JSON.parse(result.trim());
39
39
  return parsed.type === 'embedding' ? parsed.embedding : null;
@@ -122,11 +122,21 @@ function searchDb(dbPath, keywords) {
122
122
  const sanitized = keywords.map(sanitizeKeyword).filter(Boolean);
123
123
  if (sanitized.length === 0) { db.close(); return results; }
124
124
 
125
- const ftsQuery = sanitized.join(' OR ');
126
- const rows = db.prepare(
125
+ const ftsQuery = fixVersionTokens(sanitized.join(' OR '));
126
+ const project = path.basename(process.cwd());
127
+
128
+ // v0.6.1: Project-scoped search with global fallback
129
+ let rows = db.prepare(
127
130
  `SELECT path, slug, description, category, title, tags, rank
128
- FROM mindlore_fts WHERE mindlore_fts MATCH ? ORDER BY rank LIMIT ?`
129
- ).all(ftsQuery, MAX_RESULTS * 2);
131
+ FROM mindlore_fts WHERE project = ? AND mindlore_fts MATCH ? ORDER BY rank LIMIT ?`
132
+ ).all(project, ftsQuery, MAX_RESULTS * 2);
133
+
134
+ if (rows.length === 0) {
135
+ rows = db.prepare(
136
+ `SELECT path, slug, description, category, title, tags, rank
137
+ FROM mindlore_fts WHERE mindlore_fts MATCH ? ORDER BY rank LIMIT ?`
138
+ ).all(ftsQuery, MAX_RESULTS * 2);
139
+ }
130
140
 
131
141
  for (const r of rows) {
132
142
  results.push({
@@ -237,17 +247,16 @@ function main() {
237
247
  let totalUsed = 0;
238
248
  for (const r of relevant) {
239
249
  if (totalUsed >= totalChars) break;
240
- const meta = r.meta || {};
241
250
  const relativePath = path.relative(r.baseDir, r.path).replace(/\\/g, '/');
242
251
 
243
252
  const headings = r.headings || [];
244
253
 
245
- const category = meta.category || path.dirname(relativePath).split('/')[0];
246
- const title = meta.title || meta.slug || path.basename(r.path, '.md');
247
- const description = meta.description || '';
254
+ const category = r.category || path.dirname(relativePath).split('/')[0];
255
+ const title = r.title || r.slug || path.basename(r.path, '.md');
256
+ const description = r.description || '';
248
257
 
249
258
  const headingStr = headings.length > 0 ? `\nBasliklar: ${headings.join(', ')}` : '';
250
- const tagsStr = meta.tags ? `\nTags: ${meta.tags}` : '';
259
+ const tagsStr = r.tags ? `\nTags: ${r.tags}` : '';
251
260
  const entry = `[Mindlore: ${category}/${title}] ${description}\nDosya: ${relativePath}${tagsStr}${headingStr}`;
252
261
  const truncated = entry.slice(0, perResultChars);
253
262
  totalUsed += truncated.length;
@@ -54,6 +54,7 @@ if (process.argv.includes('--worker')) {
54
54
  execFileSync(nodeExe, [scriptPath, ...args], {
55
55
  timeout: timeoutMs,
56
56
  env: { ...process.env, MINDLORE_HOME: baseDir },
57
+ windowsHide: true,
57
58
  });
58
59
  hookLog('session-end', 'info', label + ' completed');
59
60
  } catch (err) {
@@ -112,6 +113,7 @@ function getRecentGitInfo() {
112
113
  encoding: 'utf8',
113
114
  timeout: 5000,
114
115
  stdio: ['pipe', 'pipe', 'pipe'],
116
+ windowsHide: true,
115
117
  }).trim();
116
118
  if (!raw) return { commits: [], changedFiles: [] };
117
119
 
@@ -219,6 +221,20 @@ function main() {
219
221
  fs.appendFileSync(logPath, logEntry, 'utf8');
220
222
  }
221
223
 
224
+ // Raw accumulation warning
225
+ try {
226
+ const rawDir = path.join(baseDir, 'raw');
227
+ const sourcesDir = path.join(baseDir, 'sources');
228
+ if (fs.existsSync(rawDir) && fs.existsSync(sourcesDir)) {
229
+ const rawFiles = fs.readdirSync(rawDir).filter(f => f.endsWith('.md'));
230
+ const sourceFiles = new Set(fs.readdirSync(sourcesDir).filter(f => f.endsWith('.md')));
231
+ const unpromoted = rawFiles.filter(f => !sourceFiles.has(f)).length;
232
+ if (unpromoted >= 5) {
233
+ process.stdout.write(`[Mindlore] ${unpromoted} raw dosya promote bekliyor — \`/mindlore-maintain triage\` ile listele\n`);
234
+ }
235
+ }
236
+ } catch (_err) { /* graceful skip */ }
237
+
222
238
  // Heavy ops: detach into child process so CC can exit immediately.
223
239
  // Fixes "Hook cancelled" when CC kills the hook before completion.
224
240
  // See: https://github.com/anthropics/claude-code/issues/41577
@@ -234,6 +250,7 @@ function main() {
234
250
  detached: true,
235
251
  stdio: 'ignore',
236
252
  cwd: process.cwd(),
253
+ windowsHide: true,
237
254
  });
238
255
  child.unref();
239
256
  } catch (_err) {
@@ -433,29 +450,25 @@ function syncObsidian(baseDir) {
433
450
  const destBase = path.join(vaultPath, 'mindlore');
434
451
  let exported = 0;
435
452
 
436
- for (const dir of EXPORT_DIRS) {
437
- const srcDir = path.join(baseDir, dir);
438
- if (!fs.existsSync(srcDir)) continue;
439
-
440
- const destDir = path.join(destBase, dir);
453
+ function walkAndExport(srcDir, destDir) {
454
+ if (!fs.existsSync(srcDir)) return;
441
455
  fs.mkdirSync(destDir, { recursive: true });
442
-
443
- for (const file of fs.readdirSync(srcDir).filter(f => f.endsWith('.md') && !f.startsWith('_'))) {
444
- if (exportMdFile(path.join(srcDir, file), path.join(destDir, file), convertFn)) exported++;
445
- }
446
-
447
- // One-level subdirectories (e.g. diary/mindlore/)
448
456
  for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
449
- if (!entry.isDirectory() || entry.name.startsWith('_') || entry.name.startsWith('.')) continue;
450
- const subSrc = path.join(srcDir, entry.name);
451
- const subDest = path.join(destDir, entry.name);
452
- fs.mkdirSync(subDest, { recursive: true });
453
- for (const file of fs.readdirSync(subSrc).filter(f => f.endsWith('.md') && !f.startsWith('_'))) {
454
- if (exportMdFile(path.join(subSrc, file), path.join(subDest, file), convertFn)) exported++;
457
+ if (entry.name.startsWith('_') || entry.name.startsWith('.')) continue;
458
+ const srcPath = path.join(srcDir, entry.name);
459
+ const destPath = path.join(destDir, entry.name);
460
+ if (entry.isDirectory()) {
461
+ walkAndExport(srcPath, destPath);
462
+ } else if (entry.isFile() && entry.name.endsWith('.md')) {
463
+ if (exportMdFile(srcPath, destPath, convertFn)) exported++;
455
464
  }
456
465
  }
457
466
  }
458
467
 
468
+ for (const dir of EXPORT_DIRS) {
469
+ walkAndExport(path.join(baseDir, dir), path.join(destBase, dir));
470
+ }
471
+
459
472
  for (const rootFile of ['INDEX.md', 'log.md']) {
460
473
  const srcPath = path.join(baseDir, rootFile);
461
474
  if (!fs.existsSync(srcPath)) continue;
@@ -490,7 +503,7 @@ function syncGlobalRepo() {
490
503
  if (!fs.existsSync(gitDir)) return;
491
504
 
492
505
  const git = resolveGitBin();
493
- const execOpts = (timeout) => ({ cwd: gDir, encoding: 'utf8', timeout, stdio: 'pipe' });
506
+ const execOpts = (timeout) => ({ cwd: gDir, encoding: 'utf8', timeout, stdio: 'pipe', windowsHide: true });
494
507
 
495
508
  // Check for changes
496
509
  const status = execSync(`"${git}" status --porcelain`, execOpts(5000)).trim();
@@ -502,7 +515,7 @@ function syncGlobalRepo() {
502
515
 
503
516
  // Push — graceful fail if no remote or offline
504
517
  try {
505
- execSync(`"${git}" push`, execOpts(15000));
518
+ execSync(`"${git}" push`, execOpts(30000));
506
519
  } catch (_pushErr) {
507
520
  hookLog('session-end', 'warn', 'git push failed (offline?): ' + (_pushErr?.message ?? '').slice(0, 100));
508
521
  }