mindlore 0.6.1 → 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 +2 -0
- package/dist/scripts/cc-session-sync.d.ts.map +1 -1
- package/dist/scripts/cc-session-sync.js +56 -0
- package/dist/scripts/cc-session-sync.js.map +1 -1
- package/dist/scripts/fetch-raw.js +133 -8
- package/dist/scripts/fetch-raw.js.map +1 -1
- package/dist/scripts/init.js +28 -43
- 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/episodes.d.ts +1 -1
- package/dist/scripts/lib/episodes.d.ts.map +1 -1
- package/dist/scripts/lib/episodes.js +1 -1
- package/dist/scripts/lib/episodes.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 +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-v062.d.ts +3 -0
- package/dist/scripts/lib/migrations-v062.d.ts.map +1 -0
- package/dist/scripts/lib/migrations-v062.js +35 -0
- package/dist/scripts/lib/migrations-v062.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 +43 -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/lib/triage.d.ts +18 -0
- package/dist/scripts/lib/triage.d.ts.map +1 -0
- package/dist/scripts/lib/triage.js +81 -0
- package/dist/scripts/lib/triage.js.map +1 -0
- package/dist/scripts/mindlore-doctor.d.ts +1 -0
- package/dist/scripts/mindlore-doctor.d.ts.map +1 -1
- package/dist/scripts/mindlore-doctor.js +26 -1
- package/dist/scripts/mindlore-doctor.js.map +1 -1
- 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-health-check.d.ts +10 -0
- package/dist/scripts/mindlore-health-check.d.ts.map +1 -1
- package/dist/scripts/mindlore-health-check.js +26 -1
- package/dist/scripts/mindlore-health-check.js.map +1 -1
- package/dist/scripts/mindlore-perf.d.ts +3 -3
- package/dist/scripts/mindlore-perf.d.ts.map +1 -1
- package/dist/scripts/mindlore-perf.js +10 -8
- package/dist/scripts/mindlore-perf.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.d.ts +2 -0
- package/dist/tests/compaction-snapshot.test.d.ts.map +1 -0
- package/dist/tests/compaction-snapshot.test.js +102 -0
- package/dist/tests/compaction-snapshot.test.js.map +1 -0
- package/dist/tests/daemon-integration.test.js +5 -5
- package/dist/tests/daemon-integration.test.js.map +1 -1
- package/dist/tests/diary.test.js +3 -3
- package/dist/tests/diary.test.js.map +1 -1
- package/dist/tests/doctor.test.js +13 -0
- package/dist/tests/doctor.test.js.map +1 -1
- package/dist/tests/fetch-raw.test.js +12 -5
- package/dist/tests/fetch-raw.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/health-check-memory.test.js +27 -0
- package/dist/tests/health-check-memory.test.js.map +1 -1
- package/dist/tests/helpers/db.d.ts.map +1 -1
- package/dist/tests/helpers/db.js +4 -1
- package/dist/tests/helpers/db.js.map +1 -1
- package/dist/tests/init.test.js +20 -0
- package/dist/tests/init.test.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-v062.test.d.ts +2 -0
- package/dist/tests/migrations-v062.test.d.ts.map +1 -0
- package/dist/tests/migrations-v062.test.js +61 -0
- package/dist/tests/migrations-v062.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/nomination.test.js +1 -1
- 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/savings.test.d.ts +2 -0
- package/dist/tests/savings.test.d.ts.map +1 -0
- package/dist/tests/savings.test.js +87 -0
- package/dist/tests/savings.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.d.ts +2 -0
- package/dist/tests/session-summary.test.d.ts.map +1 -0
- package/dist/tests/session-summary.test.js +138 -0
- package/dist/tests/session-summary.test.js.map +1 -0
- 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/telemetry-perf.test.js +7 -0
- package/dist/tests/telemetry-perf.test.js.map +1 -1
- package/dist/tests/triage.test.d.ts +2 -0
- package/dist/tests/triage.test.d.ts.map +1 -0
- package/dist/tests/triage.test.js +69 -0
- package/dist/tests/triage.test.js.map +1 -0
- package/hooks/lib/mindlore-common.cjs +74 -13
- package/hooks/mindlore-index.cjs +6 -0
- package/hooks/mindlore-post-compact.cjs +23 -3
- package/hooks/mindlore-pre-compact.cjs +86 -3
- package/hooks/mindlore-search.cjs +90 -214
- package/hooks/mindlore-session-end.cjs +22 -18
- package/hooks/mindlore-session-focus.cjs +92 -71
- package/package.json +6 -5
- package/plugin.json +1 -1
- package/skills/mindlore-maintain/SKILL.md +1 -0
- package/templates/config.json +1 -1
|
@@ -11,7 +11,63 @@
|
|
|
11
11
|
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
|
-
const { findMindloreDir, 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();
|
|
@@ -34,11 +90,10 @@ function main() {
|
|
|
34
90
|
|
|
35
91
|
const now = new Date();
|
|
36
92
|
const iso = now.toISOString();
|
|
93
|
+
const ts = iso.replace(/[:.]/g, '-');
|
|
37
94
|
|
|
38
|
-
// Write pre-compact episode
|
|
39
95
|
const episodesDir = path.join(baseDir, 'episodes');
|
|
40
96
|
try {
|
|
41
|
-
const ts = iso.replace(/[:.]/g, '-');
|
|
42
97
|
const episodePath = path.join(episodesDir, `pre-compact-${ts}.md`);
|
|
43
98
|
const content = [
|
|
44
99
|
'---',
|
|
@@ -61,6 +116,34 @@ function main() {
|
|
|
61
116
|
fs.appendFileSync(logPath, entry, 'utf8');
|
|
62
117
|
} catch (_err) { /* log file may not exist */ }
|
|
63
118
|
|
|
119
|
+
// Build compaction snapshot (#17)
|
|
120
|
+
const diaryDir = path.join(baseDir, 'diary');
|
|
121
|
+
try {
|
|
122
|
+
const sections = [];
|
|
123
|
+
sections.push(...collectRecentEpisodes(baseDir));
|
|
124
|
+
sections.push(...collectGitDiff());
|
|
125
|
+
sections.push(...getActivePlan());
|
|
126
|
+
|
|
127
|
+
if (sections.length > 0) {
|
|
128
|
+
const snapshotContent = [
|
|
129
|
+
'---',
|
|
130
|
+
'type: compaction-snapshot',
|
|
131
|
+
`date: ${iso.slice(0, 10)}`,
|
|
132
|
+
`project: ${path.basename(process.cwd())}`,
|
|
133
|
+
'---',
|
|
134
|
+
'',
|
|
135
|
+
...sections,
|
|
136
|
+
].join('\n');
|
|
137
|
+
fs.writeFileSync(path.join(diaryDir, `compaction-snapshot-${ts}.md`), snapshotContent);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const snapshots = listSnapshots(diaryDir).filter(f => f.startsWith('compaction-'));
|
|
141
|
+
while (snapshots.length > 5) {
|
|
142
|
+
const oldest = snapshots.shift();
|
|
143
|
+
if (oldest) fs.unlinkSync(path.join(diaryDir, oldest));
|
|
144
|
+
}
|
|
145
|
+
} catch (_err) { /* snapshot is best-effort */ }
|
|
146
|
+
|
|
64
147
|
process.stdout.write('[Mindlore: pre-compact FTS5 flush complete]\n');
|
|
65
148
|
}
|
|
66
149
|
|
|
@@ -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'),
|
|
@@ -436,29 +444,25 @@ function syncObsidian(baseDir) {
|
|
|
436
444
|
const destBase = path.join(vaultPath, 'mindlore');
|
|
437
445
|
let exported = 0;
|
|
438
446
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
if (!fs.existsSync(srcDir)) continue;
|
|
442
|
-
|
|
443
|
-
const destDir = path.join(destBase, dir);
|
|
447
|
+
function walkAndExport(srcDir, destDir) {
|
|
448
|
+
if (!fs.existsSync(srcDir)) return;
|
|
444
449
|
fs.mkdirSync(destDir, { recursive: true });
|
|
445
|
-
|
|
446
|
-
for (const file of fs.readdirSync(srcDir).filter(f => f.endsWith('.md') && !f.startsWith('_'))) {
|
|
447
|
-
if (exportMdFile(path.join(srcDir, file), path.join(destDir, file), convertFn)) exported++;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// One-level subdirectories (e.g. diary/mindlore/)
|
|
451
450
|
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
452
|
-
if (
|
|
453
|
-
const
|
|
454
|
-
const
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
451
|
+
if (entry.name.startsWith('_') || entry.name.startsWith('.')) continue;
|
|
452
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
453
|
+
const destPath = path.join(destDir, entry.name);
|
|
454
|
+
if (entry.isDirectory()) {
|
|
455
|
+
walkAndExport(srcPath, destPath);
|
|
456
|
+
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
457
|
+
if (exportMdFile(srcPath, destPath, convertFn)) exported++;
|
|
458
458
|
}
|
|
459
459
|
}
|
|
460
460
|
}
|
|
461
461
|
|
|
462
|
+
for (const dir of EXPORT_DIRS) {
|
|
463
|
+
walkAndExport(path.join(baseDir, dir), path.join(destBase, dir));
|
|
464
|
+
}
|
|
465
|
+
|
|
462
466
|
for (const rootFile of ['INDEX.md', 'log.md']) {
|
|
463
467
|
const srcPath = path.join(baseDir, rootFile);
|
|
464
468
|
if (!fs.existsSync(srcPath)) continue;
|