mindlore 0.6.7 → 0.6.8

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 (169) hide show
  1. package/README.md +259 -259
  2. package/SCHEMA.md +292 -292
  3. package/dist/scripts/cc-memory-bulk-sync.d.ts.map +1 -1
  4. package/dist/scripts/cc-memory-bulk-sync.js +47 -42
  5. package/dist/scripts/cc-memory-bulk-sync.js.map +1 -1
  6. package/dist/scripts/cc-session-sync.d.ts.map +1 -1
  7. package/dist/scripts/cc-session-sync.js +58 -48
  8. package/dist/scripts/cc-session-sync.js.map +1 -1
  9. package/dist/scripts/init.js +8 -8
  10. package/dist/scripts/init.js.map +1 -1
  11. package/dist/scripts/lib/all-migrations.d.ts.map +1 -1
  12. package/dist/scripts/lib/all-migrations.js +4 -1
  13. package/dist/scripts/lib/all-migrations.js.map +1 -1
  14. package/dist/scripts/lib/consolidation.d.ts +4 -3
  15. package/dist/scripts/lib/consolidation.d.ts.map +1 -1
  16. package/dist/scripts/lib/consolidation.js +10 -10
  17. package/dist/scripts/lib/consolidation.js.map +1 -1
  18. package/dist/scripts/lib/constants.d.ts +1 -7
  19. package/dist/scripts/lib/constants.d.ts.map +1 -1
  20. package/dist/scripts/lib/constants.js +2 -9
  21. package/dist/scripts/lib/constants.js.map +1 -1
  22. package/dist/scripts/lib/db-helpers.d.ts +0 -15
  23. package/dist/scripts/lib/db-helpers.d.ts.map +1 -1
  24. package/dist/scripts/lib/db-helpers.js +1 -51
  25. package/dist/scripts/lib/db-helpers.js.map +1 -1
  26. package/dist/scripts/lib/decay.d.ts.map +1 -1
  27. package/dist/scripts/lib/decay.js +9 -9
  28. package/dist/scripts/lib/decay.js.map +1 -1
  29. package/dist/scripts/lib/episodes.js +23 -23
  30. package/dist/scripts/lib/migrations-v061.js +21 -21
  31. package/dist/scripts/lib/migrations-v062.js +11 -11
  32. package/dist/scripts/lib/migrations-v063.js +14 -14
  33. package/dist/scripts/lib/migrations-v067.js +11 -11
  34. package/dist/scripts/lib/migrations-v068.d.ts +3 -0
  35. package/dist/scripts/lib/migrations-v068.d.ts.map +1 -0
  36. package/dist/scripts/lib/migrations-v068.js +37 -0
  37. package/dist/scripts/lib/migrations-v068.js.map +1 -0
  38. package/dist/scripts/lib/migrations.d.ts.map +1 -1
  39. package/dist/scripts/lib/migrations.js +0 -15
  40. package/dist/scripts/lib/migrations.js.map +1 -1
  41. package/dist/scripts/lib/schema-version.js +6 -6
  42. package/dist/scripts/lib/search-cache.js +11 -11
  43. package/dist/scripts/lib/session-payload.d.ts.map +1 -1
  44. package/dist/scripts/lib/session-payload.js +7 -7
  45. package/dist/scripts/lib/session-payload.js.map +1 -1
  46. package/dist/scripts/lib/triage.js +3 -3
  47. package/dist/scripts/mindlore-backup.js +9 -9
  48. package/dist/scripts/mindlore-fts5-index.d.ts +1 -2
  49. package/dist/scripts/mindlore-fts5-index.d.ts.map +1 -1
  50. package/dist/scripts/mindlore-fts5-index.js +12 -64
  51. package/dist/scripts/mindlore-fts5-index.js.map +1 -1
  52. package/dist/scripts/mindlore-health-check.d.ts.map +1 -1
  53. package/dist/scripts/mindlore-health-check.js +0 -11
  54. package/dist/scripts/mindlore-health-check.js.map +1 -1
  55. package/dist/tests/cc-memory-bulk-sync.test.js +23 -0
  56. package/dist/tests/cc-memory-bulk-sync.test.js.map +1 -1
  57. package/dist/tests/cc-session-sync.test.js +25 -0
  58. package/dist/tests/cc-session-sync.test.js.map +1 -1
  59. package/dist/tests/compaction-snapshot.test.js +2 -2
  60. package/dist/tests/consolidation.test.js +5 -5
  61. package/dist/tests/consolidation.test.js.map +1 -1
  62. package/dist/tests/decay.test.js +9 -9
  63. package/dist/tests/diary.test.js +4 -4
  64. package/dist/tests/episode-kind-constant.test.d.ts +2 -0
  65. package/dist/tests/episode-kind-constant.test.d.ts.map +1 -0
  66. package/dist/tests/episode-kind-constant.test.js +28 -0
  67. package/dist/tests/episode-kind-constant.test.js.map +1 -0
  68. package/dist/tests/episodes-inject.test.js +9 -9
  69. package/dist/tests/fts5.test.js +66 -125
  70. package/dist/tests/fts5.test.js.map +1 -1
  71. package/dist/tests/globalSetup.d.ts +2 -0
  72. package/dist/tests/globalSetup.d.ts.map +1 -0
  73. package/dist/tests/globalSetup.js +36 -0
  74. package/dist/tests/globalSetup.js.map +1 -0
  75. package/dist/tests/helpers/db.d.ts +13 -5
  76. package/dist/tests/helpers/db.d.ts.map +1 -1
  77. package/dist/tests/helpers/db.js +60 -33
  78. package/dist/tests/helpers/db.js.map +1 -1
  79. package/dist/tests/lesson-graduation.test.js +11 -11
  80. package/dist/tests/lesson-graduation.test.js.map +1 -1
  81. package/dist/tests/migrations-v053.test.js +16 -16
  82. package/dist/tests/migrations-v061.test.js +10 -10
  83. package/dist/tests/migrations-v063.test.js +2 -2
  84. package/dist/tests/migrations-v068.test.d.ts +2 -0
  85. package/dist/tests/migrations-v068.test.d.ts.map +1 -0
  86. package/dist/tests/migrations-v068.test.js +53 -0
  87. package/dist/tests/migrations-v068.test.js.map +1 -0
  88. package/dist/tests/nomination-counts.test.d.ts +2 -0
  89. package/dist/tests/nomination-counts.test.d.ts.map +1 -0
  90. package/dist/tests/nomination-counts.test.js +51 -0
  91. package/dist/tests/nomination-counts.test.js.map +1 -0
  92. package/dist/tests/recall-telemetry.test.js +8 -8
  93. package/dist/tests/schema-version.test.js +3 -7
  94. package/dist/tests/schema-version.test.js.map +1 -1
  95. package/dist/tests/search-hook.test.js +2 -2
  96. package/dist/tests/sec-regression.test.js +0 -50
  97. package/dist/tests/sec-regression.test.js.map +1 -1
  98. package/dist/tests/session-end-cleanup.test.js +3 -20
  99. package/dist/tests/session-end-cleanup.test.js.map +1 -1
  100. package/dist/tests/session-focus.test.js +7 -30
  101. package/dist/tests/session-focus.test.js.map +1 -1
  102. package/dist/tests/session-payload.test.js +1 -1
  103. package/dist/tests/session-summary.test.js +1 -1
  104. package/hooks/lib/constants.cjs +15 -0
  105. package/hooks/lib/mindlore-common.cjs +974 -1042
  106. package/hooks/mindlore-cwd-changed.cjs +57 -57
  107. package/hooks/mindlore-decision-detector.cjs +54 -54
  108. package/hooks/mindlore-dont-repeat.cjs +222 -222
  109. package/hooks/mindlore-fts5-sync.cjs +97 -88
  110. package/hooks/mindlore-index.cjs +229 -229
  111. package/hooks/mindlore-model-router.cjs +54 -54
  112. package/hooks/mindlore-post-compact.cjs +69 -69
  113. package/hooks/mindlore-post-read.cjs +106 -106
  114. package/hooks/mindlore-pre-compact.cjs +154 -154
  115. package/hooks/mindlore-read-guard.cjs +105 -105
  116. package/hooks/mindlore-research-guard.cjs +176 -176
  117. package/hooks/mindlore-search.cjs +200 -200
  118. package/hooks/mindlore-session-end.cjs +509 -526
  119. package/hooks/mindlore-session-focus.cjs +256 -259
  120. package/package.json +75 -78
  121. package/plugin.json +1 -1
  122. package/skills/mindlore-diary/SKILL.md +85 -85
  123. package/skills/mindlore-evolve/SKILL.md +126 -126
  124. package/skills/mindlore-explore/SKILL.md +109 -109
  125. package/skills/mindlore-ingest/SKILL.md +195 -195
  126. package/skills/mindlore-maintain/SKILL.md +125 -125
  127. package/skills/mindlore-query/SKILL.md +151 -151
  128. package/skills/mindlore-reflect/SKILL.md +141 -141
  129. package/skills/mindlore-stats/SKILL.md +106 -106
  130. package/templates/INDEX.md +14 -14
  131. package/templates/SCHEMA.md +292 -292
  132. package/templates/config.json +1 -1
  133. package/templates/extraction/article.md +15 -15
  134. package/templates/extraction/changelog.md +15 -15
  135. package/templates/extraction/default.md +15 -15
  136. package/templates/extraction/docs.md +15 -15
  137. package/templates/extraction/github-repo.md +17 -17
  138. package/dist/scripts/lib/daemon.d.ts +0 -16
  139. package/dist/scripts/lib/daemon.d.ts.map +0 -1
  140. package/dist/scripts/lib/daemon.js +0 -133
  141. package/dist/scripts/lib/daemon.js.map +0 -1
  142. package/dist/scripts/lib/embedding.d.ts +0 -5
  143. package/dist/scripts/lib/embedding.d.ts.map +0 -1
  144. package/dist/scripts/lib/embedding.js +0 -44
  145. package/dist/scripts/lib/embedding.js.map +0 -1
  146. package/dist/scripts/mindlore-daemon.d.ts +0 -2
  147. package/dist/scripts/mindlore-daemon.d.ts.map +0 -1
  148. package/dist/scripts/mindlore-daemon.js +0 -117
  149. package/dist/scripts/mindlore-daemon.js.map +0 -1
  150. package/dist/tests/daemon-integration.test.d.ts +0 -2
  151. package/dist/tests/daemon-integration.test.d.ts.map +0 -1
  152. package/dist/tests/daemon-integration.test.js +0 -37
  153. package/dist/tests/daemon-integration.test.js.map +0 -1
  154. package/dist/tests/daemon.test.d.ts +0 -2
  155. package/dist/tests/daemon.test.d.ts.map +0 -1
  156. package/dist/tests/daemon.test.js +0 -187
  157. package/dist/tests/daemon.test.js.map +0 -1
  158. package/dist/tests/embedding-hf-integration.test.d.ts +0 -2
  159. package/dist/tests/embedding-hf-integration.test.d.ts.map +0 -1
  160. package/dist/tests/embedding-hf-integration.test.js +0 -52
  161. package/dist/tests/embedding-hf-integration.test.js.map +0 -1
  162. package/dist/tests/embedding.test.d.ts +0 -6
  163. package/dist/tests/embedding.test.d.ts.map +0 -1
  164. package/dist/tests/embedding.test.js +0 -71
  165. package/dist/tests/embedding.test.js.map +0 -1
  166. package/dist/tests/sqlite-vec-v12.test.d.ts +0 -2
  167. package/dist/tests/sqlite-vec-v12.test.d.ts.map +0 -1
  168. package/dist/tests/sqlite-vec-v12.test.js +0 -72
  169. package/dist/tests/sqlite-vec-v12.test.js.map +0 -1
