mindlore 0.6.2 → 0.6.3

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 (122) hide show
  1. package/LICENSE +661 -21
  2. package/README.md +3 -3
  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 +3 -2
  6. package/dist/scripts/cc-session-sync.js.map +1 -1
  7. package/dist/scripts/fetch-raw.js +96 -7
  8. package/dist/scripts/fetch-raw.js.map +1 -1
  9. package/dist/scripts/init.js +6 -25
  10. package/dist/scripts/init.js.map +1 -1
  11. package/dist/scripts/lib/chunker.d.ts +12 -0
  12. package/dist/scripts/lib/chunker.d.ts.map +1 -0
  13. package/dist/scripts/lib/chunker.js +94 -0
  14. package/dist/scripts/lib/chunker.js.map +1 -0
  15. package/dist/scripts/lib/fuzzy.d.ts +10 -0
  16. package/dist/scripts/lib/fuzzy.d.ts.map +1 -0
  17. package/dist/scripts/lib/fuzzy.js +88 -0
  18. package/dist/scripts/lib/fuzzy.js.map +1 -0
  19. package/dist/scripts/lib/hybrid-search.d.ts +1 -0
  20. package/dist/scripts/lib/hybrid-search.d.ts.map +1 -1
  21. package/dist/scripts/lib/hybrid-search.js.map +1 -1
  22. package/dist/scripts/lib/merge-defaults.d.ts +5 -0
  23. package/dist/scripts/lib/merge-defaults.d.ts.map +1 -0
  24. package/dist/scripts/lib/merge-defaults.js +25 -0
  25. package/dist/scripts/lib/merge-defaults.js.map +1 -0
  26. package/dist/scripts/lib/migrations-v063.d.ts +7 -0
  27. package/dist/scripts/lib/migrations-v063.d.ts.map +1 -0
  28. package/dist/scripts/lib/migrations-v063.js +58 -0
  29. package/dist/scripts/lib/migrations-v063.js.map +1 -0
  30. package/dist/scripts/lib/proximity.d.ts +3 -0
  31. package/dist/scripts/lib/proximity.d.ts.map +1 -0
  32. package/dist/scripts/lib/proximity.js +53 -0
  33. package/dist/scripts/lib/proximity.js.map +1 -0
  34. package/dist/scripts/lib/rrf.d.ts +23 -0
  35. package/dist/scripts/lib/rrf.d.ts.map +1 -0
  36. package/dist/scripts/lib/rrf.js +63 -0
  37. package/dist/scripts/lib/rrf.js.map +1 -0
  38. package/dist/scripts/lib/search-cache.d.ts +20 -0
  39. package/dist/scripts/lib/search-cache.d.ts.map +1 -0
  40. package/dist/scripts/lib/search-cache.js +60 -0
  41. package/dist/scripts/lib/search-cache.js.map +1 -0
  42. package/dist/scripts/lib/search-engine.d.ts +22 -0
  43. package/dist/scripts/lib/search-engine.d.ts.map +1 -0
  44. package/dist/scripts/lib/search-engine.js +105 -0
  45. package/dist/scripts/lib/search-engine.js.map +1 -0
  46. package/dist/scripts/lib/session-payload.d.ts +2 -4
  47. package/dist/scripts/lib/session-payload.d.ts.map +1 -1
  48. package/dist/scripts/lib/session-payload.js +31 -43
  49. package/dist/scripts/lib/session-payload.js.map +1 -1
  50. package/dist/scripts/lib/snippet.d.ts +2 -0
  51. package/dist/scripts/lib/snippet.d.ts.map +1 -0
  52. package/dist/scripts/lib/snippet.js +32 -0
  53. package/dist/scripts/lib/snippet.js.map +1 -0
  54. package/dist/scripts/mindlore-fts5-index.js +44 -3
  55. package/dist/scripts/mindlore-fts5-index.js.map +1 -1
  56. package/dist/scripts/mindlore-fts5-search.d.ts +3 -5
  57. package/dist/scripts/mindlore-fts5-search.d.ts.map +1 -1
  58. package/dist/scripts/mindlore-fts5-search.js +41 -116
  59. package/dist/scripts/mindlore-fts5-search.js.map +1 -1
  60. package/dist/tests/chunker.test.d.ts +2 -0
  61. package/dist/tests/chunker.test.d.ts.map +1 -0
  62. package/dist/tests/chunker.test.js +40 -0
  63. package/dist/tests/chunker.test.js.map +1 -0
  64. package/dist/tests/chunks-migration.test.d.ts +2 -0
  65. package/dist/tests/chunks-migration.test.d.ts.map +1 -0
  66. package/dist/tests/chunks-migration.test.js +55 -0
  67. package/dist/tests/chunks-migration.test.js.map +1 -0
  68. package/dist/tests/compaction-snapshot.test.js +47 -0
  69. package/dist/tests/compaction-snapshot.test.js.map +1 -1
  70. package/dist/tests/daemon-integration.test.js +5 -5
  71. package/dist/tests/daemon-integration.test.js.map +1 -1
  72. package/dist/tests/fuzzy.test.d.ts +2 -0
  73. package/dist/tests/fuzzy.test.d.ts.map +1 -0
  74. package/dist/tests/fuzzy.test.js +70 -0
  75. package/dist/tests/fuzzy.test.js.map +1 -0
  76. package/dist/tests/helpers/db.d.ts.map +1 -1
  77. package/dist/tests/helpers/db.js +2 -1
  78. package/dist/tests/helpers/db.js.map +1 -1
  79. package/dist/tests/merge-defaults.test.d.ts +2 -0
  80. package/dist/tests/merge-defaults.test.d.ts.map +1 -0
  81. package/dist/tests/merge-defaults.test.js +35 -0
  82. package/dist/tests/merge-defaults.test.js.map +1 -0
  83. package/dist/tests/migrations-v063.test.d.ts +2 -0
  84. package/dist/tests/migrations-v063.test.d.ts.map +1 -0
  85. package/dist/tests/migrations-v063.test.js +84 -0
  86. package/dist/tests/migrations-v063.test.js.map +1 -0
  87. package/dist/tests/proximity.test.d.ts +2 -0
  88. package/dist/tests/proximity.test.d.ts.map +1 -0
  89. package/dist/tests/proximity.test.js +31 -0
  90. package/dist/tests/proximity.test.js.map +1 -0
  91. package/dist/tests/rrf.test.d.ts +2 -0
  92. package/dist/tests/rrf.test.d.ts.map +1 -0
  93. package/dist/tests/rrf.test.js +100 -0
  94. package/dist/tests/rrf.test.js.map +1 -0
  95. package/dist/tests/search-cache.test.d.ts +2 -0
  96. package/dist/tests/search-cache.test.d.ts.map +1 -0
  97. package/dist/tests/search-cache.test.js +95 -0
  98. package/dist/tests/search-cache.test.js.map +1 -0
  99. package/dist/tests/search-engine.test.d.ts +2 -0
  100. package/dist/tests/search-engine.test.d.ts.map +1 -0
  101. package/dist/tests/search-engine.test.js +125 -0
  102. package/dist/tests/search-engine.test.js.map +1 -0
  103. package/dist/tests/search-hook.test.js +3 -3
  104. package/dist/tests/search-hook.test.js.map +1 -1
  105. package/dist/tests/session-payload.test.d.ts +1 -1
  106. package/dist/tests/session-payload.test.js +1 -14
  107. package/dist/tests/session-payload.test.js.map +1 -1
  108. package/dist/tests/session-summary.test.js +40 -4
  109. package/dist/tests/session-summary.test.js.map +1 -1
  110. package/dist/tests/snippet.test.d.ts +2 -0
  111. package/dist/tests/snippet.test.d.ts.map +1 -0
  112. package/dist/tests/snippet.test.js +30 -0
  113. package/dist/tests/snippet.test.js.map +1 -0
  114. package/hooks/lib/mindlore-common.cjs +56 -3
  115. package/hooks/mindlore-index.cjs +6 -0
  116. package/hooks/mindlore-pre-compact.cjs +61 -52
  117. package/hooks/mindlore-search.cjs +90 -214
  118. package/hooks/mindlore-session-end.cjs +9 -15
  119. package/hooks/mindlore-session-focus.cjs +11 -26
  120. package/package.json +2 -2
  121. package/plugin.json +1 -1
  122. package/templates/config.json +1 -1
