mindlore 0.5.3 → 0.5.4

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 (78) 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 +38 -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 +1 -1
  11. package/dist/scripts/lib/constants.d.ts.map +1 -1
  12. package/dist/scripts/lib/constants.js +2 -0
  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/db-helpers.d.ts.map +1 -1
  19. package/dist/scripts/lib/db-helpers.js +7 -1
  20. package/dist/scripts/lib/db-helpers.js.map +1 -1
  21. package/dist/scripts/lib/decay.d.ts +7 -2
  22. package/dist/scripts/lib/decay.d.ts.map +1 -1
  23. package/dist/scripts/lib/decay.js +28 -5
  24. package/dist/scripts/lib/decay.js.map +1 -1
  25. package/dist/scripts/lib/git-snapshot.d.ts +3 -0
  26. package/dist/scripts/lib/git-snapshot.d.ts.map +1 -0
  27. package/dist/scripts/lib/git-snapshot.js +38 -0
  28. package/dist/scripts/lib/git-snapshot.js.map +1 -0
  29. package/dist/scripts/lib/session-payload.d.ts +19 -0
  30. package/dist/scripts/lib/session-payload.d.ts.map +1 -0
  31. package/dist/scripts/lib/session-payload.js +85 -0
  32. package/dist/scripts/lib/session-payload.js.map +1 -0
  33. package/dist/scripts/mindlore-fts5-index.js +18 -6
  34. package/dist/scripts/mindlore-fts5-index.js.map +1 -1
  35. package/dist/scripts/mindlore-health-check.d.ts.map +1 -1
  36. package/dist/scripts/mindlore-health-check.js +12 -0
  37. package/dist/scripts/mindlore-health-check.js.map +1 -1
  38. package/dist/tests/backfill.test.d.ts +2 -0
  39. package/dist/tests/backfill.test.d.ts.map +1 -0
  40. package/dist/tests/backfill.test.js +127 -0
  41. package/dist/tests/backfill.test.js.map +1 -0
  42. package/dist/tests/contradiction-extended.test.d.ts +2 -0
  43. package/dist/tests/contradiction-extended.test.d.ts.map +1 -0
  44. package/dist/tests/contradiction-extended.test.js +182 -0
  45. package/dist/tests/contradiction-extended.test.js.map +1 -0
  46. package/dist/tests/decay.test.js +51 -0
  47. package/dist/tests/decay.test.js.map +1 -1
  48. package/dist/tests/fts5.test.js +181 -0
  49. package/dist/tests/fts5.test.js.map +1 -1
  50. package/dist/tests/git-snapshot.test.js +53 -10
  51. package/dist/tests/git-snapshot.test.js.map +1 -1
  52. package/dist/tests/helpers/db.d.ts.map +1 -1
  53. package/dist/tests/helpers/db.js +3 -1
  54. package/dist/tests/helpers/db.js.map +1 -1
  55. package/dist/tests/init.test.js +36 -3
  56. package/dist/tests/init.test.js.map +1 -1
  57. package/dist/tests/research-guard-scope.test.d.ts +2 -0
  58. package/dist/tests/research-guard-scope.test.d.ts.map +1 -0
  59. package/dist/tests/research-guard-scope.test.js +154 -0
  60. package/dist/tests/research-guard-scope.test.js.map +1 -0
  61. package/dist/tests/research-guard.test.js +60 -0
  62. package/dist/tests/research-guard.test.js.map +1 -1
  63. package/dist/tests/search-hook.test.js +37 -0
  64. package/dist/tests/search-hook.test.js.map +1 -1
  65. package/dist/tests/session-focus.test.js +86 -0
  66. package/dist/tests/session-focus.test.js.map +1 -1
  67. package/dist/tests/session-payload.test.d.ts +5 -0
  68. package/dist/tests/session-payload.test.d.ts.map +1 -0
  69. package/dist/tests/session-payload.test.js +135 -0
  70. package/dist/tests/session-payload.test.js.map +1 -0
  71. package/hooks/lib/mindlore-common.cjs +13 -7
  72. package/hooks/mindlore-research-guard.cjs +25 -2
  73. package/hooks/mindlore-search.cjs +20 -16
  74. package/hooks/mindlore-session-end.cjs +35 -0
  75. package/hooks/mindlore-session-focus.cjs +24 -35
  76. package/package.json +1 -1
  77. package/plugin.json +1 -1
  78. 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) {
@@ -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,7 @@
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 } = require('./lib/mindlore-common.cjs');
14
14
 
15
15
  const MAX_RESULTS = 3;
16
16
  const MIN_QUERY_WORDS = 3;
