mindlore 0.6.8 → 0.6.9

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 (124) hide show
  1. package/README.md +259 -259
  2. package/SCHEMA.md +292 -292
  3. package/dist/scripts/cc-memory-bulk-sync.d.ts.map +1 -1
  4. package/dist/scripts/cc-memory-bulk-sync.js +2 -1
  5. package/dist/scripts/cc-memory-bulk-sync.js.map +1 -1
  6. package/dist/scripts/cc-session-sync.d.ts.map +1 -1
  7. package/dist/scripts/cc-session-sync.js +6 -5
  8. package/dist/scripts/cc-session-sync.js.map +1 -1
  9. package/dist/scripts/fetch-raw.js +5 -4
  10. package/dist/scripts/fetch-raw.js.map +1 -1
  11. package/dist/scripts/init.js +12 -11
  12. package/dist/scripts/init.js.map +1 -1
  13. package/dist/scripts/lib/consolidation.js +10 -10
  14. package/dist/scripts/lib/constants.d.ts +2 -0
  15. package/dist/scripts/lib/constants.d.ts.map +1 -1
  16. package/dist/scripts/lib/constants.js +10 -1
  17. package/dist/scripts/lib/constants.js.map +1 -1
  18. package/dist/scripts/lib/decay.js +9 -9
  19. package/dist/scripts/lib/episodes.js +23 -23
  20. package/dist/scripts/lib/migrations-v061.js +21 -21
  21. package/dist/scripts/lib/migrations-v062.js +11 -11
  22. package/dist/scripts/lib/migrations-v063.js +14 -14
  23. package/dist/scripts/lib/migrations-v067.js +11 -11
  24. package/dist/scripts/lib/schema-version.js +6 -6
  25. package/dist/scripts/lib/search-cache.js +11 -11
  26. package/dist/scripts/lib/secure-io.d.ts +11 -0
  27. package/dist/scripts/lib/secure-io.d.ts.map +1 -0
  28. package/dist/scripts/lib/secure-io.js +26 -0
  29. package/dist/scripts/lib/secure-io.js.map +1 -0
  30. package/dist/scripts/lib/session-payload.js +7 -7
  31. package/dist/scripts/lib/triage.js +3 -3
  32. package/dist/scripts/lib/validate-manifest.d.ts +8 -0
  33. package/dist/scripts/lib/validate-manifest.d.ts.map +1 -0
  34. package/dist/scripts/lib/validate-manifest.js +80 -0
  35. package/dist/scripts/lib/validate-manifest.js.map +1 -0
  36. package/dist/scripts/mindlore-backup.js +9 -9
  37. package/dist/scripts/mindlore-fts5-index.js +10 -10
  38. package/dist/scripts/validate-manifest-cli.d.ts +2 -0
  39. package/dist/scripts/validate-manifest-cli.d.ts.map +1 -0
  40. package/dist/scripts/validate-manifest-cli.js +38 -0
  41. package/dist/scripts/validate-manifest-cli.js.map +1 -0
  42. package/dist/tests/cc-memory-sync.test.js +3 -3
  43. package/dist/tests/chunks-migration.test.js +1 -1
  44. package/dist/tests/compaction-snapshot.test.js +2 -2
  45. package/dist/tests/consolidation.test.js +3 -3
  46. package/dist/tests/decay.test.js +9 -9
  47. package/dist/tests/diary.test.js +4 -4
  48. package/dist/tests/episode-file.test.js +2 -1
  49. package/dist/tests/episode-file.test.js.map +1 -1
  50. package/dist/tests/episodes-inject.test.js +9 -9
  51. package/dist/tests/fetch-raw.test.js +1 -2
  52. package/dist/tests/fetch-raw.test.js.map +1 -1
  53. package/dist/tests/fts5.test.js +75 -75
  54. package/dist/tests/fuzzy.test.js +1 -1
  55. package/dist/tests/git-snapshot.test.js +3 -5
  56. package/dist/tests/git-snapshot.test.js.map +1 -1
  57. package/dist/tests/helpers/db.d.ts +1 -2
  58. package/dist/tests/helpers/db.d.ts.map +1 -1
  59. package/dist/tests/helpers/db.js +18 -26
  60. package/dist/tests/helpers/db.js.map +1 -1
  61. package/dist/tests/manifest-v2.test.d.ts +2 -0
  62. package/dist/tests/manifest-v2.test.d.ts.map +1 -0
  63. package/dist/tests/manifest-v2.test.js +74 -0
  64. package/dist/tests/manifest-v2.test.js.map +1 -0
  65. package/dist/tests/migrations-v053.test.js +16 -16
  66. package/dist/tests/migrations-v061.test.js +10 -10
  67. package/dist/tests/migrations-v063.test.js +4 -4
  68. package/dist/tests/migrations-v068.test.js +6 -3
  69. package/dist/tests/migrations-v068.test.js.map +1 -1
  70. package/dist/tests/nomination-counts.test.js +6 -6
  71. package/dist/tests/nomination-counts.test.js.map +1 -1
  72. package/dist/tests/pre-compact.test.js +2 -1
  73. package/dist/tests/pre-compact.test.js.map +1 -1
  74. package/dist/tests/recall-telemetry.test.js +12 -11
  75. package/dist/tests/recall-telemetry.test.js.map +1 -1
  76. package/dist/tests/rrf.test.js +3 -3
  77. package/dist/tests/search-engine.test.js +1 -1
  78. package/dist/tests/search-hook.test.js +2 -2
  79. package/dist/tests/search-offload.test.js +1 -1
  80. package/dist/tests/search-offload.test.js.map +1 -1
  81. package/dist/tests/secure-io.test.d.ts +2 -0
  82. package/dist/tests/secure-io.test.d.ts.map +1 -0
  83. package/dist/tests/secure-io.test.js +65 -0
  84. package/dist/tests/secure-io.test.js.map +1 -0
  85. package/dist/tests/session-payload.test.js +1 -1
  86. package/dist/tests/session-summary.test.js +1 -1
  87. package/dist/tests/similarity.test.js +1 -1
  88. package/dist/tests/skill-path-resolution.test.js +22 -3
  89. package/dist/tests/skill-path-resolution.test.js.map +1 -1
  90. package/dist/tests/triage.test.js +1 -1
  91. package/hooks/lib/constants.cjs +15 -15
  92. package/hooks/lib/mindlore-common.cjs +975 -974
  93. package/hooks/mindlore-cwd-changed.cjs +57 -57
  94. package/hooks/mindlore-decision-detector.cjs +54 -54
  95. package/hooks/mindlore-dont-repeat.cjs +222 -222
  96. package/hooks/mindlore-fts5-sync.cjs +98 -97
  97. package/hooks/mindlore-index.cjs +230 -229
  98. package/hooks/mindlore-model-router.cjs +54 -54
  99. package/hooks/mindlore-post-compact.cjs +69 -69
  100. package/hooks/mindlore-post-read.cjs +106 -106
  101. package/hooks/mindlore-pre-compact.cjs +154 -154
  102. package/hooks/mindlore-read-guard.cjs +105 -105
  103. package/hooks/mindlore-research-guard.cjs +176 -176
  104. package/hooks/mindlore-search.cjs +200 -200
  105. package/hooks/mindlore-session-end.cjs +510 -509
  106. package/hooks/mindlore-session-focus.cjs +256 -256
  107. package/package.json +76 -75
  108. package/plugin.json +2 -1
  109. package/skills/mindlore-diary/SKILL.md +85 -85
  110. package/skills/mindlore-evolve/SKILL.md +126 -126
  111. package/skills/mindlore-explore/SKILL.md +109 -109
  112. package/skills/mindlore-ingest/SKILL.md +195 -195
  113. package/skills/mindlore-maintain/SKILL.md +125 -125
  114. package/skills/mindlore-query/SKILL.md +151 -151
  115. package/skills/mindlore-reflect/SKILL.md +141 -141
  116. package/skills/mindlore-stats/SKILL.md +106 -106
  117. package/templates/INDEX.md +14 -14
  118. package/templates/SCHEMA.md +292 -292
  119. package/templates/config.json +1 -1
  120. package/templates/extraction/article.md +15 -15
  121. package/templates/extraction/changelog.md +15 -15
  122. package/templates/extraction/default.md +15 -15
  123. package/templates/extraction/docs.md +15 -15
  124. package/templates/extraction/github-repo.md +17 -17
