mindlore 0.5.3 → 0.5.5

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 (96) hide show
  1. package/README.md +5 -5
  2. package/dist/scripts/fetch-raw.js +14 -7
  3. package/dist/scripts/fetch-raw.js.map +1 -1
  4. package/dist/scripts/init.js +39 -1
  5. package/dist/scripts/init.js.map +1 -1
  6. package/dist/scripts/lib/backfill.d.ts +9 -0
  7. package/dist/scripts/lib/backfill.d.ts.map +1 -0
  8. package/dist/scripts/lib/backfill.js +74 -0
  9. package/dist/scripts/lib/backfill.js.map +1 -0
  10. package/dist/scripts/lib/constants.d.ts +5 -1
  11. package/dist/scripts/lib/constants.d.ts.map +1 -1
  12. package/dist/scripts/lib/constants.js +8 -1
  13. package/dist/scripts/lib/constants.js.map +1 -1
  14. package/dist/scripts/lib/contradiction.d.ts +7 -0
  15. package/dist/scripts/lib/contradiction.d.ts.map +1 -0
  16. package/dist/scripts/lib/contradiction.js +205 -0
  17. package/dist/scripts/lib/contradiction.js.map +1 -0
  18. package/dist/scripts/lib/daemon.d.ts +15 -0
  19. package/dist/scripts/lib/daemon.d.ts.map +1 -0
  20. package/dist/scripts/lib/daemon.js +129 -0
  21. package/dist/scripts/lib/daemon.js.map +1 -0
  22. package/dist/scripts/lib/db-helpers.d.ts.map +1 -1
  23. package/dist/scripts/lib/db-helpers.js +7 -1
  24. package/dist/scripts/lib/db-helpers.js.map +1 -1
  25. package/dist/scripts/lib/decay.d.ts +7 -2
  26. package/dist/scripts/lib/decay.d.ts.map +1 -1
  27. package/dist/scripts/lib/decay.js +31 -5
  28. package/dist/scripts/lib/decay.js.map +1 -1
  29. package/dist/scripts/lib/git-snapshot.d.ts +3 -0
  30. package/dist/scripts/lib/git-snapshot.d.ts.map +1 -0
  31. package/dist/scripts/lib/git-snapshot.js +57 -0
  32. package/dist/scripts/lib/git-snapshot.js.map +1 -0
  33. package/dist/scripts/lib/session-payload.d.ts +19 -0
  34. package/dist/scripts/lib/session-payload.d.ts.map +1 -0
  35. package/dist/scripts/lib/session-payload.js +85 -0
  36. package/dist/scripts/lib/session-payload.js.map +1 -0
  37. package/dist/scripts/mindlore-daemon.d.ts +2 -0
  38. package/dist/scripts/mindlore-daemon.d.ts.map +1 -0
  39. package/dist/scripts/mindlore-daemon.js +118 -0
  40. package/dist/scripts/mindlore-daemon.js.map +1 -0
  41. package/dist/scripts/mindlore-fts5-index.js +18 -6
  42. package/dist/scripts/mindlore-fts5-index.js.map +1 -1
  43. package/dist/scripts/mindlore-health-check.d.ts.map +1 -1
  44. package/dist/scripts/mindlore-health-check.js +12 -0
  45. package/dist/scripts/mindlore-health-check.js.map +1 -1
  46. package/dist/tests/backfill.test.d.ts +2 -0
  47. package/dist/tests/backfill.test.d.ts.map +1 -0
  48. package/dist/tests/backfill.test.js +127 -0
  49. package/dist/tests/backfill.test.js.map +1 -0
  50. package/dist/tests/contradiction-extended.test.d.ts +2 -0
  51. package/dist/tests/contradiction-extended.test.d.ts.map +1 -0
  52. package/dist/tests/contradiction-extended.test.js +182 -0
  53. package/dist/tests/contradiction-extended.test.js.map +1 -0
  54. package/dist/tests/daemon-integration.test.d.ts +2 -0
  55. package/dist/tests/daemon-integration.test.d.ts.map +1 -0
  56. package/dist/tests/daemon-integration.test.js +37 -0
  57. package/dist/tests/daemon-integration.test.js.map +1 -0
  58. package/dist/tests/daemon.test.d.ts +2 -0
  59. package/dist/tests/daemon.test.d.ts.map +1 -0
  60. package/dist/tests/daemon.test.js +187 -0
  61. package/dist/tests/daemon.test.js.map +1 -0
  62. package/dist/tests/decay.test.js +51 -0
  63. package/dist/tests/decay.test.js.map +1 -1
  64. package/dist/tests/fts5.test.js +181 -0
  65. package/dist/tests/fts5.test.js.map +1 -1
  66. package/dist/tests/git-snapshot.test.js +54 -10
  67. package/dist/tests/git-snapshot.test.js.map +1 -1
  68. package/dist/tests/helpers/db.d.ts.map +1 -1
  69. package/dist/tests/helpers/db.js +3 -1
  70. package/dist/tests/helpers/db.js.map +1 -1
  71. package/dist/tests/init.test.js +36 -3
  72. package/dist/tests/init.test.js.map +1 -1
  73. package/dist/tests/research-guard-scope.test.d.ts +2 -0
  74. package/dist/tests/research-guard-scope.test.d.ts.map +1 -0
  75. package/dist/tests/research-guard-scope.test.js +154 -0
  76. package/dist/tests/research-guard-scope.test.js.map +1 -0
  77. package/dist/tests/research-guard.test.js +60 -0
  78. package/dist/tests/research-guard.test.js.map +1 -1
  79. package/dist/tests/search-hook.test.js +37 -0
  80. package/dist/tests/search-hook.test.js.map +1 -1
  81. package/dist/tests/session-focus.test.js +106 -3
  82. package/dist/tests/session-focus.test.js.map +1 -1
  83. package/dist/tests/session-payload.test.d.ts +5 -0
  84. package/dist/tests/session-payload.test.d.ts.map +1 -0
  85. package/dist/tests/session-payload.test.js +135 -0
  86. package/dist/tests/session-payload.test.js.map +1 -0
  87. package/hooks/lib/mindlore-common.cjs +37 -7
  88. package/hooks/mindlore-research-guard.cjs +25 -2
  89. package/hooks/mindlore-search.cjs +65 -16
  90. package/hooks/mindlore-session-end.cjs +35 -0
  91. package/hooks/mindlore-session-focus.cjs +58 -47
  92. package/package.json +2 -2
  93. package/plugin.json +1 -1
  94. package/skills/mindlore-health/SKILL.md +1 -0
  95. package/skills/mindlore-query/SKILL.md +7 -0
  96. package/templates/config.json +11 -9
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ /**
3
+ * session-payload tests — 4-section builder with token budget + cache-lock.
4
+ */
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const db_js_1 = require("./helpers/db.js");
12
+ const session_payload_js_1 = require("../scripts/lib/session-payload.js");
13
+ let env;
14
+ let db;
15
+ let baseDir;
16
+ const PROJECT = 'test-project';
17
+ function insertEpisode(kind, summary, opts = {}) {
18
+ const id = `ep-${Math.random().toString(36).slice(2, 10)}`;
19
+ db.prepare(`INSERT INTO episodes (id, kind, scope, project, summary, status, created_at)
20
+ VALUES (?, ?, 'project', ?, ?, ?, ?)`).run(id, kind, opts.project ?? PROJECT, summary, opts.status ?? 'active', opts.created_at ?? new Date().toISOString());
21
+ }
22
+ function writeDelta(filename, content) {
23
+ const diaryDir = path_1.default.join(baseDir, 'diary');
24
+ fs_1.default.mkdirSync(diaryDir, { recursive: true });
25
+ fs_1.default.writeFileSync(path_1.default.join(diaryDir, filename), content, 'utf8');
26
+ }
27
+ beforeEach(() => {
28
+ env = (0, db_js_1.createEpisodesTestEnv)('session-payload');
29
+ db = env.db;
30
+ baseDir = env.tmpDir;
31
+ (0, session_payload_js_1.resetCache)();
32
+ });
33
+ afterEach(() => {
34
+ (0, db_js_1.destroyEpisodesTestEnv)(env);
35
+ });
36
+ describe('buildSessionPayload', () => {
37
+ test('produces 4 sections for a full DB', () => {
38
+ writeDelta('delta-2026-04-19.md', '# Session\n- Did X\n- Did Y');
39
+ insertEpisode('decision', 'Use TypeScript for all scripts');
40
+ insertEpisode('friction', 'CI pipeline is slow');
41
+ insertEpisode('learning', 'Always build before test');
42
+ const payload = (0, session_payload_js_1.buildSessionPayload)(db, baseDir, PROJECT);
43
+ expect(payload.sections).toHaveLength(4);
44
+ expect(payload.sections[0].label).toBe('Session');
45
+ expect(payload.sections[1].label).toBe('Decisions');
46
+ expect(payload.sections[2].label).toBe('Friction');
47
+ expect(payload.sections[3].label).toBe('Learnings');
48
+ expect(payload.totalTokens).toBeGreaterThan(0);
49
+ expect(payload.skipInjection).toBe(false);
50
+ expect(payload.contentHash).toMatch(/^[0-9a-f]{8}$/);
51
+ });
52
+ test('trims sections from end when over budget', () => {
53
+ writeDelta('delta-2026-04-19.md', '# Session\n- Short');
54
+ insertEpisode('decision', 'Short decision');
55
+ insertEpisode('friction', 'Short friction');
56
+ insertEpisode('learning', 'Short learning');
57
+ const payload = (0, session_payload_js_1.buildSessionPayload)(db, baseDir, PROJECT, 20);
58
+ expect(payload.sections.length).toBeLessThan(4);
59
+ expect(payload.sections[0].label).toBe('Session');
60
+ expect(payload.totalTokens).toBeLessThanOrEqual(20);
61
+ });
62
+ test('trim order: Learnings dropped first, then Friction, then Decisions', () => {
63
+ writeDelta('delta-2026-04-19.md', '# Session\n- Work');
64
+ insertEpisode('decision', 'Dec');
65
+ insertEpisode('friction', 'Fric');
66
+ insertEpisode('learning', 'Learn');
67
+ const full = (0, session_payload_js_1.buildSessionPayload)(db, baseDir, PROJECT, 99999);
68
+ const sessionTokens = full.sections[0].tokens;
69
+ const decisionTokens = full.sections[1].tokens;
70
+ (0, session_payload_js_1.resetCache)();
71
+ const budgetFor2 = sessionTokens + decisionTokens + 1;
72
+ const trimmed = (0, session_payload_js_1.buildSessionPayload)(db, baseDir, PROJECT, budgetFor2);
73
+ expect(trimmed.sections.map(s => s.label)).toEqual(['Session', 'Decisions']);
74
+ });
75
+ test('skips injection when content unchanged (cache-lock)', () => {
76
+ writeDelta('delta-2026-04-19.md', '# Session\n- Same content');
77
+ insertEpisode('decision', 'Same decision');
78
+ const first = (0, session_payload_js_1.buildSessionPayload)(db, baseDir, PROJECT);
79
+ expect(first.skipInjection).toBe(false);
80
+ const second = (0, session_payload_js_1.buildSessionPayload)(db, baseDir, PROJECT);
81
+ expect(second.skipInjection).toBe(true);
82
+ expect(second.contentHash).toBe(first.contentHash);
83
+ });
84
+ test('handles empty DB gracefully', () => {
85
+ const payload = (0, session_payload_js_1.buildSessionPayload)(db, baseDir, PROJECT);
86
+ expect(payload.sections).toHaveLength(4);
87
+ expect(payload.sections[0].content).toContain('No previous session data');
88
+ expect(payload.sections[1].content).toContain('No recent decisions');
89
+ expect(payload.sections[2].content).toContain('No active friction');
90
+ expect(payload.sections[3].content).toContain('No recent learnings');
91
+ expect(payload.skipInjection).toBe(false);
92
+ });
93
+ test('handles missing diary directory', () => {
94
+ insertEpisode('decision', 'A decision');
95
+ const payload = (0, session_payload_js_1.buildSessionPayload)(db, baseDir, PROJECT);
96
+ expect(payload.sections[0].label).toBe('Session');
97
+ expect(payload.sections[0].content).toContain('No previous session data');
98
+ expect(payload.sections).toHaveLength(4);
99
+ });
100
+ test('token estimation is reasonable', () => {
101
+ const text = 'abcd'; // 4 chars = 1 token
102
+ writeDelta('delta-2026-04-19.md', `# H\n- ${text}`);
103
+ const payload = (0, session_payload_js_1.buildSessionPayload)(db, baseDir, PROJECT);
104
+ for (const section of payload.sections) {
105
+ const expectedTokens = Math.ceil(section.content.length / 4);
106
+ expect(section.tokens).toBe(expectedTokens);
107
+ }
108
+ const expectedTotal = payload.sections.reduce((s, sec) => s + sec.tokens, 0);
109
+ expect(payload.totalTokens).toBe(expectedTotal);
110
+ });
111
+ test('reads latest delta file when multiple exist', () => {
112
+ writeDelta('delta-2026-04-18.md', '# Old\n- Old stuff');
113
+ writeDelta('delta-2026-04-19.md', '# Latest\n- Latest stuff');
114
+ const payload = (0, session_payload_js_1.buildSessionPayload)(db, baseDir, PROJECT);
115
+ expect(payload.sections[0].content).toContain('Latest stuff');
116
+ expect(payload.sections[0].content).not.toContain('Old stuff');
117
+ });
118
+ test('only returns episodes for matching project', () => {
119
+ insertEpisode('decision', 'My project decision', { project: PROJECT });
120
+ insertEpisode('decision', 'Other project decision', { project: 'other-project' });
121
+ const payload = (0, session_payload_js_1.buildSessionPayload)(db, baseDir, PROJECT);
122
+ expect(payload.sections[1].content).toContain('My project decision');
123
+ expect(payload.sections[1].content).not.toContain('Other project decision');
124
+ });
125
+ test('Session section is always kept even when over budget', () => {
126
+ writeDelta('delta-2026-04-19.md', '# Session\n- Important context');
127
+ insertEpisode('decision', 'Dec');
128
+ insertEpisode('friction', 'Fric');
129
+ insertEpisode('learning', 'Learn');
130
+ const payload = (0, session_payload_js_1.buildSessionPayload)(db, baseDir, PROJECT, 1);
131
+ expect(payload.sections).toHaveLength(1);
132
+ expect(payload.sections[0].label).toBe('Session');
133
+ });
134
+ });
135
+ //# sourceMappingURL=session-payload.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-payload.test.js","sourceRoot":"","sources":["../../tests/session-payload.test.ts"],"names":[],"mappings":"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"}
@@ -186,14 +186,20 @@ function requireDatabase() {
186
186
  }