@@ -26,12 +26,16 @@ try {
26
26
  /**
27
27
  * Search a single DB and return scored results with their baseDir.
28
28
  */
29
- function searchDb(dbPath, keywords, Database) {
29
+ function searchDb(dbPath, keywords) {
30
30
  const baseDir = path.dirname(dbPath);
31
- const db = new Database(dbPath, { readonly: true });
31
+ const db = openDatabase(dbPath, { readonly: true });
32
+ if (!db) return [];
32
33
  const results = [];
33
34
 
34
35
  // v0.5.0: Try hybrid search with synonym expansion (no embedding — hooks are sync)
36
+ if (!hybridSearchMod) {
37
+ hookLog('search', 'info', 'No hybridSearchMod — FTS5-only mode');
38
+ }
35
39
  if (hybridSearchMod && loadSqliteVecCjs(db) && hasVecTableCjs(db)) {
36
40
  try {
37
41
  const config = readConfig(baseDir);
@@ -75,8 +79,8 @@ function searchDb(dbPath, keywords, Database) {
75
79
  db.close();
76
80
  return results;
77
81
  }
78
- } catch (_err) {
79
- // Hybrid search failed fall through to FTS5
82
+ } catch (hybridErr) {
83
+ hookLog('search', 'warn', `Hybrid search fallback to FTS5: ${hybridErr?.message || hybridErr}`);
80
84
  }
81
85
  }
82
86
 
@@ -145,12 +149,9 @@ function main() {
145
149
  const keywords = extractKeywords(userMessage);
146
150
  if (keywords.length < MIN_QUERY_WORDS) return;
147
151
 
148
- const Database = requireDatabase();
149
- if (!Database) return;
150
-
151
152
  const allScores = [];
152
153
  for (const dbPath of dbPaths) {
153
- allScores.push(...searchDb(dbPath, keywords, Database));
154
+ allScores.push(...searchDb(dbPath, keywords));
154
155
  }
155
156
 
156
157
  // Sort: most keyword hits first, then best rank
@@ -171,12 +172,14 @@ function main() {
171
172
  if (relevant.length === 0) return;
172
173
 
173
174
  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();
175
+ const db = openDatabase(dbPaths[0]);
176
+ if (db) {
177
+ const txn = db.transaction(() => {
178
+ for (const r of relevant) incrementRecallCount(db, r.path);
179
+ });
180
+ txn();
181
+ db.close();
182
+ }
180
183
  } catch (_e) { /* graceful — never block search output */ }
181
184
 
182
185
  // Populate headings only for final results (avoid reading extra files)
@@ -222,7 +225,8 @@ function main() {
222
225
  if (relevant.length < MAX_RESULTS) {
223
226
  for (const dbPath of dbPaths) {
224
227
  try {
225
- const db = new Database(dbPath, { readonly: true });
228
+ const db = openDatabase(dbPath, { readonly: true });
229
+ if (!db) continue;
226
230
  const episodeResults = searchEpisodesFts(db, keywords);
227
231
  db.close();
228
232
  if (episodeResults.length > 0) {
@@ -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, getAllMdFiles, hookLog, getProjectName, parseFrontmatter } = 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"
@@ -129,15 +127,6 @@ function main() {
129
127
  }
130
128
  } catch (_healthErr) { /* skip */ }
131
129
 
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
130
  hookLog('session-focus', 'info', 'session started');
142
131
 
143
132
  // Token budget for session inject
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mindlore",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "description": "AI-native knowledge system for Claude Code",
5
5
  "type": "commonjs",
6
6
  "bin": {
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.4",
5
5
  "skills": [
6
6
  {
7
7
  "name": "mindlore-ingest",
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.5.3",
2
+ "version": "0.5.4",
3
3
  "models": {
4
4
  "ingest": "haiku",
5
5
  "evolve": "sonnet",
@@ -13,14 +13,7 @@
13
13
  "max_episodes": 3,
14
14
  "multi_session_days": 3
15
15
  },
16
- "synonyms": {
17
- "auth": ["authentication", "login", "kimlik doğrulama"],
18
- "güvenlik": ["security", "hardening", "sertleştirme"],
19
- "db": ["database", "veritabanı", "sqlite"],
20
- "api": ["endpoint", "rest", "graphql"],
21
- "ui": ["frontend", "arayüz", "interface"],
22
- "test": ["testing", "jest", "unit test"]
23
- },
16
+ "synonyms": {},
24
17
  "hybrid": {
25
18
  "enabled": true,
26
19
  "ftsWeight": 0.4,
@@ -38,5 +31,14 @@
38
31
  },
39
32
  "consolidation": {
40
33
  "threshold": 50
34
+ },
35
+ "backup": {
36
+ "autoOnSessionEnd": false,
37
+ "remote": null
38
+ },
39
+ "reminders": {
40
+ "diaryReflectThreshold": 10,
41
+ "consolidationThreshold": 50,
42
+ "evolveIntervalDays": 30
41
43
  }
42
44
  }