@@ -1,200 +1,200 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * mindlore-search — UserPromptSubmit hook
6
- *
7
- * Thin wrapper over search-engine.ts pipeline.
8
- * Extracts keywords from user prompt, delegates search to modular engine,
9
- * injects top results with description + headings.
10
- */
11
-
12
- const fs = require('fs');
13
- const path = require('path');
14
- const { getAllDbs, openDatabase, extractHeadings, readHookStdin, readConfig, hookLog, incrementRecallCount, withTelemetry } = require('./lib/mindlore-common.cjs');
15
-
16
- const MAX_RESULTS = 3;
17
- const MIN_QUERY_WORDS = 3;
18
-
19
- let searchEngineMod;
20
- try {
21
- searchEngineMod = require('../dist/scripts/lib/search-engine.js');
22
- } catch (_err) {
23
- // search-engine not built yet
24
- }
25
-
26
- let SearchCacheMod;
27
- try {
28
- SearchCacheMod = require('../dist/scripts/lib/search-cache.js');
29
- } catch (_err) {
30
- // search-cache not built yet
31
- }
32
-
33
- function main() {
34
- const userMessage = readHookStdin(['prompt', 'content', 'message', 'query']);
35
- if (!userMessage || userMessage.length < MIN_QUERY_WORDS) return;
36
-
37
- const dbPaths = getAllDbs();
38
- if (dbPaths.length === 0) return;
39
-
40
- if (!searchEngineMod) {
41
- hookLog('search', 'warn', 'search-engine module not available — skipping');
42
- return;
43
- }
44
-
45
- const project = path.basename(process.cwd());
46
- const config = readConfig(path.dirname(dbPaths[0]));
47
- const synonyms = (config && config.synonyms) ? config.synonyms : {};
48
-
49
- // Read session_id from stdin for throttling
50
- let sessionId;
51
- try {
52
- const stdinData = JSON.parse(process.env.CLAUDE_HOOK_STDIN || '{}');
53
- sessionId = stdinData.session_id || 'unknown';
54
- } catch (_) {
55
- sessionId = 'unknown';
56
- }
57
-
58
- const allResults = [];
59
- for (const dbPath of dbPaths) {
60
- const db = openDatabase(dbPath);
61
- if (!db) continue;
62
- try {
63
- // Cache + throttle
64
- let cache;
65
- let effectiveMax = MAX_RESULTS;
66
- if (SearchCacheMod) {
67
- cache = new SearchCacheMod.SearchCache(db, { ttlMs: 300000 });
68
- const throttle = new SearchCacheMod.SearchThrottle(db);
69
- const callCount = throttle.incrementCallCount(sessionId);
70
- effectiveMax = throttle.getMaxResults(callCount);
71
- if (effectiveMax === 0) {
72
- hookLog('search', 'info', `Throttled (call #${callCount})`);
73
- db.close();
74
- continue;
75
- }
76
- const cached = cache.get(userMessage);
77
- if (cached) {
78
- const baseDir = path.dirname(dbPath);
79
- for (const r of cached) allResults.push({ ...r, baseDir });
80
- db.close();
81
- continue;
82
- }
83
- }
84
-
85
- const results = searchEngineMod.search(db, userMessage, {
86
- project,
87
- maxResults: effectiveMax,
88
- synonyms,
89
- });
90
-
91
- if (cache) cache.set(userMessage, results);
92
-
93
- const baseDir = path.dirname(dbPath);
94
- for (const r of results) {
95
- allResults.push({ ...r, baseDir });
96
- }
97
-
98
- // Recall count inside loop — avoid reopening DB
99
- try {
100
- const txn = db.transaction(() => {
101
- for (const r of results) incrementRecallCount(db, r.path);
102
- });
103
- txn();
104
- } catch (_e) { /* graceful */ }
105
- } catch (err) {
106
- hookLog('search', 'warn', `Search error: ${err?.message || err}`);
107
- } finally {
108
- db.close();
109
- }
110
- }
111
-
112
- // Deduplicate by full path
113
- const seen = new Set();
114
- const unique = [];
115
- for (const r of allResults) {
116
- const normalized = path.resolve(r.path);
117
- if (!seen.has(normalized)) {
118
- seen.add(normalized);
119
- unique.push(r);
120
- }
121
- }
122
-
123
- // Sort by score descending, take top N
124
- unique.sort((a, b) => b.score - a.score);
125
- const relevant = unique.slice(0, MAX_RESULTS);
126
- if (relevant.length === 0) return;
127
-
128
- // Token budget from config
129
- const budget = (config && config.tokenBudget) || {};
130
- const perResultChars = ((budget.perResult || 500) * 4);
131
- const totalChars = ((budget.searchResults || 1500) * 4);
132
-
133
- // Build output
134
- const output = [];
135
- let totalUsed = 0;
136
- for (const r of relevant) {
137
- if (totalUsed >= totalChars) break;
138
- const relativePath = path.relative(r.baseDir, r.path).replace(/\\/g, '/');
139
-
140
- let headings = [];
141
- const contentStr = r.content || '';
142
- if (contentStr) {
143
- try {
144
- headings = extractHeadings(contentStr, 3);
145
- } catch (_err) { /* skip */ }
146
- }
147
-
148
- const category = r.category || path.dirname(relativePath).split('/')[0];
149
- const title = r.title || r.slug || path.basename(r.path, '.md');
150
- const description = r.description || '';
151
-
152
- const headingStr = headings.length > 0 ? `\nBasliklar: ${headings.join(', ')}` : '';
153
- const tagsStr = r.tags ? `\nTags: ${r.tags}` : '';
154
- const snippetOrDesc = r.snippet || description;
155
- const entry = `[Mindlore: ${category}/${title}] ${snippetOrDesc}\nDosya: ${relativePath}${tagsStr}${headingStr}`;
156
- const truncated = entry.slice(0, perResultChars);
157
- totalUsed += truncated.length;
158
- output.push(truncated);
159
- }
160
-
161
- if (output.length > 0) {
162
- let outputStr = output.join('\n\n') + '\n';
163
-
164
- const OFFLOAD_THRESHOLD = 10240;
165
- if (outputStr.length > OFFLOAD_THRESHOLD) {
166
- const baseDir = path.dirname(dbPaths[0]);
167
- const tmpDir = path.join(baseDir, 'tmp');
168
- fs.mkdirSync(tmpDir, { recursive: true });
169
-
170
- try {
171
- const oneHourAgo = Date.now() - 3600000;
172
- const files = fs.readdirSync(tmpDir)
173
- .filter(f => f.startsWith('search-'))
174
- .map(f => ({ name: f, mtime: fs.statSync(path.join(tmpDir, f)).mtimeMs }))
175
- .sort((a, b) => b.mtime - a.mtime);
176
- for (let i = 0; i < files.length; i++) {
177
- if (i >= 20 || files[i].mtime < oneHourAgo) {
178
- try { fs.unlinkSync(path.join(tmpDir, files[i].name)); } catch { /* ignore */ }
179
- }
180
- }
181
- } catch { /* cleanup is best-effort */ }
182
- const fileName = `search-${Date.now()}.md`;
183
- const filePath = path.join(tmpDir, fileName);
184
- fs.writeFileSync(filePath, outputStr, 'utf8');
185
-
186
- const summary = outputStr.slice(0, 500).replace(/\n/g, ' ').trim();
187
- outputStr = `[Mindlore Search: ${outputStr.length} chars offloaded to ${filePath}]\n` +
188
- `Summary: ${summary}...\n` +
189
- `[Read full results: ${filePath}]`;
190
- hookLog('search', 'info', 'offloaded to tmp/ (' + outputStr.length + ' chars)');
191
- }
192
-
193
- process.stdout.write(outputStr);
194
- }
195
- }
196
-
197
- withTelemetry('mindlore-search', main).catch(err => {
198
- hookLog('mindlore-search', 'error', err?.message ?? String(err));
199
- process.exit(0);
200
- });
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * mindlore-search — UserPromptSubmit hook
6
+ *
7
+ * Thin wrapper over search-engine.ts pipeline.
8
+ * Extracts keywords from user prompt, delegates search to modular engine,
9
+ * injects top results with description + headings.
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const { getAllDbs, openDatabase, extractHeadings, readHookStdin, readConfig, hookLog, incrementRecallCount, withTelemetry } = require('./lib/mindlore-common.cjs');
15
+
16
+ const MAX_RESULTS = 3;
17
+ const MIN_QUERY_WORDS = 3;
18
+
19
+ let searchEngineMod;
20
+ try {
21
+ searchEngineMod = require('../dist/scripts/lib/search-engine.js');
22
+ } catch (_err) {
23
+ // search-engine not built yet
24
+ }
25
+
26
+ let SearchCacheMod;
27
+ try {
28
+ SearchCacheMod = require('../dist/scripts/lib/search-cache.js');
29
+ } catch (_err) {
30
+ // search-cache not built yet
31
+ }
32
+
33
+ function main() {
34
+ const userMessage = readHookStdin(['prompt', 'content', 'message', 'query']);
35
+ if (!userMessage || userMessage.length < MIN_QUERY_WORDS) return;
36
+
37
+ const dbPaths = getAllDbs();
38
+ if (dbPaths.length === 0) return;
39
+
40
+ if (!searchEngineMod) {
41
+ hookLog('search', 'warn', 'search-engine module not available — skipping');
42
+ return;
43
+ }
44
+
45
+ const project = path.basename(process.cwd());
46
+ const config = readConfig(path.dirname(dbPaths[0]));
47
+ const synonyms = (config && config.synonyms) ? config.synonyms : {};
48
+
49
+ // Read session_id from stdin for throttling
50
+ let sessionId;
51
+ try {
52
+ const stdinData = JSON.parse(process.env.CLAUDE_HOOK_STDIN || '{}');
53
+ sessionId = stdinData.session_id || 'unknown';
54
+ } catch (_) {
55
+ sessionId = 'unknown';
56
+ }
57
+
58
+ const allResults = [];
59
+ for (const dbPath of dbPaths) {
60
+ const db = openDatabase(dbPath);
61
+ if (!db) continue;
62
+ try {
63
+ // Cache + throttle
64
+ let cache;
65
+ let effectiveMax = MAX_RESULTS;
66
+ if (SearchCacheMod) {
67
+ cache = new SearchCacheMod.SearchCache(db, { ttlMs: 300000 });
68
+ const throttle = new SearchCacheMod.SearchThrottle(db);
69
+ const callCount = throttle.incrementCallCount(sessionId);
70
+ effectiveMax = throttle.getMaxResults(callCount);
71
+ if (effectiveMax === 0) {
72
+ hookLog('search', 'info', `Throttled (call #${callCount})`);
73
+ db.close();
74
+ continue;
75
+ }
76
+ const cached = cache.get(userMessage);
77
+ if (cached) {
78
+ const baseDir = path.dirname(dbPath);
79
+ for (const r of cached) allResults.push({ ...r, baseDir });
80
+ db.close();
81
+ continue;
82
+ }
83
+ }
84
+
85
+ const results = searchEngineMod.search(db, userMessage, {
86
+ project,
87
+ maxResults: effectiveMax,
88
+ synonyms,
89
+ });
90
+
91
+ if (cache) cache.set(userMessage, results);
92
+
93
+ const baseDir = path.dirname(dbPath);
94
+ for (const r of results) {
95
+ allResults.push({ ...r, baseDir });
96
+ }
97
+
98
+ // Recall count inside loop — avoid reopening DB
99
+ try {
100
+ const txn = db.transaction(() => {
101
+ for (const r of results) incrementRecallCount(db, r.path);
102
+ });
103
+ txn();
104
+ } catch (_e) { /* graceful */ }
105
+ } catch (err) {
106
+ hookLog('search', 'warn', `Search error: ${err?.message || err}`);
107
+ } finally {
108
+ db.close();
109
+ }
110
+ }
111
+
112
+ // Deduplicate by full path
113
+ const seen = new Set();
114
+ const unique = [];
115
+ for (const r of allResults) {
116
+ const normalized = path.resolve(r.path);
117
+ if (!seen.has(normalized)) {
118
+ seen.add(normalized);
119
+ unique.push(r);
120
+ }
121
+ }
122
+
123
+ // Sort by score descending, take top N
124
+ unique.sort((a, b) => b.score - a.score);
125
+ const relevant = unique.slice(0, MAX_RESULTS);
126
+ if (relevant.length === 0) return;
127
+
128
+ // Token budget from config
129
+ const budget = (config && config.tokenBudget) || {};
130
+ const perResultChars = ((budget.perResult || 500) * 4);
131
+ const totalChars = ((budget.searchResults || 1500) * 4);
132
+
133
+ // Build output
134
+ const output = [];
135
+ let totalUsed = 0;
136
+ for (const r of relevant) {
137
+ if (totalUsed >= totalChars) break;
138
+ const relativePath = path.relative(r.baseDir, r.path).replace(/\\/g, '/');
139
+
140
+ let headings = [];
141
+ const contentStr = r.content || '';
142
+ if (contentStr) {
143
+ try {
144
+ headings = extractHeadings(contentStr, 3);
145
+ } catch (_err) { /* skip */ }
146
+ }
147
+
148
+ const category = r.category || path.dirname(relativePath).split('/')[0];
149
+ const title = r.title || r.slug || path.basename(r.path, '.md');
150
+ const description = r.description || '';
151
+
152
+ const headingStr = headings.length > 0 ? `\nBasliklar: ${headings.join(', ')}` : '';
153
+ const tagsStr = r.tags ? `\nTags: ${r.tags}` : '';
154
+ const snippetOrDesc = r.snippet || description;
155
+ const entry = `[Mindlore: ${category}/${title}] ${snippetOrDesc}\nDosya: ${relativePath}${tagsStr}${headingStr}`;
156
+ const truncated = entry.slice(0, perResultChars);
157
+ totalUsed += truncated.length;
158
+ output.push(truncated);
159
+ }
160
+
161
+ if (output.length > 0) {
162
+ let outputStr = output.join('\n\n') + '\n';
163
+
164
+ const OFFLOAD_THRESHOLD = 10240;
165
+ if (outputStr.length > OFFLOAD_THRESHOLD) {
166
+ const baseDir = path.dirname(dbPaths[0]);
167
+ const tmpDir = path.join(baseDir, 'tmp');
168
+ fs.mkdirSync(tmpDir, { recursive: true });
169
+
170
+ try {
171
+ const oneHourAgo = Date.now() - 3600000;
172
+ const files = fs.readdirSync(tmpDir)
173
+ .filter(f => f.startsWith('search-'))
174
+ .map(f => ({ name: f, mtime: fs.statSync(path.join(tmpDir, f)).mtimeMs }))
175
+ .sort((a, b) => b.mtime - a.mtime);
176
+ for (let i = 0; i < files.length; i++) {
177
+ if (i >= 20 || files[i].mtime < oneHourAgo) {
178
+ try { fs.unlinkSync(path.join(tmpDir, files[i].name)); } catch { /* ignore */ }
179
+ }
180
+ }
181
+ } catch { /* cleanup is best-effort */ }
182
+ const fileName = `search-${Date.now()}.md`;
183
+ const filePath = path.join(tmpDir, fileName);
184
+ fs.writeFileSync(filePath, outputStr, 'utf8');
185
+
186
+ const summary = outputStr.slice(0, 500).replace(/\n/g, ' ').trim();
187
+ outputStr = `[Mindlore Search: ${outputStr.length} chars offloaded to ${filePath}]\n` +
188
+ `Summary: ${summary}...\n` +
189
+ `[Read full results: ${filePath}]`;
190
+ hookLog('search', 'info', 'offloaded to tmp/ (' + outputStr.length + ' chars)');
191
+ }
192
+
193
+ process.stdout.write(outputStr);
194
+ }
195
+ }
196
+
197
+ withTelemetry('mindlore-search', main).catch(err => {
198
+ hookLog('mindlore-search', 'error', err?.message ?? String(err));
199
+ process.exit(0);
200
+ });