mindlore 0.6.7 → 0.6.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +259 -259
- package/SCHEMA.md +292 -292
- package/dist/scripts/cc-memory-bulk-sync.d.ts.map +1 -1
- package/dist/scripts/cc-memory-bulk-sync.js +47 -42
- package/dist/scripts/cc-memory-bulk-sync.js.map +1 -1
- package/dist/scripts/cc-session-sync.d.ts.map +1 -1
- package/dist/scripts/cc-session-sync.js +58 -48
- package/dist/scripts/cc-session-sync.js.map +1 -1
- package/dist/scripts/init.js +8 -8
- package/dist/scripts/init.js.map +1 -1
- package/dist/scripts/lib/all-migrations.d.ts.map +1 -1
- package/dist/scripts/lib/all-migrations.js +4 -1
- package/dist/scripts/lib/all-migrations.js.map +1 -1
- package/dist/scripts/lib/consolidation.d.ts +4 -3
- package/dist/scripts/lib/consolidation.d.ts.map +1 -1
- package/dist/scripts/lib/consolidation.js +10 -10
- package/dist/scripts/lib/consolidation.js.map +1 -1
- package/dist/scripts/lib/constants.d.ts +1 -7
- package/dist/scripts/lib/constants.d.ts.map +1 -1
- package/dist/scripts/lib/constants.js +2 -9
- package/dist/scripts/lib/constants.js.map +1 -1
- package/dist/scripts/lib/db-helpers.d.ts +0 -15
- package/dist/scripts/lib/db-helpers.d.ts.map +1 -1
- package/dist/scripts/lib/db-helpers.js +1 -51
- package/dist/scripts/lib/db-helpers.js.map +1 -1
- package/dist/scripts/lib/decay.d.ts.map +1 -1
- package/dist/scripts/lib/decay.js +9 -9
- package/dist/scripts/lib/decay.js.map +1 -1
- package/dist/scripts/lib/episodes.js +23 -23
- package/dist/scripts/lib/migrations-v061.js +21 -21
- package/dist/scripts/lib/migrations-v062.js +11 -11
- package/dist/scripts/lib/migrations-v063.js +14 -14
- package/dist/scripts/lib/migrations-v067.js +11 -11
- package/dist/scripts/lib/migrations-v068.d.ts +3 -0
- package/dist/scripts/lib/migrations-v068.d.ts.map +1 -0
- package/dist/scripts/lib/migrations-v068.js +37 -0
- package/dist/scripts/lib/migrations-v068.js.map +1 -0
- package/dist/scripts/lib/migrations.d.ts.map +1 -1
- package/dist/scripts/lib/migrations.js +0 -15
- package/dist/scripts/lib/migrations.js.map +1 -1
- package/dist/scripts/lib/schema-version.js +6 -6
- package/dist/scripts/lib/search-cache.js +11 -11
- package/dist/scripts/lib/session-payload.d.ts.map +1 -1
- package/dist/scripts/lib/session-payload.js +7 -7
- package/dist/scripts/lib/session-payload.js.map +1 -1
- package/dist/scripts/lib/triage.js +3 -3
- package/dist/scripts/mindlore-backup.js +9 -9
- package/dist/scripts/mindlore-fts5-index.d.ts +1 -2
- package/dist/scripts/mindlore-fts5-index.d.ts.map +1 -1
- package/dist/scripts/mindlore-fts5-index.js +12 -64
- package/dist/scripts/mindlore-fts5-index.js.map +1 -1
- package/dist/scripts/mindlore-health-check.d.ts.map +1 -1
- package/dist/scripts/mindlore-health-check.js +0 -11
- package/dist/scripts/mindlore-health-check.js.map +1 -1
- package/dist/tests/cc-memory-bulk-sync.test.js +23 -0
- package/dist/tests/cc-memory-bulk-sync.test.js.map +1 -1
- package/dist/tests/cc-session-sync.test.js +25 -0
- package/dist/tests/cc-session-sync.test.js.map +1 -1
- package/dist/tests/compaction-snapshot.test.js +2 -2
- package/dist/tests/consolidation.test.js +5 -5
- package/dist/tests/consolidation.test.js.map +1 -1
- package/dist/tests/decay.test.js +9 -9
- package/dist/tests/diary.test.js +4 -4
- package/dist/tests/episode-kind-constant.test.d.ts +2 -0
- package/dist/tests/episode-kind-constant.test.d.ts.map +1 -0
- package/dist/tests/episode-kind-constant.test.js +28 -0
- package/dist/tests/episode-kind-constant.test.js.map +1 -0
- package/dist/tests/episodes-inject.test.js +9 -9
- package/dist/tests/fts5.test.js +66 -125
- package/dist/tests/fts5.test.js.map +1 -1
- package/dist/tests/globalSetup.d.ts +2 -0
- package/dist/tests/globalSetup.d.ts.map +1 -0
- package/dist/tests/globalSetup.js +36 -0
- package/dist/tests/globalSetup.js.map +1 -0
- package/dist/tests/helpers/db.d.ts +13 -5
- package/dist/tests/helpers/db.d.ts.map +1 -1
- package/dist/tests/helpers/db.js +60 -33
- package/dist/tests/helpers/db.js.map +1 -1
- package/dist/tests/lesson-graduation.test.js +11 -11
- package/dist/tests/lesson-graduation.test.js.map +1 -1
- package/dist/tests/migrations-v053.test.js +16 -16
- package/dist/tests/migrations-v061.test.js +10 -10
- package/dist/tests/migrations-v063.test.js +2 -2
- package/dist/tests/migrations-v068.test.d.ts +2 -0
- package/dist/tests/migrations-v068.test.d.ts.map +1 -0
- package/dist/tests/migrations-v068.test.js +53 -0
- package/dist/tests/migrations-v068.test.js.map +1 -0
- package/dist/tests/nomination-counts.test.d.ts +2 -0
- package/dist/tests/nomination-counts.test.d.ts.map +1 -0
- package/dist/tests/nomination-counts.test.js +51 -0
- package/dist/tests/nomination-counts.test.js.map +1 -0
- package/dist/tests/recall-telemetry.test.js +8 -8
- package/dist/tests/schema-version.test.js +3 -7
- package/dist/tests/schema-version.test.js.map +1 -1
- package/dist/tests/search-hook.test.js +2 -2
- package/dist/tests/sec-regression.test.js +0 -50
- package/dist/tests/sec-regression.test.js.map +1 -1
- package/dist/tests/session-end-cleanup.test.js +3 -20
- package/dist/tests/session-end-cleanup.test.js.map +1 -1
- package/dist/tests/session-focus.test.js +7 -30
- package/dist/tests/session-focus.test.js.map +1 -1
- package/dist/tests/session-payload.test.js +1 -1
- package/dist/tests/session-summary.test.js +1 -1
- package/hooks/lib/constants.cjs +15 -0
- package/hooks/lib/mindlore-common.cjs +974 -1042
- package/hooks/mindlore-cwd-changed.cjs +57 -57
- package/hooks/mindlore-decision-detector.cjs +54 -54
- package/hooks/mindlore-dont-repeat.cjs +222 -222
- package/hooks/mindlore-fts5-sync.cjs +97 -88
- package/hooks/mindlore-index.cjs +229 -229
- package/hooks/mindlore-model-router.cjs +54 -54
- package/hooks/mindlore-post-compact.cjs +69 -69
- package/hooks/mindlore-post-read.cjs +106 -106
- package/hooks/mindlore-pre-compact.cjs +154 -154
- package/hooks/mindlore-read-guard.cjs +105 -105
- package/hooks/mindlore-research-guard.cjs +176 -176
- package/hooks/mindlore-search.cjs +200 -200
- package/hooks/mindlore-session-end.cjs +509 -526
- package/hooks/mindlore-session-focus.cjs +256 -259
- package/package.json +75 -78
- package/plugin.json +1 -1
- package/skills/mindlore-diary/SKILL.md +85 -85
- package/skills/mindlore-evolve/SKILL.md +126 -126
- package/skills/mindlore-explore/SKILL.md +109 -109
- package/skills/mindlore-ingest/SKILL.md +195 -195
- package/skills/mindlore-maintain/SKILL.md +125 -125
- package/skills/mindlore-query/SKILL.md +151 -151
- package/skills/mindlore-reflect/SKILL.md +141 -141
- package/skills/mindlore-stats/SKILL.md +106 -106
- package/templates/INDEX.md +14 -14
- package/templates/SCHEMA.md +292 -292
- package/templates/config.json +1 -1
- package/templates/extraction/article.md +15 -15
- package/templates/extraction/changelog.md +15 -15
- package/templates/extraction/default.md +15 -15
- package/templates/extraction/docs.md +15 -15
- package/templates/extraction/github-repo.md +17 -17
- package/dist/scripts/lib/daemon.d.ts +0 -16
- package/dist/scripts/lib/daemon.d.ts.map +0 -1
- package/dist/scripts/lib/daemon.js +0 -133
- package/dist/scripts/lib/daemon.js.map +0 -1
- package/dist/scripts/lib/embedding.d.ts +0 -5
- package/dist/scripts/lib/embedding.d.ts.map +0 -1
- package/dist/scripts/lib/embedding.js +0 -44
- package/dist/scripts/lib/embedding.js.map +0 -1
- package/dist/scripts/mindlore-daemon.d.ts +0 -2
- package/dist/scripts/mindlore-daemon.d.ts.map +0 -1
- package/dist/scripts/mindlore-daemon.js +0 -117
- package/dist/scripts/mindlore-daemon.js.map +0 -1
- package/dist/tests/daemon-integration.test.d.ts +0 -2
- package/dist/tests/daemon-integration.test.d.ts.map +0 -1
- package/dist/tests/daemon-integration.test.js +0 -37
- package/dist/tests/daemon-integration.test.js.map +0 -1
- package/dist/tests/daemon.test.d.ts +0 -2
- package/dist/tests/daemon.test.d.ts.map +0 -1
- package/dist/tests/daemon.test.js +0 -187
- package/dist/tests/daemon.test.js.map +0 -1
- package/dist/tests/embedding-hf-integration.test.d.ts +0 -2
- package/dist/tests/embedding-hf-integration.test.d.ts.map +0 -1
- package/dist/tests/embedding-hf-integration.test.js +0 -52
- package/dist/tests/embedding-hf-integration.test.js.map +0 -1
- package/dist/tests/embedding.test.d.ts +0 -6
- package/dist/tests/embedding.test.d.ts.map +0 -1
- package/dist/tests/embedding.test.js +0 -71
- package/dist/tests/embedding.test.js.map +0 -1
- package/dist/tests/sqlite-vec-v12.test.d.ts +0 -2
- package/dist/tests/sqlite-vec-v12.test.d.ts.map +0 -1
- package/dist/tests/sqlite-vec-v12.test.js +0 -72
- package/dist/tests/sqlite-vec-v12.test.js.map +0 -1
|
@@ -1,88 +1,97 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* mindlore-fts5-sync — FileChanged hook (incremental re-index)
|
|
6
|
-
*
|
|
7
|
-
* Handles bulk file changes by checking all .mindlore/ .md files
|
|
8
|
-
* against their content hashes and re-indexing only changed ones.
|
|
9
|
-
*
|
|
10
|
-
* Lightweight complement to mindlore-index.cjs which handles single files.
|
|
11
|
-
* This hook catches cases where multiple files change at once (e.g., git pull).
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const fs = require('fs');
|
|
15
|
-
const path = require('path');
|
|
16
|
-
const { DB_NAME, sha256, openDatabase, getAllMdFiles, parseFrontmatter, extractFtsMetadata, insertFtsRow, readHookStdin, getActiveMindloreDir, getProjectName, resolveProject, hookLog, withTelemetry, SQL_FTS_SESSIONS_INSERT, isSessionCategory, isInsideMindloreDir } = require('./lib/mindlore-common.cjs');
|
|
17
|
-
|
|
18
|
-
function main() {
|
|
19
|
-
const filePath = readHookStdin(['path', 'file_path']);
|
|
20
|
-
|
|
21
|
-
if (!filePath) return;
|
|
22
|
-
const resolved = path.resolve(filePath);
|
|
23
|
-
if (!isInsideMindloreDir(resolved)) return;
|
|
24
|
-
|
|
25
|
-
// Skip if this is a single .md file change — mindlore-index.cjs handles those.
|
|
26
|
-
// This hook is for bulk changes (git pull, manual batch edits).
|
|
27
|
-
if (filePath.endsWith('.md')) return;
|
|
28
|
-
|
|
29
|
-
const baseDir = getActiveMindloreDir();
|
|
30
|
-
if (!fs.existsSync(baseDir)) return;
|
|
31
|
-
|
|
32
|
-
const dbPath = path.join(baseDir, DB_NAME);
|
|
33
|
-
if (!fs.existsSync(dbPath)) return;
|
|
34
|
-
|
|
35
|
-
const db = openDatabase(dbPath);
|
|
36
|
-
if (!db) return;
|
|
37
|
-
|
|
38
|
-
const mdFiles = getAllMdFiles(baseDir);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const getHash = db.prepare('SELECT content_hash FROM file_hashes WHERE path = ?');
|
|
42
|
-
const deleteFts = db.prepare('DELETE FROM mindlore_fts WHERE path = ?');
|
|
43
|
-
const deleteFtsSessions = db.prepare('DELETE FROM mindlore_fts_sessions WHERE path = ?');
|
|
44
|
-
const insertFtsSessions = db.prepare(SQL_FTS_SESSIONS_INSERT);
|
|
45
|
-
const upsertHash = db.prepare(`
|
|
46
|
-
INSERT INTO file_hashes (path, content_hash, last_indexed)
|
|
47
|
-
VALUES (?, ?, ?)
|
|
48
|
-
ON CONFLICT(path) DO UPDATE SET
|
|
49
|
-
content_hash = excluded.content_hash,
|
|
50
|
-
last_indexed = excluded.last_indexed
|
|
51
|
-
`);
|
|
52
|
-
|
|
53
|
-
const now = new Date().toISOString();
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
const project = getProjectName();
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* mindlore-fts5-sync — FileChanged hook (incremental re-index)
|
|
6
|
+
*
|
|
7
|
+
* Handles bulk file changes by checking all .mindlore/ .md files
|
|
8
|
+
* against their content hashes and re-indexing only changed ones.
|
|
9
|
+
*
|
|
10
|
+
* Lightweight complement to mindlore-index.cjs which handles single files.
|
|
11
|
+
* This hook catches cases where multiple files change at once (e.g., git pull).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const { DB_NAME, sha256, openDatabase, getAllMdFiles, parseFrontmatter, extractFtsMetadata, insertFtsRow, readHookStdin, getActiveMindloreDir, getProjectName, resolveProject, hookLog, withTelemetry, SQL_FTS_SESSIONS_INSERT, isSessionCategory, isInsideMindloreDir } = require('./lib/mindlore-common.cjs');
|
|
17
|
+
|
|
18
|
+
function main() {
|
|
19
|
+
const filePath = readHookStdin(['path', 'file_path']);
|
|
20
|
+
|
|
21
|
+
if (!filePath) return;
|
|
22
|
+
const resolved = path.resolve(filePath);
|
|
23
|
+
if (!isInsideMindloreDir(resolved)) return;
|
|
24
|
+
|
|
25
|
+
// Skip if this is a single .md file change — mindlore-index.cjs handles those.
|
|
26
|
+
// This hook is for bulk changes (git pull, manual batch edits).
|
|
27
|
+
if (filePath.endsWith('.md')) return;
|
|
28
|
+
|
|
29
|
+
const baseDir = getActiveMindloreDir();
|
|
30
|
+
if (!fs.existsSync(baseDir)) return;
|
|
31
|
+
|
|
32
|
+
const dbPath = path.join(baseDir, DB_NAME);
|
|
33
|
+
if (!fs.existsSync(dbPath)) return;
|
|
34
|
+
|
|
35
|
+
const db = openDatabase(dbPath);
|
|
36
|
+
if (!db) return;
|
|
37
|
+
|
|
38
|
+
const mdFiles = getAllMdFiles(baseDir);
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
const getHash = db.prepare('SELECT content_hash FROM file_hashes WHERE path = ?');
|
|
42
|
+
const deleteFts = db.prepare('DELETE FROM mindlore_fts WHERE path = ?');
|
|
43
|
+
const deleteFtsSessions = db.prepare('DELETE FROM mindlore_fts_sessions WHERE path = ?');
|
|
44
|
+
const insertFtsSessions = db.prepare(SQL_FTS_SESSIONS_INSERT);
|
|
45
|
+
const upsertHash = db.prepare(`
|
|
46
|
+
INSERT INTO file_hashes (path, content_hash, last_indexed)
|
|
47
|
+
VALUES (?, ?, ?)
|
|
48
|
+
ON CONFLICT(path) DO UPDATE SET
|
|
49
|
+
content_hash = excluded.content_hash,
|
|
50
|
+
last_indexed = excluded.last_indexed
|
|
51
|
+
`);
|
|
52
|
+
|
|
53
|
+
const now = new Date().toISOString();
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const project = getProjectName();
|
|
57
|
+
|
|
58
|
+
// Pre-read all files outside the DB transaction to avoid holding
|
|
59
|
+
// the write lock during slow file I/O (R4 root cause fix).
|
|
60
|
+
const changedFiles = [];
|
|
61
|
+
for (const file of mdFiles) {
|
|
62
|
+
const content = fs.readFileSync(file, 'utf8').replace(/\r\n/g, '\n');
|
|
63
|
+
const hash = sha256(content);
|
|
64
|
+
|
|
65
|
+
const existing = getHash.get(file);
|
|
66
|
+
if (existing && existing.content_hash === hash) continue;
|
|
67
|
+
|
|
68
|
+
const { meta, body } = parseFrontmatter(content);
|
|
69
|
+
const { slug, description, type, category, title, tags, quality, dateCaptured, project: ftsProject } = extractFtsMetadata(meta, body, file, baseDir);
|
|
70
|
+
const resolvedProject = resolveProject(ftsProject, file, project);
|
|
71
|
+
changedFiles.push({ file, hash, slug, description, type, category, title, tags, quality, dateCaptured, resolvedProject, body });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// DB transaction: only DB writes — no file I/O inside to minimize lock hold time
|
|
75
|
+
const transaction = db.transaction(() => {
|
|
76
|
+
for (const item of changedFiles) {
|
|
77
|
+
deleteFts.run(item.file);
|
|
78
|
+
deleteFtsSessions.run(item.file);
|
|
79
|
+
if (isSessionCategory(item.category)) {
|
|
80
|
+
insertFtsSessions.run(item.file, item.slug, item.description, item.type, item.category, item.title, item.body, item.tags, item.quality ?? null, item.dateCaptured ?? null, item.resolvedProject);
|
|
81
|
+
} else {
|
|
82
|
+
insertFtsRow(db, { path: item.file, slug: item.slug, description: item.description, type: item.type, category: item.category, title: item.title, content: item.body, tags: item.tags, quality: item.quality, dateCaptured: item.dateCaptured, project: item.resolvedProject });
|
|
83
|
+
}
|
|
84
|
+
upsertHash.run(item.file, item.hash, now);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
transaction();
|
|
88
|
+
} finally {
|
|
89
|
+
db.close();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
withTelemetry('mindlore-fts5-sync', main).catch(err => {
|
|
95
|
+
hookLog('mindlore-fts5-sync', 'error', err?.message ?? String(err));
|
|
96
|
+
process.exit(0);
|
|
97
|
+
});
|
package/hooks/mindlore-index.cjs
CHANGED
|
@@ -1,229 +1,229 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* mindlore-index — FileChanged hook
|
|
6
|
-
*
|
|
7
|
-
* When a .md file in .mindlore/ changes, update its FTS5 entry.
|
|
8
|
-
* Reads changed file path from stdin (CC FileChanged event).
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const fs = require('fs');
|
|
12
|
-
const path = require('path');
|
|
13
|
-
const { DB_NAME, SKIP_FILES, sha256, openDatabase, parseFrontmatter, extractFtsMetadata, insertFtsRow, readHookStdin, getProjectName, resolveProject, globalDir, hookLog, withTelemetry, isInsideMindloreDir, extractMindloreBaseDir } = require('./lib/mindlore-common.cjs');
|
|
14
|
-
|
|
15
|
-
function invalidateSearchCache(db) {
|
|
16
|
-
try { db.exec('DELETE FROM search_cache'); } catch (_) { /* table may not exist */ }
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function main() {
|
|
20
|
-
const filePath = readHookStdin(['path', 'file_path']);
|
|
21
|
-
if (!filePath) return;
|
|
22
|
-
|
|
23
|
-
// Only process .md files inside .mindlore/ (resolved path check prevents traversal)
|
|
24
|
-
if (!filePath.endsWith('.md')) return;
|
|
25
|
-
const resolvedFile = path.resolve(filePath);
|
|
26
|
-
if (!isInsideMindloreDir(resolvedFile)) {
|
|
27
|
-
// CC memory path (~/.claude/projects/*/memory/*.md) — index to global mindlore DB
|
|
28
|
-
const isCcMemory = resolvedFile.includes(path.sep + '.claude' + path.sep + 'projects' + path.sep)
|
|
29
|
-
&& resolvedFile.includes(path.sep + 'memory' + path.sep)
|
|
30
|
-
&& resolvedFile.endsWith('.md');
|
|
31
|
-
if (!isCcMemory) return;
|
|
32
|
-
|
|
33
|
-
// CC memory path — index to global mindlore DB
|
|
34
|
-
indexCcMemory(resolvedFile);
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const fileName = path.basename(filePath);
|
|
39
|
-
|
|
40
|
-
const baseDir = extractMindloreBaseDir(resolvedFile);
|
|
41
|
-
if (!baseDir) return;
|
|
42
|
-
const dbPath = path.join(baseDir, DB_NAME);
|
|
43
|
-
|
|
44
|
-
if (!fs.existsSync(dbPath)) return;
|
|
45
|
-
|
|
46
|
-
// Catch-up scan: when INDEX.md or log.md triggers, index recently-modified files
|
|
47
|
-
if (['INDEX.md', 'log.md'].includes(fileName)) {
|
|
48
|
-
catchUpScan(baseDir, dbPath);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (SKIP_FILES.has(fileName)) return;
|
|
53
|
-
|
|
54
|
-
if (!fs.existsSync(filePath)) {
|
|
55
|
-
// File was deleted — remove from index
|
|
56
|
-
const db = openDatabase(dbPath);
|
|
57
|
-
if (!db) return;
|
|
58
|
-
try {
|
|
59
|
-
db.prepare('DELETE FROM mindlore_fts WHERE path = ?').run(filePath);
|
|
60
|
-
db.prepare('DELETE FROM file_hashes WHERE path = ?').run(filePath);
|
|
61
|
-
invalidateSearchCache(db);
|
|
62
|
-
} finally {
|
|
63
|
-
db.close();
|
|
64
|
-
}
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
|
69
|
-
const hash = sha256(content);
|
|
70
|
-
|
|
71
|
-
const db = openDatabase(dbPath);
|
|
72
|
-
if (!db) return;
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
// Check if content changed
|
|
76
|
-
const existing = db
|
|
77
|
-
.prepare('SELECT content_hash FROM file_hashes WHERE path = ?')
|
|
78
|
-
.get(filePath);
|
|
79
|
-
|
|
80
|
-
if (existing && existing.content_hash === hash) return; // Unchanged
|
|
81
|
-
|
|
82
|
-
// Parse frontmatter for rich FTS5 columns
|
|
83
|
-
const { meta, body } = parseFrontmatter(content);
|
|
84
|
-
const { slug, description, type, category, title, tags, quality, dateCaptured, project: ftsProject } = extractFtsMetadata(meta, body, filePath, baseDir);
|
|
85
|
-
|
|
86
|
-
// Update FTS5 + hash atomically
|
|
87
|
-
const updateIndex = db.transaction(() => {
|
|
88
|
-
db.prepare('DELETE FROM mindlore_fts WHERE path = ?').run(filePath);
|
|
89
|
-
insertFtsRow(db, { path: filePath, slug, description, type, category, title, content: body, tags, quality, dateCaptured, project: resolveProject(ftsProject, filePath, getProjectName()) });
|
|
90
|
-
db.prepare(
|
|
91
|
-
`INSERT INTO file_hashes (path, content_hash, last_indexed)
|
|
92
|
-
VALUES (?, ?, ?)
|
|
93
|
-
ON CONFLICT(path) DO UPDATE SET
|
|
94
|
-
content_hash = excluded.content_hash,
|
|
95
|
-
last_indexed = excluded.last_indexed`
|
|
96
|
-
).run(filePath, hash, new Date().toISOString());
|
|
97
|
-
});
|
|
98
|
-
updateIndex();
|
|
99
|
-
invalidateSearchCache(db);
|
|
100
|
-
} finally {
|
|
101
|
-
db.close();
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function indexCcMemory(filePath) {
|
|
106
|
-
const CC_MEMORY_CATEGORY = 'cc-memory';
|
|
107
|
-
// CC memory constants live in TS (scripts/lib/constants.ts) — CJS hooks can't require TS directly
|
|
108
|
-
const globalBase = globalDir();
|
|
109
|
-
const dbPath = path.join(globalBase, DB_NAME);
|
|
110
|
-
|
|
111
|
-
const content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
|
112
|
-
if (!content.trim()) return;
|
|
113
|
-
|
|
114
|
-
// Privacy filter — redact secrets before DB write
|
|
115
|
-
let cleaned = content;
|
|
116
|
-
try {
|
|
117
|
-
const { redactSecrets } = require('../dist/scripts/lib/privacy-filter.js');
|
|
118
|
-
cleaned = redactSecrets(content);
|
|
119
|
-
} catch (_err) {
|
|
120
|
-
// privacy-filter not built — use raw content
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// SHA256 dedup
|
|
124
|
-
const hash = sha256(cleaned);
|
|
125
|
-
const db = openDatabase(dbPath);
|
|
126
|
-
if (!db) return;
|
|
127
|
-
|
|
128
|
-
try {
|
|
129
|
-
const existing = db.prepare('SELECT content_hash FROM file_hashes WHERE path = ?').get(filePath);
|
|
130
|
-
if (existing && existing.content_hash === hash) {
|
|
131
|
-
return; // unchanged — finally handles db.close()
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const { meta, body } = parseFrontmatter(cleaned);
|
|
135
|
-
const memType = String(meta.type || 'unknown');
|
|
136
|
-
|
|
137
|
-
// Extract project scope from path: ~/.claude/projects/C--Users-X-proj/memory/
|
|
138
|
-
const projMatch = filePath.match(/projects[/\\]([^/\\]+)[/\\]memory/);
|
|
139
|
-
const projectScope = projMatch ? projMatch[1] : null;
|
|
140
|
-
|
|
141
|
-
const ftsData = extractFtsMetadata(meta, body, filePath, globalBase);
|
|
142
|
-
|
|
143
|
-
// Update FTS5 + hash atomically
|
|
144
|
-
const updateIndex = db.transaction(() => {
|
|
145
|
-
db.prepare('DELETE FROM mindlore_fts WHERE path = ?').run(filePath);
|
|
146
|
-
insertFtsRow(db, {
|
|
147
|
-
path: filePath,
|
|
148
|
-
...ftsData,
|
|
149
|
-
category: CC_MEMORY_CATEGORY,
|
|
150
|
-
type: memType,
|
|
151
|
-
project: projectScope,
|
|
152
|
-
});
|
|
153
|
-
db.prepare(
|
|
154
|
-
`INSERT INTO file_hashes (path, content_hash, last_indexed, source_type, project_scope)
|
|
155
|
-
VALUES (?, ?, ?, ?, ?)
|
|
156
|
-
ON CONFLICT(path) DO UPDATE SET
|
|
157
|
-
content_hash = excluded.content_hash,
|
|
158
|
-
last_indexed = excluded.last_indexed,
|
|
159
|
-
source_type = excluded.source_type,
|
|
160
|
-
project_scope = excluded.project_scope`
|
|
161
|
-
).run(filePath, hash, new Date().toISOString(), CC_MEMORY_CATEGORY, projectScope);
|
|
162
|
-
});
|
|
163
|
-
updateIndex();
|
|
164
|
-
|
|
165
|
-
// Copy to ~/.mindlore/memory/{project}/ for git-sync + obsidian
|
|
166
|
-
const memoryDir = path.join(globalBase, 'memory', projectScope || '_global');
|
|
167
|
-
fs.mkdirSync(memoryDir, { recursive: true, mode: 0o700 });
|
|
168
|
-
const destPath = path.join(memoryDir, path.basename(filePath));
|
|
169
|
-
fs.writeFileSync(destPath, cleaned, { encoding: 'utf8', mode: 0o600 });
|
|
170
|
-
} finally {
|
|
171
|
-
db.close();
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function catchUpScan(baseDir, dbPath) {
|
|
176
|
-
const CATCH_UP_DIRS = ['raw', 'sources', 'analyses', 'diary'];
|
|
177
|
-
const fiveMinAgo = Date.now() - 5 * 60 * 1000;
|
|
178
|
-
|
|
179
|
-
const db = openDatabase(dbPath);
|
|
180
|
-
if (!db) return;
|
|
181
|
-
|
|
182
|
-
try {
|
|
183
|
-
let indexed = 0;
|
|
184
|
-
for (const dir of CATCH_UP_DIRS) {
|
|
185
|
-
const dirPath = path.join(baseDir, dir);
|
|
186
|
-
if (!fs.existsSync(dirPath)) continue;
|
|
187
|
-
|
|
188
|
-
const files = fs.readdirSync(dirPath).filter(f => f.endsWith('.md'));
|
|
189
|
-
for (const file of files) {
|
|
190
|
-
const filePath = path.join(dirPath, file);
|
|
191
|
-
const stat = fs.statSync(filePath);
|
|
192
|
-
if (stat.mtimeMs < fiveMinAgo) continue;
|
|
193
|
-
|
|
194
|
-
const content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
|
195
|
-
const hash = sha256(content);
|
|
196
|
-
|
|
197
|
-
const existing = db.prepare('SELECT content_hash FROM file_hashes WHERE path = ?').get(filePath);
|
|
198
|
-
if (existing && existing.content_hash === hash) continue;
|
|
199
|
-
|
|
200
|
-
const { meta, body } = parseFrontmatter(content);
|
|
201
|
-
const ftsData = extractFtsMetadata(meta, body, filePath, baseDir);
|
|
202
|
-
|
|
203
|
-
const update = db.transaction(() => {
|
|
204
|
-
db.prepare('DELETE FROM mindlore_fts WHERE path = ?').run(filePath);
|
|
205
|
-
insertFtsRow(db, { path: filePath, ...ftsData, project: resolveProject(ftsData.project, filePath, getProjectName()) });
|
|
206
|
-
db.prepare(
|
|
207
|
-
`INSERT INTO file_hashes (path, content_hash, last_indexed)
|
|
208
|
-
VALUES (?, ?, ?)
|
|
209
|
-
ON CONFLICT(path) DO UPDATE SET
|
|
210
|
-
content_hash = excluded.content_hash,
|
|
211
|
-
last_indexed = excluded.last_indexed`
|
|
212
|
-
).run(filePath, hash, new Date().toISOString());
|
|
213
|
-
});
|
|
214
|
-
update();
|
|
215
|
-
indexed++;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
if (indexed > 0) {
|
|
219
|
-
hookLog(`catch-up: ${indexed} file(s) indexed`);
|
|
220
|
-
}
|
|
221
|
-
} finally {
|
|
222
|
-
db.close();
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
withTelemetry('mindlore-index', main).catch(err => {
|
|
227
|
-
hookLog('mindlore-index', 'error', err?.message ?? String(err));
|
|
228
|
-
process.exit(0);
|
|
229
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* mindlore-index — FileChanged hook
|
|
6
|
+
*
|
|
7
|
+
* When a .md file in .mindlore/ changes, update its FTS5 entry.
|
|
8
|
+
* Reads changed file path from stdin (CC FileChanged event).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { DB_NAME, SKIP_FILES, sha256, openDatabase, parseFrontmatter, extractFtsMetadata, insertFtsRow, readHookStdin, getProjectName, resolveProject, globalDir, hookLog, withTelemetry, isInsideMindloreDir, extractMindloreBaseDir } = require('./lib/mindlore-common.cjs');
|
|
14
|
+
|
|
15
|
+
function invalidateSearchCache(db) {
|
|
16
|
+
try { db.exec('DELETE FROM search_cache'); } catch (_) { /* table may not exist */ }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function main() {
|
|
20
|
+
const filePath = readHookStdin(['path', 'file_path']);
|
|
21
|
+
if (!filePath) return;
|
|
22
|
+
|
|
23
|
+
// Only process .md files inside .mindlore/ (resolved path check prevents traversal)
|
|
24
|
+
if (!filePath.endsWith('.md')) return;
|
|
25
|
+
const resolvedFile = path.resolve(filePath);
|
|
26
|
+
if (!isInsideMindloreDir(resolvedFile)) {
|
|
27
|
+
// CC memory path (~/.claude/projects/*/memory/*.md) — index to global mindlore DB
|
|
28
|
+
const isCcMemory = resolvedFile.includes(path.sep + '.claude' + path.sep + 'projects' + path.sep)
|
|
29
|
+
&& resolvedFile.includes(path.sep + 'memory' + path.sep)
|
|
30
|
+
&& resolvedFile.endsWith('.md');
|
|
31
|
+
if (!isCcMemory) return;
|
|
32
|
+
|
|
33
|
+
// CC memory path — index to global mindlore DB
|
|
34
|
+
indexCcMemory(resolvedFile);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const fileName = path.basename(filePath);
|
|
39
|
+
|
|
40
|
+
const baseDir = extractMindloreBaseDir(resolvedFile);
|
|
41
|
+
if (!baseDir) return;
|
|
42
|
+
const dbPath = path.join(baseDir, DB_NAME);
|
|
43
|
+
|
|
44
|
+
if (!fs.existsSync(dbPath)) return;
|
|
45
|
+
|
|
46
|
+
// Catch-up scan: when INDEX.md or log.md triggers, index recently-modified files
|
|
47
|
+
if (['INDEX.md', 'log.md'].includes(fileName)) {
|
|
48
|
+
catchUpScan(baseDir, dbPath);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (SKIP_FILES.has(fileName)) return;
|
|
53
|
+
|
|
54
|
+
if (!fs.existsSync(filePath)) {
|
|
55
|
+
// File was deleted — remove from index
|
|
56
|
+
const db = openDatabase(dbPath);
|
|
57
|
+
if (!db) return;
|
|
58
|
+
try {
|
|
59
|
+
db.prepare('DELETE FROM mindlore_fts WHERE path = ?').run(filePath);
|
|
60
|
+
db.prepare('DELETE FROM file_hashes WHERE path = ?').run(filePath);
|
|
61
|
+
invalidateSearchCache(db);
|
|
62
|
+
} finally {
|
|
63
|
+
db.close();
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
|
69
|
+
const hash = sha256(content);
|
|
70
|
+
|
|
71
|
+
const db = openDatabase(dbPath);
|
|
72
|
+
if (!db) return;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
// Check if content changed
|
|
76
|
+
const existing = db
|
|
77
|
+
.prepare('SELECT content_hash FROM file_hashes WHERE path = ?')
|
|
78
|
+
.get(filePath);
|
|
79
|
+
|
|
80
|
+
if (existing && existing.content_hash === hash) return; // Unchanged
|
|
81
|
+
|
|
82
|
+
// Parse frontmatter for rich FTS5 columns
|
|
83
|
+
const { meta, body } = parseFrontmatter(content);
|
|
84
|
+
const { slug, description, type, category, title, tags, quality, dateCaptured, project: ftsProject } = extractFtsMetadata(meta, body, filePath, baseDir);
|
|
85
|
+
|
|
86
|
+
// Update FTS5 + hash atomically
|
|
87
|
+
const updateIndex = db.transaction(() => {
|
|
88
|
+
db.prepare('DELETE FROM mindlore_fts WHERE path = ?').run(filePath);
|
|
89
|
+
insertFtsRow(db, { path: filePath, slug, description, type, category, title, content: body, tags, quality, dateCaptured, project: resolveProject(ftsProject, filePath, getProjectName()) });
|
|
90
|
+
db.prepare(
|
|
91
|
+
`INSERT INTO file_hashes (path, content_hash, last_indexed)
|
|
92
|
+
VALUES (?, ?, ?)
|
|
93
|
+
ON CONFLICT(path) DO UPDATE SET
|
|
94
|
+
content_hash = excluded.content_hash,
|
|
95
|
+
last_indexed = excluded.last_indexed`
|
|
96
|
+
).run(filePath, hash, new Date().toISOString());
|
|
97
|
+
});
|
|
98
|
+
updateIndex();
|
|
99
|
+
invalidateSearchCache(db);
|
|
100
|
+
} finally {
|
|
101
|
+
db.close();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function indexCcMemory(filePath) {
|
|
106
|
+
const CC_MEMORY_CATEGORY = 'cc-memory';
|
|
107
|
+
// CC memory constants live in TS (scripts/lib/constants.ts) — CJS hooks can't require TS directly
|
|
108
|
+
const globalBase = globalDir();
|
|
109
|
+
const dbPath = path.join(globalBase, DB_NAME);
|
|
110
|
+
|
|
111
|
+
const content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
|
112
|
+
if (!content.trim()) return;
|
|
113
|
+
|
|
114
|
+
// Privacy filter — redact secrets before DB write
|
|
115
|
+
let cleaned = content;
|
|
116
|
+
try {
|
|
117
|
+
const { redactSecrets } = require('../dist/scripts/lib/privacy-filter.js');
|
|
118
|
+
cleaned = redactSecrets(content);
|
|
119
|
+
} catch (_err) {
|
|
120
|
+
// privacy-filter not built — use raw content
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// SHA256 dedup
|
|
124
|
+
const hash = sha256(cleaned);
|
|
125
|
+
const db = openDatabase(dbPath);
|
|
126
|
+
if (!db) return;
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const existing = db.prepare('SELECT content_hash FROM file_hashes WHERE path = ?').get(filePath);
|
|
130
|
+
if (existing && existing.content_hash === hash) {
|
|
131
|
+
return; // unchanged — finally handles db.close()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const { meta, body } = parseFrontmatter(cleaned);
|
|
135
|
+
const memType = String(meta.type || 'unknown');
|
|
136
|
+
|
|
137
|
+
// Extract project scope from path: ~/.claude/projects/C--Users-X-proj/memory/
|
|
138
|
+
const projMatch = filePath.match(/projects[/\\]([^/\\]+)[/\\]memory/);
|
|
139
|
+
const projectScope = projMatch ? projMatch[1] : null;
|
|
140
|
+
|
|
141
|
+
const ftsData = extractFtsMetadata(meta, body, filePath, globalBase);
|
|
142
|
+
|
|
143
|
+
// Update FTS5 + hash atomically
|
|
144
|
+
const updateIndex = db.transaction(() => {
|
|
145
|
+
db.prepare('DELETE FROM mindlore_fts WHERE path = ?').run(filePath);
|
|
146
|
+
insertFtsRow(db, {
|
|
147
|
+
path: filePath,
|
|
148
|
+
...ftsData,
|
|
149
|
+
category: CC_MEMORY_CATEGORY,
|
|
150
|
+
type: memType,
|
|
151
|
+
project: projectScope,
|
|
152
|
+
});
|
|
153
|
+
db.prepare(
|
|
154
|
+
`INSERT INTO file_hashes (path, content_hash, last_indexed, source_type, project_scope)
|
|
155
|
+
VALUES (?, ?, ?, ?, ?)
|
|
156
|
+
ON CONFLICT(path) DO UPDATE SET
|
|
157
|
+
content_hash = excluded.content_hash,
|
|
158
|
+
last_indexed = excluded.last_indexed,
|
|
159
|
+
source_type = excluded.source_type,
|
|
160
|
+
project_scope = excluded.project_scope`
|
|
161
|
+
).run(filePath, hash, new Date().toISOString(), CC_MEMORY_CATEGORY, projectScope);
|
|
162
|
+
});
|
|
163
|
+
updateIndex();
|
|
164
|
+
|
|
165
|
+
// Copy to ~/.mindlore/memory/{project}/ for git-sync + obsidian
|
|
166
|
+
const memoryDir = path.join(globalBase, 'memory', projectScope || '_global');
|
|
167
|
+
fs.mkdirSync(memoryDir, { recursive: true, mode: 0o700 });
|
|
168
|
+
const destPath = path.join(memoryDir, path.basename(filePath));
|
|
169
|
+
fs.writeFileSync(destPath, cleaned, { encoding: 'utf8', mode: 0o600 });
|
|
170
|
+
} finally {
|
|
171
|
+
db.close();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function catchUpScan(baseDir, dbPath) {
|
|
176
|
+
const CATCH_UP_DIRS = ['raw', 'sources', 'analyses', 'diary'];
|
|
177
|
+
const fiveMinAgo = Date.now() - 5 * 60 * 1000;
|
|
178
|
+
|
|
179
|
+
const db = openDatabase(dbPath);
|
|
180
|
+
if (!db) return;
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
let indexed = 0;
|
|
184
|
+
for (const dir of CATCH_UP_DIRS) {
|
|
185
|
+
const dirPath = path.join(baseDir, dir);
|
|
186
|
+
if (!fs.existsSync(dirPath)) continue;
|
|
187
|
+
|
|
188
|
+
const files = fs.readdirSync(dirPath).filter(f => f.endsWith('.md'));
|
|
189
|
+
for (const file of files) {
|
|
190
|
+
const filePath = path.join(dirPath, file);
|
|
191
|
+
const stat = fs.statSync(filePath);
|
|
192
|
+
if (stat.mtimeMs < fiveMinAgo) continue;
|
|
193
|
+
|
|
194
|
+
const content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
|
195
|
+
const hash = sha256(content);
|
|
196
|
+
|
|
197
|
+
const existing = db.prepare('SELECT content_hash FROM file_hashes WHERE path = ?').get(filePath);
|
|
198
|
+
if (existing && existing.content_hash === hash) continue;
|
|
199
|
+
|
|
200
|
+
const { meta, body } = parseFrontmatter(content);
|
|
201
|
+
const ftsData = extractFtsMetadata(meta, body, filePath, baseDir);
|
|
202
|
+
|
|
203
|
+
const update = db.transaction(() => {
|
|
204
|
+
db.prepare('DELETE FROM mindlore_fts WHERE path = ?').run(filePath);
|
|
205
|
+
insertFtsRow(db, { path: filePath, ...ftsData, project: resolveProject(ftsData.project, filePath, getProjectName()) });
|
|
206
|
+
db.prepare(
|
|
207
|
+
`INSERT INTO file_hashes (path, content_hash, last_indexed)
|
|
208
|
+
VALUES (?, ?, ?)
|
|
209
|
+
ON CONFLICT(path) DO UPDATE SET
|
|
210
|
+
content_hash = excluded.content_hash,
|
|
211
|
+
last_indexed = excluded.last_indexed`
|
|
212
|
+
).run(filePath, hash, new Date().toISOString());
|
|
213
|
+
});
|
|
214
|
+
update();
|
|
215
|
+
indexed++;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (indexed > 0) {
|
|
219
|
+
hookLog(`catch-up: ${indexed} file(s) indexed`);
|
|
220
|
+
}
|
|
221
|
+
} finally {
|
|
222
|
+
db.close();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
withTelemetry('mindlore-index', main).catch(err => {
|
|
227
|
+
hookLog('mindlore-index', 'error', err?.message ?? String(err));
|
|
228
|
+
process.exit(0);
|
|
229
|
+
});
|