@@ -4,206 +4,114 @@
4
4
  /**
5
5
  * mindlore-search — UserPromptSubmit hook
6
6
  *
7
- * Extracts keywords from user prompt, searches FTS5 with per-keyword scoring,
8
- * injects top results with description + headings (matching old knowledge system quality).
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.
9
10
  */
10
11
 
11
12
  const fs = require('fs');
12
13
  const path = require('path');
13
- const { getAllDbs, openDatabase, extractHeadings, readHookStdin, extractKeywords, sanitizeKeyword, readConfig, loadSqliteVecCjs, hasVecTableCjs, hookLog, incrementRecallCount, getDaemonPortFile, withTelemetry, fixVersionTokens } = require('./lib/mindlore-common.cjs');
14
-
15
- const { execFileSync } = require('child_process');
14
+ const { getAllDbs, openDatabase, extractHeadings, readHookStdin, readConfig, hookLog, incrementRecallCount, withTelemetry } = require('./lib/mindlore-common.cjs');
16
15
 
17
16
  const MAX_RESULTS = 3;
18
17
  const MIN_QUERY_WORDS = 3;
19
18
 
20
- // Try to load hybrid search module (built TS)
21
- let hybridSearchMod;
19
+ let searchEngineMod;
22
20
  try {
23
- hybridSearchMod = require('../dist/scripts/lib/hybrid-search.js');
21
+ searchEngineMod = require('../dist/scripts/lib/search-engine.js');
24
22
  } catch (_err) {
25
- // hybrid-search not built yet — pure FTS5 mode
23
+ // search-engine not built yet
26
24
  }
