mindlore 0.7.5 → 0.7.7

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 (148) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/.claude-plugin/plugin.json +3 -3
  3. package/README.md +3 -1
  4. package/dist/scripts/bundle-hooks.js +5 -1
  5. package/dist/scripts/bundle-hooks.js.map +1 -1
  6. package/dist/scripts/init.js +49 -10
  7. package/dist/scripts/init.js.map +1 -1
  8. package/dist/scripts/lib/constants.d.ts +8 -0
  9. package/dist/scripts/lib/constants.d.ts.map +1 -1
  10. package/dist/scripts/lib/constants.js +9 -1
  11. package/dist/scripts/lib/constants.js.map +1 -1
  12. package/dist/scripts/lib/db-helpers.d.ts +6 -0
  13. package/dist/scripts/lib/db-helpers.d.ts.map +1 -1
  14. package/dist/scripts/lib/db-helpers.js +6 -0
  15. package/dist/scripts/lib/db-helpers.js.map +1 -1
  16. package/dist/scripts/lib/read-guard-core.d.ts +21 -0
  17. package/dist/scripts/lib/read-guard-core.d.ts.map +1 -0
  18. package/dist/scripts/lib/read-guard-core.js +40 -0
  19. package/dist/scripts/lib/read-guard-core.js.map +1 -0
  20. package/dist/scripts/lib/search-cache.d.ts +1 -7
  21. package/dist/scripts/lib/search-cache.d.ts.map +1 -1
  22. package/dist/scripts/lib/search-cache.js +3 -25
  23. package/dist/scripts/lib/search-cache.js.map +1 -1
  24. package/dist/scripts/lib/search-throttle.d.ts +10 -0
  25. package/dist/scripts/lib/search-throttle.d.ts.map +1 -0
  26. package/dist/scripts/lib/search-throttle.js +32 -0
  27. package/dist/scripts/lib/search-throttle.js.map +1 -0
  28. package/dist/scripts/lib/settings-cleanup.d.ts.map +1 -1
  29. package/dist/scripts/lib/settings-cleanup.js +2 -1
  30. package/dist/scripts/lib/settings-cleanup.js.map +1 -1
  31. package/dist/scripts/lib/setup-wizard.d.ts +13 -0
  32. package/dist/scripts/lib/setup-wizard.d.ts.map +1 -0
  33. package/dist/scripts/lib/setup-wizard.js +51 -0
  34. package/dist/scripts/lib/setup-wizard.js.map +1 -0
  35. package/dist/scripts/lib/tool-adapters/decide-adapter.d.ts.map +1 -1
  36. package/dist/scripts/lib/tool-adapters/decide-adapter.js +3 -3
  37. package/dist/scripts/lib/tool-adapters/decide-adapter.js.map +1 -1
  38. package/dist/scripts/lib/tool-adapters/get-adapter.d.ts.map +1 -1
  39. package/dist/scripts/lib/tool-adapters/get-adapter.js +10 -2
  40. package/dist/scripts/lib/tool-adapters/get-adapter.js.map +1 -1
  41. package/dist/scripts/lib/tool-adapters/ingest-adapter.d.ts.map +1 -1
  42. package/dist/scripts/lib/tool-adapters/ingest-adapter.js +3 -3
  43. package/dist/scripts/lib/tool-adapters/ingest-adapter.js.map +1 -1
  44. package/dist/scripts/lib/transcript-token-estimator.d.ts +8 -0
  45. package/dist/scripts/lib/transcript-token-estimator.d.ts.map +1 -0
  46. package/dist/scripts/lib/transcript-token-estimator.js +164 -0
  47. package/dist/scripts/lib/transcript-token-estimator.js.map +1 -0
  48. package/dist/scripts/maintain-cleanup.d.ts.map +1 -1
  49. package/dist/scripts/maintain-cleanup.js +2 -1
  50. package/dist/scripts/maintain-cleanup.js.map +1 -1
  51. package/dist/scripts/mcp-server.js +3 -3
  52. package/dist/scripts/mcp-server.js.map +1 -1
  53. package/dist/scripts/mindlore-backup.d.ts.map +1 -1
  54. package/dist/scripts/mindlore-backup.js +3 -2
  55. package/dist/scripts/mindlore-backup.js.map +1 -1
  56. package/dist/scripts/mindlore-clean-cache.d.ts +4 -1
  57. package/dist/scripts/mindlore-clean-cache.d.ts.map +1 -1
  58. package/dist/scripts/mindlore-clean-cache.js +47 -20
  59. package/dist/scripts/mindlore-clean-cache.js.map +1 -1
  60. package/dist/scripts/mindlore-doctor.d.ts +4 -0
  61. package/dist/scripts/mindlore-doctor.d.ts.map +1 -1
  62. package/dist/scripts/mindlore-doctor.js +9 -1
  63. package/dist/scripts/mindlore-doctor.js.map +1 -1
  64. package/dist/scripts/mindlore-health-check.d.ts +7 -0
  65. package/dist/scripts/mindlore-health-check.d.ts.map +1 -1
  66. package/dist/scripts/mindlore-health-check.js +57 -4
  67. package/dist/scripts/mindlore-health-check.js.map +1 -1
  68. package/dist/scripts/mindlore-learnings.d.ts +6 -0
  69. package/dist/scripts/mindlore-learnings.d.ts.map +1 -0
  70. package/dist/scripts/mindlore-learnings.js +106 -0
  71. package/dist/scripts/mindlore-learnings.js.map +1 -0
  72. package/dist/scripts/mindlore-obsidian.js +6 -5
  73. package/dist/scripts/mindlore-obsidian.js.map +1 -1
  74. package/dist/scripts/quality-populate.js +2 -1
  75. package/dist/scripts/quality-populate.js.map +1 -1
  76. package/dist/scripts/uninstall.js +2 -1
  77. package/dist/scripts/uninstall.js.map +1 -1
  78. package/dist/tests/clean-cache-eperm.test.d.ts +2 -0
  79. package/dist/tests/clean-cache-eperm.test.d.ts.map +1 -0
  80. package/dist/tests/clean-cache-eperm.test.js +74 -0
  81. package/dist/tests/clean-cache-eperm.test.js.map +1 -0
  82. package/dist/tests/doctor.test.js +18 -0
  83. package/dist/tests/doctor.test.js.map +1 -1
  84. package/dist/tests/health-check.test.d.ts +2 -0
  85. package/dist/tests/health-check.test.d.ts.map +1 -0
  86. package/dist/tests/health-check.test.js +61 -0
  87. package/dist/tests/health-check.test.js.map +1 -0
  88. package/dist/tests/helpers/db.d.ts +7 -2
  89. package/dist/tests/helpers/db.d.ts.map +1 -1
  90. package/dist/tests/helpers/db.js +16 -14
  91. package/dist/tests/helpers/db.js.map +1 -1
  92. package/dist/tests/mindlore-ingest-extraction.test.d.ts +2 -0
  93. package/dist/tests/mindlore-ingest-extraction.test.d.ts.map +1 -0
  94. package/dist/tests/mindlore-ingest-extraction.test.js +48 -0
  95. package/dist/tests/mindlore-ingest-extraction.test.js.map +1 -0
  96. package/dist/tests/mindlore-learnings.test.d.ts +2 -0
  97. package/dist/tests/mindlore-learnings.test.d.ts.map +1 -0
  98. package/dist/tests/mindlore-learnings.test.js +63 -0
  99. package/dist/tests/mindlore-learnings.test.js.map +1 -0
  100. package/dist/tests/read-guard-perf.test.d.ts +2 -0
  101. package/dist/tests/read-guard-perf.test.d.ts.map +1 -0
  102. package/dist/tests/read-guard-perf.test.js +31 -0
  103. package/dist/tests/read-guard-perf.test.js.map +1 -0
  104. package/dist/tests/reflect-trigger.test.js +8 -1
  105. package/dist/tests/reflect-trigger.test.js.map +1 -1
  106. package/dist/tests/search-cache.test.js +0 -35
  107. package/dist/tests/search-cache.test.js.map +1 -1
  108. package/dist/tests/search-throttle.test.d.ts +2 -0
  109. package/dist/tests/search-throttle.test.d.ts.map +1 -0
  110. package/dist/tests/search-throttle.test.js +53 -0
  111. package/dist/tests/search-throttle.test.js.map +1 -0
  112. package/dist/tests/session-focus-reflect-nudge.test.js +3 -3
  113. package/dist/tests/session-focus-reflect-nudge.test.js.map +1 -1
  114. package/dist/tests/setup-wizard.test.d.ts +2 -0
  115. package/dist/tests/setup-wizard.test.d.ts.map +1 -0
  116. package/dist/tests/setup-wizard.test.js +35 -0
  117. package/dist/tests/setup-wizard.test.js.map +1 -0
  118. package/dist/tests/transcript-token-estimator.test.d.ts +2 -0
  119. package/dist/tests/transcript-token-estimator.test.d.ts.map +1 -0
  120. package/dist/tests/transcript-token-estimator.test.js +122 -0
  121. package/dist/tests/transcript-token-estimator.test.js.map +1 -0
  122. package/hooks/cc-memory-bulk-sync.cjs +9 -1
  123. package/hooks/cc-session-sync.cjs +9 -1
  124. package/hooks/lib/learnings-loader.cjs +21 -11
  125. package/hooks/lib/reflect-trigger.cjs +6 -3
  126. package/hooks/mindlore-cwd-changed.cjs +4 -5
  127. package/hooks/mindlore-dont-repeat.cjs +2 -1
  128. package/hooks/mindlore-post-read.cjs +2 -1
  129. package/hooks/mindlore-pre-compact.cjs +3 -2
  130. package/hooks/mindlore-read-guard.cjs +62 -35
  131. package/hooks/mindlore-search.cjs +231 -31
  132. package/hooks/mindlore-session-end.cjs +6 -8
  133. package/hooks/mindlore-session-focus.cjs +472 -23
  134. package/hooks/src/mindlore-cwd-changed.cjs +4 -5
  135. package/hooks/src/mindlore-dont-repeat.cjs +2 -1
  136. package/hooks/src/mindlore-post-read.cjs +2 -1
  137. package/hooks/src/mindlore-pre-compact.cjs +3 -2
  138. package/hooks/src/mindlore-read-guard.cjs +15 -55
  139. package/hooks/src/mindlore-search.cjs +39 -10
  140. package/hooks/src/mindlore-session-end.cjs +6 -8
  141. package/hooks/src/mindlore-session-focus.cjs +29 -21
  142. package/mcp-server.cjs +37944 -0
  143. package/package.json +4 -2
  144. package/plugin.json +6 -1
  145. package/skills/mindlore-ingest/SKILL.md +21 -0
  146. package/skills/mindlore-learnings/SKILL.md +30 -0
  147. package/start.cjs +141 -0
  148. package/templates/config.json +1 -1
