mindlore 0.6.2 → 0.6.4
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 +97 -0
- package/dist/scripts/lib/chunker.js.map +1 -0
- package/dist/scripts/lib/constants.d.ts +5 -0
- package/dist/scripts/lib/constants.d.ts.map +1 -1
- package/dist/scripts/lib/constants.js +35 -1
- package/dist/scripts/lib/constants.js.map +1 -1
- 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 +90 -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 +59 -0
- package/dist/scripts/lib/proximity.js.map +1 -0
- package/dist/scripts/lib/rrf.d.ts +28 -0
- package/dist/scripts/lib/rrf.d.ts.map +1 -0
- package/dist/scripts/lib/rrf.js +67 -0
- package/dist/scripts/lib/rrf.js.map +1 -0
- package/dist/scripts/lib/search-cache.d.ts +34 -0
- package/dist/scripts/lib/search-cache.d.ts.map +1 -0
- package/dist/scripts/lib/search-cache.js +102 -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 +104 -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/scripts/mindlore-rrf-bench.d.ts +2 -0
- package/dist/scripts/mindlore-rrf-bench.d.ts.map +1 -0
- package/dist/scripts/mindlore-rrf-bench.js +30 -0
- package/dist/scripts/mindlore-rrf-bench.js.map +1 -0
- 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 +139 -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 +127 -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/dist/tests/stop-words.test.d.ts +2 -0
- package/dist/tests/stop-words.test.d.ts.map +1 -0
- package/dist/tests/stop-words.test.js +21 -0
- package/dist/tests/stop-words.test.js.map +1 -0
- package/hooks/lib/mindlore-common.cjs +72 -35
- package/hooks/mindlore-index.cjs +6 -0
- package/hooks/mindlore-pre-compact.cjs +61 -52
- package/hooks/mindlore-search.cjs +91 -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
package/hooks/mindlore-index.cjs
CHANGED
|
@@ -12,6 +12,10 @@ const fs = require('fs');
|
|
|
12
12
|
const path = require('path');
|
|
13
13
|
const { MINDLORE_DIR, DB_NAME, SKIP_FILES, sha256, openDatabase, parseFrontmatter, extractFtsMetadata, insertFtsRow, readHookStdin, getProjectName, resolveProject, globalDir, hookLog, withTelemetry } = require('./lib/mindlore-common.cjs');
|
|
14
14
|
|
|
15
|
+
function invalidateSearchCache(db) {
|
|
16
|
+
try { db.exec('DELETE FROM search_cache'); } catch (_) { /* table may not exist */ }
|
|
17
|
+
}
|
|
18
|
+
|
|
15
19
|
function main() {
|
|
16
20
|
const filePath = readHookStdin(['path', 'file_path']);
|
|
17
21
|
if (!filePath) return;
|
|
@@ -55,6 +59,7 @@ function main() {
|
|
|
55
59
|
try {
|
|
56
60
|
db.prepare('DELETE FROM mindlore_fts WHERE path = ?').run(filePath);
|
|
57
61
|
db.prepare('DELETE FROM file_hashes WHERE path = ?').run(filePath);
|
|
62
|
+
invalidateSearchCache(db);
|
|
58
63
|
} finally {
|
|
59
64
|
db.close();
|
|
60
65
|
}
|
|
@@ -92,6 +97,7 @@ function main() {
|
|
|
92
97
|
).run(filePath, hash, new Date().toISOString());
|
|
93
98
|
});
|
|
94
99
|
updateIndex();
|
|
100
|
+
invalidateSearchCache(db);
|
|
95
101
|
} finally {
|
|
96
102
|
db.close();
|
|
97
103
|
}
|
|
@@ -11,7 +11,63 @@
|
|
|
11
11
|
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
|
-
const { findMindloreDir, openDatabase, hookLog, withTelemetry } = require('./lib/mindlore-common.cjs');
|
|
14
|
+
const { findMindloreDir, openDatabase, hookLog, withTelemetry, listSnapshots } = require('./lib/mindlore-common.cjs');
|
|
15
|
+
|
|
16
|
+
function collectRecentEpisodes(baseDir) {
|
|
17
|
+
try {
|
|
18
|
+
const dbPath = path.join(baseDir, 'mindlore.db');
|
|
19
|
+
const db = openDatabase(dbPath, { readonly: true });
|
|
20
|
+
if (!db) return [];
|
|
21
|
+
try {
|
|
22
|
+
const episodes = db.prepare(
|
|
23
|
+
"SELECT kind, summary FROM episodes WHERE created_at > datetime('now', '-4 hours') ORDER BY created_at DESC LIMIT 20"
|
|
24
|
+
).all();
|
|
25
|
+
if (episodes.length === 0) return [];
|
|
26
|
+
const grouped = {};
|
|
27
|
+
for (const ep of episodes) {
|
|
28
|
+
const kind = ep.kind || 'other';
|
|
29
|
+
if (!grouped[kind]) grouped[kind] = [];
|
|
30
|
+
grouped[kind].push(ep.summary);
|
|
31
|
+
}
|
|
32
|
+
const lines = ['## Session Episodes'];
|
|
33
|
+
for (const [kind, items] of Object.entries(grouped)) {
|
|
34
|
+
lines.push(`### ${kind}`);
|
|
35
|
+
for (const item of items) lines.push(`- ${item}`);
|
|
36
|
+
}
|
|
37
|
+
return lines;
|
|
38
|
+
} finally {
|
|
39
|
+
db.close();
|
|
40
|
+
}
|
|
41
|
+
} catch (_err) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function collectGitDiff() {
|
|
47
|
+
try {
|
|
48
|
+
const { execSync } = require('child_process');
|
|
49
|
+
const diffStat = execSync('git diff --stat HEAD 2>/dev/null || echo ""', {
|
|
50
|
+
encoding: 'utf8', timeout: 2000, windowsHide: true,
|
|
51
|
+
}).trim();
|
|
52
|
+
if (diffStat) return ['## Changed Files (uncommitted)', '```', diffStat, '```'];
|
|
53
|
+
return [];
|
|
54
|
+
} catch (_err) {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getActivePlan() {
|
|
60
|
+
try {
|
|
61
|
+
const plansDir = path.join(process.cwd(), '.claude', 'plans');
|
|
62
|
+
if (!fs.existsSync(plansDir)) return [];
|
|
63
|
+
const plans = fs.readdirSync(plansDir).filter(f => f.endsWith('.md'));
|
|
64
|
+
if (plans.length === 0) return [];
|
|
65
|
+
const latestPlan = plans.sort().pop();
|
|
66
|
+
return [`## Active Plan: ${latestPlan}`];
|
|
67
|
+
} catch (_err) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
15
71
|
|
|
16
72
|
function main() {
|
|
17
73
|
const baseDir = findMindloreDir();
|
|
@@ -64,54 +120,9 @@ function main() {
|
|
|
64
120
|
const diaryDir = path.join(baseDir, 'diary');
|
|
65
121
|
try {
|
|
66
122
|
const sections = [];
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const db = openDatabase(dbPath, { readonly: true });
|
|
71
|
-
if (db) {
|
|
72
|
-
try {
|
|
73
|
-
const episodes = db.prepare(
|
|
74
|
-
"SELECT kind, summary FROM episodes WHERE created_at > datetime('now', '-4 hours') ORDER BY created_at DESC LIMIT 20"
|
|
75
|
-
).all();
|
|
76
|
-
if (episodes.length > 0) {
|
|
77
|
-
const grouped = {};
|
|
78
|
-
for (const ep of episodes) {
|
|
79
|
-
const kind = ep.kind || 'other';
|
|
80
|
-
if (!grouped[kind]) grouped[kind] = [];
|
|
81
|
-
grouped[kind].push(ep.summary);
|
|
82
|
-
}
|
|
83
|
-
sections.push('## Session Episodes');
|
|
84
|
-
for (const [kind, items] of Object.entries(grouped)) {
|
|
85
|
-
sections.push(`### ${kind}`);
|
|
86
|
-
for (const item of items) sections.push(`- ${item}`);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
} finally {
|
|
90
|
-
db.close();
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
} catch (_err) { /* graceful skip */ }
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
const { execSync } = require('child_process');
|
|
97
|
-
const diffStat = execSync('git diff --stat HEAD 2>/dev/null || echo ""', {
|
|
98
|
-
encoding: 'utf8', timeout: 2000, windowsHide: true,
|
|
99
|
-
}).trim();
|
|
100
|
-
if (diffStat) {
|
|
101
|
-
sections.push('## Changed Files (uncommitted)', '```', diffStat, '```');
|
|
102
|
-
}
|
|
103
|
-
} catch (_err) { /* no git */ }
|
|
104
|
-
|
|
105
|
-
try {
|
|
106
|
-
const plansDir = path.join(process.cwd(), '.claude', 'plans');
|
|
107
|
-
if (fs.existsSync(plansDir)) {
|
|
108
|
-
const plans = fs.readdirSync(plansDir).filter(f => f.endsWith('.md'));
|
|
109
|
-
if (plans.length > 0) {
|
|
110
|
-
const latestPlan = plans.sort().pop();
|
|
111
|
-
sections.push(`## Active Plan: ${latestPlan}`);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
} catch (_err) { /* skip */ }
|
|
123
|
+
sections.push(...collectRecentEpisodes(baseDir));
|
|
124
|
+
sections.push(...collectGitDiff());
|
|
125
|
+
sections.push(...getActivePlan());
|
|
115
126
|
|
|
116
127
|
if (sections.length > 0) {
|
|
117
128
|
const snapshotContent = [
|
|
@@ -126,9 +137,7 @@ function main() {
|
|
|
126
137
|
fs.writeFileSync(path.join(diaryDir, `compaction-snapshot-${ts}.md`), snapshotContent);
|
|
127
138
|
}
|
|
128
139
|
|
|
129
|
-
const snapshots =
|
|
130
|
-
.filter(f => f.startsWith('compaction-snapshot-'))
|
|
131
|
-
.sort();
|
|
140
|
+
const snapshots = listSnapshots(diaryDir).filter(f => f.startsWith('compaction-'));
|
|
132
141
|
while (snapshots.length > 5) {
|
|
133
142
|
const oldest = snapshots.shift();
|
|
134
143
|
if (oldest) fs.unlinkSync(path.join(diaryDir, oldest));
|
|
@@ -4,206 +4,115 @@
|
|
|
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 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;
|
|
111
82
|
}
|
|
112
|
-
db.close();
|
|
113
|
-
return results;
|
|
114
83
|
}
|
|
115
|
-
} catch (hybridErr) {
|
|
116
|
-
hookLog('search', 'warn', `Hybrid search fallback to FTS5: ${hybridErr?.message || hybridErr}`);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
84
|
|
|
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,
|
|
85
|
+
const results = searchEngineMod.search(db, userMessage, {
|
|
86
|
+
project,
|
|
87
|
+
maxResults: effectiveMax,
|
|
88
|
+
synonyms,
|
|
153
89
|
});
|
|
154
|
-
}
|
|
155
|
-
} catch (_err) {
|
|
156
|
-
// FTS5 query error — silently skip
|
|
157
|
-
} finally {
|
|
158
|
-
db.close();
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return results;
|
|
162
|
-
}
|
|
163
90
|
|
|
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;
|
|
91
|
+
if (cache) cache.set(userMessage, results);
|
|
188
92
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
if (keywords.length < MIN_QUERY_WORDS) return;
|
|
93
|
+
const baseDir = path.dirname(dbPath);
|
|
94
|
+
for (const r of results) {
|
|
95
|
+
allResults.push({ ...r, baseDir });
|
|
96
|
+
}
|
|
194
97
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
+
}
|
|
198
110
|
}
|
|
199
111
|
|
|
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)
|
|
112
|
+
// Deduplicate by full path
|
|
204
113
|
const seen = new Set();
|
|
205
114
|
const unique = [];
|
|
206
|
-
for (const r of
|
|
115
|
+
for (const r of allResults) {
|
|
207
116
|
const normalized = path.resolve(r.path);
|
|
208
117
|
if (!seen.has(normalized)) {
|
|
209
118
|
seen.add(normalized);
|
|
@@ -211,45 +120,30 @@ function main() {
|
|
|
211
120
|
}
|
|
212
121
|
}
|
|
213
122
|
|
|
123
|
+
// Sort by score descending, take top N
|
|
124
|
+
unique.sort((a, b) => b.score - a.score);
|
|
214
125
|
const relevant = unique.slice(0, MAX_RESULTS);
|
|
215
126
|
if (relevant.length === 0) return;
|
|
216
127
|
|
|
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
128
|
// Token budget from config
|
|
239
|
-
const config = readConfig(path.dirname(dbPaths[0]));
|
|
240
129
|
const budget = (config && config.tokenBudget) || {};
|
|
241
|
-
|
|
242
|
-
const perResultChars = ((budget.perResult || 500) * 4); // ~4 chars/token
|
|
130
|
+
const perResultChars = ((budget.perResult || 500) * 4);
|
|
243
131
|
const totalChars = ((budget.searchResults || 1500) * 4);
|
|
244
132
|
|
|
245
|
-
// Build
|
|
133
|
+
// Build output
|
|
246
134
|
const output = [];
|
|
247
135
|
let totalUsed = 0;
|
|
248
136
|
for (const r of relevant) {
|
|
249
137
|
if (totalUsed >= totalChars) break;
|
|
250
138
|
const relativePath = path.relative(r.baseDir, r.path).replace(/\\/g, '/');
|
|
251
139
|
|
|
252
|
-
|
|
140
|
+
let headings = [];
|
|
141
|
+
const contentStr = r.content || '';
|
|
142
|
+
if (contentStr) {
|
|
143
|
+
try {
|
|
144
|
+
headings = extractHeadings(contentStr, 3);
|
|
145
|
+
} catch (_err) { /* skip */ }
|
|
146
|
+
}
|
|
253
147
|
|
|
254
148
|
const category = r.category || path.dirname(relativePath).split('/')[0];
|
|
255
149
|
const title = r.title || r.slug || path.basename(r.path, '.md');
|
|
@@ -263,32 +157,15 @@ function main() {
|
|
|
263
157
|
output.push(truncated);
|
|
264
158
|
}
|
|
265
159
|
|
|
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
160
|
if (output.length > 0) {
|
|
283
161
|
let outputStr = output.join('\n\n') + '\n';
|
|
284
162
|
|
|
285
|
-
const OFFLOAD_THRESHOLD = 10240;
|
|
163
|
+
const OFFLOAD_THRESHOLD = 10240;
|
|
286
164
|
if (outputStr.length > OFFLOAD_THRESHOLD) {
|
|
287
165
|
const baseDir = path.dirname(dbPaths[0]);
|
|
288
166
|
const tmpDir = path.join(baseDir, 'tmp');
|
|
289
167
|
fs.mkdirSync(tmpDir, { recursive: true });
|
|
290
168
|
|
|
291
|
-
// Cleanup stale tmp files before writing new one (>1h old, keep max 20)
|
|
292
169
|
try {
|
|
293
170
|
const oneHourAgo = Date.now() - 3600000;
|
|
294
171
|
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.4",
|
|
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"
|