@@ -1,256 +1,256 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * mindlore-session-focus — SessionStart hook
6
- *
7
- * Injects last delta file content + INDEX.md into session context.
8
- * Fires once at session start via stdout additionalContext.
9
- */
10
-
11
- const fs = require('fs');
12
- const path = require('path');
13
- const { findMindloreDir, readConfig, openDatabase, hasEpisodesTable, querySupersededChains, formatSupersededChains, hookLog, getProjectName, parseFrontmatter, withTelemetry, withTimeoutDb, listSnapshots, isCorruptionError, recoverCorruptDb, getNominationCounts } = require('./lib/mindlore-common.cjs');
14
-
15
- function truncateSection(content, sectionRegex, keepCount, label) {
16
- const match = content.match(sectionRegex);
17
- if (!match) return content;
18
- const lines = match[2].trim().split('\n');
19
- if (lines.length <= keepCount) return content;
20
- const kept = lines.slice(0, keepCount).join('\n');
21
- return content.replace(match[2].trim(), kept + `\n- ...ve ${lines.length - keepCount} ${label} daha`);
22
- }
23
-
24
- function truncateCommits(content) {
25
- return truncateSection(content, /(## Commits\n)((?:- [^\n]+\n?)+)/, 5, 'commit');
26
- }
27
-
28
- function truncateChangedFiles(content) {
29
- return truncateSection(content, /(## Changed Files\n)((?:- [^\n]+\n?)+)/, 10, 'dosya');
30
- }
31
-
32
- function tryOpenDb(dbPath) {
33
- return openDatabase(dbPath, { readonly: true });
34
- }
35
-
36
- function getEpisodeStats(db, config, project) {
37
- const chains = querySupersededChains(db, { project, days: 7, limit: 5 });
38
- let consolidationMsg = null;
39
- try {
40
- const rawCount = withTimeoutDb(db,
41
- "SELECT COUNT(*) as cnt FROM episodes WHERE consolidation_status = 'raw' OR consolidation_status IS NULL",
42
- [], { mode: 'get' });
43
- const cnt = rawCount?.cnt ?? 0;
44
- const consolThreshold = config?.consolidation?.threshold ?? 50;
45
- if (cnt >= consolThreshold) {
46
- consolidationMsg = `[Mindlore] ${cnt} raw episode birikti — \`/mindlore-maintain consolidate\` ile birleştirmeyi düşün.`;
47
- }
48
- } catch (_err) { /* consolidation_status column may not exist yet */ }
49
- return { chains, consolidationMsg };
50
- }
51
-
52
- function checkStaleContent(db) {
53
- try {
54
- const thirtyDaysAgo = new Date(Date.now() - (30 * 24 * 60 * 60 * 1000)).toISOString();
55
- const row = withTimeoutDb(db, 'SELECT COUNT(*) as cnt FROM file_hashes WHERE last_indexed < ?', [thirtyDaysAgo], { mode: 'get' });
56
- const staleCount = row?.cnt ?? 0;
57
- if (staleCount > 3) {
58
- return `[Mindlore: ${staleCount} dosya 30+ gundur guncellenmemis — \`/mindlore-evolve\` dusun]`;
59
- }
60
- } catch (_staleErr) { /* file_hashes may not exist */ }
61
- return null;
62
- }
63
-
64
- function loadDbContent({ db, baseDir, config, output, timings, latestDeltaContent, sessionId }) {
65
- const project = path.basename(process.cwd());
66
- // Session payload: Session summary, Decisions, Friction, Learnings
67
- const tPayload = Date.now();
68
- try {
69
- const { buildSessionPayload } = require('../dist/scripts/lib/session-payload.js');
70
- const payloadBudget = config?.tokenBudget?.sessionInject ?? 2000;
71
- const payload = buildSessionPayload({ db, baseDir, project, tokenBudget: payloadBudget, latestDeltaContent, sessionId });
72
- for (const section of payload.sections) {
73
- output.push(`[Mindlore ${section.label}]\n${section.content}`);
74
- }
75
- } catch (_payloadErr) {
76
- // Session payload is optional — don't break session start
77
- }
78
- timings.db_payload = Date.now() - tPayload;
79
-
80
- // Supersedes chain display + episode consolidation reminder
81
- const tSuperseded = Date.now();
82
- if (hasEpisodesTable(db)) {
83
- const { chains, consolidationMsg } = getEpisodeStats(db, config, project);
84
- if (chains.length > 0) {
85
- output.push(`[Mindlore Supersedes]\n${formatSupersededChains(chains)}`);
86
- }
87
- if (consolidationMsg) {
88
- output.push(consolidationMsg);
89
- }
90
- }
91
- timings.db_episodes = Date.now() - tSuperseded;
92
-
93
- // Stale content check
94
- const tStale = Date.now();
95
- const staleMsg = checkStaleContent(db);
96
- if (staleMsg) {
97
- output.push(staleMsg);
98
- }
99
- timings.db_stale = Date.now() - tStale;
100
-
101
- // Auto reflect trigger (Q1) + Graduated lesson count (Q3)
102
- try {
103
- const counts = getNominationCounts(db, project);
104
- if (counts.staged >= (config?.graduation?.reflectThreshold ?? 5)) {
105
- output.push(`[Mindlore] ${counts.staged} bekleyen nomination var — \`/mindlore-reflect\` çalıştır`);
106
- }
107
- if (counts.graduated > 0) {
108
- output.push(`[Mindlore Graduation] ${counts.graduated} lesson mezun oldu`);
109
- }
110
- } catch (_reflectErr) { /* graduation not available */ }
111
- }
112
-
113
- function main() {
114
- const t0 = Date.now();
115
- const baseDir = findMindloreDir();
116
- if (!baseDir) return; // No .mindlore/ found, silently skip
117
-
118
- // Read session_id from stdin (Claude Code passes { session_id } to SessionStart hooks)
119
- let sessionId;
120
- try {
121
- const stdinData = JSON.parse(fs.readFileSync(0, 'utf8') || '{}');
122
- sessionId = stdinData.session_id || undefined;
123
- } catch { sessionId = undefined; }
124
-
125
- const output = [];
126
- const config = readConfig(baseDir);
127
- const timings = {};
128
- let sourceChars = 0;
129
-
130
- // Inject INDEX.md
131
- const tIndex = Date.now();
132
- const indexPath = path.join(baseDir, 'INDEX.md');
133
- if (fs.existsSync(indexPath)) {
134
- const content = fs.readFileSync(indexPath, 'utf8').trim();
135
- sourceChars += content.length;
136
- output.push(`[Mindlore INDEX]\n${content}`);
137
- }
138
- timings.index_read = Date.now() - tIndex;
139
-
140
- // Inject latest delta + reflect trigger (single readdirSync)
141
- const tDiary = Date.now();
142
- const diaryDir = path.join(baseDir, 'diary');
143
- let latestDeltaContent = undefined;
144
- if (fs.existsSync(diaryDir)) {
145
- try {
146
- const diaryFiles = listSnapshots(diaryDir).filter(f => f.startsWith('delta-'));
147
-
148
- if (diaryFiles.length > 0) {
149
- const latestName = diaryFiles[diaryFiles.length - 1];
150
- const latestPath = path.join(diaryDir, latestName);
151
- const deltaContent = fs.readFileSync(latestPath, 'utf8').trim();
152
- sourceChars += deltaContent.length;
153
- latestDeltaContent = deltaContent;
154
- const { meta } = parseFrontmatter(deltaContent);
155
- const deltaProject = meta.project || null;
156
- const currentProject = getProjectName();
157
- if (!deltaProject || deltaProject.toLowerCase() === currentProject.toLowerCase()) {
158
- output.push(`[Mindlore Delta: ${latestName}]\n${truncateChangedFiles(truncateCommits(deltaContent))}`);
159
- }
160
- }
161
-
162
- // Reflect trigger
163
- const threshold = config?.reflect?.threshold ?? 5;
164
- if (diaryFiles.length >= threshold) {
165
- output.push(`[Mindlore] ${diaryFiles.length} diary entry birikti — \`/mindlore-log reflect\` calistirmayi dusun.`);
166
- }
167
- } catch (_err) { /* skip */ }
168
- }
169
- timings.diary_walk = Date.now() - tDiary;
170
-
171
- // Version check: compare .version (installed) vs .pkg-version (package)
172
- const tVersion = Date.now();
173
- // Both are flat strings written by init — no JSON parse needed on session start
174
- const versionPath = path.join(baseDir, '.version');
175
- const pkgVersionPath = path.join(baseDir, '.pkg-version');
176
- try {
177
- if (fs.existsSync(versionPath) && fs.existsSync(pkgVersionPath)) {
178
- const installed = fs.readFileSync(versionPath, 'utf8').trim();
179
- const pkgVersion = fs.readFileSync(pkgVersionPath, 'utf8').trim();
180
- if (pkgVersion && pkgVersion !== installed) {
181
- output.push(`[Mindlore: Guncelleme mevcut (${installed} → ${pkgVersion}). \`npx mindlore init\` calistirin.]`);
182
- }
183
- }
184
- } catch (_err) { /* skip */ }
185
- timings.version_check = Date.now() - tVersion;
186
-
187
- // v0.5.4: Consolidated session payload (replaces scattered episodes/activity/alerts injection)
188
- const tDb = Date.now();
189
- const outputLenBeforeDb = output.reduce((s, o) => s + o.length, 0);
190
- try {
191
- const dbPath = path.join(baseDir, 'mindlore.db');
192
- const tDbOpen = Date.now();
193
- const db = tryOpenDb(dbPath);
194
- timings.db_open = Date.now() - tDbOpen;
195
- timings.db_integrity = 0;
196
-
197
- if (db) {
198
- try {
199
- // Schema version check: warn if DB is behind expected version
200
- const tSchema = Date.now();
201
- try {
202
- const { EXPECTED_SCHEMA_VERSION } = require('../dist/scripts/lib/all-migrations.js');
203
- const row = db.prepare('SELECT MAX(version) as v FROM schema_versions').get();
204
- const current = row?.v ?? 0;
205
- if (current < EXPECTED_SCHEMA_VERSION) {
206
- output.push(`[Mindlore: schema güncel değil (v${current} → v${EXPECTED_SCHEMA_VERSION}). \`npx mindlore upgrade\` çalıştır.]`);
207
- }
208
- } catch (_schemaErr) { /* schema_versions may not exist yet */ }
209
- timings.schema_check = Date.now() - tSchema;
210
-
211
- loadDbContent({ db, baseDir, config, output, timings, latestDeltaContent, sessionId });
212
- } catch (err) {
213
- if (isCorruptionError(err)) {
214
- recoverCorruptDb(db, dbPath, 'session-focus');
215
- }
216
- } finally {
217
- try { db.close(); } catch { /* already closed by recovery */ }
218
- }
219
- }
220
- } catch (_err) { /* graceful skip */ }
221
- const outputLenAfterDb = output.reduce((s, o) => s + o.length, 0);
222
- sourceChars += (outputLenAfterDb - outputLenBeforeDb);
223
- timings.db_total = Date.now() - tDb;
224
-
225
- timings.total = Date.now() - t0;
226
- hookLog('session-focus', 'info', `timings: ${JSON.stringify(timings)}`);
227
-
228
- // Token budget for session inject
229
- // Defaults match DEFAULT_TOKEN_BUDGET in scripts/lib/constants.ts
230
- const budgetConfig = config?.tokenBudget ?? {};
231
- const maxInjectChars = (budgetConfig.sessionInject || 2000) * 4;
232
-
233
- let joined = output.join('\n\n');
234
- if (joined.length > maxInjectChars) {
235
- joined = joined.slice(0, maxInjectChars) + '\n[...truncated by token budget]';
236
- }
237
-
238
- // v0.6.1: Daemon auto-start removed (daemon deprecated — MCP Server in v0.7)
239
-
240
- if (joined.length > 0) {
241
- process.stdout.write(joined + '\n');
242
- }
243
-
244
- const inject_tokens = Math.ceil(joined.length / 4);
245
- const source_tokens = Math.ceil(sourceChars / 4);
246
- return { inject_tokens, source_tokens };
247
- }
248
-
249
- withTelemetry('mindlore-session-focus', main).catch(err => {
250
- hookLog('mindlore-session-focus', 'error', err?.message ?? String(err));
251
- process.exit(0);
252
- });
253
-
254
- if (typeof module !== 'undefined') {
255
- module.exports = { truncateCommits, truncateChangedFiles, getEpisodeStats, checkStaleContent };
256
- }
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * mindlore-session-focus — SessionStart hook
6
+ *
7
+ * Injects last delta file content + INDEX.md into session context.
8
+ * Fires once at session start via stdout additionalContext.
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const { findMindloreDir, readConfig, openDatabase, hasEpisodesTable, querySupersededChains, formatSupersededChains, hookLog, getProjectName, parseFrontmatter, withTelemetry, withTimeoutDb, listSnapshots, isCorruptionError, recoverCorruptDb, getNominationCounts } = require('./lib/mindlore-common.cjs');
14
+
15
+ function truncateSection(content, sectionRegex, keepCount, label) {
16
+ const match = content.match(sectionRegex);
17
+ if (!match) return content;
18
+ const lines = match[2].trim().split('\n');
19
+ if (lines.length <= keepCount) return content;
20
+ const kept = lines.slice(0, keepCount).join('\n');
21
+ return content.replace(match[2].trim(), kept + `\n- ...ve ${lines.length - keepCount} ${label} daha`);
22
+ }
23
+
24
+ function truncateCommits(content) {
25
+ return truncateSection(content, /(## Commits\n)((?:- [^\n]+\n?)+)/, 5, 'commit');
26
+ }
27
+
28
+ function truncateChangedFiles(content) {
29
+ return truncateSection(content, /(## Changed Files\n)((?:- [^\n]+\n?)+)/, 10, 'dosya');
30
+ }
31
+
32
+ function tryOpenDb(dbPath) {
33
+ return openDatabase(dbPath, { readonly: true });
34
+ }
35
+
36
+ function getEpisodeStats(db, config, project) {
37
+ const chains = querySupersededChains(db, { project, days: 7, limit: 5 });
38
+ let consolidationMsg = null;
39
+ try {
40
+ const rawCount = withTimeoutDb(db,
41
+ "SELECT COUNT(*) as cnt FROM episodes WHERE consolidation_status = 'raw' OR consolidation_status IS NULL",
42
+ [], { mode: 'get' });
43
+ const cnt = rawCount?.cnt ?? 0;
44
+ const consolThreshold = config?.consolidation?.threshold ?? 50;
45
+ if (cnt >= consolThreshold) {
46
+ consolidationMsg = `[Mindlore] ${cnt} raw episode birikti — \`/mindlore-maintain consolidate\` ile birleştirmeyi düşün.`;
47
+ }
48
+ } catch (_err) { /* consolidation_status column may not exist yet */ }
49
+ return { chains, consolidationMsg };
50
+ }
51
+
52
+ function checkStaleContent(db) {
53
+ try {
54
+ const thirtyDaysAgo = new Date(Date.now() - (30 * 24 * 60 * 60 * 1000)).toISOString();
55
+ const row = withTimeoutDb(db, 'SELECT COUNT(*) as cnt FROM file_hashes WHERE last_indexed < ?', [thirtyDaysAgo], { mode: 'get' });
56
+ const staleCount = row?.cnt ?? 0;
57
+ if (staleCount > 3) {
58
+ return `[Mindlore: ${staleCount} dosya 30+ gundur guncellenmemis — \`/mindlore-evolve\` dusun]`;
59
+ }
60
+ } catch (_staleErr) { /* file_hashes may not exist */ }
61
+ return null;
62
+ }
63
+
64
+ function loadDbContent({ db, baseDir, config, output, timings, latestDeltaContent, sessionId }) {
65
+ const project = path.basename(process.cwd());
66
+ // Session payload: Session summary, Decisions, Friction, Learnings
67
+ const tPayload = Date.now();
68
+ try {
69
+ const { buildSessionPayload } = require('../dist/scripts/lib/session-payload.js');
70
+ const payloadBudget = config?.tokenBudget?.sessionInject ?? 2000;
71
+ const payload = buildSessionPayload({ db, baseDir, project, tokenBudget: payloadBudget, latestDeltaContent, sessionId });
72
+ for (const section of payload.sections) {
73
+ output.push(`[Mindlore ${section.label}]\n${section.content}`);
74
+ }
75
+ } catch (_payloadErr) {
76
+ // Session payload is optional — don't break session start
77
+ }
78
+ timings.db_payload = Date.now() - tPayload;
79
+
80
+ // Supersedes chain display + episode consolidation reminder
81
+ const tSuperseded = Date.now();
82
+ if (hasEpisodesTable(db)) {
83
+ const { chains, consolidationMsg } = getEpisodeStats(db, config, project);
84
+ if (chains.length > 0) {
85
+ output.push(`[Mindlore Supersedes]\n${formatSupersededChains(chains)}`);
86
+ }
87
+ if (consolidationMsg) {
88
+ output.push(consolidationMsg);
89
+ }
90
+ }
91
+ timings.db_episodes = Date.now() - tSuperseded;
92
+
93
+ // Stale content check
94
+ const tStale = Date.now();
95
+ const staleMsg = checkStaleContent(db);
96
+ if (staleMsg) {
97
+ output.push(staleMsg);
98
+ }
99
+ timings.db_stale = Date.now() - tStale;
100
+
101
+ // Auto reflect trigger (Q1) + Graduated lesson count (Q3)
102
+ try {
103
+ const counts = getNominationCounts(db, project);
104
+ if (counts.staged >= (config?.graduation?.reflectThreshold ?? 5)) {
105
+ output.push(`[Mindlore] ${counts.staged} bekleyen nomination var — \`/mindlore-reflect\` çalıştır`);
106
+ }
107
+ if (counts.graduated > 0) {
108
+ output.push(`[Mindlore Graduation] ${counts.graduated} lesson mezun oldu`);
109
+ }
110
+ } catch (_reflectErr) { /* graduation not available */ }
111
+ }
112
+
113
+ function main() {
114
+ const t0 = Date.now();
115
+ const baseDir = findMindloreDir();
116
+ if (!baseDir) return; // No .mindlore/ found, silently skip
117
+
118
+ // Read session_id from stdin (Claude Code passes { session_id } to SessionStart hooks)
119
+ let sessionId;
120
+ try {
121
+ const stdinData = JSON.parse(fs.readFileSync(0, 'utf8') || '{}');
122
+ sessionId = stdinData.session_id || undefined;
123
+ } catch { sessionId = undefined; }
124
+
125
+ const output = [];
126
+ const config = readConfig(baseDir);
127
+ const timings = {};
128
+ let sourceChars = 0;
129
+
130
+ // Inject INDEX.md
131
+ const tIndex = Date.now();
132
+ const indexPath = path.join(baseDir, 'INDEX.md');
133
+ if (fs.existsSync(indexPath)) {
134
+ const content = fs.readFileSync(indexPath, 'utf8').trim();
135
+ sourceChars += content.length;
136
+ output.push(`[Mindlore INDEX]\n${content}`);
137
+ }
138
+ timings.index_read = Date.now() - tIndex;
139
+
140
+ // Inject latest delta + reflect trigger (single readdirSync)
141
+ const tDiary = Date.now();
142
+ const diaryDir = path.join(baseDir, 'diary');
143
+ let latestDeltaContent = undefined;
144
+ if (fs.existsSync(diaryDir)) {
145
+ try {
146
+ const diaryFiles = listSnapshots(diaryDir).filter(f => f.startsWith('delta-'));
147
+
148
+ if (diaryFiles.length > 0) {
149
+ const latestName = diaryFiles[diaryFiles.length - 1];
150
+ const latestPath = path.join(diaryDir, latestName);
151
+ const deltaContent = fs.readFileSync(latestPath, 'utf8').trim();
152
+ sourceChars += deltaContent.length;
153
+ latestDeltaContent = deltaContent;
154
+ const { meta } = parseFrontmatter(deltaContent);
155
+ const deltaProject = meta.project || null;
156
+ const currentProject = getProjectName();
157
+ if (!deltaProject || deltaProject.toLowerCase() === currentProject.toLowerCase()) {
158
+ output.push(`[Mindlore Delta: ${latestName}]\n${truncateChangedFiles(truncateCommits(deltaContent))}`);
159
+ }
160
+ }
161
+
162
+ // Reflect trigger
163
+ const threshold = config?.reflect?.threshold ?? 5;
164
+ if (diaryFiles.length >= threshold) {
165
+ output.push(`[Mindlore] ${diaryFiles.length} diary entry birikti — \`/mindlore-log reflect\` calistirmayi dusun.`);
166
+ }
167
+ } catch (_err) { /* skip */ }
168
+ }
169
+ timings.diary_walk = Date.now() - tDiary;
170
+
171
+ // Version check: compare .version (installed) vs .pkg-version (package)
172
+ const tVersion = Date.now();
173
+ // Both are flat strings written by init — no JSON parse needed on session start
174
+ const versionPath = path.join(baseDir, '.version');
175
+ const pkgVersionPath = path.join(baseDir, '.pkg-version');
176
+ try {
177
+ if (fs.existsSync(versionPath) && fs.existsSync(pkgVersionPath)) {
178
+ const installed = fs.readFileSync(versionPath, 'utf8').trim();
179
+ const pkgVersion = fs.readFileSync(pkgVersionPath, 'utf8').trim();
180
+ if (pkgVersion && pkgVersion !== installed) {
181
+ output.push(`[Mindlore: Guncelleme mevcut (${installed} → ${pkgVersion}). \`npx mindlore init\` calistirin.]`);
182
+ }
183
+ }
184
+ } catch (_err) { /* skip */ }
185
+ timings.version_check = Date.now() - tVersion;
186
+
187
+ // v0.5.4: Consolidated session payload (replaces scattered episodes/activity/alerts injection)
188
+ const tDb = Date.now();
189
+ const outputLenBeforeDb = output.reduce((s, o) => s + o.length, 0);
190
+ try {
191
+ const dbPath = path.join(baseDir, 'mindlore.db');
192
+ const tDbOpen = Date.now();
193
+ const db = tryOpenDb(dbPath);
194
+ timings.db_open = Date.now() - tDbOpen;
195
+ timings.db_integrity = 0;
196
+
197
+ if (db) {
198
+ try {
199
+ // Schema version check: warn if DB is behind expected version
200
+ const tSchema = Date.now();
201
+ try {
202
+ const { EXPECTED_SCHEMA_VERSION } = require('../dist/scripts/lib/all-migrations.js');
203
+ const row = db.prepare('SELECT MAX(version) as v FROM schema_versions').get();
204
+ const current = row?.v ?? 0;
205
+ if (current < EXPECTED_SCHEMA_VERSION) {
206
+ output.push(`[Mindlore: schema güncel değil (v${current} → v${EXPECTED_SCHEMA_VERSION}). \`npx mindlore upgrade\` çalıştır.]`);
207
+ }
208
+ } catch (_schemaErr) { /* schema_versions may not exist yet */ }
209
+ timings.schema_check = Date.now() - tSchema;
210
+
211
+ loadDbContent({ db, baseDir, config, output, timings, latestDeltaContent, sessionId });
212
+ } catch (err) {
213
+ if (isCorruptionError(err)) {
214
+ recoverCorruptDb(db, dbPath, 'session-focus');
215
+ }
216
+ } finally {
217
+ try { db.close(); } catch { /* already closed by recovery */ }
218
+ }
219
+ }
220
+ } catch (_err) { /* graceful skip */ }
221
+ const outputLenAfterDb = output.reduce((s, o) => s + o.length, 0);
222
+ sourceChars += (outputLenAfterDb - outputLenBeforeDb);
223
+ timings.db_total = Date.now() - tDb;
224
+
225
+ timings.total = Date.now() - t0;
226
+ hookLog('session-focus', 'info', `timings: ${JSON.stringify(timings)}`);
227
+
228
+ // Token budget for session inject
229
+ // Defaults match DEFAULT_TOKEN_BUDGET in scripts/lib/constants.ts
230
+ const budgetConfig = config?.tokenBudget ?? {};
231
+ const maxInjectChars = (budgetConfig.sessionInject || 2000) * 4;
232
+
233
+ let joined = output.join('\n\n');
234
+ if (joined.length > maxInjectChars) {
235
+ joined = joined.slice(0, maxInjectChars) + '\n[...truncated by token budget]';
236
+ }
237
+
238
+ // v0.6.1: Daemon auto-start removed (daemon deprecated — MCP Server in v0.7)
239
+
240
+ if (joined.length > 0) {
241
+ process.stdout.write(joined + '\n');
242
+ }
243
+
244
+ const inject_tokens = Math.ceil(joined.length / 4);
245
+ const source_tokens = Math.ceil(sourceChars / 4);
246
+ return { inject_tokens, source_tokens };
247
+ }
248
+
249
+ withTelemetry('mindlore-session-focus', main).catch(err => {
250
+ hookLog('mindlore-session-focus', 'error', err?.message ?? String(err));
251
+ process.exit(0);
252
+ });
253
+
254
+ if (typeof module !== 'undefined') {
255
+ module.exports = { truncateCommits, truncateChangedFiles, getEpisodeStats, checkStaleContent };
256
+ }