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.
- package/LICENSE +661 -21
- package/README.md +3 -3
- package/dist/scripts/cc-session-sync.d.ts +1 -0
- package/dist/scripts/cc-session-sync.d.ts.map +1 -1
- package/dist/scripts/cc-session-sync.js +3 -2
- package/dist/scripts/cc-session-sync.js.map +1 -1
- package/dist/scripts/fetch-raw.js +96 -7
- package/dist/scripts/fetch-raw.js.map +1 -1
- package/dist/scripts/init.js +6 -25
- package/dist/scripts/init.js.map +1 -1
- package/dist/scripts/lib/chunker.d.ts +12 -0
- package/dist/scripts/lib/chunker.d.ts.map +1 -0
- package/dist/scripts/lib/chunker.js +94 -0
- package/dist/scripts/lib/chunker.js.map +1 -0
- package/dist/scripts/lib/fuzzy.d.ts +10 -0
- package/dist/scripts/lib/fuzzy.d.ts.map +1 -0
- package/dist/scripts/lib/fuzzy.js +88 -0
- package/dist/scripts/lib/fuzzy.js.map +1 -0
- package/dist/scripts/lib/hybrid-search.d.ts +1 -0
- package/dist/scripts/lib/hybrid-search.d.ts.map +1 -1
- package/dist/scripts/lib/hybrid-search.js.map +1 -1
- package/dist/scripts/lib/merge-defaults.d.ts +5 -0
- package/dist/scripts/lib/merge-defaults.d.ts.map +1 -0
- package/dist/scripts/lib/merge-defaults.js +25 -0
- package/dist/scripts/lib/merge-defaults.js.map +1 -0
- package/dist/scripts/lib/migrations-v063.d.ts +7 -0
- package/dist/scripts/lib/migrations-v063.d.ts.map +1 -0
- package/dist/scripts/lib/migrations-v063.js +58 -0
- package/dist/scripts/lib/migrations-v063.js.map +1 -0
- package/dist/scripts/lib/proximity.d.ts +3 -0
- package/dist/scripts/lib/proximity.d.ts.map +1 -0
- package/dist/scripts/lib/proximity.js +53 -0
- package/dist/scripts/lib/proximity.js.map +1 -0
- package/dist/scripts/lib/rrf.d.ts +23 -0
- package/dist/scripts/lib/rrf.d.ts.map +1 -0
- package/dist/scripts/lib/rrf.js +63 -0
- package/dist/scripts/lib/rrf.js.map +1 -0
- package/dist/scripts/lib/search-cache.d.ts +20 -0
- package/dist/scripts/lib/search-cache.d.ts.map +1 -0
- package/dist/scripts/lib/search-cache.js +60 -0
- package/dist/scripts/lib/search-cache.js.map +1 -0
- package/dist/scripts/lib/search-engine.d.ts +22 -0
- package/dist/scripts/lib/search-engine.d.ts.map +1 -0
- package/dist/scripts/lib/search-engine.js +105 -0
- package/dist/scripts/lib/search-engine.js.map +1 -0
- package/dist/scripts/lib/session-payload.d.ts +2 -4
- package/dist/scripts/lib/session-payload.d.ts.map +1 -1
- package/dist/scripts/lib/session-payload.js +31 -43
- package/dist/scripts/lib/session-payload.js.map +1 -1
- package/dist/scripts/lib/snippet.d.ts +2 -0
- package/dist/scripts/lib/snippet.d.ts.map +1 -0
- package/dist/scripts/lib/snippet.js +32 -0
- package/dist/scripts/lib/snippet.js.map +1 -0
- package/dist/scripts/mindlore-fts5-index.js +44 -3
- package/dist/scripts/mindlore-fts5-index.js.map +1 -1
- package/dist/scripts/mindlore-fts5-search.d.ts +3 -5
- package/dist/scripts/mindlore-fts5-search.d.ts.map +1 -1
- package/dist/scripts/mindlore-fts5-search.js +41 -116
- package/dist/scripts/mindlore-fts5-search.js.map +1 -1
- package/dist/tests/chunker.test.d.ts +2 -0
- package/dist/tests/chunker.test.d.ts.map +1 -0
- package/dist/tests/chunker.test.js +40 -0
- package/dist/tests/chunker.test.js.map +1 -0
- package/dist/tests/chunks-migration.test.d.ts +2 -0
- package/dist/tests/chunks-migration.test.d.ts.map +1 -0
- package/dist/tests/chunks-migration.test.js +55 -0
- package/dist/tests/chunks-migration.test.js.map +1 -0
- package/dist/tests/compaction-snapshot.test.js +47 -0
- package/dist/tests/compaction-snapshot.test.js.map +1 -1
- package/dist/tests/daemon-integration.test.js +5 -5
- package/dist/tests/daemon-integration.test.js.map +1 -1
- package/dist/tests/fuzzy.test.d.ts +2 -0
- package/dist/tests/fuzzy.test.d.ts.map +1 -0
- package/dist/tests/fuzzy.test.js +70 -0
- package/dist/tests/fuzzy.test.js.map +1 -0
- package/dist/tests/helpers/db.d.ts.map +1 -1
- package/dist/tests/helpers/db.js +2 -1
- package/dist/tests/helpers/db.js.map +1 -1
- package/dist/tests/merge-defaults.test.d.ts +2 -0
- package/dist/tests/merge-defaults.test.d.ts.map +1 -0
- package/dist/tests/merge-defaults.test.js +35 -0
- package/dist/tests/merge-defaults.test.js.map +1 -0
- package/dist/tests/migrations-v063.test.d.ts +2 -0
- package/dist/tests/migrations-v063.test.d.ts.map +1 -0
- package/dist/tests/migrations-v063.test.js +84 -0
- package/dist/tests/migrations-v063.test.js.map +1 -0
- package/dist/tests/proximity.test.d.ts +2 -0
- package/dist/tests/proximity.test.d.ts.map +1 -0
- package/dist/tests/proximity.test.js +31 -0
- package/dist/tests/proximity.test.js.map +1 -0
- package/dist/tests/rrf.test.d.ts +2 -0
- package/dist/tests/rrf.test.d.ts.map +1 -0
- package/dist/tests/rrf.test.js +100 -0
- package/dist/tests/rrf.test.js.map +1 -0
- package/dist/tests/search-cache.test.d.ts +2 -0
- package/dist/tests/search-cache.test.d.ts.map +1 -0
- package/dist/tests/search-cache.test.js +95 -0
- package/dist/tests/search-cache.test.js.map +1 -0
- package/dist/tests/search-engine.test.d.ts +2 -0
- package/dist/tests/search-engine.test.d.ts.map +1 -0
- package/dist/tests/search-engine.test.js +125 -0
- package/dist/tests/search-engine.test.js.map +1 -0
- package/dist/tests/search-hook.test.js +3 -3
- package/dist/tests/search-hook.test.js.map +1 -1
- package/dist/tests/session-payload.test.d.ts +1 -1
- package/dist/tests/session-payload.test.js +1 -14
- package/dist/tests/session-payload.test.js.map +1 -1
- package/dist/tests/session-summary.test.js +40 -4
- package/dist/tests/session-summary.test.js.map +1 -1
- package/dist/tests/snippet.test.d.ts +2 -0
- package/dist/tests/snippet.test.d.ts.map +1 -0
- package/dist/tests/snippet.test.js +30 -0
- package/dist/tests/snippet.test.js.map +1 -0
- package/hooks/lib/mindlore-common.cjs +56 -3
- package/hooks/mindlore-index.cjs +6 -0
- package/hooks/mindlore-pre-compact.cjs +61 -52
- package/hooks/mindlore-search.cjs +90 -214
- package/hooks/mindlore-session-end.cjs +9 -15
- package/hooks/mindlore-session-focus.cjs +11 -26
- package/package.json +2 -2
- package/plugin.json +1 -1
- package/templates/config.json +1 -1
|
@@ -4,206 +4,114 @@
|
|
|
4
4
|
/**
|
|
5
5
|
* mindlore-search — UserPromptSubmit hook
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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,
|
|
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
|
-
|
|
21
|
-
let hybridSearchMod;
|
|
19
|
+
let searchEngineMod;
|
|
22
20
|
try {
|
|
23
|
-
|
|
21
|
+
searchEngineMod = require('../dist/scripts/lib/search-engine.js');
|
|
24
22
|
} catch (_err) {
|
|
25
|
-
//
|
|
23
|
+
// search-engine not built yet
|
|
26
24
|
}
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
55
|
-
if (
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
42
|
-
|
|
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 =
|
|
98
|
+
const diaryFiles = listSnapshots(diaryDir).filter(f => f.startsWith('delta-'));
|
|
114
99
|
|
|
115
100
|
if (diaryFiles.length > 0) {
|
|
116
|
-
const
|
|
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,
|
|
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.
|
|
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": "
|
|
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
package/templates/config.json
CHANGED