mindlore 0.6.7 → 0.6.8

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 (169) 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 +47 -42
  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 +58 -48
  8. package/dist/scripts/cc-session-sync.js.map +1 -1
  9. package/dist/scripts/init.js +8 -8
  10. package/dist/scripts/init.js.map +1 -1
  11. package/dist/scripts/lib/all-migrations.d.ts.map +1 -1
  12. package/dist/scripts/lib/all-migrations.js +4 -1
  13. package/dist/scripts/lib/all-migrations.js.map +1 -1
  14. package/dist/scripts/lib/consolidation.d.ts +4 -3
  15. package/dist/scripts/lib/consolidation.d.ts.map +1 -1
  16. package/dist/scripts/lib/consolidation.js +10 -10
  17. package/dist/scripts/lib/consolidation.js.map +1 -1
  18. package/dist/scripts/lib/constants.d.ts +1 -7
  19. package/dist/scripts/lib/constants.d.ts.map +1 -1
  20. package/dist/scripts/lib/constants.js +2 -9
  21. package/dist/scripts/lib/constants.js.map +1 -1
  22. package/dist/scripts/lib/db-helpers.d.ts +0 -15
  23. package/dist/scripts/lib/db-helpers.d.ts.map +1 -1
  24. package/dist/scripts/lib/db-helpers.js +1 -51
  25. package/dist/scripts/lib/db-helpers.js.map +1 -1
  26. package/dist/scripts/lib/decay.d.ts.map +1 -1
  27. package/dist/scripts/lib/decay.js +9 -9
  28. package/dist/scripts/lib/decay.js.map +1 -1
  29. package/dist/scripts/lib/episodes.js +23 -23
  30. package/dist/scripts/lib/migrations-v061.js +21 -21
  31. package/dist/scripts/lib/migrations-v062.js +11 -11
  32. package/dist/scripts/lib/migrations-v063.js +14 -14
  33. package/dist/scripts/lib/migrations-v067.js +11 -11
  34. package/dist/scripts/lib/migrations-v068.d.ts +3 -0
  35. package/dist/scripts/lib/migrations-v068.d.ts.map +1 -0
  36. package/dist/scripts/lib/migrations-v068.js +37 -0
  37. package/dist/scripts/lib/migrations-v068.js.map +1 -0
  38. package/dist/scripts/lib/migrations.d.ts.map +1 -1
  39. package/dist/scripts/lib/migrations.js +0 -15
  40. package/dist/scripts/lib/migrations.js.map +1 -1
  41. package/dist/scripts/lib/schema-version.js +6 -6
  42. package/dist/scripts/lib/search-cache.js +11 -11
  43. package/dist/scripts/lib/session-payload.d.ts.map +1 -1
  44. package/dist/scripts/lib/session-payload.js +7 -7
  45. package/dist/scripts/lib/session-payload.js.map +1 -1
  46. package/dist/scripts/lib/triage.js +3 -3
  47. package/dist/scripts/mindlore-backup.js +9 -9
  48. package/dist/scripts/mindlore-fts5-index.d.ts +1 -2
  49. package/dist/scripts/mindlore-fts5-index.d.ts.map +1 -1
  50. package/dist/scripts/mindlore-fts5-index.js +12 -64
  51. package/dist/scripts/mindlore-fts5-index.js.map +1 -1
  52. package/dist/scripts/mindlore-health-check.d.ts.map +1 -1
  53. package/dist/scripts/mindlore-health-check.js +0 -11
  54. package/dist/scripts/mindlore-health-check.js.map +1 -1
  55. package/dist/tests/cc-memory-bulk-sync.test.js +23 -0
  56. package/dist/tests/cc-memory-bulk-sync.test.js.map +1 -1
  57. package/dist/tests/cc-session-sync.test.js +25 -0
  58. package/dist/tests/cc-session-sync.test.js.map +1 -1
  59. package/dist/tests/compaction-snapshot.test.js +2 -2
  60. package/dist/tests/consolidation.test.js +5 -5
  61. package/dist/tests/consolidation.test.js.map +1 -1
  62. package/dist/tests/decay.test.js +9 -9
  63. package/dist/tests/diary.test.js +4 -4
  64. package/dist/tests/episode-kind-constant.test.d.ts +2 -0
  65. package/dist/tests/episode-kind-constant.test.d.ts.map +1 -0
  66. package/dist/tests/episode-kind-constant.test.js +28 -0
  67. package/dist/tests/episode-kind-constant.test.js.map +1 -0
  68. package/dist/tests/episodes-inject.test.js +9 -9
  69. package/dist/tests/fts5.test.js +66 -125
  70. package/dist/tests/fts5.test.js.map +1 -1
  71. package/dist/tests/globalSetup.d.ts +2 -0
  72. package/dist/tests/globalSetup.d.ts.map +1 -0
  73. package/dist/tests/globalSetup.js +36 -0
  74. package/dist/tests/globalSetup.js.map +1 -0
  75. package/dist/tests/helpers/db.d.ts +13 -5
  76. package/dist/tests/helpers/db.d.ts.map +1 -1
  77. package/dist/tests/helpers/db.js +60 -33
  78. package/dist/tests/helpers/db.js.map +1 -1
  79. package/dist/tests/lesson-graduation.test.js +11 -11
  80. package/dist/tests/lesson-graduation.test.js.map +1 -1
  81. package/dist/tests/migrations-v053.test.js +16 -16
  82. package/dist/tests/migrations-v061.test.js +10 -10
  83. package/dist/tests/migrations-v063.test.js +2 -2
  84. package/dist/tests/migrations-v068.test.d.ts +2 -0
  85. package/dist/tests/migrations-v068.test.d.ts.map +1 -0
  86. package/dist/tests/migrations-v068.test.js +53 -0
  87. package/dist/tests/migrations-v068.test.js.map +1 -0
  88. package/dist/tests/nomination-counts.test.d.ts +2 -0
  89. package/dist/tests/nomination-counts.test.d.ts.map +1 -0
  90. package/dist/tests/nomination-counts.test.js +51 -0
  91. package/dist/tests/nomination-counts.test.js.map +1 -0
  92. package/dist/tests/recall-telemetry.test.js +8 -8
  93. package/dist/tests/schema-version.test.js +3 -7
  94. package/dist/tests/schema-version.test.js.map +1 -1
  95. package/dist/tests/search-hook.test.js +2 -2
  96. package/dist/tests/sec-regression.test.js +0 -50
  97. package/dist/tests/sec-regression.test.js.map +1 -1
  98. package/dist/tests/session-end-cleanup.test.js +3 -20
  99. package/dist/tests/session-end-cleanup.test.js.map +1 -1
  100. package/dist/tests/session-focus.test.js +7 -30
  101. package/dist/tests/session-focus.test.js.map +1 -1
  102. package/dist/tests/session-payload.test.js +1 -1
  103. package/dist/tests/session-summary.test.js +1 -1
  104. package/hooks/lib/constants.cjs +15 -0
  105. package/hooks/lib/mindlore-common.cjs +974 -1042
  106. package/hooks/mindlore-cwd-changed.cjs +57 -57
  107. package/hooks/mindlore-decision-detector.cjs +54 -54
  108. package/hooks/mindlore-dont-repeat.cjs +222 -222
  109. package/hooks/mindlore-fts5-sync.cjs +97 -88
  110. package/hooks/mindlore-index.cjs +229 -229
  111. package/hooks/mindlore-model-router.cjs +54 -54
  112. package/hooks/mindlore-post-compact.cjs +69 -69
  113. package/hooks/mindlore-post-read.cjs +106 -106
  114. package/hooks/mindlore-pre-compact.cjs +154 -154
  115. package/hooks/mindlore-read-guard.cjs +105 -105
  116. package/hooks/mindlore-research-guard.cjs +176 -176
  117. package/hooks/mindlore-search.cjs +200 -200
  118. package/hooks/mindlore-session-end.cjs +509 -526
  119. package/hooks/mindlore-session-focus.cjs +256 -259
  120. package/package.json +75 -78
  121. package/plugin.json +1 -1
  122. package/skills/mindlore-diary/SKILL.md +85 -85
  123. package/skills/mindlore-evolve/SKILL.md +126 -126
  124. package/skills/mindlore-explore/SKILL.md +109 -109
  125. package/skills/mindlore-ingest/SKILL.md +195 -195
  126. package/skills/mindlore-maintain/SKILL.md +125 -125
  127. package/skills/mindlore-query/SKILL.md +151 -151
  128. package/skills/mindlore-reflect/SKILL.md +141 -141
  129. package/skills/mindlore-stats/SKILL.md +106 -106
  130. package/templates/INDEX.md +14 -14
  131. package/templates/SCHEMA.md +292 -292
  132. package/templates/config.json +1 -1
  133. package/templates/extraction/article.md +15 -15
  134. package/templates/extraction/changelog.md +15 -15
  135. package/templates/extraction/default.md +15 -15
  136. package/templates/extraction/docs.md +15 -15
  137. package/templates/extraction/github-repo.md +17 -17
  138. package/dist/scripts/lib/daemon.d.ts +0 -16
  139. package/dist/scripts/lib/daemon.d.ts.map +0 -1
  140. package/dist/scripts/lib/daemon.js +0 -133
  141. package/dist/scripts/lib/daemon.js.map +0 -1
  142. package/dist/scripts/lib/embedding.d.ts +0 -5
  143. package/dist/scripts/lib/embedding.d.ts.map +0 -1
  144. package/dist/scripts/lib/embedding.js +0 -44
  145. package/dist/scripts/lib/embedding.js.map +0 -1
  146. package/dist/scripts/mindlore-daemon.d.ts +0 -2
  147. package/dist/scripts/mindlore-daemon.d.ts.map +0 -1
  148. package/dist/scripts/mindlore-daemon.js +0 -117
  149. package/dist/scripts/mindlore-daemon.js.map +0 -1
  150. package/dist/tests/daemon-integration.test.d.ts +0 -2
  151. package/dist/tests/daemon-integration.test.d.ts.map +0 -1
  152. package/dist/tests/daemon-integration.test.js +0 -37
  153. package/dist/tests/daemon-integration.test.js.map +0 -1
  154. package/dist/tests/daemon.test.d.ts +0 -2
  155. package/dist/tests/daemon.test.d.ts.map +0 -1
  156. package/dist/tests/daemon.test.js +0 -187
  157. package/dist/tests/daemon.test.js.map +0 -1
  158. package/dist/tests/embedding-hf-integration.test.d.ts +0 -2
  159. package/dist/tests/embedding-hf-integration.test.d.ts.map +0 -1
  160. package/dist/tests/embedding-hf-integration.test.js +0 -52
  161. package/dist/tests/embedding-hf-integration.test.js.map +0 -1
  162. package/dist/tests/embedding.test.d.ts +0 -6
  163. package/dist/tests/embedding.test.d.ts.map +0 -1
  164. package/dist/tests/embedding.test.js +0 -71
  165. package/dist/tests/embedding.test.js.map +0 -1
  166. package/dist/tests/sqlite-vec-v12.test.d.ts +0 -2
  167. package/dist/tests/sqlite-vec-v12.test.d.ts.map +0 -1
  168. package/dist/tests/sqlite-vec-v12.test.js +0 -72
  169. package/dist/tests/sqlite-vec-v12.test.js.map +0 -1
