mindlore 0.7.0 → 0.7.1
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 +30 -3
- package/dist/scripts/bundle-hooks.d.ts +2 -0
- package/dist/scripts/bundle-hooks.d.ts.map +1 -0
- package/dist/scripts/bundle-hooks.js +68 -0
- package/dist/scripts/bundle-hooks.js.map +1 -0
- package/dist/scripts/init.js +0 -3
- package/dist/scripts/init.js.map +1 -1
- package/dist/scripts/lib/constants.d.ts +0 -2
- package/dist/scripts/lib/constants.d.ts.map +1 -1
- package/dist/scripts/lib/constants.js +0 -21
- package/dist/scripts/lib/constants.js.map +1 -1
- package/dist/tests/hook-smoke.test.js +1 -1
- package/dist/tests/hook-smoke.test.js.map +1 -1
- package/dist/tests/search-hook.test.js +1 -1
- package/dist/tests/search-hook.test.js.map +1 -1
- package/hooks/cc-memory-bulk-sync.cjs +592 -0
- package/hooks/cc-session-sync.cjs +842 -0
- package/hooks/hooks.json +149 -0
- package/hooks/lib/mindlore-common.cjs +2 -2
- package/hooks/lib/secure-io.cjs +17 -0
- package/hooks/mindlore-cwd-changed.cjs +19 -34
- package/hooks/mindlore-decision-detector.cjs +40 -31
- package/hooks/mindlore-dont-repeat.cjs +57 -115
- package/hooks/mindlore-fts5-sync.cjs +15 -44
- package/hooks/mindlore-index.cjs +100 -101
- package/hooks/mindlore-model-router.cjs +20 -32
- package/hooks/mindlore-post-compact.cjs +26 -42
- package/hooks/mindlore-post-read.cjs +35 -60
- package/hooks/mindlore-pre-compact.cjs +55 -73
- package/hooks/mindlore-read-guard.cjs +28 -51
- package/hooks/mindlore-research-guard.cjs +63 -101
- package/hooks/mindlore-search.cjs +1142 -93
- package/hooks/mindlore-session-end.cjs +155 -276
- package/hooks/mindlore-session-focus.cjs +639 -110
- package/hooks/src/lib/constants.cjs +15 -0
- package/hooks/src/lib/mindlore-common.cjs +975 -0
- package/hooks/src/lib/mindlore-common.d.cts +72 -0
- package/hooks/src/lib/secure-io.cjs +17 -0
- package/hooks/src/lib/types.d.ts +58 -0
- package/hooks/src/mindlore-cwd-changed.cjs +57 -0
- package/hooks/src/mindlore-decision-detector.cjs +54 -0
- package/hooks/src/mindlore-dont-repeat.cjs +222 -0
- package/hooks/src/mindlore-fts5-sync.cjs +98 -0
- package/hooks/src/mindlore-index.cjs +230 -0
- package/hooks/src/mindlore-model-router.cjs +54 -0
- package/hooks/src/mindlore-post-compact.cjs +69 -0
- package/hooks/src/mindlore-post-read.cjs +106 -0
- package/hooks/src/mindlore-pre-compact.cjs +154 -0
- package/hooks/src/mindlore-read-guard.cjs +105 -0
- package/hooks/src/mindlore-research-guard.cjs +176 -0
- package/hooks/src/mindlore-search.cjs +200 -0
- package/hooks/src/mindlore-session-end.cjs +511 -0
- package/hooks/src/mindlore-session-focus.cjs +256 -0
- package/package.json +7 -3
- package/plugin.json +3 -3
- package/templates/config.json +1 -1
|
@@ -0,0 +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
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mindlore",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "AI-native knowledge system for Claude Code",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"test": "jest --config jest.config.cjs",
|
|
13
13
|
"lint": "eslint -c eslint.config.cjs scripts/ hooks/ tests/",
|
|
14
14
|
"typecheck": "tsc --noEmit",
|
|
15
|
-
"prepublishOnly": "npm run build",
|
|
15
|
+
"prepublishOnly": "npm run build && npm run bundle",
|
|
16
16
|
"health": "node dist/scripts/mindlore-health-check.js",
|
|
17
17
|
"index": "node dist/scripts/mindlore-fts5-index.js",
|
|
18
18
|
"search": "node dist/scripts/mindlore-fts5-search.js",
|
|
@@ -23,7 +23,10 @@
|
|
|
23
23
|
"perf": "node dist/scripts/mindlore-perf.js",
|
|
24
24
|
"doctor": "node dist/scripts/mindlore-doctor.js",
|
|
25
25
|
"audit": "npm audit --audit-level=moderate",
|
|
26
|
-
"validate-manifest": "node dist/scripts/validate-manifest-cli.js"
|
|
26
|
+
"validate-manifest": "node dist/scripts/validate-manifest-cli.js",
|
|
27
|
+
"bundle:hooks": "npx tsx scripts/bundle-hooks.ts",
|
|
28
|
+
"bundle:mcp": "npx esbuild scripts/mcp-server.ts --bundle --platform=node --target=node20 --format=cjs --outfile=mcp-server.cjs --external:better-sqlite3",
|
|
29
|
+
"bundle": "npm run bundle:hooks && npm run bundle:mcp"
|
|
27
30
|
},
|
|
28
31
|
"keywords": [
|
|
29
32
|
"claude-code",
|
|
@@ -59,6 +62,7 @@
|
|
|
59
62
|
"@types/node": "^25.6.0",
|
|
60
63
|
"@typescript-eslint/eslint-plugin": "^8.59.0",
|
|
61
64
|
"@typescript-eslint/parser": "^8.58.1",
|
|
65
|
+
"esbuild": "^0.28.0",
|
|
62
66
|
"eslint": "^10.2.0",
|
|
63
67
|
"globals": "^17.5.0",
|
|
64
68
|
"jest": "^30.3.0",
|
package/plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"manifestVersion": 2,
|
|
3
3
|
"name": "mindlore",
|
|
4
4
|
"description": "AI-native knowledge system for Claude Code. Persistent, searchable, evolving knowledge base with FTS5.",
|
|
5
|
-
"version": "0.7.
|
|
5
|
+
"version": "0.7.1",
|
|
6
6
|
"skills": [
|
|
7
7
|
{
|
|
8
8
|
"name": "mindlore-ingest",
|
|
@@ -79,8 +79,8 @@
|
|
|
79
79
|
],
|
|
80
80
|
"mcpServers": {
|
|
81
81
|
"mindlore": {
|
|
82
|
-
"command": "
|
|
83
|
-
"args": ["
|
|
82
|
+
"command": "node",
|
|
83
|
+
"args": ["${CLAUDE_PLUGIN_ROOT}/mcp-server.cjs"]
|
|
84
84
|
}
|
|
85
85
|
},
|
|
86
86
|
"hooks": [
|
package/templates/config.json
CHANGED