27
25
 
28
- // v0.5.5: Request embedding from daemon via execFileSync bridge
29
- function requestEmbeddingSync(query) {
30
- try {
31
- const portFile = getDaemonPortFile();
32
- if (!fs.existsSync(portFile)) return null;
33
- const clientScript = path.join(__dirname, '..', 'scripts', 'lib', 'daemon-client.js');
34
- if (!fs.existsSync(clientScript)) return null;
35
- const result = execFileSync(process.execPath, [clientScript, portFile, query, '300'], {
36
- timeout: 500, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true,
37
- });
38
- const parsed = JSON.parse(result.trim());
39
- return parsed.type === 'embedding' ? parsed.embedding : null;
40
- } catch {
41
- return null;
42
- }
26
+ let SearchCacheMod;
27
+ try {
28
+ SearchCacheMod = require('../dist/scripts/lib/search-cache.js');
29
+ } catch (_err) {
30
+ // search-cache not built yet
43
31
  }
44
32
 
45
- /**
46
- * Search a single DB and return scored results with their baseDir.
47
- */
48
- function searchDb(dbPath, keywords) {
49
- const baseDir = path.dirname(dbPath);
50
- const db = openDatabase(dbPath, { readonly: true });
51
- if (!db) return [];
52
- const results = [];
33
+ function main() {
34
+ const userMessage = readHookStdin(['prompt', 'content', 'message', 'query']);
35
+ if (!userMessage || userMessage.length < MIN_QUERY_WORDS) return;
53
36
 
54
- // v0.5.0: Try hybrid search with synonym expansion (no embedding — hooks are sync)
55
- if (!hybridSearchMod) {
56
- hookLog('search', 'info', 'No hybridSearchMod — FTS5-only mode');
57
- }
58
- if (hybridSearchMod && loadSqliteVecCjs(db) && hasVecTableCjs(db)) {
59
- try {
60
- const config = readConfig(baseDir);
61
- const synonyms = (config && config.synonyms) ? config.synonyms : {};
37
+ const dbPaths = getAllDbs();
38
+ if (dbPaths.length === 0) return;
62
39
 
63
- // Expand keywords with synonyms
64
- const expandedTerms = keywords.slice();
65
- for (const kw of keywords) {
66
- const lower = kw.toLowerCase();
67
- if (synonyms[lower]) {
68
- expandedTerms.push(...synonyms[lower]);
69
- }
70
- }
40
+ if (!searchEngineMod) {
41
+ hookLog('search', 'warn', 'search-engine module not available — skipping');
42
+ return;
43
+ }
71
44
 
72
- // v0.5.5: Try to get queryEmbedding from daemon
73
- let queryEmbedding = null;
74
- try {
75
- queryEmbedding = requestEmbeddingSync(expandedTerms.join(' '));
76
- if (!queryEmbedding) {
77
- hookLog('search', 'info', 'Daemon not available — FTS5-only hybrid mode');
78
- }
79
- } catch {
80
- hookLog('search', 'info', 'Daemon connection failed — FTS5-only hybrid mode');
81
- }
45
+ const project = path.basename(process.cwd());
46
+ const config = readConfig(path.dirname(dbPaths[0]));
47
+ const synonyms = (config && config.synonyms) ? config.synonyms : {};
82
48
 
83
- const fusedResults = hybridSearchMod.hybridSearch(db, expandedTerms.join(' '), {
84
- maxResults: MAX_RESULTS,
85
- project: path.basename(process.cwd()),
86
- queryEmbedding,
87
- });
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
+ }
88
57
 
89
- if (fusedResults.length > 0) {
90
- for (const r of fusedResults) {
91
- const filePath = r.path || '';
92
- let headings = [];
93
- if (filePath) {
94
- try {
95
- const content = fs.readFileSync(filePath, 'utf8');
96
- headings = extractHeadings(content, 3);
97
- } catch (_err) { /* file may have been deleted */ }
98
- }
99
- results.push({
100
- path: filePath,
101
- slug: r.slug,
102
- description: r.description || '',
103
- category: r.category || '',
104
- title: r.title || '',
105
- tags: r.tags || '',
106
- headings,
107
- hits: 1,
108
- rank: r.score,
109
- baseDir,
110
- });
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 callCount = cache.incrementCallCount(sessionId);
69
+ effectiveMax = cache.getMaxResults(callCount);
70
+ if (effectiveMax === 0) {
71
+ hookLog('search', 'info', `Throttled (call #${callCount})`);
72
+ db.close();
73
+ continue;
74
+ }
75
+ const cached = cache.get(userMessage);
76
+ if (cached) {
77
+ const baseDir = path.dirname(dbPath);
78
+ for (const r of cached) allResults.push({ ...r, baseDir });
79
+ db.close();
80
+ continue;
111
81
  }
112
- db.close();
113
- return results;
114
82
  }
115
- } catch (hybridErr) {
116
- hookLog('search', 'warn', `Hybrid search fallback to FTS5: ${hybridErr?.message || hybridErr}`);
117
- }
118
- }
119
83
 
120
- // FTS5-only fallback: OR-joined single query (replaces O(docs×keywords) nested loop)
121
- try {
122
- const sanitized = keywords.map(sanitizeKeyword).filter(Boolean);
123
- if (sanitized.length === 0) { db.close(); return results; }
124
-
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(
130
- `SELECT path, slug, description, category, title, tags, rank
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
- }
140
-
141
- for (const r of rows) {
142
- results.push({
143
- path: r.path || '',
144
- slug: r.slug,
145
- description: r.description || '',
146
- category: r.category || '',
147
- title: r.title || '',
148
- tags: r.tags || '',
149
- headings: [], // populated later in main() after slicing
150
- hits: sanitized.length,
151
- rank: r.rank,
152
- baseDir,
84
+ const results = searchEngineMod.search(db, userMessage, {
85
+ project,
86
+ maxResults: effectiveMax,
87
+ synonyms,
153
88
  });
154
- }
155
- } catch (_err) {
156
- // FTS5 query error — silently skip
157
- } finally {
158
- db.close();
159
- }
160
-
161
- return results;
162
- }
163
89
 
164
- /**
165
- * Search episodes via FTS5 mirror (type = 'episode').
166
- * Reuses an already-open DB handle — no extra sqlite3_open.
167
- */
168
- function searchEpisodesFts(db, keywords) {
169
- try {
170
- const ftsQuery = keywords.map(sanitizeKeyword).filter(Boolean).join(' OR ');
171
- const rows = db.prepare(
172
- "SELECT title, category, slug, tags FROM mindlore_fts WHERE type = 'episode' AND mindlore_fts MATCH ? LIMIT 2"
173
- ).all(ftsQuery);
174
-
175
- return rows.map(r => {
176
- const tags = r.tags || '';
177
- const kind = tags.split(',')[0]?.trim() || 'episode';
178
- return `[episode] ${kind}: ${r.title || r.slug}`;
179
- });
180
- } catch (_err) {
181
- return [];
182
- }
183
- }
184
-
185
- function main() {
186
- const userMessage = readHookStdin(['prompt', 'content', 'message', 'query']);
187
- if (!userMessage || userMessage.length < MIN_QUERY_WORDS) return;
90
+ if (cache) cache.set(userMessage, results);
188
91
 
189
- const dbPaths = getAllDbs();
190
- if (dbPaths.length === 0) return;
191
-
192
- const keywords = extractKeywords(userMessage);
193
- if (keywords.length < MIN_QUERY_WORDS) return;
92
+ const baseDir = path.dirname(dbPath);
93
+ for (const r of results) {
94
+ allResults.push({ ...r, baseDir });
95
+ }
194
96
 
195
- const allScores = [];
196
- for (const dbPath of dbPaths) {
197
- allScores.push(...searchDb(dbPath, keywords));
97
+ // Recall count inside loop — avoid reopening DB
98
+ try {
99
+ const txn = db.transaction(() => {
100
+ for (const r of results) incrementRecallCount(db, r.path);
101
+ });
102
+ txn();
103
+ } catch (_e) { /* graceful */ }
104
+ } catch (err) {
105
+ hookLog('search', 'warn', `Search error: ${err?.message || err}`);
106
+ } finally {
107
+ db.close();
108
+ }
198
109
  }
199
110
 
200
- // Sort: most keyword hits first, then best rank
201
- allScores.sort((a, b) => b.hits - a.hits || a.rank - b.rank);
202
-
203
- // Deduplicate by full path (project version wins — appears first in sort)
111
+ // Deduplicate by full path
204
112
  const seen = new Set();
205
113
  const unique = [];
206
- for (const r of allScores) {
114
+ for (const r of allResults) {
207
115
  const normalized = path.resolve(r.path);
208
116
  if (!seen.has(normalized)) {
209
117
  seen.add(normalized);
@@ -211,45 +119,30 @@ function main() {
211
119
  }
212
120
  }
213
121
 
122
+ // Sort by score descending, take top N
123
+ unique.sort((a, b) => b.score - a.score);
214
124
  const relevant = unique.slice(0, MAX_RESULTS);
215
125
  if (relevant.length === 0) return;
216
126
 
217
- try {
218
- const db = openDatabase(dbPaths[0]);
219
- if (db) {
220
- const txn = db.transaction(() => {
221
- for (const r of relevant) incrementRecallCount(db, r.path);
222
- });
223
- txn();
224
- db.close();
225
- }
226
- } catch (_e) { /* graceful — never block search output */ }
227
-
228
- // Populate headings only for final results (avoid reading extra files)
229
- for (const r of relevant) {
230
- if (r.path && r.headings.length === 0 && fs.existsSync(r.path)) {
231
- try {
232
- const content = fs.readFileSync(r.path, 'utf8');
233
- r.headings = extractHeadings(content, 3);
234
- } catch (_err) { /* skip */ }
235
- }
236
- }
237
-
238
127
  // Token budget from config
239
- const config = readConfig(path.dirname(dbPaths[0]));
240
128
  const budget = (config && config.tokenBudget) || {};
241
- // Defaults match DEFAULT_TOKEN_BUDGET in scripts/lib/constants.ts
242
- const perResultChars = ((budget.perResult || 500) * 4); // ~4 chars/token
129
+ const perResultChars = ((budget.perResult || 500) * 4);
243
130
  const totalChars = ((budget.searchResults || 1500) * 4);
244
131
 
245
- // Build rich inject output
132
+ // Build output
246
133
  const output = [];
247
134
  let totalUsed = 0;
248
135
  for (const r of relevant) {
249
136
  if (totalUsed >= totalChars) break;
250
137
  const relativePath = path.relative(r.baseDir, r.path).replace(/\\/g, '/');
251
138
 
252
- const headings = r.headings || [];
139
+ let headings = [];
140
+ const contentStr = r.content || '';
141
+ if (contentStr) {
142
+ try {
143
+ headings = extractHeadings(contentStr, 3);
144
+ } catch (_err) { /* skip */ }
145
+ }
253
146
 
254
147
  const category = r.category || path.dirname(relativePath).split('/')[0];
255
148
  const title = r.title || r.slug || path.basename(r.path, '.md');
@@ -263,32 +156,15 @@ function main() {
263
156
  output.push(truncated);
264
157
  }
265
158
 
266
- // v0.4.0: Search episode mirrors in FTS5 (reuses searchDb's DB path, no extra open)
267
- if (relevant.length < MAX_RESULTS) {
268
- for (const dbPath of dbPaths) {
269
- try {
270
- const db = openDatabase(dbPath, { readonly: true });
271
- if (!db) continue;
272
- const episodeResults = searchEpisodesFts(db, keywords);
273
- db.close();
274
- if (episodeResults.length > 0) {
275
- output.push(`[Mindlore Episodes]\n${episodeResults.join('\n')}`);
276
- break;
277
- }
278
- } catch (_err) { /* skip */ }
279
- }
280
- }
281
-
282
159
  if (output.length > 0) {
283
160
  let outputStr = output.join('\n\n') + '\n';
284
161
 
285
- const OFFLOAD_THRESHOLD = 10240; // 10KB
162
+ const OFFLOAD_THRESHOLD = 10240;
286
163
  if (outputStr.length > OFFLOAD_THRESHOLD) {
287
164
  const baseDir = path.dirname(dbPaths[0]);
288
165
  const tmpDir = path.join(baseDir, 'tmp');
289
166
  fs.mkdirSync(tmpDir, { recursive: true });
290
167
 
291
- // Cleanup stale tmp files before writing new one (>1h old, keep max 20)
292
168
  try {
293
169
  const oneHourAgo = Date.now() - 3600000;
294
170
  const files = fs.readdirSync(tmpDir)
@@ -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, withTelemetry } = require('./lib/mindlore-common.cjs');
16
+ const { findMindloreDir, globalDir, getProjectName, openDatabase, ensureEpisodesTable, hasEpisodesTable, insertBareEpisode, insertFtsRow, hookLog, SHARED_EXPORT_DIRS, resolveWin32Bin, withTelemetry, getUnpromotedRawFiles } = require('./lib/mindlore-common.cjs');
17
17
 
18
18
  const EXPORT_DIRS = SHARED_EXPORT_DIRS;
19
19
 
@@ -82,6 +82,14 @@ if (process.argv.includes('--worker')) {
82
82
  }
83
83
  }, 'embed-trigger');
84
84
 
85
+ // Raw accumulation warning (moved from main to worker — off hot path)
86
+ await safeRunAsync(() => {
87
+ const unpromoted = getUnpromotedRawFiles(baseDir);
88
+ if (unpromoted.length >= 5) {
89
+ hookLog('session-end', 'info', `${unpromoted.length} raw files unpromoted`);
90
+ }
91
+ }, 'raw-check');
92
+
85
93
  // Obsidian + git-sync are independent — run in parallel
86
94
  await Promise.allSettled([
87
95
  safeRunAsync(() => syncObsidian(baseDir), 'obsidian'),
@@ -221,20 +229,6 @@ function main() {
221
229
  fs.appendFileSync(logPath, logEntry, 'utf8');
222
230
  }
223
231
 
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
-
238
232
  // Heavy ops: detach into child process so CC can exit immediately.
239
233
  // Fixes "Hook cancelled" when CC kills the hook before completion.
240
234
  // See: https://github.com/anthropics/claude-code/issues/41577
@@ -10,38 +10,22 @@
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, withTelemetry, withTimeoutDb } = require('./lib/mindlore-common.cjs');
14
-
15
- function isCorruptionError(err) {
16
- const code = err?.code ?? '';
17
- const msg = String(err?.message ?? err);
18
- return code === 'SQLITE_CORRUPT' || code === 'SQLITE_NOTADB' || /corrupt|malformed/i.test(msg);
19
- }
20
-
21
- function recoverCorruptDb(db, dbPath, reason) {
22
- try { db.close(); } catch { /* already closed */ }
23
- const bakPath = dbPath + '.corrupt.bak';
24
- try { fs.copyFileSync(dbPath, bakPath); } catch { /* best effort */ }
25
- try { fs.unlinkSync(dbPath); } catch { /* best effort */ }
26
- hookLog('session-focus', 'warn', reason);
27
- }
13
+ const { findMindloreDir, readConfig, openDatabase, hasEpisodesTable, querySupersededChains, formatSupersededChains, hookLog, getProjectName, parseFrontmatter, withTelemetry, withTimeoutDb, listSnapshots, isCorruptionError, recoverCorruptDb } = require('./lib/mindlore-common.cjs');
28
14
 
29
15
  function tryOpenDb(dbPath) {
30
16
  return openDatabase(dbPath, { readonly: true });
31
17
  }
32
18
 
33
- function loadDbContent(db, baseDir, config, output, timings) {
19
+ function loadDbContent(db, baseDir, config, output, timings, latestDeltaContent) {
34
20
  // Session payload: Session summary, Decisions, Friction, Learnings
35
21
  const tPayload = Date.now();
36
22
  try {
37
23
  const { buildSessionPayload } = require('../dist/scripts/lib/session-payload.js');
38
24
  const project = path.basename(process.cwd());
39
25
  const payloadBudget = config?.tokenBudget?.sessionInject ?? 2000;
40
- const payload = buildSessionPayload(db, baseDir, project, payloadBudget);
41
- if (!payload.skipInjection) {
42
- for (const section of payload.sections) {
43
- output.push(`[Mindlore ${section.label}]\n${section.content}`);
44
- }
26
+ const payload = buildSessionPayload(db, baseDir, project, payloadBudget, latestDeltaContent);
27
+ for (const section of payload.sections) {
28
+ output.push(`[Mindlore ${section.label}]\n${section.content}`);
45
29
  }
46
30
  } catch (_payloadErr) {
47
31
  // Session payload is optional — don't break session start
@@ -108,16 +92,17 @@ function main() {
108
92
  // Inject latest delta + reflect trigger (single readdirSync)
109
93
  const tDiary = Date.now();
110
94
  const diaryDir = path.join(baseDir, 'diary');
95
+ let latestDeltaContent = undefined;
111
96
  if (fs.existsSync(diaryDir)) {
112
97
  try {
113
- const diaryFiles = fs.readdirSync(diaryDir).filter(f => f.startsWith('delta-') && f.endsWith('.md'));
98
+ const diaryFiles = listSnapshots(diaryDir).filter(f => f.startsWith('delta-'));
114
99
 
115
100
  if (diaryFiles.length > 0) {
116
- const sorted = [...diaryFiles].sort();
117
- const latestName = sorted[sorted.length - 1];
101
+ const latestName = diaryFiles[diaryFiles.length - 1];
118
102
  const latestPath = path.join(diaryDir, latestName);
119
103
  sourceChars += fs.statSync(latestPath).size;
120
104
  const deltaContent = fs.readFileSync(latestPath, 'utf8').trim();
105
+ latestDeltaContent = deltaContent;
121
106
  const { meta } = parseFrontmatter(deltaContent);
122
107
  const deltaProject = meta.project || null;
123
108
  const currentProject = getProjectName();
@@ -163,10 +148,10 @@ function main() {
163
148
 
164
149
  if (db) {
165
150
  try {
166
- loadDbContent(db, baseDir, config, output, timings);
151
+ loadDbContent(db, baseDir, config, output, timings, latestDeltaContent);
167
152
  } catch (err) {
168
153
  if (isCorruptionError(err)) {
169
- recoverCorruptDb(db, dbPath, `Corrupt DB detected during query: ${err?.message ?? err}`);
154
+ recoverCorruptDb(db, dbPath, 'session-focus');
170
155
  }
171
156
  } finally {
172
157
  try { db.close(); } catch { /* already closed by recovery */ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mindlore",
3
- "version": "0.6.2",
3
+ "version": "0.6.3",
4
4
  "description": "AI-native knowledge system for Claude Code",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -36,7 +36,7 @@
36
36
  "wiki"
37
37
  ],
38
38
  "author": "omrfc",
39
- "license": "MIT",
39
+ "license": "AGPL-3.0-only",
40
40
  "repository": {
41
41
  "type": "git",
42
42
  "url": "git+https://github.com/mindlore/mindlore.git"
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.6.2",
4
+ "version": "0.6.3",
5
5
  "skills": [
6
6
  {
7
7
  "name": "mindlore-ingest",
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.6.2",
2
+ "version": "0.6.3",
3
3
  "models": {
4
4
  "ingest": "haiku",
5
5
  "evolve": "sonnet",