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.
- package/README.md +5 -5
- package/dist/scripts/fetch-raw.js +14 -7
- package/dist/scripts/fetch-raw.js.map +1 -1
- package/dist/scripts/init.js +39 -1
- package/dist/scripts/init.js.map +1 -1
- package/dist/scripts/lib/backfill.d.ts +9 -0
- package/dist/scripts/lib/backfill.d.ts.map +1 -0
- package/dist/scripts/lib/backfill.js +74 -0
- package/dist/scripts/lib/backfill.js.map +1 -0
- package/dist/scripts/lib/constants.d.ts +5 -1
- package/dist/scripts/lib/constants.d.ts.map +1 -1
- package/dist/scripts/lib/constants.js +8 -1
- package/dist/scripts/lib/constants.js.map +1 -1
- package/dist/scripts/lib/contradiction.d.ts +7 -0
- package/dist/scripts/lib/contradiction.d.ts.map +1 -0
- package/dist/scripts/lib/contradiction.js +205 -0
- package/dist/scripts/lib/contradiction.js.map +1 -0
- package/dist/scripts/lib/daemon.d.ts +15 -0
- package/dist/scripts/lib/daemon.d.ts.map +1 -0
- package/dist/scripts/lib/daemon.js +129 -0
- package/dist/scripts/lib/daemon.js.map +1 -0
- package/dist/scripts/lib/db-helpers.d.ts.map +1 -1
- package/dist/scripts/lib/db-helpers.js +7 -1
- package/dist/scripts/lib/db-helpers.js.map +1 -1
- package/dist/scripts/lib/decay.d.ts +7 -2
- package/dist/scripts/lib/decay.d.ts.map +1 -1
- package/dist/scripts/lib/decay.js +31 -5
- package/dist/scripts/lib/decay.js.map +1 -1
- package/dist/scripts/lib/git-snapshot.d.ts +3 -0
- package/dist/scripts/lib/git-snapshot.d.ts.map +1 -0
- package/dist/scripts/lib/git-snapshot.js +57 -0
- package/dist/scripts/lib/git-snapshot.js.map +1 -0
- package/dist/scripts/lib/session-payload.d.ts +19 -0
- package/dist/scripts/lib/session-payload.d.ts.map +1 -0
- package/dist/scripts/lib/session-payload.js +85 -0
- package/dist/scripts/lib/session-payload.js.map +1 -0
- package/dist/scripts/mindlore-daemon.d.ts +2 -0
- package/dist/scripts/mindlore-daemon.d.ts.map +1 -0
- package/dist/scripts/mindlore-daemon.js +118 -0
- package/dist/scripts/mindlore-daemon.js.map +1 -0
- package/dist/scripts/mindlore-fts5-index.js +18 -6
- 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 +12 -0
- package/dist/scripts/mindlore-health-check.js.map +1 -1
- package/dist/tests/backfill.test.d.ts +2 -0
- package/dist/tests/backfill.test.d.ts.map +1 -0
- package/dist/tests/backfill.test.js +127 -0
- package/dist/tests/backfill.test.js.map +1 -0
- package/dist/tests/contradiction-extended.test.d.ts +2 -0
- package/dist/tests/contradiction-extended.test.d.ts.map +1 -0
- package/dist/tests/contradiction-extended.test.js +182 -0
- package/dist/tests/contradiction-extended.test.js.map +1 -0
- package/dist/tests/daemon-integration.test.d.ts +2 -0
- package/dist/tests/daemon-integration.test.d.ts.map +1 -0
- package/dist/tests/daemon-integration.test.js +37 -0
- package/dist/tests/daemon-integration.test.js.map +1 -0
- package/dist/tests/daemon.test.d.ts +2 -0
- package/dist/tests/daemon.test.d.ts.map +1 -0
- package/dist/tests/daemon.test.js +187 -0
- package/dist/tests/daemon.test.js.map +1 -0
- package/dist/tests/decay.test.js +51 -0
- package/dist/tests/decay.test.js.map +1 -1
- package/dist/tests/fts5.test.js +181 -0
- package/dist/tests/fts5.test.js.map +1 -1
- package/dist/tests/git-snapshot.test.js +54 -10
- package/dist/tests/git-snapshot.test.js.map +1 -1
- package/dist/tests/helpers/db.d.ts.map +1 -1
- package/dist/tests/helpers/db.js +3 -1
- package/dist/tests/helpers/db.js.map +1 -1
- package/dist/tests/init.test.js +36 -3
- package/dist/tests/init.test.js.map +1 -1
- package/dist/tests/research-guard-scope.test.d.ts +2 -0
- package/dist/tests/research-guard-scope.test.d.ts.map +1 -0
- package/dist/tests/research-guard-scope.test.js +154 -0
- package/dist/tests/research-guard-scope.test.js.map +1 -0
- package/dist/tests/research-guard.test.js +60 -0
- package/dist/tests/research-guard.test.js.map +1 -1
- package/dist/tests/search-hook.test.js +37 -0
- package/dist/tests/search-hook.test.js.map +1 -1
- package/dist/tests/session-focus.test.js +106 -3
- package/dist/tests/session-focus.test.js.map +1 -1
- package/dist/tests/session-payload.test.d.ts +5 -0
- package/dist/tests/session-payload.test.d.ts.map +1 -0
- package/dist/tests/session-payload.test.js +135 -0
- package/dist/tests/session-payload.test.js.map +1 -0
- package/hooks/lib/mindlore-common.cjs +37 -7
- package/hooks/mindlore-research-guard.cjs +25 -2
- package/hooks/mindlore-search.cjs +65 -16
- package/hooks/mindlore-session-end.cjs +35 -0
- package/hooks/mindlore-session-focus.cjs +58 -47
- package/package.json +2 -2
- package/plugin.json +1 -1
- package/skills/mindlore-health/SKILL.md +1 -0
- package/skills/mindlore-query/SKILL.md +7 -0
- 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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
db
|
|
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
|
-
//
|
|
112
|
-
|
|
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,
|
|
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
|
|
48
|
+
function searchDb(dbPath, keywords) {
|
|
30
49
|
const baseDir = path.dirname(dbPath);
|
|
31
|
-
const db =
|
|
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 (
|
|
79
|
-
|
|
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
|
|
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 =
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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 =
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
73
|
-
|
|
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
|
|
76
|
-
|
|
77
|
-
if (
|
|
78
|
-
const
|
|
79
|
-
|
|
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
|
+
"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": "^
|
|
48
|
+
"better-sqlite3": "^11.10.0",
|
|
49
49
|
"zod": "^4.3.6"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
package/plugin.json
CHANGED