@@ -1,20 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- /**
5
- * mindlore-read-guard — PreToolUse hook (if: "Read")
6
- *
7
- * Repeated-read detection: detects files read multiple times
8
- * in the same session and emits a soft warning.
9
- * Does NOT block (exit 0) — advisory only.
10
- *
11
- * Storage: .mindlore/diary/_session-reads.json
12
- * Cleanup: session-end hook writes stats to delta then deletes the file.
13
- */
14
-
15
4
  const fs = require('fs');
16
5
  const path = require('path');
17
6
  const { findMindloreDir, readHookStdin, getProjectName, hookLog, extractSkeleton, withTelemetrySync } = require('./lib/mindlore-common.cjs');
7
+ const { safeMkdir, safeWriteFile } = require('./lib/secure-io.cjs');
8
+ const { runReadGuard } = require('../../dist/scripts/lib/read-guard-core.js');
18
9
 
19
10
  function main() {
20
11
  const baseDir = findMindloreDir();
@@ -23,64 +14,33 @@ function main() {
23
14
  const filePath = readHookStdin(['file_path', 'path']);
24
15
  if (!filePath) return;
25
16
 
26
- // Only track CWD-relative files, skip .mindlore/ internals
27
17
  const cwd = process.cwd();
28
18
  const resolved = path.resolve(filePath);
29
19
  if (!resolved.startsWith(cwd)) return;
30
20
  if (resolved.startsWith(path.resolve(baseDir))) return;
31
21
 
32
- // Load or create session reads tracker
33
22
  const diaryDir = path.join(baseDir, 'diary');
34
- if (!fs.existsSync(diaryDir)) {
35
- fs.mkdirSync(diaryDir, { recursive: true });
36
- }
23
+ safeMkdir(diaryDir);
37
24
 
38
25
  const readsPath = path.join(diaryDir, `_session-reads-${getProjectName()}.json`);
39
26
  let reads = {};
40
- if (fs.existsSync(readsPath)) {
41
- try {
42
- reads = JSON.parse(fs.readFileSync(readsPath, 'utf8'));
43
- } catch (_err) {
44
- reads = {};
45
- }
27
+ try {
28
+ reads = JSON.parse(fs.readFileSync(readsPath, 'utf8'));
29
+ } catch (err) {
30
+ if (err.code !== 'ENOENT') hookLog('read-guard', 'warn', `read error: ${err.message}`);
31
+ reads = {};
46
32
  }
47
33
 
48
- const normalizedPath = path.resolve(filePath);
49
- const existing = reads[normalizedPath];
50
-
51
- // Support both old format (number) and new format (object with tokens)
52
- let count, tokens;
53
- if (typeof existing === 'number') {
54
- count = existing + 1;
55
- tokens = 0;
56
- reads[normalizedPath] = { count, tokens: 0, chars: 0 };
57
- } else if (existing && typeof existing === 'object') {
58
- count = (existing.count || 0) + 1;
59
- tokens = existing.tokens || 0;
60
- existing.count = count;
61
- reads[normalizedPath] = existing;
62
- } else {
63
- count = 1;
64
- tokens = 0;
65
- reads[normalizedPath] = { count, tokens: 0, chars: 0 };
66
- }
67
-
68
- // Write updated reads
69
- fs.writeFileSync(readsPath, JSON.stringify(reads, null, 2), 'utf8');
70
-
71
- const basename = path.basename(filePath);
72
- const tokenInfo = tokens > 0 ? ` (~${tokens} token)` : '';
34
+ const decision = runReadGuard({ filePath: resolved, basename: path.basename(filePath) }, reads);
35
+ reads[resolved] = decision.updatedReadsEntry;
36
+ safeWriteFile(readsPath, JSON.stringify(reads, null, 2));
73
37
 
74
- // Block on 3+ repeated reads (exit 2 = block tool call)
75
- if (count >= 3) {
76
- const totalWaste = tokens > 0 ? ` Toplam israf: ~${tokens * (count - 1)} token.` : '';
77
- process.stderr.write(`[Mindlore BLOCK] ${basename}${tokenInfo} bu session'da ${count}. kez okunuyor.${totalWaste} Edit icin gerekiyorsa once degisikligini yap, sonra tekrar oku. Analiz icin ctx_execute_file kullan.`);
38
+ if (decision.block) {
39
+ process.stderr.write(decision.warning);
78
40
  process.exit(2);
79
41
  }
80
42
 
81
- // Warn on 2nd read (exit 0 = allow but warn)
82
- if (count > 1) {
83
- const totalWaste = tokens > 0 ? ` Toplam tekrar: ~${tokens * (count - 1)} token.` : '';
43
+ if (decision.additionalContext) {
84
44
  let skeletonSection = '';
85
45
  try {
86
46
  const ext = path.extname(filePath).slice(1);
@@ -96,7 +56,7 @@ function main() {
96
56
  process.stdout.write(JSON.stringify({
97
57
  hookSpecificOutput: {
98
58
  hookEventName: 'PreToolUse',
99
- additionalContext: `[Mindlore: ${basename}${tokenInfo} bu session'da ${count}. kez okunuyor.${totalWaste} Bir sonraki okuma engellenecek — Edit gerekiyorsa simdi yap.]${skeletonSection}`
59
+ additionalContext: decision.additionalContext + skeletonSection
100
60
  }
101
61
  }));
102
62
  }
@@ -12,6 +12,7 @@
12
12
  const fs = require('fs');
13
13
  const path = require('path');
14
14
  const { getAllDbs, openDatabase, extractHeadings, readConfig, hookLog, incrementRecallCount, withTelemetry } = require('./lib/mindlore-common.cjs');
15
+ const { safeMkdir, safeWriteFile } = require('./lib/secure-io.cjs');
15
16
 
16
17
  const MAX_RESULTS = 3;
17
18
  const MIN_QUERY_WORDS = 3;
@@ -30,22 +31,42 @@ try {
30
31
  // search-cache not built yet
31
32
  }
32
33
 
34
+ let TokenEstimatorMod;
35
+ try {
36
+ TokenEstimatorMod = require('../dist/scripts/lib/transcript-token-estimator.js');
37
+ } catch (_err) {
38
+ // estimator not built yet
39
+ }
40
+
33
41
  function parseStdin() {
34
42
  try {
35
43
  const raw = fs.readFileSync(0, 'utf8').trim();
36
- if (!raw) return { userMessage: '', sessionId: 'unknown' };
44
+ if (!raw) return { userMessage: '', sessionId: 'unknown', transcriptPath: undefined };
37
45
  const parsed = JSON.parse(raw);
38
46
  const userMessage = parsed.prompt || parsed.content || parsed.message || parsed.query || raw;
39
47
  const sessionId = parsed.session_id || 'unknown';
40
- return { userMessage, sessionId };
48
+ const transcriptPath = parsed.transcript_path || parsed.transcriptPath;
49
+ return { userMessage, sessionId, transcriptPath };
41
50
  } catch (_err) {
42
- return { userMessage: '', sessionId: 'unknown' };
51
+ return { userMessage: '', sessionId: 'unknown', transcriptPath: undefined };
52
+ }
53
+ }
54
+
55
+ function getBaseMax(transcriptPath) {
56
+ if (TokenEstimatorMod) {
57
+ try {
58
+ return TokenEstimatorMod.adaptiveResultCount(transcriptPath);
59
+ } catch (_err) {
60
+ // fall through to default
61
+ }
43
62
  }
63
+ return MAX_RESULTS;
44
64
  }
45
65
 
46
66
  function main() {
47
- const { userMessage, sessionId } = parseStdin();
67
+ const { userMessage, sessionId, transcriptPath } = parseStdin();
48
68
  if (!userMessage || userMessage.length < MIN_QUERY_WORDS) return;
69
+ const baseMax = getBaseMax(transcriptPath);
49
70
  let searchMs = 0;
50
71
 
51
72
  const dbPaths = getAllDbs();
@@ -61,18 +82,26 @@ function main() {
61
82
  const synonyms = (config && config.synonyms) ? config.synonyms : {};
62
83
 
63
84
  const allResults = [];
85
+ // Track tightest throttle cap across DBs — applied to the final result slice
86
+ // so cache hits (which return previously-stored arrays) still respect throttle
87
+ // state in subsequent calls of the same session.
88
+ let sessionEffectiveMax = baseMax;
64
89
  for (const dbPath of dbPaths) {
65
90
  const db = openDatabase(dbPath);
66
91
  if (!db) continue;
67
92
  try {
68
93
  // Cache + throttle
69
94
  let cache;
70
- let effectiveMax = MAX_RESULTS;
95
+ let effectiveMax = baseMax;
71
96
  if (SearchCacheMod) {
72
97
  cache = new SearchCacheMod.SearchCache(db, { ttlMs: 300000 });
73
98
  const throttle = new SearchCacheMod.SearchThrottle(db);
74
99
  const callCount = throttle.incrementCallCount(sessionId);
75
- effectiveMax = throttle.getMaxResults(callCount);
100
+ // Throttle is baseMax-aware: when callCount <= 10 it returns baseMax,
101
+ // so adaptive expansion (up to 5 in low-context sessions) is honored.
102
+ // Above 10 calls it clamps to 1, above 20 to 0 (skip).
103
+ effectiveMax = throttle.getMaxResults(callCount, baseMax);
104
+ if (effectiveMax < sessionEffectiveMax) sessionEffectiveMax = effectiveMax;
76
105
  if (effectiveMax === 0) {
77
106
  hookLog('search', 'info', `Throttled (call #${callCount})`);
78
107
  db.close();
@@ -81,7 +110,7 @@ function main() {
81
110
  const cached = cache.get(userMessage);
82
111
  if (cached) {
83
112
  const baseDir = path.dirname(dbPath);
84
- for (const r of cached) allResults.push({ ...r, baseDir });
113
+ for (const r of cached.slice(0, effectiveMax)) allResults.push({ ...r, baseDir });
85
114
  db.close();
86
115
  continue;
87
116
  }
@@ -129,7 +158,7 @@ function main() {
129
158
 
130
159
  // Sort by score descending, take top N
131
160
  unique.sort((a, b) => b.score - a.score);
132
- const relevant = unique.slice(0, MAX_RESULTS);
161
+ const relevant = unique.slice(0, sessionEffectiveMax);
133
162
  if (relevant.length === 0) return;
134
163
 
135
164
  // Token budget from config
@@ -172,7 +201,7 @@ function main() {
172
201
  if (outputStr.length > OFFLOAD_THRESHOLD) {
173
202
  const baseDir = path.dirname(dbPaths[0]);
174
203
  const tmpDir = path.join(baseDir, 'tmp');
175
- fs.mkdirSync(tmpDir, { recursive: true });
204
+ safeMkdir(tmpDir);
176
205
 
177
206
  try {
178
207
  const oneHourAgo = Date.now() - 3600000;
@@ -188,7 +217,7 @@ function main() {
188
217
  } catch { /* cleanup is best-effort */ }
189
218
  const fileName = `search-${Date.now()}.md`;
190
219
  const filePath = path.join(tmpDir, fileName);
191
- fs.writeFileSync(filePath, outputStr, 'utf8');
220
+ safeWriteFile(filePath, outputStr);
192
221
 
193
222
  const summary = outputStr.slice(0, 500).replace(/\n/g, ' ').trim();
194
223
  outputStr = `[Mindlore Search: ${outputStr.length} chars offloaded to ${filePath}]\n` +
@@ -13,7 +13,7 @@ const fs = require('fs');
13
13
  const path = require('path');
14
14
  const os = require('os');
15
15
  const { execFileSync, spawn } = require('child_process');
16
- const { safeWriteFile, safeWriteJson } = require('../dist/scripts/lib/secure-io.js');
16
+ const { safeMkdir, safeWriteFile, safeWriteJson } = require('./lib/secure-io.cjs');
17
17
  const { findMindloreDir, globalDir, getProjectName, openDatabase, ensureEpisodesTable, hasEpisodesTable, insertBareEpisode, insertFtsRow, hookLog, SHARED_EXPORT_DIRS, resolveWin32Bin, withTelemetry, getUnpromotedRawFiles, cleanupExpiredInjectLog } = require('./lib/mindlore-common.cjs');
18
18
 
19
19
  const EXPORT_DIRS = SHARED_EXPORT_DIRS;
@@ -151,9 +151,7 @@ function main() {
151
151
  if (!baseDir) return;
152
152
 
153
153
  const diaryDir = path.join(baseDir, 'diary');
154
- if (!fs.existsSync(diaryDir)) {
155
- fs.mkdirSync(diaryDir, { recursive: true });
156
- }
154
+ safeMkdir(diaryDir);
157
155
 
158
156
  const now = new Date();
159
157
  const dateStr = formatDate(now);
@@ -323,7 +321,7 @@ function writeBareEpisode(baseDir, project, commits, changedFiles, reads) {
323
321
  */
324
322
  function writeEpisodeFile(baseDir, project, commits, changedFiles, reads) {
325
323
  const projDir = path.join(baseDir, 'diary', project || 'unknown');
326
- if (!fs.existsSync(projDir)) fs.mkdirSync(projDir, { recursive: true });
324
+ safeMkdir(projDir);
327
325
 
328
326
  const now = process.env.MINDLORE_EPISODE_TS ? new Date(process.env.MINDLORE_EPISODE_TS) : new Date();
329
327
  const ts = formatDate(now);
@@ -406,7 +404,7 @@ function exportMdFile(srcPath, destPath, convertFn) {
406
404
  }
407
405
  let content = fs.readFileSync(srcPath, 'utf8');
408
406
  content = convertFn(content);
409
- fs.writeFileSync(destPath, content, 'utf8');
407
+ safeWriteFile(destPath, content);
410
408
  return true;
411
409
  }
412
410
 
@@ -434,7 +432,7 @@ function syncObsidian(baseDir) {
434
432
 
435
433
  function walkAndExport(srcDir, destDir) {
436
434
  if (!fs.existsSync(srcDir)) return;
437
- fs.mkdirSync(destDir, { recursive: true });
435
+ safeMkdir(destDir);
438
436
  for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
439
437
  if (entry.name.startsWith('_') || entry.name.startsWith('.')) continue;
440
438
  const srcPath = path.join(srcDir, entry.name);
@@ -454,7 +452,7 @@ function syncObsidian(baseDir) {
454
452
  for (const rootFile of ['INDEX.md', 'log.md']) {
455
453
  const srcPath = path.join(baseDir, rootFile);
456
454
  if (!fs.existsSync(srcPath)) continue;
457
- fs.mkdirSync(destBase, { recursive: true });
455
+ safeMkdir(destBase);
458
456
  if (exportMdFile(srcPath, path.join(destBase, rootFile), convertFn)) exported++;
459
457
  }
460
458
 
@@ -12,7 +12,7 @@ const fs = require('fs');
12
12
  const path = require('path');
13
13
  const { findMindloreDir, readConfig, openDatabase, hasEpisodesTable, querySupersededChains, formatSupersededChains, hookLog, getProjectName, parseFrontmatter, withTelemetry, withTimeoutDb, listSnapshots, isCorruptionError, recoverCorruptDb, getNominationCounts, resolveMindloreHome } = require('./lib/mindlore-common.cjs');
14
14
  const { loadLearningsBlock } = require('./lib/learnings-loader.cjs');
15
- const { shouldNudgeReflect } = require('../lib/reflect-trigger.cjs');
15
+ const { shouldNudgeReflect, buildNudgeMessage } = require('../lib/reflect-trigger.cjs');
16
16
 
17
17
  function truncateSection(content, sectionRegex, keepCount, label) {
18
18
  const match = content.match(sectionRegex);
@@ -138,10 +138,16 @@ function main() {
138
138
  // Inject INDEX.md
139
139
  const tIndex = Date.now();
140
140
  const indexPath = path.join(baseDir, 'INDEX.md');
141
- if (fs.existsSync(indexPath)) {
142
- const content = fs.readFileSync(indexPath, 'utf8').trim();
143
- sourceChars += content.length;
144
- output.push(`[Mindlore INDEX]\n${content}`);
141
+ let indexContent;
142
+ try {
143
+ indexContent = fs.readFileSync(indexPath, 'utf8').trim();
144
+ } catch (err) {
145
+ if (err.code !== 'ENOENT') throw err;
146
+ indexContent = null;
147
+ }
148
+ if (indexContent !== null) {
149
+ sourceChars += indexContent.length;
150
+ output.push(`[Mindlore INDEX]\n${indexContent}`);
145
151
  }
146
152
  timings.index_read = Date.now() - tIndex;
147
153
 
@@ -181,15 +187,13 @@ function main() {
181
187
  // Both are flat strings written by init — no JSON parse needed on session start
182
188
  const versionPath = path.join(baseDir, '.version');
183
189
  const pkgVersionPath = path.join(baseDir, '.pkg-version');
184
- try {
185
- if (fs.existsSync(versionPath) && fs.existsSync(pkgVersionPath)) {
186
- const installed = fs.readFileSync(versionPath, 'utf8').trim();
187
- const pkgVersion = fs.readFileSync(pkgVersionPath, 'utf8').trim();
188
- if (pkgVersion && pkgVersion !== installed) {
189
- output.push(`[Mindlore: Guncelleme mevcut (${installed} → ${pkgVersion}). \`npx mindlore init\` calistirin.]`);
190
- }
191
- }
192
- } catch (_err) { /* skip */ }
190
+ let installed;
191
+ try { installed = fs.readFileSync(versionPath, 'utf8').trim(); } catch (err) { if (err.code !== 'ENOENT') throw err; }
192
+ let pkgVersion;
193
+ try { pkgVersion = fs.readFileSync(pkgVersionPath, 'utf8').trim(); } catch (err) { if (err.code !== 'ENOENT') throw err; }
194
+ if (installed !== undefined && pkgVersion !== undefined && pkgVersion !== installed) {
195
+ output.push(`[Mindlore: Guncelleme mevcut (${installed} → ${pkgVersion}). \`npx mindlore init\` calistirin.]`);
196
+ }
193
197
  timings.version_check = Date.now() - tVersion;
194
198
 
195
199
  // v0.5.4: Consolidated session payload (replaces scattered episodes/activity/alerts injection)
@@ -220,14 +224,18 @@ function main() {
220
224
 
221
225
  // Auto-reflect nudge (7-day threshold + 24h cooldown)
222
226
  try {
223
- const reflectRow = db.prepare(
224
- "SELECT value FROM skill_memory WHERE skill_name = 'mindlore-reflect' AND key = 'last_reflect_date'"
225
- ).get();
226
- const nudgeRow = db.prepare(
227
- "SELECT value FROM skill_memory WHERE skill_name = 'mindlore-reflect' AND key = 'last_nudge_date'"
228
- ).get();
227
+ const rows = db.prepare(
228
+ "SELECT key, value FROM skill_memory WHERE skill_name = 'mindlore-reflect' AND key IN ('last_reflect_date', 'last_nudge_date')"
229
+ ).all();
230
+ const byKey = Object.fromEntries(rows.map(r => [r.key, r.value]));
231
+ const reflectRow = byKey['last_reflect_date'] ? { value: byKey['last_reflect_date'] } : undefined;
232
+ const nudgeRow = byKey['last_nudge_date'] ? { value: byKey['last_nudge_date'] } : undefined;
229
233
  if (shouldNudgeReflect(reflectRow?.value ?? null, nudgeRow?.value ?? null, new Date())) {
230
- output.push('[Mindlore] 7+ gün reflect yapılmadı `/mindlore-reflect` çalıştır');
234
+ const daysSince = reflectRow?.value ? Math.floor((Date.now() - new Date(reflectRow.value).getTime()) / 86400000) : 999;
235
+ const episodeCount = hasEpisodesTable(db) ? db.prepare("SELECT count(*) AS c FROM episodes").get()?.c ?? 0 : 0;
236
+ const diaryDirPath = path.join(baseDir, 'diary');
237
+ const diaryCount = fs.existsSync(diaryDirPath) ? fs.readdirSync(diaryDirPath).length : 0;
238
+ output.push(buildNudgeMessage({ daysSince, episodeCount, diaryCount }));
231
239
  const nowIso = new Date().toISOString();
232
240
  db.prepare(`
233
241
  INSERT INTO skill_memory (skill_name, key, value, updated_at)