187
187
 
188
188
  function openDatabase(dbPath, opts) {
189
- const Database = requireDatabase();
190
- if (!Database) return null;
191
-
192
- const db = new Database(dbPath, opts);
193
- if (!opts || !opts.readonly) {
194
- db.pragma('journal_mode = WAL');
189
+ try {
190
+ const Database = requireDatabase();
191
+ if (!Database) return null;
192
+ if (!fs.existsSync(dbPath)) return null;
193
+ const readonly = opts?.readonly ?? false;
194
+ const db = new Database(dbPath, { readonly });
195
+ if (!readonly) {
196
+ db.pragma('journal_mode = WAL');
197
+ db.pragma('busy_timeout = 5000');
198
+ }
199
+ return db;
200
+ } catch (_err) {
201
+ return null;
195
202
  }
196
- return db;
197
203
  }
198
204
 
199
205
  function getAllMdFiles(dir, skip) {
@@ -679,8 +685,32 @@ module.exports = {
679
685
  extractSkeleton,
680
686
  // Recall telemetry (v0.5.3)
681
687
  incrementRecallCount,
688
+ // Daemon helpers (v0.5.5)
689
+ isDaemonRunning,
690
+ getDaemonPortFile,
691
+ getDaemonPidFile,
682
692
  };
683
693
 
694
+ function isDaemonRunning(pidFile) {
695
+ if (!fs.existsSync(pidFile)) return { running: false };
696
+ const pid = parseInt(fs.readFileSync(pidFile, 'utf8').trim());
697
+ try {
698
+ process.kill(pid, 0);
699
+ return { running: true, pid };
700
+ } catch {
701
+ try { fs.unlinkSync(pidFile); } catch { /* stale file already gone */ }
702
+ return { running: false };
703
+ }
704
+ }
705
+
706
+ function getDaemonPortFile() {
707
+ return path.join(globalDir(), 'mindlore-daemon.port');
708
+ }
709
+
710
+ function getDaemonPidFile() {
711
+ return path.join(globalDir(), 'mindlore-daemon.pid');
712
+ }
713
+
684
714
  /**
685
715
  * Try to load sqlite-vec extension. Returns true if successful.
686
716
  * @param {import('better-sqlite3').Database} db
@@ -103,13 +103,36 @@ function main() {
103
103
  if (toolName !== 'Agent') return;
104
104
 
105
105
  const toolInput = input.tool_input || {};
106
+
107
+ // Only block agents with web access — let local-only agents pass
108
+ const WEB_CAPABLE_TYPES = ['researcher', 'general-purpose'];
109
+ const LOCAL_ONLY_TYPES = [
110
+ 'Explore', 'coder', 'code-reviewer', 'Plan',
111
+ 'bug-analyzer', 'security-reviewer', 'contrarian',
112
+ 'scope-guardian', 'quality-gate', 'test-runner',
113
+ ];
114
+ const subagentType = toolInput.subagent_type || '';
115
+ const description = (toolInput.description || '').toLowerCase();
116
+
117
+ // Known local-only agent → always pass
118
+ if (LOCAL_ONLY_TYPES.includes(subagentType)) return;
119
+
120
+ // Known web-capable agent → continue to FTS5 check
121
+ // Unknown or empty subagent_type → check description for research intent
122
+ if (subagentType && !WEB_CAPABLE_TYPES.includes(subagentType)) return;
123
+
124
+ // If no subagent_type, check description for web research intent (reuse RESEARCH_REGEX)
125
+ if (!subagentType && !RESEARCH_REGEX.test(description)) return;
126
+
106
127
  const prompt = (toolInput.prompt || '') + ' ' + (toolInput.description || '');
107
128
 
108
129
  // Skip mindlore internal operations and explicit overrides
109
130
  if (EXCLUDE_REGEX.test(prompt)) return;
110
131
 
111
- // Only trigger on research-intent agents
112
- if (!RESEARCH_REGEX.test(prompt)) return;
132
+ // If subagent_type is a known research type, skip prompt-level regex check
133
+ // Otherwise require research signals in the prompt text
134
+ const isKnownResearchType = WEB_CAPABLE_TYPES.includes(subagentType);
135
+ if (!isKnownResearchType && !RESEARCH_REGEX.test(prompt)) return;
113
136
 
114
137
  const keywords = extractKeywords(prompt, 10);
115
138
  if (keywords.length < 2) return;
@@ -10,7 +10,9 @@
10
10
 
11
11
  const fs = require('fs');
12
12
  const path = require('path');
13
- const { getAllDbs, requireDatabase, extractHeadings, readHookStdin, extractKeywords, sanitizeKeyword, readConfig, loadSqliteVecCjs, hasVecTableCjs, hookLog, incrementRecallCount } = require('./lib/mindlore-common.cjs');
13
+ const { getAllDbs, openDatabase, extractHeadings, readHookStdin, extractKeywords, sanitizeKeyword, readConfig, loadSqliteVecCjs, hasVecTableCjs, hookLog, incrementRecallCount, getDaemonPortFile } = require('./lib/mindlore-common.cjs');
14
+
15
+ const { execFileSync } = require('child_process');
14
16
 
15
17
  const MAX_RESULTS = 3;
16
18
  const MIN_QUERY_WORDS = 3;
@@ -23,15 +25,36 @@ try {
23
25
  // hybrid-search not built yet — pure FTS5 mode
24
26
  }
25
27
 
28
+ // v0.5.5: Request embedding from daemon via execFileSync bridge
29
+ function requestEmbeddingSync(query) {
30
+ try {
31
+ const portFile = getDaemonPortFile();
32
+ if (!fs.existsSync(portFile)) return null;
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']
37
+ });
38
+ const parsed = JSON.parse(result.trim());
39
+ return parsed.type === 'embedding' ? parsed.embedding : null;
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+
26
45
  /**
27
46
  * Search a single DB and return scored results with their baseDir.
28
47
  */
29
- function searchDb(dbPath, keywords, Database) {
48
+ function searchDb(dbPath, keywords) {
30
49
  const baseDir = path.dirname(dbPath);
31
- const db = new Database(dbPath, { readonly: true });
50
+ const db = openDatabase(dbPath, { readonly: true });
51
+ if (!db) return [];
32
52
  const results = [];
33
53
 
34
54
  // v0.5.0: Try hybrid search with synonym expansion (no embedding — hooks are sync)
55
+ if (!hybridSearchMod) {
56
+ hookLog('search', 'info', 'No hybridSearchMod — FTS5-only mode');
57
+ }
35
58
  if (hybridSearchMod && loadSqliteVecCjs(db) && hasVecTableCjs(db)) {
36
59
  try {
37
60
  const config = readConfig(baseDir);
@@ -46,9 +69,21 @@ function searchDb(dbPath, keywords, Database) {
46
69
  }
47
70
  }
48
71
 
72
+ // v0.5.5: Try to get queryEmbedding from daemon
73
+ let queryEmbedding = null;
74
+ try {
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
+ }
82
+
49
83
  const fusedResults = hybridSearchMod.hybridSearch(db, expandedTerms.join(' '), {
50
84
  maxResults: MAX_RESULTS,
51
85
  project: path.basename(process.cwd()),
86
+ queryEmbedding,
52
87
  });
53
88
 
54
89
  if (fusedResults.length > 0) {
@@ -75,8 +110,8 @@ function searchDb(dbPath, keywords, Database) {
75
110
  db.close();
76
111
  return results;
77
112
  }
78
- } catch (_err) {
79
- // Hybrid search failed fall through to FTS5
113
+ } catch (hybridErr) {
114
+ hookLog('search', 'warn', `Hybrid search fallback to FTS5: ${hybridErr?.message || hybridErr}`);
80
115
  }
81
116
  }
82
117
 
@@ -145,12 +180,9 @@ function main() {
145
180
  const keywords = extractKeywords(userMessage);
146
181
  if (keywords.length < MIN_QUERY_WORDS) return;
147
182
 
148
- const Database = requireDatabase();
149
- if (!Database) return;
150
-
151
183
  const allScores = [];
152
184
  for (const dbPath of dbPaths) {
153
- allScores.push(...searchDb(dbPath, keywords, Database));
185
+ allScores.push(...searchDb(dbPath, keywords));
154
186
  }
155
187
 
156
188
  // Sort: most keyword hits first, then best rank
@@ -171,12 +203,14 @@ function main() {
171
203
  if (relevant.length === 0) return;
172
204
 
173
205
  try {
174
- const db = new Database(dbPaths[0]);
175
- const txn = db.transaction(() => {
176
- for (const r of relevant) incrementRecallCount(db, r.path);
177
- });
178
- txn();
179
- db.close();
206
+ const db = openDatabase(dbPaths[0]);
207
+ if (db) {
208
+ const txn = db.transaction(() => {
209
+ for (const r of relevant) incrementRecallCount(db, r.path);
210
+ });
211
+ txn();
212
+ db.close();
213
+ }
180
214
  } catch (_e) { /* graceful — never block search output */ }
181
215
 
182
216
  // Populate headings only for final results (avoid reading extra files)
@@ -222,7 +256,8 @@ function main() {
222
256
  if (relevant.length < MAX_RESULTS) {
223
257
  for (const dbPath of dbPaths) {
224
258
  try {
225
- const db = new Database(dbPath, { readonly: true });
259
+ const db = openDatabase(dbPath, { readonly: true });
260
+ if (!db) continue;
226
261
  const episodeResults = searchEpisodesFts(db, keywords);
227
262
  db.close();
228
263
  if (episodeResults.length > 0) {
@@ -241,6 +276,20 @@ function main() {
241
276
  const baseDir = path.dirname(dbPaths[0]);
242
277
  const tmpDir = path.join(baseDir, 'tmp');
243
278
  fs.mkdirSync(tmpDir, { recursive: true });
279
+
280
+ // Cleanup stale tmp files before writing new one (>1h old, keep max 20)
281
+ try {
282
+ const oneHourAgo = Date.now() - 3600000;
283
+ const files = fs.readdirSync(tmpDir)
284
+ .filter(f => f.startsWith('search-'))
285
+ .map(f => ({ name: f, mtime: fs.statSync(path.join(tmpDir, f)).mtimeMs }))
286
+ .sort((a, b) => b.mtime - a.mtime);
287
+ for (let i = 0; i < files.length; i++) {
288
+ if (i >= 20 || files[i].mtime < oneHourAgo) {
289
+ try { fs.unlinkSync(path.join(tmpDir, files[i].name)); } catch { /* ignore */ }
290
+ }
291
+ }
292
+ } catch { /* cleanup is best-effort */ }
244
293
  const fileName = `search-${Date.now()}.md`;
245
294
  const filePath = path.join(tmpDir, fileName);
246
295
  fs.writeFileSync(filePath, outputStr, 'utf8');
@@ -46,6 +46,41 @@ if (process.argv.includes('--worker')) {
46
46
  await safeRunAsync(() => writeBareEpisode(baseDir, project, commits, changedFiles, reads), 'episode');
47
47
  await safeRunAsync(() => writeEpisodeFile(baseDir, project, commits, changedFiles, reads), 'episode-file');
48
48
 
49
+ // CC memory bulk sync — synchronous with timeout
50
+ await safeRunAsync(async () => {
51
+ const syncScript = path.join(__dirname, '..', 'dist', 'scripts', 'cc-memory-bulk-sync.js');
52
+ if (fs.existsSync(syncScript)) {
53
+ const nodeExe = resolveWin32Bin('node') || process.execPath;
54
+ const { execFileSync } = require('child_process');
55
+ try {
56
+ execFileSync(nodeExe, [syncScript, '--auto'], {
57
+ timeout: 10000,
58
+ env: { ...process.env, MINDLORE_HOME: baseDir },
59
+ });
60
+ hookLog('session-end', 'info', 'CC memory sync completed');
61
+ } catch (syncErr) {
62
+ hookLog('session-end', 'warn', `CC memory sync failed: ${syncErr?.message || syncErr}`);
63
+ }
64
+ }
65
+ }, 'cc-memory-sync');
66
+
67
+ // Embed trigger — detached, fire-and-forget
68
+ await safeRunAsync(async () => {
69
+ const indexScript = path.join(__dirname, '..', 'dist', 'scripts', 'mindlore-fts5-index.js');
70
+ if (fs.existsSync(indexScript)) {
71
+ const nodeExe = resolveWin32Bin('node') || process.execPath;
72
+ const { spawn: spawnChild } = require('child_process');
73
+ const embedProc = spawnChild(nodeExe, [indexScript, '--embed'], {
74
+ detached: true,
75
+ stdio: 'ignore',
76
+ windowsHide: true,
77
+ env: { ...process.env, MINDLORE_HOME: baseDir },
78
+ });
79
+ embedProc.unref();
80
+ hookLog('session-end', 'info', 'embed subprocess spawned, pid=' + embedProc.pid);
81
+ }
82
+ }, 'embed-trigger');
83
+
49
84
  // Obsidian + git-sync are independent — run in parallel
50
85
  await Promise.allSettled([
51
86
  safeRunAsync(() => syncObsidian(baseDir), 'obsidian'),
@@ -10,7 +10,7 @@
10
10
 
11
11
  const fs = require('fs');
12
12
  const path = require('path');
13
- const { findMindloreDir, readConfig, openDatabase, hasEpisodesTable, queryRecentEpisodes, querySupersededChains, formatSupersededChains, queryMultiSessionEpisodes, formatMultiSessionEpisodes, getAllMdFiles, getRecentHookErrors, hookLog } = require('./lib/mindlore-common.cjs');
13
+ const { findMindloreDir, readConfig, openDatabase, hasEpisodesTable, querySupersededChains, formatSupersededChains, hookLog, getProjectName, parseFrontmatter, isDaemonRunning, getDaemonPidFile } = require('./lib/mindlore-common.cjs');
14
14
 
15
15
  function main() {
16
16
  const baseDir = findMindloreDir();
@@ -32,13 +32,17 @@ function main() {
32
32
  try {
33
33
  const diaryFiles = fs.readdirSync(diaryDir).filter(f => f.startsWith('delta-') && f.endsWith('.md'));
34
34
 
35
- // Latest delta
36
35
  if (diaryFiles.length > 0) {
37
36
  const sorted = [...diaryFiles].sort();
38
37
  const latestName = sorted[sorted.length - 1];
39
38
  const latestPath = path.join(diaryDir, latestName);
40
39
  const deltaContent = fs.readFileSync(latestPath, 'utf8').trim();
41
- output.push(`[Mindlore Delta: ${latestName}]\n${deltaContent}`);
40
+ const { meta } = parseFrontmatter(deltaContent);
41
+ const deltaProject = meta.project || null;
42
+ const currentProject = getProjectName();
43
+ if (!deltaProject || deltaProject.toLowerCase() === currentProject.toLowerCase()) {
44
+ output.push(`[Mindlore Delta: ${latestName}]\n${deltaContent}`);
45
+ }
42
46
  }
43
47
 
44
48
  // Reflect trigger
@@ -63,43 +67,37 @@ function main() {
63
67
  }
64
68
  } catch (_err) { /* skip */ }
65
69
 
66
- // v0.4.0: Inject recent episodes
70
+ // v0.5.4: Consolidated session payload (replaces scattered episodes/activity/alerts injection)
67
71
  try {
68
72
  const dbPath = path.join(baseDir, 'mindlore.db');
69
73
  const db = openDatabase(dbPath, { readonly: true });
70
74
  if (db) {
71
75
  try {
72
- if (hasEpisodesTable(db)) {
73
- const maxEpisodes = config?.session_focus?.max_episodes ?? 3;
76
+ // Session payload: Session summary, Decisions, Friction, Learnings
77
+ try {
78
+ const { buildSessionPayload } = require('../dist/scripts/lib/session-payload.js');
74
79
  const project = path.basename(process.cwd());
75
- const episodes = queryRecentEpisodes(db, { project, limit: maxEpisodes });
76
-
77
- if (episodes.length > 0) {
78
- const lines = episodes.map(ep => {
79
- const date = (ep.created_at || '').slice(0, 10);
80
- const summary = String(ep.summary || '').slice(0, 100);
81
- return `- [${date}] ${ep.kind}: ${summary}`;
82
- });
83
- output.push(`[Mindlore Episodes]\n${lines.join('\n')}`);
84
- }
85
-
86
- // v0.4.1: Enriched multi-session episodes
87
- const multiDays = config?.session_focus?.multi_session_days ?? 3;
88
- const enriched = queryMultiSessionEpisodes(db, { project, days: multiDays, limit: 20 });
89
- if (enriched.length > 0) {
90
- const formatted = formatMultiSessionEpisodes(enriched);
91
- if (formatted) {
92
- output.push(`[Mindlore Recent Activity]\n${formatted}`);
80
+ const payloadBudget = config?.tokenBudget?.sessionInject ?? 2000;
81
+ const payload = buildSessionPayload(db, baseDir, project, payloadBudget);
82
+ if (!payload.skipInjection) {
83
+ for (const section of payload.sections) {
84
+ output.push(`[Mindlore ${section.label}]\n${section.content}`);
93
85
  }
94
86
  }
87
+ } catch (_payloadErr) {
88
+ // Session payload is optional — don't break session start
89
+ }
90
+
91
+ // v0.4.1: Supersedes chain display (kept — not covered by session-payload)
92
+ if (hasEpisodesTable(db)) {
93
+ const project = path.basename(process.cwd());
95
94
 
96
- // v0.4.1: Supersedes chain display
97
95
  const chains = querySupersededChains(db, { project, days: 7, limit: 5 });
98
96
  if (chains.length > 0) {
99
97
  output.push(`[Mindlore Supersedes]\n${formatSupersededChains(chains)}`);
100
98
  }
101
99
 
102
- // v0.5.3: Episode consolidation reminder
100
+ // v0.5.3: Episode consolidation reminder (kept — threshold-based reminder)
103
101
  try {
104
102
  const rawCount = db.prepare(
105
103
  "SELECT COUNT(*) as cnt FROM episodes WHERE consolidation_status = 'raw' OR consolidation_status IS NULL"
@@ -111,33 +109,22 @@ function main() {
111
109
  }
112
110
  } catch (_err) { /* consolidation_status column may not exist yet */ }
113
111
  }
112
+
113
+ // v0.5.5: Stale content check (reuses open DB handle)
114
+ try {
115
+ const thirtyDaysAgo = new Date(Date.now() - (30 * 24 * 60 * 60 * 1000)).toISOString();
116
+ const row = db.prepare('SELECT COUNT(*) as cnt FROM file_hashes WHERE last_indexed < ?').get(thirtyDaysAgo);
117
+ const staleCount = row?.cnt ?? 0;
118
+ if (staleCount > 3) {
119
+ output.push(`[Mindlore: ${staleCount} dosya 30+ gundur guncellenmemis — \`/mindlore-evolve\` dusun]`);
120
+ }
121
+ } catch (_staleErr) { /* file_hashes may not exist */ }
114
122
  } finally {
115
123
  db.close();
116
124
  }
117
125
  }
118
126
  } catch (_err) { /* graceful skip */ }
119
127
 
120
- // v0.4.1: Lightweight stale content check (monitors fallback)
121
- try {
122
- const allFiles = getAllMdFiles(baseDir);
123
- const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000);
124
- const staleCount = allFiles.reduce((count, f) => {
125
- try { return fs.statSync(f).mtimeMs < thirtyDaysAgo ? count + 1 : count; } catch { return count; }
126
- }, 0);
127
- if (staleCount > 3) {
128
- output.push(`[Mindlore: ${staleCount} dosya 30+ gundur guncellenmemis — \`/mindlore-evolve\` dusun]`);
129
- }
130
- } catch (_healthErr) { /* skip */ }
131
-
132
- // Check for recent hook errors — inject warnings into CC context
133
- try {
134
- const errors = getRecentHookErrors();
135
- if (errors.length > 0) {
136
- const lines = errors.map(e => `- [${e.ts.slice(0, 19)}] **${e.hook}** (${e.level}): ${e.msg}`);
137
- output.push(`[Mindlore Hook Alerts]\n${lines.join('\n')}`);
138
- }
139
- } catch (_hookLogErr) { /* skip */ }
140
-
141
128
  hookLog('session-focus', 'info', 'session started');
142
129
 
143
130
  // Token budget for session inject
@@ -150,6 +137,30 @@ function main() {
150
137
  joined = joined.slice(0, maxInjectChars) + '\n[...truncated by token budget]';
151
138
  }
152
139
 
140
+ // v0.5.5: Auto-start embedding daemon if not already running
141
+ // Skip in test environments to avoid file lock issues
142
+ const skipDaemon = process.env.NODE_ENV === 'test' || baseDir.includes('.test-');
143
+ if (!skipDaemon) {
144
+ try {
145
+ const status = isDaemonRunning(getDaemonPidFile());
146
+ if (!status.running) {
147
+ const daemonScript = path.join(__dirname, '..', 'dist', 'scripts', 'mindlore-daemon.js');
148
+ if (fs.existsSync(daemonScript)) {
149
+ const { spawn } = require('child_process');
150
+ const child = spawn(process.execPath, [daemonScript, 'start'], {
151
+ detached: true,
152
+ stdio: 'ignore',
153
+ windowsHide: true,
154
+ });
155
+ child.unref();
156
+ hookLog('session-focus', 'info', 'Daemon auto-started, pid=' + child.pid);
157
+ }
158
+ }
159
+ } catch (_err) {
160
+ hookLog('session-focus', 'warn', 'Daemon auto-start failed: ' + (_err?.message || _err));
161
+ }
162
+ }
163
+
153
164
  if (joined.length > 0) {
154
165
  process.stdout.write(joined + '\n');
155
166
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mindlore",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "description": "AI-native knowledge system for Claude Code",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -45,7 +45,7 @@
45
45
  "node": ">=20.0.0"
46
46
  },
47
47
  "dependencies": {
48
- "better-sqlite3": "^12.9.0",
48
+ "better-sqlite3": "^11.10.0",
49
49
  "zod": "^4.3.6"
50
50
  },
51
51
  "devDependencies": {
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mindlore",
3
3
  "description": "AI-native knowledge system for Claude Code. Persistent, searchable, evolving knowledge base with FTS5.",
4
- "version": "0.5.3",
4
+ "version": "0.5.5",
5
5
  "skills": [
6
6
  {
7
7
  "name": "mindlore-ingest",
@@ -2,6 +2,7 @@
2
2
  name: mindlore-health
3
3
  description: Run 16-point structural health check on .mindlore/ knowledge base
4
4
  effort: low
5
+ context: fork
5
6
  allowed-tools: [Bash, Read]
6
7
  ---
7
8
 
@@ -1,3 +1,10 @@
1
+ ---
2
+ name: mindlore-query
3
+ description: Search, ask, stats, brief — compounding pipeline
4
+ context: fork
5
+ allowed-tools: [Bash, Read, Grep, Glob]
6
+ ---
7
+
1
8
  # Skill: Mindlore Query
2
9
 
3
10
  Search, ask, analyze, and retrieve knowledge from `.mindlore/`.