mindlore 0.3.4 → 0.4.0
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 +9 -6
- package/dist/scripts/init.js +23 -10
- package/dist/scripts/init.js.map +1 -1
- package/dist/scripts/lib/constants.d.ts.map +1 -1
- package/dist/scripts/lib/constants.js +2 -1
- package/dist/scripts/lib/constants.js.map +1 -1
- package/dist/scripts/lib/db-helpers.d.ts +22 -0
- package/dist/scripts/lib/db-helpers.d.ts.map +1 -0
- package/dist/scripts/lib/db-helpers.js +36 -0
- package/dist/scripts/lib/db-helpers.js.map +1 -0
- package/dist/scripts/lib/episodes.d.ts +66 -0
- package/dist/scripts/lib/episodes.d.ts.map +1 -0
- package/dist/scripts/lib/episodes.js +180 -0
- package/dist/scripts/lib/episodes.js.map +1 -0
- package/dist/scripts/lib/safe-parse.d.ts +11 -0
- package/dist/scripts/lib/safe-parse.d.ts.map +1 -0
- package/dist/scripts/lib/safe-parse.js +33 -0
- package/dist/scripts/lib/safe-parse.js.map +1 -0
- package/dist/scripts/lib/schemas.js +1 -1
- package/dist/scripts/lib/schemas.js.map +1 -1
- package/dist/scripts/mindlore-episodes.d.ts +12 -0
- package/dist/scripts/mindlore-episodes.d.ts.map +1 -0
- package/dist/scripts/mindlore-episodes.js +193 -0
- package/dist/scripts/mindlore-episodes.js.map +1 -0
- package/dist/scripts/mindlore-fts5-index.js +6 -3
- package/dist/scripts/mindlore-fts5-index.js.map +1 -1
- package/dist/scripts/mindlore-fts5-search.js +13 -15
- package/dist/scripts/mindlore-fts5-search.js.map +1 -1
- package/dist/scripts/mindlore-health-check.js +6 -7
- package/dist/scripts/mindlore-health-check.js.map +1 -1
- package/dist/scripts/mindlore-obsidian.js +21 -5
- package/dist/scripts/mindlore-obsidian.js.map +1 -1
- package/dist/scripts/quality-populate.js +6 -3
- package/dist/scripts/quality-populate.js.map +1 -1
- package/dist/scripts/uninstall.js +4 -3
- package/dist/scripts/uninstall.js.map +1 -1
- package/dist/tests/backup.test.js +2 -1
- package/dist/tests/backup.test.js.map +1 -1
- package/dist/tests/compounding.test.js +3 -4
- package/dist/tests/compounding.test.js.map +1 -1
- package/dist/tests/cwd-changed.test.js +3 -2
- package/dist/tests/cwd-changed.test.js.map +1 -1
- package/dist/tests/decision.test.js +2 -2
- package/dist/tests/decision.test.js.map +1 -1
- package/dist/tests/dedup.test.js +6 -15
- package/dist/tests/dedup.test.js.map +1 -1
- package/dist/tests/diary.test.d.ts +6 -0
- package/dist/tests/diary.test.d.ts.map +1 -0
- package/dist/tests/diary.test.js +169 -0
- package/dist/tests/diary.test.js.map +1 -0
- package/dist/tests/dont-repeat.test.js +2 -1
- package/dist/tests/dont-repeat.test.js.map +1 -1
- package/dist/tests/e2e-pipeline.test.js +6 -5
- package/dist/tests/e2e-pipeline.test.js.map +1 -1
- package/dist/tests/episodes-inject.test.d.ts +6 -0
- package/dist/tests/episodes-inject.test.d.ts.map +1 -0
- package/dist/tests/episodes-inject.test.js +161 -0
- package/dist/tests/episodes-inject.test.js.map +1 -0
- package/dist/tests/episodes.test.d.ts +5 -0
- package/dist/tests/episodes.test.d.ts.map +1 -0
- package/dist/tests/episodes.test.js +254 -0
- package/dist/tests/episodes.test.js.map +1 -0
- package/dist/tests/evolve.test.js +3 -2
- package/dist/tests/evolve.test.js.map +1 -1
- package/dist/tests/explore.test.js +3 -2
- package/dist/tests/explore.test.js.map +1 -1
- package/dist/tests/frontmatter.test.js +2 -1
- package/dist/tests/frontmatter.test.js.map +1 -1
- package/dist/tests/fts5-sync.test.js +4 -0
- package/dist/tests/fts5-sync.test.js.map +1 -1
- package/dist/tests/fts5.test.js +13 -20
- package/dist/tests/fts5.test.js.map +1 -1
- package/dist/tests/helpers/db.d.ts +7 -0
- package/dist/tests/helpers/db.d.ts.map +1 -1
- package/dist/tests/helpers/db.js +20 -1
- package/dist/tests/helpers/db.js.map +1 -1
- package/dist/tests/helpers/exec.d.ts +11 -0
- package/dist/tests/helpers/exec.d.ts.map +1 -0
- package/dist/tests/helpers/exec.js +24 -0
- package/dist/tests/helpers/exec.js.map +1 -0
- package/dist/tests/init.test.js +10 -12
- package/dist/tests/init.test.js.map +1 -1
- package/dist/tests/log.test.js +1 -1
- package/dist/tests/log.test.js.map +1 -1
- package/dist/tests/model-router.test.js +2 -2
- package/dist/tests/model-router.test.js.map +1 -1
- package/dist/tests/obsidian.test.js +4 -2
- package/dist/tests/obsidian.test.js.map +1 -1
- package/dist/tests/post-read.test.js +4 -2
- package/dist/tests/post-read.test.js.map +1 -1
- package/dist/tests/project-namespace.test.js +7 -18
- package/dist/tests/project-namespace.test.js.map +1 -1
- package/dist/tests/quality-populate.test.js +10 -9
- package/dist/tests/quality-populate.test.js.map +1 -1
- package/dist/tests/read-guard.test.js +5 -8
- package/dist/tests/read-guard.test.js.map +1 -1
- package/dist/tests/search-hook.test.js +3 -4
- package/dist/tests/search-hook.test.js.map +1 -1
- package/dist/tests/uninstall.test.js +3 -2
- package/dist/tests/uninstall.test.js.map +1 -1
- package/hooks/lib/mindlore-common.cjs +119 -0
- package/hooks/lib/mindlore-common.d.cts +72 -0
- package/hooks/mindlore-post-read.cjs +11 -2
- package/hooks/mindlore-read-guard.cjs +12 -4
- package/hooks/mindlore-search.cjs +36 -0
- package/hooks/mindlore-session-end.cjs +105 -22
- package/hooks/mindlore-session-focus.cjs +28 -1
- package/package.json +1 -1
- package/skills/mindlore-log/SKILL.md +74 -14
- package/skills/mindlore-query/SKILL.md +8 -5
- package/templates/config.json +4 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type BetterSqlite3 from 'better-sqlite3';
|
|
2
|
+
type Database = BetterSqlite3.Database;
|
|
3
|
+
|
|
4
|
+
export const MINDLORE_DIR: string;
|
|
5
|
+
export const GLOBAL_MINDLORE_DIR: string;
|
|
6
|
+
export const DB_NAME: string;
|
|
7
|
+
export const SKIP_FILES: Set<string>;
|
|
8
|
+
export const SQL_FTS_CREATE: string;
|
|
9
|
+
export const SQL_FTS_INSERT: string;
|
|
10
|
+
export const DEFAULT_MODELS: Record<string, string>;
|
|
11
|
+
|
|
12
|
+
export function globalDir(): string;
|
|
13
|
+
export function findMindloreDir(): string | null;
|
|
14
|
+
export function getActiveMindloreDir(): string | null;
|
|
15
|
+
export function getAllDbs(): string[];
|
|
16
|
+
export function getProjectName(): string;
|
|
17
|
+
export function getLatestDelta(diaryDir: string): string | null;
|
|
18
|
+
export function sha256(content: string): string;
|
|
19
|
+
|
|
20
|
+
export interface FrontmatterResult {
|
|
21
|
+
meta: Record<string, unknown>;
|
|
22
|
+
body: string;
|
|
23
|
+
}
|
|
24
|
+
export function parseFrontmatter(content: string): FrontmatterResult;
|
|
25
|
+
|
|
26
|
+
export interface FtsMetadata {
|
|
27
|
+
slug: string;
|
|
28
|
+
description: string;
|
|
29
|
+
type: string;
|
|
30
|
+
category: string;
|
|
31
|
+
title: string;
|
|
32
|
+
tags: string;
|
|
33
|
+
quality: string | null;
|
|
34
|
+
dateCaptured: string | null;
|
|
35
|
+
}
|
|
36
|
+
export function extractFtsMetadata(
|
|
37
|
+
meta: Record<string, unknown>,
|
|
38
|
+
body: string,
|
|
39
|
+
filePath: string,
|
|
40
|
+
baseDir: string,
|
|
41
|
+
): FtsMetadata;
|
|
42
|
+
|
|
43
|
+
export function readHookStdin(fields: string[]): string;
|
|
44
|
+
|
|
45
|
+
export interface FtsEntry {
|
|
46
|
+
path: string;
|
|
47
|
+
slug: string;
|
|
48
|
+
description: string;
|
|
49
|
+
type: string;
|
|
50
|
+
category: string;
|
|
51
|
+
title: string;
|
|
52
|
+
content: string;
|
|
53
|
+
tags: string;
|
|
54
|
+
quality: string | null;
|
|
55
|
+
dateCaptured: string | null;
|
|
56
|
+
project: string;
|
|
57
|
+
}
|
|
58
|
+
export function insertFtsRow(db: Database, entry: FtsEntry): void;
|
|
59
|
+
|
|
60
|
+
export function extractHeadings(content: string, max?: number): string[];
|
|
61
|
+
export function requireDatabase(): typeof import('better-sqlite3');
|
|
62
|
+
export function openDatabase(dbPath: string, opts?: { readonly?: boolean }): Database | null;
|
|
63
|
+
export function getAllMdFiles(dir: string, skip?: Set<string>): string[];
|
|
64
|
+
|
|
65
|
+
export interface MindloreConfig {
|
|
66
|
+
version?: string;
|
|
67
|
+
created?: string;
|
|
68
|
+
models?: Record<string, string>;
|
|
69
|
+
[key: string]: unknown;
|
|
70
|
+
}
|
|
71
|
+
export function readConfig(mindloreDir: string): MindloreConfig | null;
|
|
72
|
+
export function detectSchemaVersion(db: unknown): number;
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* After a file is read, estimate its token count
|
|
8
8
|
* and store in _session-reads.json for the read-guard to reference.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* Outputs token estimate via additionalContext JSON.
|
|
11
|
+
* Also stores token info in _session-reads.json for read-guard.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
const fs = require('fs');
|
|
@@ -87,6 +87,15 @@ function main() {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
fs.writeFileSync(readsPath, JSON.stringify(reads, null, 2), 'utf8');
|
|
90
|
+
|
|
91
|
+
// Output token estimate to Claude via additionalContext
|
|
92
|
+
const basename = path.basename(filePath);
|
|
93
|
+
process.stdout.write(JSON.stringify({
|
|
94
|
+
hookSpecificOutput: {
|
|
95
|
+
hookEventName: 'PostToolUse',
|
|
96
|
+
additionalContext: `[Mindlore: ${basename} — ~${tokens} token (${charCount} char). Edit etmeyeceksen ctx_execute_file kullan.]`
|
|
97
|
+
}
|
|
98
|
+
}));
|
|
90
99
|
} catch {
|
|
91
100
|
// Silent fail
|
|
92
101
|
}
|
|
@@ -68,15 +68,23 @@ function main() {
|
|
|
68
68
|
// Write updated reads
|
|
69
69
|
fs.writeFileSync(readsPath, JSON.stringify(reads, null, 2), 'utf8');
|
|
70
70
|
|
|
71
|
-
|
|
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)
|
|
72
82
|
if (count > 1) {
|
|
73
|
-
const basename = path.basename(filePath);
|
|
74
|
-
const tokenInfo = tokens > 0 ? ` (~${tokens} token)` : '';
|
|
75
83
|
const totalWaste = tokens > 0 ? ` Toplam tekrar: ~${tokens * (count - 1)} token.` : '';
|
|
76
84
|
process.stdout.write(JSON.stringify({
|
|
77
85
|
hookSpecificOutput: {
|
|
78
86
|
hookEventName: 'PreToolUse',
|
|
79
|
-
additionalContext: `[Mindlore: ${basename}${tokenInfo} bu session'da ${count}. kez okunuyor.${totalWaste}
|
|
87
|
+
additionalContext: `[Mindlore: ${basename}${tokenInfo} bu session'da ${count}. kez okunuyor.${totalWaste} Bir sonraki okuma engellenecek — Edit gerekiyorsa simdi yap.]`
|
|
80
88
|
}
|
|
81
89
|
}));
|
|
82
90
|
}
|
|
@@ -104,6 +104,27 @@ function searchDb(dbPath, keywords, Database) {
|
|
|
104
104
|
return results;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Search episodes via FTS5 mirror (type = 'episode').
|
|
109
|
+
* Reuses an already-open DB handle — no extra sqlite3_open.
|
|
110
|
+
*/
|
|
111
|
+
function searchEpisodesFts(db, keywords) {
|
|
112
|
+
try {
|
|
113
|
+
const ftsQuery = keywords.map(kw => '"' + kw + '"').join(' OR ');
|
|
114
|
+
const rows = db.prepare(
|
|
115
|
+
"SELECT title, category, slug, tags FROM mindlore_fts WHERE type = 'episode' AND mindlore_fts MATCH ? LIMIT 2"
|
|
116
|
+
).all(ftsQuery);
|
|
117
|
+
|
|
118
|
+
return rows.map(r => {
|
|
119
|
+
const tags = r.tags || '';
|
|
120
|
+
const kind = tags.split(',')[0]?.trim() || 'episode';
|
|
121
|
+
return `[episode] ${kind}: ${r.title || r.slug}`;
|
|
122
|
+
});
|
|
123
|
+
} catch (_err) {
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
107
128
|
function main() {
|
|
108
129
|
const userMessage = readHookStdin(['prompt', 'content', 'message', 'query']);
|
|
109
130
|
if (!userMessage || userMessage.length < MIN_QUERY_WORDS) return;
|
|
@@ -162,6 +183,21 @@ function main() {
|
|
|
162
183
|
);
|
|
163
184
|
}
|
|
164
185
|
|
|
186
|
+
// v0.4.0: Search episode mirrors in FTS5 (reuses searchDb's DB path, no extra open)
|
|
187
|
+
if (relevant.length < MAX_RESULTS) {
|
|
188
|
+
for (const dbPath of dbPaths) {
|
|
189
|
+
try {
|
|
190
|
+
const db = new Database(dbPath, { readonly: true });
|
|
191
|
+
const episodeResults = searchEpisodesFts(db, keywords);
|
|
192
|
+
db.close();
|
|
193
|
+
if (episodeResults.length > 0) {
|
|
194
|
+
output.push(`[Mindlore Episodes]\n${episodeResults.join('\n')}`);
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
} catch (_err) { /* skip */ }
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
165
201
|
if (output.length > 0) {
|
|
166
202
|
process.stdout.write(output.join('\n\n') + '\n');
|
|
167
203
|
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const { execSync } = require('child_process');
|
|
15
|
-
const { findMindloreDir, globalDir, getProjectName } = require('./lib/mindlore-common.cjs');
|
|
15
|
+
const { findMindloreDir, globalDir, getProjectName, openDatabase, ensureEpisodesTable, hasEpisodesTable, insertBareEpisode, insertFtsRow } = require('./lib/mindlore-common.cjs');
|
|
16
16
|
|
|
17
17
|
function formatDate(date) {
|
|
18
18
|
const y = date.getFullYear();
|
|
@@ -23,29 +23,33 @@ function formatDate(date) {
|
|
|
23
23
|
return `${y}-${m}-${d}-${h}${min}`;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Get recent commits and changed files in a single git call.
|
|
28
|
+
* Returns { commits: string[], changedFiles: string[] }
|
|
29
|
+
*/
|
|
30
|
+
function getRecentGitInfo() {
|
|
27
31
|
try {
|
|
28
|
-
|
|
32
|
+
// --name-only includes file names after each commit entry
|
|
33
|
+
const raw = execSync('git log --oneline -5 --name-only 2>/dev/null', {
|
|
29
34
|
encoding: 'utf8',
|
|
30
35
|
timeout: 5000,
|
|
31
36
|
}).trim();
|
|
32
|
-
if (!raw) return [];
|
|
33
|
-
return raw.split('\n').filter(Boolean).slice(0, 20);
|
|
34
|
-
} catch (_err) {
|
|
35
|
-
return [];
|
|
36
|
-
}
|
|
37
|
-
}
|
|
37
|
+
if (!raw) return { commits: [], changedFiles: [] };
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
const commits = [];
|
|
40
|
+
const fileSet = new Set();
|
|
41
|
+
for (const line of raw.split('\n')) {
|
|
42
|
+
if (!line) continue;
|
|
43
|
+
// Commit lines start with a short hash (7+ hex chars)
|
|
44
|
+
if (/^[0-9a-f]{7,}\s/.test(line)) {
|
|
45
|
+
commits.push(line);
|
|
46
|
+
} else {
|
|
47
|
+
fileSet.add(line);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { commits, changedFiles: [...fileSet].slice(0, 20) };
|
|
47
51
|
} catch (_err) {
|
|
48
|
-
return [];
|
|
52
|
+
return { commits: [], changedFiles: [] };
|
|
49
53
|
}
|
|
50
54
|
}
|
|
51
55
|
|
|
@@ -55,7 +59,11 @@ function getSessionReads(baseDir) {
|
|
|
55
59
|
try {
|
|
56
60
|
const data = JSON.parse(fs.readFileSync(readsPath, 'utf8'));
|
|
57
61
|
const count = Object.keys(data).length;
|
|
58
|
-
const repeats = Object.values(data).filter((v) =>
|
|
62
|
+
const repeats = Object.values(data).filter((v) => {
|
|
63
|
+
if (typeof v === 'number') return v > 1;
|
|
64
|
+
if (v && typeof v === 'object') return (v.count || 0) > 1;
|
|
65
|
+
return false;
|
|
66
|
+
}).length;
|
|
59
67
|
// Clean up session file
|
|
60
68
|
fs.unlinkSync(readsPath);
|
|
61
69
|
return { count, repeats };
|
|
@@ -80,9 +88,8 @@ function main() {
|
|
|
80
88
|
// Don't overwrite existing delta (idempotent)
|
|
81
89
|
if (fs.existsSync(deltaPath)) return;
|
|
82
90
|
|
|
83
|
-
// Gather structured data
|
|
84
|
-
const commits =
|
|
85
|
-
const changedFiles = getRecentGitChanges();
|
|
91
|
+
// Gather structured data (single git call)
|
|
92
|
+
const { commits, changedFiles } = getRecentGitInfo();
|
|
86
93
|
const reads = getSessionReads(baseDir);
|
|
87
94
|
|
|
88
95
|
const project = getProjectName();
|
|
@@ -133,10 +140,86 @@ function main() {
|
|
|
133
140
|
fs.appendFileSync(logPath, logEntry, 'utf8');
|
|
134
141
|
}
|
|
135
142
|
|
|
143
|
+
// v0.4.0: Write bare episode to episodes table
|
|
144
|
+
writeBareEpisode(baseDir, project, commits, changedFiles, reads);
|
|
145
|
+
|
|
136
146
|
// Git auto-commit + push for global ~/.mindlore/ only
|
|
137
147
|
syncGlobalRepo();
|
|
138
148
|
}
|
|
139
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Write a bare session episode to the episodes table.
|
|
152
|
+
* Deterministic — no LLM needed. Captures commits, files, read stats.
|
|
153
|
+
*/
|
|
154
|
+
function writeBareEpisode(baseDir, project, commits, changedFiles, reads) {
|
|
155
|
+
try {
|
|
156
|
+
const dbPath = path.join(baseDir, 'mindlore.db');
|
|
157
|
+
const db = openDatabase(dbPath);
|
|
158
|
+
if (!db) return;
|
|
159
|
+
|
|
160
|
+
if (!hasEpisodesTable(db)) {
|
|
161
|
+
ensureEpisodesTable(db);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const commitList = commits.length > 0 ? commits.join(', ') : 'no commits';
|
|
165
|
+
const fileCount = changedFiles.length;
|
|
166
|
+
const summary = `Session: ${commitList} (${fileCount} files)`;
|
|
167
|
+
|
|
168
|
+
const bodyParts = [];
|
|
169
|
+
if (commits.length > 0) {
|
|
170
|
+
bodyParts.push('## Commits\n' + commits.map(c => `- ${c}`).join('\n'));
|
|
171
|
+
}
|
|
172
|
+
if (changedFiles.length > 0) {
|
|
173
|
+
bodyParts.push('## Changed Files\n' + changedFiles.map(f => `- ${f}`).join('\n'));
|
|
174
|
+
}
|
|
175
|
+
if (reads) {
|
|
176
|
+
bodyParts.push(`## Read Stats\n- ${reads.count} files read, ${reads.repeats} repeated`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const entities = changedFiles.slice(0, 10);
|
|
180
|
+
const body = bodyParts.join('\n\n') || null;
|
|
181
|
+
const truncatedSummary = summary.slice(0, 300);
|
|
182
|
+
|
|
183
|
+
// Atomic: episode + FTS5 mirror in single transaction
|
|
184
|
+
const writeBoth = db.transaction(() => {
|
|
185
|
+
const epId = insertBareEpisode(db, {
|
|
186
|
+
kind: 'session',
|
|
187
|
+
scope: 'project',
|
|
188
|
+
project: project,
|
|
189
|
+
summary: truncatedSummary,
|
|
190
|
+
body: body,
|
|
191
|
+
tags: 'session',
|
|
192
|
+
entities: entities.length > 0 ? entities : null,
|
|
193
|
+
source: 'hook',
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// FTS5 mirror — episode searchable via mindlore-search hook
|
|
197
|
+
try {
|
|
198
|
+
insertFtsRow(db, {
|
|
199
|
+
path: `episodes/${epId}`,
|
|
200
|
+
slug: `ep-${epId}`,
|
|
201
|
+
description: truncatedSummary,
|
|
202
|
+
type: 'episode',
|
|
203
|
+
category: 'episodes',
|
|
204
|
+
title: truncatedSummary.slice(0, 100),
|
|
205
|
+
content: [truncatedSummary, body ?? ''].join('\n').trim(),
|
|
206
|
+
tags: 'session',
|
|
207
|
+
quality: null,
|
|
208
|
+
dateCaptured: new Date().toISOString().slice(0, 10),
|
|
209
|
+
project: project,
|
|
210
|
+
});
|
|
211
|
+
} catch (_ftsErr) {
|
|
212
|
+
// FTS5 mirror optional — don't break the transaction
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
writeBoth();
|
|
216
|
+
|
|
217
|
+
db.close();
|
|
218
|
+
} catch (_err) {
|
|
219
|
+
// Graceful fail — never break session end
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
140
223
|
/**
|
|
141
224
|
* Auto-commit and push ~/.mindlore/ if it has a .git directory.
|
|
142
225
|
* Only runs for the global scope — project .mindlore/ is in the project's own git.
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
const fs = require('fs');
|
|
12
12
|
const path = require('path');
|
|
13
|
-
const { findMindloreDir, readConfig } = require('./lib/mindlore-common.cjs');
|
|
13
|
+
const { findMindloreDir, readConfig, openDatabase, hasEpisodesTable, queryRecentEpisodes } = require('./lib/mindlore-common.cjs');
|
|
14
14
|
|
|
15
15
|
function main() {
|
|
16
16
|
const baseDir = findMindloreDir();
|
|
@@ -63,6 +63,33 @@ function main() {
|
|
|
63
63
|
}
|
|
64
64
|
} catch (_err) { /* skip */ }
|
|
65
65
|
|
|
66
|
+
// v0.4.0: Inject recent episodes
|
|
67
|
+
try {
|
|
68
|
+
const dbPath = path.join(baseDir, 'mindlore.db');
|
|
69
|
+
const db = openDatabase(dbPath, { readonly: true });
|
|
70
|
+
if (db) {
|
|
71
|
+
try {
|
|
72
|
+
if (hasEpisodesTable(db)) {
|
|
73
|
+
const config = readConfig(baseDir);
|
|
74
|
+
const maxEpisodes = config?.session_focus?.max_episodes ?? 3;
|
|
75
|
+
const project = path.basename(process.cwd());
|
|
76
|
+
const episodes = queryRecentEpisodes(db, { project, limit: maxEpisodes });
|
|
77
|
+
|
|
78
|
+
if (episodes.length > 0) {
|
|
79
|
+
const lines = episodes.map(ep => {
|
|
80
|
+
const date = (ep.created_at || '').slice(0, 10);
|
|
81
|
+
const summary = String(ep.summary || '').slice(0, 100);
|
|
82
|
+
return `- [${date}] ${ep.kind}: ${summary}`;
|
|
83
|
+
});
|
|
84
|
+
output.push(`[Mindlore Episodes]\n${lines.join('\n')}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} finally {
|
|
88
|
+
db.close();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} catch (_err) { /* graceful skip */ }
|
|
92
|
+
|
|
66
93
|
if (output.length > 0) {
|
|
67
94
|
process.stdout.write(output.join('\n\n') + '\n');
|
|
68
95
|
}
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@ Determine target using `getActiveMindloreDir()` logic:
|
|
|
12
12
|
|
|
13
13
|
## Trigger
|
|
14
14
|
|
|
15
|
-
`/mindlore-log <mode>` where mode is `log`, `reflect`, `status`, or `save`.
|
|
15
|
+
`/mindlore-log <mode>` where mode is `log`, `diary`, `reflect`, `status`, or `save`.
|
|
16
16
|
|
|
17
17
|
## Modes
|
|
18
18
|
|
|
@@ -36,30 +36,90 @@ date: 2026-04-11
|
|
|
36
36
|
4. Body: user's note as-is
|
|
37
37
|
5. Append to `log.md`: `| {date} | log | {slug}.md |`
|
|
38
38
|
|
|
39
|
+
### diary
|
|
40
|
+
|
|
41
|
+
LLM-driven session analysis → enriched episodes in the episodes table.
|
|
42
|
+
|
|
43
|
+
**Trigger:** User runs `/mindlore-log diary` or Stop hook asks "Diary analizi yapayım mı?"
|
|
44
|
+
|
|
45
|
+
**Model:** `[mindlore:diary]` marker → sonnet (analysis needed)
|
|
46
|
+
|
|
47
|
+
**Flow:**
|
|
48
|
+
1. Open `~/.mindlore/mindlore.db`, ensure episodes table exists
|
|
49
|
+
2. Find the latest bare session episode for current project: `WHERE kind = 'session' AND project = ? AND source = 'hook' ORDER BY created_at DESC LIMIT 1`
|
|
50
|
+
3. Gather context:
|
|
51
|
+
- The bare episode's body (commits, files, read stats)
|
|
52
|
+
- Git log last 10 commits
|
|
53
|
+
- Decision-detector captures (if any in session)
|
|
54
|
+
4. LLM analyzes and extracts structured episodes:
|
|
55
|
+
- **Decisions** → `kind: 'decision'` — architectural/tool/format choices
|
|
56
|
+
- **Discoveries** → `kind: 'discovery'` — assumption vs reality findings
|
|
57
|
+
- **Frictions** → `kind: 'friction'` — tool errors, blockers, recurring issues
|
|
58
|
+
- **Learnings** → `kind: 'learning'` — reusable knowledge
|
|
59
|
+
- **Preferences** → `kind: 'preference'` — user behavioral preferences
|
|
60
|
+
- **Events** → `kind: 'event'` — releases, incidents, milestones
|
|
61
|
+
5. **Deduplication rule:** Each finding belongs to exactly ONE kind. Priority: `decision > discovery > friction > learning > preference > event`. Never write the same finding to multiple kinds.
|
|
62
|
+
6. Present to user, get approval
|
|
63
|
+
7. Write approved episodes to DB:
|
|
64
|
+
- `source: 'diary'`
|
|
65
|
+
- `parent_id: {bare_session_episode_id}` — links enriched episodes to source session
|
|
66
|
+
- `scope: 'project'` (default) or `'global'` if cross-project
|
|
67
|
+
8. Optionally mirror to FTS5 for text search
|
|
68
|
+
9. Append to `log.md`: `| {date} | diary | {N} episodes extracted from session |`
|
|
69
|
+
|
|
70
|
+
**Rules:**
|
|
71
|
+
- NEVER write episodes without user approval
|
|
72
|
+
- parent_id always points to the source session episode
|
|
73
|
+
- Each episode gets its own summary (max 100 chars) and body (markdown, unbounded)
|
|
74
|
+
- entities field: JSON array of relevant file paths (max 10)
|
|
75
|
+
|
|
39
76
|
### reflect
|
|
40
77
|
|
|
41
|
-
LLM-driven pattern extraction from
|
|
78
|
+
LLM-driven pattern extraction from episodes → persistent learnings.
|
|
42
79
|
|
|
43
|
-
**Flow (v0.
|
|
44
|
-
1. Read
|
|
45
|
-
2.
|
|
46
|
-
3.
|
|
47
|
-
|
|
48
|
-
- Repeated decisions (same choice
|
|
49
|
-
-
|
|
80
|
+
**Flow (v0.4 — episodes-powered):**
|
|
81
|
+
1. Read active episodes: `WHERE status = 'active' AND source IN ('hook', 'diary')`
|
|
82
|
+
2. Optionally filter by time: `--days 7` (default 7), `--days 30`
|
|
83
|
+
3. Present summary: "Found N episodes spanning DATE1 to DATE2"
|
|
84
|
+
4. LLM analyzes episodes (not deltas) for patterns:
|
|
85
|
+
- Repeated decisions (same choice 2+ times)
|
|
86
|
+
- Recurring frictions (same blocker/error)
|
|
87
|
+
- Discovery patterns (assumptions that keep breaking)
|
|
50
88
|
- Workflow patterns that worked well
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
89
|
+
5. **Structured report output:**
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
── Reflect Raporu (son {days} gün, {N} episode) ──
|
|
93
|
+
|
|
94
|
+
Friction ({count}):
|
|
95
|
+
- {summary} — {repeat_count}x tekrar
|
|
96
|
+
|
|
97
|
+
Discoveries ({count}):
|
|
98
|
+
- {summary}
|
|
99
|
+
|
|
100
|
+
Decisions ({count}):
|
|
101
|
+
- {summary}
|
|
102
|
+
|
|
103
|
+
Patterns:
|
|
104
|
+
- "{pattern_description}" → kural adayı
|
|
105
|
+
|
|
106
|
+
Önerilen:
|
|
107
|
+
[ ] {rule} ({repeat_count}x, {confidence} confidence)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
6. User approves → write to `learnings/{topic}.md`
|
|
54
111
|
7. Format: `YAPMA:` / `BEST PRACTICE:` / `KRITIK:` prefixed rules
|
|
55
112
|
8. Update relevant domain page if pattern relates to an existing domain
|
|
56
|
-
9. Mark processed
|
|
57
|
-
10. Append to `log.md`: `| {date} | reflect | {N}
|
|
113
|
+
9. Mark processed episodes: future reflect skips already-processed timeranges
|
|
114
|
+
10. Append to `log.md`: `| {date} | reflect | {N} episodes processed, {M} learnings written |`
|
|
115
|
+
|
|
116
|
+
**Fallback:** Also reads non-archived delta files if episodes table is empty (backward compat with v0.3 deltas).
|
|
58
117
|
|
|
59
118
|
**Rules:**
|
|
60
119
|
- NEVER write learnings without user approval
|
|
61
120
|
- Group related patterns into existing topic files (don't create one file per pattern)
|
|
62
121
|
- Reflect scans both project + global diary/ in `--all` mode
|
|
122
|
+
- Deduplication: same pattern found in both episodes and deltas → episodes win
|
|
63
123
|
|
|
64
124
|
### status
|
|
65
125
|
|
|
@@ -18,14 +18,17 @@ Determine search scope using `getActiveMindloreDir()` / `getAllDbs()` logic:
|
|
|
18
18
|
|
|
19
19
|
### search
|
|
20
20
|
|
|
21
|
-
FTS5 keyword search with
|
|
21
|
+
FTS5 keyword search + episodes recall with unified results.
|
|
22
22
|
|
|
23
23
|
**Flow:**
|
|
24
24
|
1. Parse user query into keywords (strip stop words)
|
|
25
|
-
2. Run FTS5 MATCH on `mindlore_fts` table
|
|
26
|
-
3.
|
|
27
|
-
4.
|
|
28
|
-
5.
|
|
25
|
+
2. Run FTS5 MATCH on `mindlore_fts` table (knowledge: "ne biliyorum")
|
|
26
|
+
3. Run LIKE search on `episodes` table (memory: "ne oldu, ne karar aldım")
|
|
27
|
+
4. Merge results: FTS5 results first, then matching episodes
|
|
28
|
+
5. Return top 5 knowledge results + top 3 episode results
|
|
29
|
+
6. Display knowledge as table with snippet preview, episodes as timeline
|
|
30
|
+
7. If `--tags <tag>` flag provided: `WHERE tags MATCH '<tag>'` filter (FTS5 only)
|
|
31
|
+
8. If `--episodes-only` flag: skip FTS5, show only episode matches
|
|
29
32
|
|
|
30
33
|
**Output format:**
|
|
31
34
|
```
|
package/templates/config.json
CHANGED