@@ -1,154 +1,154 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * mindlore-pre-compact — PreCompact hook
6
- *
7
- * Before context compaction:
8
- * 1. Ensure FTS5 index is up to date
9
- * 2. Write pre-compact episode to episodes/ and append log entry
10
- */
11
-
12
- const fs = require('fs');
13
- const path = require('path');
14
- const { findMindloreDir, openDatabase, hookLog, withTelemetry, listSnapshots } = require('./lib/mindlore-common.cjs');
15
-
16
- function collectRecentEpisodes(baseDir) {
17
- try {
18
- const dbPath = path.join(baseDir, 'mindlore.db');
19
- const db = openDatabase(dbPath, { readonly: true });
20
- if (!db) return [];
21
- try {
22
- const episodes = db.prepare(
23
- "SELECT kind, summary FROM episodes WHERE created_at > datetime('now', '-4 hours') ORDER BY created_at DESC LIMIT 20"
24
- ).all();
25
- if (episodes.length === 0) return [];
26
- const grouped = {};
27
- for (const ep of episodes) {
28
- const kind = ep.kind || 'other';
29
- if (!grouped[kind]) grouped[kind] = [];
30
- grouped[kind].push(ep.summary);
31
- }
32
- const lines = ['## Session Episodes'];
33
- for (const [kind, items] of Object.entries(grouped)) {
34
- lines.push(`### ${kind}`);
35
- for (const item of items) lines.push(`- ${item}`);
36
- }
37
- return lines;
38
- } finally {
39
- db.close();
40
- }
41
- } catch (_err) {
42
- return [];
43
- }
44
- }
45
-
46
- function collectGitDiff() {
47
- try {
48
- const { execFileSync } = require('child_process');
49
- let diffStat = '';
50
- try {
51
- diffStat = execFileSync('git', ['diff', '--stat', 'HEAD'], { encoding: 'utf8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
52
- } catch { diffStat = ''; }
53
- if (diffStat) return ['## Changed Files (uncommitted)', '```', diffStat, '```'];
54
- return [];
55
- } catch (_err) {
56
- return [];
57
- }
58
- }
59
-
60
- function getActivePlan() {
61
- try {
62
- const plansDir = path.join(process.cwd(), '.claude', 'plans');
63
- if (!fs.existsSync(plansDir)) return [];
64
- const plans = fs.readdirSync(plansDir).filter(f => f.endsWith('.md'));
65
- if (plans.length === 0) return [];
66
- const latestPlan = plans.sort().pop();
67
- return [`## Active Plan: ${latestPlan}`];
68
- } catch (_err) {
69
- return [];
70
- }
71
- }
72
-
73
- function main() {
74
- const baseDir = findMindloreDir();
75
- if (!baseDir) return;
76
-
77
- // Trigger FTS5 sync via the index script
78
- const indexScript = path.join(__dirname, '..', 'scripts', 'mindlore-fts5-index.cjs');
79
- if (fs.existsSync(indexScript)) {
80
- try {
81
- const { spawnSync } = require('child_process');
82
- spawnSync('node', [indexScript, baseDir], {
83
- timeout: 10000,
84
- stdio: 'pipe',
85
- windowsHide: true,
86
- });
87
- } catch (_err) {
88
- // Non-fatal — index might fail if better-sqlite3 not available
89
- }
90
- }
91
-
92
- const now = new Date();
93
- const iso = now.toISOString();
94
- const ts = iso.replace(/[:.]/g, '-');
95
-
96
- const episodesDir = path.join(baseDir, 'episodes');
97
- try {
98
- const episodePath = path.join(episodesDir, `pre-compact-${ts}.md`);
99
- const content = [
100
- '---',
101
- 'type: episode',
102
- 'subtype: pre-compact',
103
- `date: ${iso.slice(0, 10)}`,
104
- `project: ${path.basename(process.cwd())}`,
105
- '---',
106
- '',
107
- `Pre-compact snapshot at ${iso}.`,
108
- `Working directory: ${process.cwd()}`,
109
- ].join('\n');
110
- fs.writeFileSync(episodePath, content, 'utf8');
111
- } catch (_err) { /* episodes dir may not exist */ }
112
-
113
- // Append log entry
114
- const logPath = path.join(baseDir, 'log.md');
115
- try {
116
- const entry = `| ${iso.slice(0, 10)} | pre-compact | FTS5 flush before compaction |\n`;
117
- fs.appendFileSync(logPath, entry, 'utf8');
118
- } catch (_err) { /* log file may not exist */ }
119
-
120
- // Build compaction snapshot (#17)
121
- const diaryDir = path.join(baseDir, 'diary');
122
- try {
123
- const sections = [];
124
- sections.push(...collectRecentEpisodes(baseDir));
125
- sections.push(...collectGitDiff());
126
- sections.push(...getActivePlan());
127
-
128
- if (sections.length > 0) {
129
- const snapshotContent = [
130
- '---',
131
- 'type: compaction-snapshot',
132
- `date: ${iso.slice(0, 10)}`,
133
- `project: ${path.basename(process.cwd())}`,
134
- '---',
135
- '',
136
- ...sections,
137
- ].join('\n');
138
- fs.writeFileSync(path.join(diaryDir, `compaction-snapshot-${ts}.md`), snapshotContent);
139
- }
140
-
141
- const snapshots = listSnapshots(diaryDir).filter(f => f.startsWith('compaction-'));
142
- while (snapshots.length > 5) {
143
- const oldest = snapshots.shift();
144
- if (oldest) fs.unlinkSync(path.join(diaryDir, oldest));
145
- }
146
- } catch (_err) { /* snapshot is best-effort */ }
147
-
148
- process.stdout.write('[Mindlore: pre-compact FTS5 flush complete]\n');
149
- }
150
-
151
- withTelemetry('mindlore-pre-compact', main).catch(err => {
152
- hookLog('mindlore-pre-compact', 'error', err?.message ?? String(err));
153
- process.exit(0);
154
- });
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * mindlore-pre-compact — PreCompact hook
6
+ *
7
+ * Before context compaction:
8
+ * 1. Ensure FTS5 index is up to date
9
+ * 2. Write pre-compact episode to episodes/ and append log entry
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const { findMindloreDir, openDatabase, hookLog, withTelemetry, listSnapshots } = require('./lib/mindlore-common.cjs');
15
+
16
+ function collectRecentEpisodes(baseDir) {
17
+ try {
18
+ const dbPath = path.join(baseDir, 'mindlore.db');
19
+ const db = openDatabase(dbPath, { readonly: true });
20
+ if (!db) return [];
21
+ try {
22
+ const episodes = db.prepare(
23
+ "SELECT kind, summary FROM episodes WHERE created_at > datetime('now', '-4 hours') ORDER BY created_at DESC LIMIT 20"
24
+ ).all();
25
+ if (episodes.length === 0) return [];
26
+ const grouped = {};
27
+ for (const ep of episodes) {
28
+ const kind = ep.kind || 'other';
29
+ if (!grouped[kind]) grouped[kind] = [];
30
+ grouped[kind].push(ep.summary);
31
+ }
32
+ const lines = ['## Session Episodes'];
33
+ for (const [kind, items] of Object.entries(grouped)) {
34
+ lines.push(`### ${kind}`);
35
+ for (const item of items) lines.push(`- ${item}`);
36
+ }
37
+ return lines;
38
+ } finally {
39
+ db.close();
40
+ }
41
+ } catch (_err) {
42
+ return [];
43
+ }
44
+ }
45
+
46
+ function collectGitDiff() {
47
+ try {
48
+ const { execFileSync } = require('child_process');
49
+ let diffStat = '';
50
+ try {
51
+ diffStat = execFileSync('git', ['diff', '--stat', 'HEAD'], { encoding: 'utf8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
52
+ } catch { diffStat = ''; }
53
+ if (diffStat) return ['## Changed Files (uncommitted)', '```', diffStat, '```'];
54
+ return [];
55
+ } catch (_err) {
56
+ return [];
57
+ }
58
+ }
59
+
60
+ function getActivePlan() {
61
+ try {
62
+ const plansDir = path.join(process.cwd(), '.claude', 'plans');
63
+ if (!fs.existsSync(plansDir)) return [];
64
+ const plans = fs.readdirSync(plansDir).filter(f => f.endsWith('.md'));
65
+ if (plans.length === 0) return [];
66
+ const latestPlan = plans.sort().pop();
67
+ return [`## Active Plan: ${latestPlan}`];
68
+ } catch (_err) {
69
+ return [];
70
+ }
71
+ }
72
+
73
+ function main() {
74
+ const baseDir = findMindloreDir();
75
+ if (!baseDir) return;
76
+
77
+ // Trigger FTS5 sync via the index script
78
+ const indexScript = path.join(__dirname, '..', 'scripts', 'mindlore-fts5-index.cjs');
79
+ if (fs.existsSync(indexScript)) {
80
+ try {
81
+ const { spawnSync } = require('child_process');
82
+ spawnSync('node', [indexScript, baseDir], {
83
+ timeout: 10000,
84
+ stdio: 'pipe',
85
+ windowsHide: true,
86
+ });
87
+ } catch (_err) {
88
+ // Non-fatal — index might fail if better-sqlite3 not available
89
+ }
90
+ }
91
+
92
+ const now = new Date();
93
+ const iso = now.toISOString();
94
+ const ts = iso.replace(/[:.]/g, '-');
95
+
96
+ const episodesDir = path.join(baseDir, 'episodes');
97
+ try {
98
+ const episodePath = path.join(episodesDir, `pre-compact-${ts}.md`);
99
+ const content = [
100
+ '---',
101
+ 'type: episode',
102
+ 'subtype: pre-compact',
103
+ `date: ${iso.slice(0, 10)}`,
104
+ `project: ${path.basename(process.cwd())}`,
105
+ '---',
106
+ '',
107
+ `Pre-compact snapshot at ${iso}.`,
108
+ `Working directory: ${process.cwd()}`,
109
+ ].join('\n');
110
+ fs.writeFileSync(episodePath, content, 'utf8');
111
+ } catch (_err) { /* episodes dir may not exist */ }
112
+
113
+ // Append log entry
114
+ const logPath = path.join(baseDir, 'log.md');
115
+ try {
116
+ const entry = `| ${iso.slice(0, 10)} | pre-compact | FTS5 flush before compaction |\n`;
117
+ fs.appendFileSync(logPath, entry, 'utf8');
118
+ } catch (_err) { /* log file may not exist */ }
119
+
120
+ // Build compaction snapshot (#17)
121
+ const diaryDir = path.join(baseDir, 'diary');
122
+ try {
123
+ const sections = [];
124
+ sections.push(...collectRecentEpisodes(baseDir));
125
+ sections.push(...collectGitDiff());
126
+ sections.push(...getActivePlan());
127
+
128
+ if (sections.length > 0) {
129
+ const snapshotContent = [
130
+ '---',
131
+ 'type: compaction-snapshot',
132
+ `date: ${iso.slice(0, 10)}`,
133
+ `project: ${path.basename(process.cwd())}`,
134
+ '---',
135
+ '',
136
+ ...sections,
137
+ ].join('\n');
138
+ fs.writeFileSync(path.join(diaryDir, `compaction-snapshot-${ts}.md`), snapshotContent);
139
+ }
140
+
141
+ const snapshots = listSnapshots(diaryDir).filter(f => f.startsWith('compaction-'));
142
+ while (snapshots.length > 5) {
143
+ const oldest = snapshots.shift();
144
+ if (oldest) fs.unlinkSync(path.join(diaryDir, oldest));
145
+ }
146
+ } catch (_err) { /* snapshot is best-effort */ }
147
+
148
+ process.stdout.write('[Mindlore: pre-compact FTS5 flush complete]\n');
149
+ }
150
+
151
+ withTelemetry('mindlore-pre-compact', main).catch(err => {
152
+ hookLog('mindlore-pre-compact', 'error', err?.message ?? String(err));
153
+ process.exit(0);
154
+ });
@@ -1,105 +1,105 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * mindlore-read-guard — PreToolUse hook (if: "Read")
6
- *
7
- * Repeated-read detection: detects files read multiple times
8
- * in the same session and emits a soft warning.
9
- * Does NOT block (exit 0) — advisory only.
10
- *
11
- * Storage: .mindlore/diary/_session-reads.json
12
- * Cleanup: session-end hook writes stats to delta then deletes the file.
13
- */
14
-
15
- const fs = require('fs');
16
- const path = require('path');
17
- const { findMindloreDir, readHookStdin, getProjectName, hookLog, extractSkeleton, withTelemetrySync } = require('./lib/mindlore-common.cjs');
18
-
19
- function main() {
20
- const baseDir = findMindloreDir();
21
- if (!baseDir) return;
22
-
23
- const filePath = readHookStdin(['file_path', 'path']);
24
- if (!filePath) return;
25
-
26
- // Only track CWD-relative files, skip .mindlore/ internals
27
- const cwd = process.cwd();
28
- const resolved = path.resolve(filePath);
29
- if (!resolved.startsWith(cwd)) return;
30
- if (resolved.startsWith(path.resolve(baseDir))) return;
31
-
32
- // Load or create session reads tracker
33
- const diaryDir = path.join(baseDir, 'diary');
34
- if (!fs.existsSync(diaryDir)) {
35
- fs.mkdirSync(diaryDir, { recursive: true });
36
- }
37
-
38
- const readsPath = path.join(diaryDir, `_session-reads-${getProjectName()}.json`);
39
- let reads = {};
40
- if (fs.existsSync(readsPath)) {
41
- try {
42
- reads = JSON.parse(fs.readFileSync(readsPath, 'utf8'));
43
- } catch (_err) {
44
- reads = {};
45
- }
46
- }
47
-
48
- const normalizedPath = path.resolve(filePath);
49
- const existing = reads[normalizedPath];
50
-
51
- // Support both old format (number) and new format (object with tokens)
52
- let count, tokens;
53
- if (typeof existing === 'number') {
54
- count = existing + 1;
55
- tokens = 0;
56
- reads[normalizedPath] = { count, tokens: 0, chars: 0 };
57
- } else if (existing && typeof existing === 'object') {
58
- count = (existing.count || 0) + 1;
59
- tokens = existing.tokens || 0;
60
- existing.count = count;
61
- reads[normalizedPath] = existing;
62
- } else {
63
- count = 1;
64
- tokens = 0;
65
- reads[normalizedPath] = { count, tokens: 0, chars: 0 };
66
- }
67
-
68
- // Write updated reads
69
- fs.writeFileSync(readsPath, JSON.stringify(reads, null, 2), 'utf8');
70
-
71
- const basename = path.basename(filePath);
72
- const tokenInfo = tokens > 0 ? ` (~${tokens} token)` : '';
73
-
74
- // Block on 3+ repeated reads (exit 2 = block tool call)
75
- if (count >= 3) {
76
- const totalWaste = tokens > 0 ? ` Toplam israf: ~${tokens * (count - 1)} token.` : '';
77
- process.stderr.write(`[Mindlore BLOCK] ${basename}${tokenInfo} bu session'da ${count}. kez okunuyor.${totalWaste} Edit icin gerekiyorsa once degisikligini yap, sonra tekrar oku. Analiz icin ctx_execute_file kullan.`);
78
- process.exit(2);
79
- }
80
-
81
- // Warn on 2nd read (exit 0 = allow but warn)
82
- if (count > 1) {
83
- const totalWaste = tokens > 0 ? ` Toplam tekrar: ~${tokens * (count - 1)} token.` : '';
84
- let skeletonSection = '';
85
- try {
86
- const ext = path.extname(filePath).slice(1);
87
- const fileContent = fs.readFileSync(filePath, 'utf8');
88
- if (fileContent.length < 500_000) {
89
- const skeleton = extractSkeleton(fileContent, ext);
90
- if (skeleton !== fileContent) {
91
- const truncated = skeleton.length > 2000 ? skeleton.slice(0, 2000) + '\n...[truncated]' : skeleton;
92
- skeletonSection = '\n\n' + truncated;
93
- }
94
- }
95
- } catch (_e) { /* unreadable/binary — skip */ }
96
- process.stdout.write(JSON.stringify({
97
- hookSpecificOutput: {
98
- hookEventName: 'PreToolUse',
99
- additionalContext: `[Mindlore: ${basename}${tokenInfo} bu session'da ${count}. kez okunuyor.${totalWaste} Bir sonraki okuma engellenecek — Edit gerekiyorsa simdi yap.]${skeletonSection}`
100
- }
101
- }));
102
- }
103
- }
104
-
105
- try { withTelemetrySync('mindlore-read-guard', main); } catch (err) { hookLog('read-guard', 'error', err?.message ?? String(err)); }
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * mindlore-read-guard — PreToolUse hook (if: "Read")
6
+ *
7
+ * Repeated-read detection: detects files read multiple times
8
+ * in the same session and emits a soft warning.
9
+ * Does NOT block (exit 0) — advisory only.
10
+ *
11
+ * Storage: .mindlore/diary/_session-reads.json
12
+ * Cleanup: session-end hook writes stats to delta then deletes the file.
13
+ */
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const { findMindloreDir, readHookStdin, getProjectName, hookLog, extractSkeleton, withTelemetrySync } = require('./lib/mindlore-common.cjs');
18
+
19
+ function main() {
20
+ const baseDir = findMindloreDir();
21
+ if (!baseDir) return;
22
+
23
+ const filePath = readHookStdin(['file_path', 'path']);
24
+ if (!filePath) return;
25
+
26
+ // Only track CWD-relative files, skip .mindlore/ internals
27
+ const cwd = process.cwd();
28
+ const resolved = path.resolve(filePath);
29
+ if (!resolved.startsWith(cwd)) return;
30
+ if (resolved.startsWith(path.resolve(baseDir))) return;
31
+
32
+ // Load or create session reads tracker
33
+ const diaryDir = path.join(baseDir, 'diary');
34
+ if (!fs.existsSync(diaryDir)) {
35
+ fs.mkdirSync(diaryDir, { recursive: true });
36
+ }
37
+
38
+ const readsPath = path.join(diaryDir, `_session-reads-${getProjectName()}.json`);
39
+ let reads = {};
40
+ if (fs.existsSync(readsPath)) {
41
+ try {
42
+ reads = JSON.parse(fs.readFileSync(readsPath, 'utf8'));
43
+ } catch (_err) {
44
+ reads = {};
45
+ }
46
+ }
47
+
48
+ const normalizedPath = path.resolve(filePath);
49
+ const existing = reads[normalizedPath];
50
+
51
+ // Support both old format (number) and new format (object with tokens)
52
+ let count, tokens;
53
+ if (typeof existing === 'number') {
54
+ count = existing + 1;
55
+ tokens = 0;
56
+ reads[normalizedPath] = { count, tokens: 0, chars: 0 };
57
+ } else if (existing && typeof existing === 'object') {
58
+ count = (existing.count || 0) + 1;
59
+ tokens = existing.tokens || 0;
60
+ existing.count = count;
61
+ reads[normalizedPath] = existing;
62
+ } else {
63
+ count = 1;
64
+ tokens = 0;
65
+ reads[normalizedPath] = { count, tokens: 0, chars: 0 };
66
+ }
67
+
68
+ // Write updated reads
69
+ fs.writeFileSync(readsPath, JSON.stringify(reads, null, 2), 'utf8');
70
+
71
+ const basename = path.basename(filePath);
72
+ const tokenInfo = tokens > 0 ? ` (~${tokens} token)` : '';
73
+
74
+ // Block on 3+ repeated reads (exit 2 = block tool call)
75
+ if (count >= 3) {
76
+ const totalWaste = tokens > 0 ? ` Toplam israf: ~${tokens * (count - 1)} token.` : '';
77
+ process.stderr.write(`[Mindlore BLOCK] ${basename}${tokenInfo} bu session'da ${count}. kez okunuyor.${totalWaste} Edit icin gerekiyorsa once degisikligini yap, sonra tekrar oku. Analiz icin ctx_execute_file kullan.`);
78
+ process.exit(2);
79
+ }
80
+
81
+ // Warn on 2nd read (exit 0 = allow but warn)
82
+ if (count > 1) {
83
+ const totalWaste = tokens > 0 ? ` Toplam tekrar: ~${tokens * (count - 1)} token.` : '';
84
+ let skeletonSection = '';
85
+ try {
86
+ const ext = path.extname(filePath).slice(1);
87
+ const fileContent = fs.readFileSync(filePath, 'utf8');
88
+ if (fileContent.length < 500_000) {
89
+ const skeleton = extractSkeleton(fileContent, ext);
90
+ if (skeleton !== fileContent) {
91
+ const truncated = skeleton.length > 2000 ? skeleton.slice(0, 2000) + '\n...[truncated]' : skeleton;
92
+ skeletonSection = '\n\n' + truncated;
93
+ }
94
+ }
95
+ } catch (_e) { /* unreadable/binary — skip */ }
96
+ process.stdout.write(JSON.stringify({
97
+ hookSpecificOutput: {
98
+ hookEventName: 'PreToolUse',
99
+ additionalContext: `[Mindlore: ${basename}${tokenInfo} bu session'da ${count}. kez okunuyor.${totalWaste} Bir sonraki okuma engellenecek — Edit gerekiyorsa simdi yap.]${skeletonSection}`
100
+ }
101
+ }));
102
+ }
103
+ }
104
+
105
+ try { withTelemetrySync('mindlore-read-guard', main); } catch (err) { hookLog('read-guard', 'error', err?.message ?? String(err)); }