mindlore 0.6.8 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/README.md +259 -259
  2. package/SCHEMA.md +292 -292
  3. package/dist/scripts/cc-memory-bulk-sync.d.ts.map +1 -1
  4. package/dist/scripts/cc-memory-bulk-sync.js +4 -2
  5. package/dist/scripts/cc-memory-bulk-sync.js.map +1 -1
  6. package/dist/scripts/cc-session-sync.d.ts.map +1 -1
  7. package/dist/scripts/cc-session-sync.js +9 -7
  8. package/dist/scripts/cc-session-sync.js.map +1 -1
  9. package/dist/scripts/fetch-raw.js +5 -4
  10. package/dist/scripts/fetch-raw.js.map +1 -1
  11. package/dist/scripts/init.js +16 -12
  12. package/dist/scripts/init.js.map +1 -1
  13. package/dist/scripts/lib/consolidation.js +10 -10
  14. package/dist/scripts/lib/constants.d.ts +3 -0
  15. package/dist/scripts/lib/constants.d.ts.map +1 -1
  16. package/dist/scripts/lib/constants.js +11 -1
  17. package/dist/scripts/lib/constants.js.map +1 -1
  18. package/dist/scripts/lib/decay.js +9 -9
  19. package/dist/scripts/lib/episodes.js +23 -23
  20. package/dist/scripts/lib/err-msg.d.ts +2 -0
  21. package/dist/scripts/lib/err-msg.d.ts.map +1 -0
  22. package/dist/scripts/lib/err-msg.js +7 -0
  23. package/dist/scripts/lib/err-msg.js.map +1 -0
  24. package/dist/scripts/lib/mcp-namespace.d.ts +2 -0
  25. package/dist/scripts/lib/mcp-namespace.d.ts.map +1 -0
  26. package/dist/scripts/lib/mcp-namespace.js +21 -0
  27. package/dist/scripts/lib/mcp-namespace.js.map +1 -0
  28. package/dist/scripts/lib/mcp-telemetry.d.ts +11 -0
  29. package/dist/scripts/lib/mcp-telemetry.d.ts.map +1 -0
  30. package/dist/scripts/lib/mcp-telemetry.js +37 -0
  31. package/dist/scripts/lib/mcp-telemetry.js.map +1 -0
  32. package/dist/scripts/lib/mcp-tools.d.ts +10 -0
  33. package/dist/scripts/lib/mcp-tools.d.ts.map +1 -0
  34. package/dist/scripts/lib/mcp-tools.js +121 -0
  35. package/dist/scripts/lib/mcp-tools.js.map +1 -0
  36. package/dist/scripts/lib/migrations-v061.js +21 -21
  37. package/dist/scripts/lib/migrations-v062.js +11 -11
  38. package/dist/scripts/lib/migrations-v063.js +14 -14
  39. package/dist/scripts/lib/migrations-v067.js +11 -11
  40. package/dist/scripts/lib/rrf.d.ts.map +1 -1
  41. package/dist/scripts/lib/rrf.js +2 -1
  42. package/dist/scripts/lib/rrf.js.map +1 -1
  43. package/dist/scripts/lib/schema-version.js +6 -6
  44. package/dist/scripts/lib/search-cache.js +11 -11
  45. package/dist/scripts/lib/search-engine.d.ts +1 -0
  46. package/dist/scripts/lib/search-engine.d.ts.map +1 -1
  47. package/dist/scripts/lib/search-engine.js +9 -5
  48. package/dist/scripts/lib/search-engine.js.map +1 -1
  49. package/dist/scripts/lib/secure-io.d.ts +11 -0
  50. package/dist/scripts/lib/secure-io.d.ts.map +1 -0
  51. package/dist/scripts/lib/secure-io.js +26 -0
  52. package/dist/scripts/lib/secure-io.js.map +1 -0
  53. package/dist/scripts/lib/session-payload.js +7 -7
  54. package/dist/scripts/lib/slugify.d.ts +2 -0
  55. package/dist/scripts/lib/slugify.d.ts.map +1 -0
  56. package/dist/scripts/lib/slugify.js +13 -0
  57. package/dist/scripts/lib/slugify.js.map +1 -0
  58. package/dist/scripts/lib/smart-snippet.d.ts +9 -0
  59. package/dist/scripts/lib/smart-snippet.d.ts.map +1 -0
  60. package/dist/scripts/lib/smart-snippet.js +47 -0
  61. package/dist/scripts/lib/smart-snippet.js.map +1 -0
  62. package/dist/scripts/lib/tool-adapters/brief-adapter.d.ts +15 -0
  63. package/dist/scripts/lib/tool-adapters/brief-adapter.d.ts.map +1 -0
  64. package/dist/scripts/lib/tool-adapters/brief-adapter.js +66 -0
  65. package/dist/scripts/lib/tool-adapters/brief-adapter.js.map +1 -0
  66. package/dist/scripts/lib/tool-adapters/decide-adapter.d.ts +31 -0
  67. package/dist/scripts/lib/tool-adapters/decide-adapter.d.ts.map +1 -0
  68. package/dist/scripts/lib/tool-adapters/decide-adapter.js +71 -0
  69. package/dist/scripts/lib/tool-adapters/decide-adapter.js.map +1 -0
  70. package/dist/scripts/lib/tool-adapters/ingest-adapter.d.ts +16 -0
  71. package/dist/scripts/lib/tool-adapters/ingest-adapter.d.ts.map +1 -0
  72. package/dist/scripts/lib/tool-adapters/ingest-adapter.js +58 -0
  73. package/dist/scripts/lib/tool-adapters/ingest-adapter.js.map +1 -0
  74. package/dist/scripts/lib/tool-adapters/recall-adapter.d.ts +20 -0
  75. package/dist/scripts/lib/tool-adapters/recall-adapter.d.ts.map +1 -0
  76. package/dist/scripts/lib/tool-adapters/recall-adapter.js +69 -0
  77. package/dist/scripts/lib/tool-adapters/recall-adapter.js.map +1 -0
  78. package/dist/scripts/lib/tool-adapters/search-adapter.d.ts +22 -0
  79. package/dist/scripts/lib/tool-adapters/search-adapter.d.ts.map +1 -0
  80. package/dist/scripts/lib/tool-adapters/search-adapter.js +32 -0
  81. package/dist/scripts/lib/tool-adapters/search-adapter.js.map +1 -0
  82. package/dist/scripts/lib/tool-adapters/stats-adapter.d.ts +15 -0
  83. package/dist/scripts/lib/tool-adapters/stats-adapter.d.ts.map +1 -0
  84. package/dist/scripts/lib/tool-adapters/stats-adapter.js +66 -0
  85. package/dist/scripts/lib/tool-adapters/stats-adapter.js.map +1 -0
  86. package/dist/scripts/lib/triage.js +3 -3
  87. package/dist/scripts/lib/validate-manifest.d.ts +8 -0
  88. package/dist/scripts/lib/validate-manifest.d.ts.map +1 -0
  89. package/dist/scripts/lib/validate-manifest.js +80 -0
  90. package/dist/scripts/lib/validate-manifest.js.map +1 -0
  91. package/dist/scripts/maintain-cleanup.d.ts.map +1 -1
  92. package/dist/scripts/maintain-cleanup.js +3 -2
  93. package/dist/scripts/maintain-cleanup.js.map +1 -1
  94. package/dist/scripts/mcp-server.d.ts +3 -0
  95. package/dist/scripts/mcp-server.d.ts.map +1 -0
  96. package/dist/scripts/mcp-server.js +85 -0
  97. package/dist/scripts/mcp-server.js.map +1 -0
  98. package/dist/scripts/mindlore-backup.js +9 -9
  99. package/dist/scripts/mindlore-doctor.d.ts.map +1 -1
  100. package/dist/scripts/mindlore-doctor.js +4 -6
  101. package/dist/scripts/mindlore-doctor.js.map +1 -1
  102. package/dist/scripts/mindlore-fts5-index.js +12 -12
  103. package/dist/scripts/mindlore-fts5-index.js.map +1 -1
  104. package/dist/scripts/mindlore-health-check.d.ts.map +1 -1
  105. package/dist/scripts/mindlore-health-check.js +2 -2
  106. package/dist/scripts/mindlore-health-check.js.map +1 -1
  107. package/dist/scripts/validate-manifest-cli.d.ts +2 -0
  108. package/dist/scripts/validate-manifest-cli.d.ts.map +1 -0
  109. package/dist/scripts/validate-manifest-cli.js +38 -0
  110. package/dist/scripts/validate-manifest-cli.js.map +1 -0
  111. package/dist/tests/cc-memory-sync.test.js +3 -3
  112. package/dist/tests/chunks-migration.test.js +1 -1
  113. package/dist/tests/compaction-snapshot.test.js +2 -2
  114. package/dist/tests/consolidation.test.js +3 -3
  115. package/dist/tests/decay.test.js +9 -9
  116. package/dist/tests/diary.test.js +4 -4
  117. package/dist/tests/episode-file.test.js +2 -1
  118. package/dist/tests/episode-file.test.js.map +1 -1
  119. package/dist/tests/episodes-inject.test.js +9 -9
  120. package/dist/tests/err-msg.test.d.ts +2 -0
  121. package/dist/tests/err-msg.test.d.ts.map +1 -0
  122. package/dist/tests/err-msg.test.js +24 -0
  123. package/dist/tests/err-msg.test.js.map +1 -0
  124. package/dist/tests/fetch-raw.test.js +1 -2
  125. package/dist/tests/fetch-raw.test.js.map +1 -1
  126. package/dist/tests/fts5.test.js +75 -75
  127. package/dist/tests/fuzzy.test.js +1 -1
  128. package/dist/tests/git-snapshot.test.js +3 -5
  129. package/dist/tests/git-snapshot.test.js.map +1 -1
  130. package/dist/tests/helpers/db.d.ts +1 -2
  131. package/dist/tests/helpers/db.d.ts.map +1 -1
  132. package/dist/tests/helpers/db.js +18 -26
  133. package/dist/tests/helpers/db.js.map +1 -1
  134. package/dist/tests/manifest-v2.test.d.ts +2 -0
  135. package/dist/tests/manifest-v2.test.d.ts.map +1 -0
  136. package/dist/tests/manifest-v2.test.js +67 -0
  137. package/dist/tests/manifest-v2.test.js.map +1 -0
  138. package/dist/tests/mcp-server.test.d.ts +2 -0
  139. package/dist/tests/mcp-server.test.d.ts.map +1 -0
  140. package/dist/tests/mcp-server.test.js +118 -0
  141. package/dist/tests/mcp-server.test.js.map +1 -0
  142. package/dist/tests/mcp-tools.test.d.ts +2 -0
  143. package/dist/tests/mcp-tools.test.d.ts.map +1 -0
  144. package/dist/tests/mcp-tools.test.js +243 -0
  145. package/dist/tests/mcp-tools.test.js.map +1 -0
  146. package/dist/tests/migrations-v053.test.js +16 -16
  147. package/dist/tests/migrations-v061.test.js +10 -10
  148. package/dist/tests/migrations-v063.test.js +4 -4
  149. package/dist/tests/migrations-v068.test.js +6 -3
  150. package/dist/tests/migrations-v068.test.js.map +1 -1
  151. package/dist/tests/nomination-counts.test.js +6 -6
  152. package/dist/tests/nomination-counts.test.js.map +1 -1
  153. package/dist/tests/pre-compact.test.js +2 -1
  154. package/dist/tests/pre-compact.test.js.map +1 -1
  155. package/dist/tests/recall-telemetry.test.js +12 -11
  156. package/dist/tests/recall-telemetry.test.js.map +1 -1
  157. package/dist/tests/rrf.test.js +3 -3
  158. package/dist/tests/search-engine.test.js +1 -1
  159. package/dist/tests/search-hook.test.js +2 -2
  160. package/dist/tests/search-offload.test.js +1 -1
  161. package/dist/tests/search-offload.test.js.map +1 -1
  162. package/dist/tests/secure-io.test.d.ts +2 -0
  163. package/dist/tests/secure-io.test.d.ts.map +1 -0
  164. package/dist/tests/secure-io.test.js +65 -0
  165. package/dist/tests/secure-io.test.js.map +1 -0
  166. package/dist/tests/session-payload.test.js +1 -1
  167. package/dist/tests/session-summary.test.js +1 -1
  168. package/dist/tests/similarity.test.js +1 -1
  169. package/dist/tests/skill-path-resolution.test.js +22 -3
  170. package/dist/tests/skill-path-resolution.test.js.map +1 -1
  171. package/dist/tests/smart-snippet.test.d.ts +2 -0
  172. package/dist/tests/smart-snippet.test.d.ts.map +1 -0
  173. package/dist/tests/smart-snippet.test.js +67 -0
  174. package/dist/tests/smart-snippet.test.js.map +1 -0
  175. package/dist/tests/triage.test.js +1 -1
  176. package/hooks/lib/constants.cjs +15 -15
  177. package/hooks/lib/mindlore-common.cjs +975 -974
  178. package/hooks/mindlore-cwd-changed.cjs +57 -57
  179. package/hooks/mindlore-decision-detector.cjs +54 -54
  180. package/hooks/mindlore-dont-repeat.cjs +222 -222
  181. package/hooks/mindlore-fts5-sync.cjs +98 -97
  182. package/hooks/mindlore-index.cjs +230 -229
  183. package/hooks/mindlore-model-router.cjs +54 -54
  184. package/hooks/mindlore-post-compact.cjs +69 -69
  185. package/hooks/mindlore-post-read.cjs +106 -106
  186. package/hooks/mindlore-pre-compact.cjs +154 -154
  187. package/hooks/mindlore-read-guard.cjs +105 -105
  188. package/hooks/mindlore-research-guard.cjs +176 -176
  189. package/hooks/mindlore-search.cjs +200 -200
  190. package/hooks/mindlore-session-end.cjs +510 -509
  191. package/hooks/mindlore-session-focus.cjs +256 -256
  192. package/package.json +77 -75
  193. package/plugin.json +8 -1
  194. package/skills/mindlore-diary/SKILL.md +85 -85
  195. package/skills/mindlore-evolve/SKILL.md +126 -126
  196. package/skills/mindlore-explore/SKILL.md +109 -109
  197. package/skills/mindlore-ingest/SKILL.md +195 -195
  198. package/skills/mindlore-maintain/SKILL.md +125 -125
  199. package/skills/mindlore-query/SKILL.md +151 -151
  200. package/skills/mindlore-reflect/SKILL.md +141 -141
  201. package/skills/mindlore-stats/SKILL.md +106 -106
  202. package/templates/INDEX.md +14 -14
  203. package/templates/SCHEMA.md +292 -292
  204. package/templates/config.json +1 -1
  205. package/templates/extraction/article.md +15 -15
  206. package/templates/extraction/changelog.md +15 -15
  207. package/templates/extraction/default.md +15 -15
  208. package/templates/extraction/docs.md +15 -15
  209. package/templates/extraction/github-repo.md +17 -17
@@ -1,229 +1,230 @@
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 { safeMkdir, safeWriteFile } = require('../dist/scripts/lib/secure-io.js');
14
+ const { DB_NAME, SKIP_FILES, sha256, openDatabase, parseFrontmatter, extractFtsMetadata, insertFtsRow, readHookStdin, getProjectName, resolveProject, globalDir, hookLog, withTelemetry, isInsideMindloreDir, extractMindloreBaseDir } = require('./lib/mindlore-common.cjs');
15
+
16
+ function invalidateSearchCache(db) {
17
+ try { db.exec('DELETE FROM search_cache'); } catch (_) { /* table may not exist */ }
18
+ }
19
+
20
+ function main() {
21
+ const filePath = readHookStdin(['path', 'file_path']);
22
+ if (!filePath) return;
23
+
24
+ // Only process .md files inside .mindlore/ (resolved path check prevents traversal)
25
+ if (!filePath.endsWith('.md')) return;
26
+ const resolvedFile = path.resolve(filePath);
27
+ if (!isInsideMindloreDir(resolvedFile)) {
28
+ // CC memory path (~/.claude/projects/*/memory/*.md) index to global mindlore DB
29
+ const isCcMemory = resolvedFile.includes(path.sep + '.claude' + path.sep + 'projects' + path.sep)
30
+ && resolvedFile.includes(path.sep + 'memory' + path.sep)
31
+ && resolvedFile.endsWith('.md');
32
+ if (!isCcMemory) return;
33
+
34
+ // CC memory path — index to global mindlore DB
35
+ indexCcMemory(resolvedFile);
36
+ return;
37
+ }
38
+
39
+ const fileName = path.basename(filePath);
40
+
41
+ const baseDir = extractMindloreBaseDir(resolvedFile);
42
+ if (!baseDir) return;
43
+ const dbPath = path.join(baseDir, DB_NAME);
44
+
45
+ if (!fs.existsSync(dbPath)) return;
46
+
47
+ // Catch-up scan: when INDEX.md or log.md triggers, index recently-modified files
48
+ if (['INDEX.md', 'log.md'].includes(fileName)) {
49
+ catchUpScan(baseDir, dbPath);
50
+ return;
51
+ }
52
+
53
+ if (SKIP_FILES.has(fileName)) return;
54
+
55
+ if (!fs.existsSync(filePath)) {
56
+ // File was deleted — remove from index
57
+ const db = openDatabase(dbPath);
58
+ if (!db) return;
59
+ try {
60
+ db.prepare('DELETE FROM mindlore_fts WHERE path = ?').run(filePath);
61
+ db.prepare('DELETE FROM file_hashes WHERE path = ?').run(filePath);
62
+ invalidateSearchCache(db);
63
+ } finally {
64
+ db.close();
65
+ }
66
+ return;
67
+ }
68
+
69
+ const content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
70
+ const hash = sha256(content);
71
+
72
+ const db = openDatabase(dbPath);
73
+ if (!db) return;
74
+
75
+ try {
76
+ // Check if content changed
77
+ const existing = db
78
+ .prepare('SELECT content_hash FROM file_hashes WHERE path = ?')
79
+ .get(filePath);
80
+
81
+ if (existing && existing.content_hash === hash) return; // Unchanged
82
+
83
+ // Parse frontmatter for rich FTS5 columns
84
+ const { meta, body } = parseFrontmatter(content);
85
+ const { slug, description, type, category, title, tags, quality, dateCaptured, project: ftsProject } = extractFtsMetadata(meta, body, filePath, baseDir);
86
+
87
+ // Update FTS5 + hash atomically
88
+ const updateIndex = db.transaction(() => {
89
+ db.prepare('DELETE FROM mindlore_fts WHERE path = ?').run(filePath);
90
+ insertFtsRow(db, { path: filePath, slug, description, type, category, title, content: body, tags, quality, dateCaptured, project: resolveProject(ftsProject, filePath, getProjectName()) });
91
+ db.prepare(
92
+ `INSERT INTO file_hashes (path, content_hash, last_indexed)
93
+ VALUES (?, ?, ?)
94
+ ON CONFLICT(path) DO UPDATE SET
95
+ content_hash = excluded.content_hash,
96
+ last_indexed = excluded.last_indexed`
97
+ ).run(filePath, hash, new Date().toISOString());
98
+ });
99
+ updateIndex();
100
+ invalidateSearchCache(db);
101
+ } finally {
102
+ db.close();
103
+ }
104
+ }
105
+
106
+ function indexCcMemory(filePath) {
107
+ const CC_MEMORY_CATEGORY = 'cc-memory';
108
+ // CC memory constants live in TS (scripts/lib/constants.ts) — CJS hooks can't require TS directly
109
+ const globalBase = globalDir();
110
+ const dbPath = path.join(globalBase, DB_NAME);
111
+
112
+ const content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
113
+ if (!content.trim()) return;
114
+
115
+ // Privacy filter — redact secrets before DB write
116
+ let cleaned = content;
117
+ try {
118
+ const { redactSecrets } = require('../dist/scripts/lib/privacy-filter.js');
119
+ cleaned = redactSecrets(content);
120
+ } catch (_err) {
121
+ // privacy-filter not built — use raw content
122
+ }
123
+
124
+ // SHA256 dedup
125
+ const hash = sha256(cleaned);
126
+ const db = openDatabase(dbPath);
127
+ if (!db) return;
128
+
129
+ try {
130
+ const existing = db.prepare('SELECT content_hash FROM file_hashes WHERE path = ?').get(filePath);
131
+ if (existing && existing.content_hash === hash) {
132
+ return; // unchanged — finally handles db.close()
133
+ }
134
+
135
+ const { meta, body } = parseFrontmatter(cleaned);
136
+ const memType = String(meta.type || 'unknown');
137
+
138
+ // Extract project scope from path: ~/.claude/projects/C--Users-X-proj/memory/
139
+ const projMatch = filePath.match(/projects[/\\]([^/\\]+)[/\\]memory/);
140
+ const projectScope = projMatch ? projMatch[1] : null;
141
+
142
+ const ftsData = extractFtsMetadata(meta, body, filePath, globalBase);
143
+
144
+ // Update FTS5 + hash atomically
145
+ const updateIndex = db.transaction(() => {
146
+ db.prepare('DELETE FROM mindlore_fts WHERE path = ?').run(filePath);
147
+ insertFtsRow(db, {
148
+ path: filePath,
149
+ ...ftsData,
150
+ category: CC_MEMORY_CATEGORY,
151
+ type: memType,
152
+ project: projectScope,
153
+ });
154
+ db.prepare(
155
+ `INSERT INTO file_hashes (path, content_hash, last_indexed, source_type, project_scope)
156
+ VALUES (?, ?, ?, ?, ?)
157
+ ON CONFLICT(path) DO UPDATE SET
158
+ content_hash = excluded.content_hash,
159
+ last_indexed = excluded.last_indexed,
160
+ source_type = excluded.source_type,
161
+ project_scope = excluded.project_scope`
162
+ ).run(filePath, hash, new Date().toISOString(), CC_MEMORY_CATEGORY, projectScope);
163
+ });
164
+ updateIndex();
165
+
166
+ // Copy to ~/.mindlore/memory/{project}/ for git-sync + obsidian
167
+ const memoryDir = path.join(globalBase, 'memory', projectScope || '_global');
168
+ safeMkdir(memoryDir);
169
+ const destPath = path.join(memoryDir, path.basename(filePath));
170
+ safeWriteFile(destPath, cleaned);
171
+ } finally {
172
+ db.close();
173
+ }
174
+ }
175
+
176
+ function catchUpScan(baseDir, dbPath) {
177
+ const CATCH_UP_DIRS = ['raw', 'sources', 'analyses', 'diary'];
178
+ const fiveMinAgo = Date.now() - 5 * 60 * 1000;
179
+
180
+ const db = openDatabase(dbPath);
181
+ if (!db) return;
182
+
183
+ try {
184
+ let indexed = 0;
185
+ for (const dir of CATCH_UP_DIRS) {
186
+ const dirPath = path.join(baseDir, dir);
187
+ if (!fs.existsSync(dirPath)) continue;
188
+
189
+ const files = fs.readdirSync(dirPath).filter(f => f.endsWith('.md'));
190
+ for (const file of files) {
191
+ const filePath = path.join(dirPath, file);
192
+ const stat = fs.statSync(filePath);
193
+ if (stat.mtimeMs < fiveMinAgo) continue;
194
+
195
+ const content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
196
+ const hash = sha256(content);
197
+
198
+ const existing = db.prepare('SELECT content_hash FROM file_hashes WHERE path = ?').get(filePath);
199
+ if (existing && existing.content_hash === hash) continue;
200
+
201
+ const { meta, body } = parseFrontmatter(content);
202
+ const ftsData = extractFtsMetadata(meta, body, filePath, baseDir);
203
+
204
+ const update = db.transaction(() => {
205
+ db.prepare('DELETE FROM mindlore_fts WHERE path = ?').run(filePath);
206
+ insertFtsRow(db, { path: filePath, ...ftsData, project: resolveProject(ftsData.project, filePath, getProjectName()) });
207
+ db.prepare(
208
+ `INSERT INTO file_hashes (path, content_hash, last_indexed)
209
+ VALUES (?, ?, ?)
210
+ ON CONFLICT(path) DO UPDATE SET
211
+ content_hash = excluded.content_hash,
212
+ last_indexed = excluded.last_indexed`
213
+ ).run(filePath, hash, new Date().toISOString());
214
+ });
215
+ update();
216
+ indexed++;
217
+ }
218
+ }
219
+ if (indexed > 0) {
220
+ hookLog(`catch-up: ${indexed} file(s) indexed`);
221
+ }
222
+ } finally {
223
+ db.close();
224
+ }
225
+ }
226
+
227
+ withTelemetry('mindlore-index', main).catch(err => {
228
+ hookLog('mindlore-index', 'error', err?.message ?? String(err));
229
+ process.exit(0);
230
+ });
@@ -1,54 +1,54 @@
1
- 'use strict';
2
-
3
- /**
4
- * mindlore-model-router — PreToolUse (Agent) hook
5
- * Overrides model for [mindlore:SKILL] marked Agent spawns.
6
- */
7
-
8
- const fs = require('fs');
9
- const { findMindloreDir, readConfig, DEFAULT_MODELS, hookLog, withTelemetrySync } = require('./lib/mindlore-common.cjs');
10
-
11
- const SKILL_KEYS = Object.keys(DEFAULT_MODELS).filter((k) => k !== 'default');
12
- const MARKER_REGEX = new RegExp(`\\[mindlore:(${SKILL_KEYS.join('|')})\\]`);
13
-
14
- function main() {
15
- const mindloreDir = findMindloreDir();
16
- if (!mindloreDir) return;
17
-
18
- let input;
19
- try {
20
- const raw = fs.readFileSync(0, 'utf8').trim();
21
- if (!raw) return;
22
- input = JSON.parse(raw);
23
- } catch (_err) {
24
- return;
25
- }
26
-
27
- const toolName = input.tool_name || '';
28
- if (toolName !== 'Agent') return;
29
-
30
- const toolInput = input.tool_input || {};
31
- const prompt = toolInput.prompt || '';
32
- const match = prompt.match(MARKER_REGEX);
33
- if (!match) return;
34
-
35
- const skill = match[1];
36
-
37
- // Resolve model: config.json → config default → hardcoded
38
- const config = readConfig(mindloreDir);
39
- const models = (config && config.models) || {};
40
- const model = models[skill] || models.default || DEFAULT_MODELS[skill] || DEFAULT_MODELS.default;
41
-
42
- const updatedInput = { ...toolInput, model: model };
43
-
44
- const output = {
45
- hookSpecificOutput: {
46
- hookEventName: 'PreToolUse',
47
- updatedInput: updatedInput,
48
- },
49
- };
50
-
51
- process.stdout.write(JSON.stringify(output));
52
- }
53
-
54
- try { withTelemetrySync('mindlore-model-router', main); } catch (err) { hookLog('model-router', 'error', err?.message ?? String(err)); }
1
+ 'use strict';
2
+
3
+ /**
4
+ * mindlore-model-router — PreToolUse (Agent) hook
5
+ * Overrides model for [mindlore:SKILL] marked Agent spawns.
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const { findMindloreDir, readConfig, DEFAULT_MODELS, hookLog, withTelemetrySync } = require('./lib/mindlore-common.cjs');
10
+
11
+ const SKILL_KEYS = Object.keys(DEFAULT_MODELS).filter((k) => k !== 'default');
12
+ const MARKER_REGEX = new RegExp(`\\[mindlore:(${SKILL_KEYS.join('|')})\\]`);
13
+
14
+ function main() {
15
+ const mindloreDir = findMindloreDir();
16
+ if (!mindloreDir) return;
17
+
18
+ let input;
19
+ try {
20
+ const raw = fs.readFileSync(0, 'utf8').trim();
21
+ if (!raw) return;
22
+ input = JSON.parse(raw);
23
+ } catch (_err) {
24
+ return;
25
+ }
26
+
27
+ const toolName = input.tool_name || '';
28
+ if (toolName !== 'Agent') return;
29
+
30
+ const toolInput = input.tool_input || {};
31
+ const prompt = toolInput.prompt || '';
32
+ const match = prompt.match(MARKER_REGEX);
33
+ if (!match) return;
34
+
35
+ const skill = match[1];
36
+
37
+ // Resolve model: config.json → config default → hardcoded
38
+ const config = readConfig(mindloreDir);
39
+ const models = (config && config.models) || {};
40
+ const model = models[skill] || models.default || DEFAULT_MODELS[skill] || DEFAULT_MODELS.default;
41
+
42
+ const updatedInput = { ...toolInput, model: model };
43
+
44
+ const output = {
45
+ hookSpecificOutput: {
46
+ hookEventName: 'PreToolUse',
47
+ updatedInput: updatedInput,
48
+ },
49
+ };
50
+
51
+ process.stdout.write(JSON.stringify(output));
52
+ }
53
+
54
+ try { withTelemetrySync('mindlore-model-router', main); } catch (err) { hookLog('model-router', 'error', err?.message ?? String(err)); }