mindlore 0.0.1 → 0.2.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/LICENSE +21 -21
- package/README.md +61 -4
- package/SCHEMA.md +71 -12
- package/hooks/lib/mindlore-common.cjs +106 -0
- package/hooks/mindlore-decision-detector.cjs +51 -0
- package/hooks/mindlore-fts5-sync.cjs +13 -24
- package/hooks/mindlore-index.cjs +11 -27
- package/hooks/mindlore-post-compact.cjs +2 -2
- package/hooks/mindlore-pre-compact.cjs +1 -1
- package/hooks/mindlore-read-guard.cjs +62 -0
- package/hooks/mindlore-search.cjs +94 -76
- package/hooks/mindlore-session-end.cjs +74 -8
- package/hooks/mindlore-session-focus.cjs +2 -2
- package/package.json +1 -1
- package/plugin.json +26 -2
- package/scripts/init.cjs +126 -13
- package/scripts/mindlore-fts5-index.cjs +7 -10
- package/scripts/mindlore-fts5-search.cjs +3 -6
- package/scripts/mindlore-health-check.cjs +98 -29
- package/scripts/uninstall.cjs +186 -0
- package/skills/mindlore-decide/SKILL.md +71 -0
- package/skills/mindlore-ingest/SKILL.md +13 -2
- package/skills/mindlore-log/SKILL.md +79 -0
- package/skills/mindlore-query/SKILL.md +125 -0
- package/templates/SCHEMA.md +280 -0
|
@@ -13,6 +13,7 @@ const fs = require('fs');
|
|
|
13
13
|
const path = require('path');
|
|
14
14
|
|
|
15
15
|
const { DB_NAME } = require('./lib/constants.cjs');
|
|
16
|
+
const { openDatabase } = require('../hooks/lib/mindlore-common.cjs');
|
|
16
17
|
const MAX_RESULTS = 3;
|
|
17
18
|
|
|
18
19
|
// ── Helpers ────────────────────────────────────────────────────────────
|
|
@@ -46,16 +47,12 @@ function main() {
|
|
|
46
47
|
process.exit(1);
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
Database = require('better-sqlite3');
|
|
52
|
-
} catch (_err) {
|
|
50
|
+
const db = openDatabase(dbPath, { readonly: true });
|
|
51
|
+
if (!db) {
|
|
53
52
|
console.error(' better-sqlite3 not installed.');
|
|
54
53
|
process.exit(1);
|
|
55
54
|
}
|
|
56
55
|
|
|
57
|
-
const db = new Database(dbPath, { readonly: true });
|
|
58
|
-
|
|
59
56
|
try {
|
|
60
57
|
// Sanitize query for FTS5 (escape special chars)
|
|
61
58
|
const sanitized = query.replace(/['"(){}[\]*:^~!]/g, ' ').trim();
|
|
@@ -15,35 +15,19 @@ const path = require('path');
|
|
|
15
15
|
// ── Constants ──────────────────────────────────────────────────────────
|
|
16
16
|
|
|
17
17
|
const { DIRECTORIES, TYPE_TO_DIR } = require('./lib/constants.cjs');
|
|
18
|
+
const { parseFrontmatter: _parseFm, getAllMdFiles: _getAllMd } = require('../hooks/lib/mindlore-common.cjs');
|
|
18
19
|
|
|
19
20
|
// ── Helpers ────────────────────────────────────────────────────────────
|
|
20
21
|
|
|
22
|
+
// Wrapper: shared parseFrontmatter returns { meta, body }, health-check expects flat object or null
|
|
21
23
|
function parseFrontmatter(content) {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const fm = {};
|
|
26
|
-
const lines = match[1].split('\n');
|
|
27
|
-
for (const line of lines) {
|
|
28
|
-
const colonIdx = line.indexOf(':');
|
|
29
|
-
if (colonIdx === -1) continue;
|
|
30
|
-
const key = line.slice(0, colonIdx).trim();
|
|
31
|
-
let value = line.slice(colonIdx + 1).trim();
|
|
32
|
-
// Handle arrays
|
|
33
|
-
if (value.startsWith('[') && value.endsWith(']')) {
|
|
34
|
-
value = value
|
|
35
|
-
.slice(1, -1)
|
|
36
|
-
.split(',')
|
|
37
|
-
.map((s) => s.trim());
|
|
38
|
-
}
|
|
39
|
-
fm[key] = value;
|
|
40
|
-
}
|
|
41
|
-
return fm;
|
|
24
|
+
const { meta } = _parseFm(content);
|
|
25
|
+
return Object.keys(meta).length > 0 ? meta : null;
|
|
42
26
|
}
|
|
43
27
|
|
|
44
28
|
// Health check needs ALL .md files (no skip), so pass empty set
|
|
45
29
|
function getAllMdFiles(dir) {
|
|
46
|
-
return
|
|
30
|
+
return _getAllMd(dir, new Set());
|
|
47
31
|
}
|
|
48
32
|
|
|
49
33
|
// ── Checks ─────────────────────────────────────────────────────────────
|
|
@@ -116,10 +100,10 @@ class HealthChecker {
|
|
|
116
100
|
}
|
|
117
101
|
const content = fs.readFileSync(indexPath, 'utf8');
|
|
118
102
|
const lines = content.trim().split('\n');
|
|
119
|
-
if (lines.length >
|
|
103
|
+
if (lines.length > 60) {
|
|
120
104
|
return {
|
|
121
105
|
warn: true,
|
|
122
|
-
detail: `${lines.length} lines (should be ~15-
|
|
106
|
+
detail: `${lines.length} lines (should be ~15-60, consider trimming)`,
|
|
123
107
|
};
|
|
124
108
|
}
|
|
125
109
|
return { ok: true, detail: `${lines.length} lines` };
|
|
@@ -147,9 +131,31 @@ class HealthChecker {
|
|
|
147
131
|
const hashResult = db
|
|
148
132
|
.prepare('SELECT count(*) as cnt FROM file_hashes')
|
|
149
133
|
.get();
|
|
134
|
+
|
|
135
|
+
// Verify 9-column schema (slug, description, type, category, title, content, tags, quality + path)
|
|
136
|
+
let schemaVersion = 0;
|
|
137
|
+
try {
|
|
138
|
+
db.prepare('SELECT tags, quality FROM mindlore_fts LIMIT 0').run();
|
|
139
|
+
schemaVersion = 9;
|
|
140
|
+
} catch (_err) {
|
|
141
|
+
try {
|
|
142
|
+
db.prepare('SELECT slug, description, category, title FROM mindlore_fts LIMIT 0').run();
|
|
143
|
+
schemaVersion = 7;
|
|
144
|
+
} catch (_err2) {
|
|
145
|
+
schemaVersion = 2;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (schemaVersion < 9) {
|
|
150
|
+
return {
|
|
151
|
+
warn: true,
|
|
152
|
+
detail: `${result.cnt} indexed, ${hashResult.cnt} hashes — ${schemaVersion}-col schema (run: npx mindlore init to upgrade to 9-col)`,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
150
156
|
return {
|
|
151
157
|
ok: true,
|
|
152
|
-
detail: `${result.cnt} indexed, ${hashResult.cnt} hashes`,
|
|
158
|
+
detail: `${result.cnt} indexed, ${hashResult.cnt} hashes, 9-col schema`,
|
|
153
159
|
};
|
|
154
160
|
} catch (err) {
|
|
155
161
|
return { ok: false, detail: `FTS5 error: ${err.message}` };
|
|
@@ -263,11 +269,72 @@ class HealthChecker {
|
|
|
263
269
|
detail: `${mdFiles.length} files validated`,
|
|
264
270
|
};
|
|
265
271
|
}
|
|
266
|
-
|
|
267
|
-
ok:
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
272
|
+
if (wrongDir > 0) {
|
|
273
|
+
return { ok: false, detail: issues.join(', ') };
|
|
274
|
+
}
|
|
275
|
+
return { warn: true, detail: issues.join(', ') };
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Check 17: Stale deltas (30+ days without archived: true)
|
|
280
|
+
checkStaleDeltas() {
|
|
281
|
+
this.check('Stale deltas', () => {
|
|
282
|
+
const diaryDir = path.join(this.baseDir, 'diary');
|
|
283
|
+
if (!fs.existsSync(diaryDir)) return { ok: true, detail: 'no diary dir' };
|
|
284
|
+
|
|
285
|
+
const now = Date.now();
|
|
286
|
+
const thirtyDays = 30 * 24 * 60 * 60 * 1000;
|
|
287
|
+
let stale = 0;
|
|
288
|
+
|
|
289
|
+
const files = fs.readdirSync(diaryDir).filter((f) => f.startsWith('delta-') && f.endsWith('.md'));
|
|
290
|
+
for (const file of files) {
|
|
291
|
+
const fullPath = path.join(diaryDir, file);
|
|
292
|
+
const content = fs.readFileSync(fullPath, 'utf8').replace(/\r\n/g, '\n');
|
|
293
|
+
const fm = parseFrontmatter(content);
|
|
294
|
+
if (fm && fm.archived === 'true') continue;
|
|
295
|
+
|
|
296
|
+
const stat = fs.statSync(fullPath);
|
|
297
|
+
if (now - stat.mtimeMs > thirtyDays) stale++;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (stale === 0) return { ok: true, detail: `${files.length} deltas, none stale` };
|
|
301
|
+
return { warn: true, detail: `${stale} deltas older than 30 days without archived flag — run /mindlore-log reflect` };
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Check 18: Conflicting analyses (same tags, different confidence)
|
|
306
|
+
checkConflictingAnalyses() {
|
|
307
|
+
this.check('Conflicting analyses', () => {
|
|
308
|
+
const analysesDir = path.join(this.baseDir, 'analyses');
|
|
309
|
+
if (!fs.existsSync(analysesDir)) return { ok: true, detail: 'no analyses dir' };
|
|
310
|
+
|
|
311
|
+
const files = fs.readdirSync(analysesDir).filter((f) => f.endsWith('.md'));
|
|
312
|
+
if (files.length < 2) return { ok: true, detail: `${files.length} analyses, no conflict possible` };
|
|
313
|
+
|
|
314
|
+
const tagMap = {};
|
|
315
|
+
for (const file of files) {
|
|
316
|
+
const content = fs.readFileSync(path.join(analysesDir, file), 'utf8').replace(/\r\n/g, '\n');
|
|
317
|
+
const fm = parseFrontmatter(content);
|
|
318
|
+
if (!fm || !fm.tags || !fm.confidence) continue;
|
|
319
|
+
|
|
320
|
+
const tags = Array.isArray(fm.tags) ? fm.tags : String(fm.tags).split(',').map((t) => t.trim());
|
|
321
|
+
for (const tag of tags) {
|
|
322
|
+
if (!tagMap[tag]) tagMap[tag] = [];
|
|
323
|
+
tagMap[tag].push({ file, confidence: fm.confidence });
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const conflicts = [];
|
|
328
|
+
for (const [tag, entries] of Object.entries(tagMap)) {
|
|
329
|
+
if (entries.length < 2) continue;
|
|
330
|
+
const confidences = new Set(entries.map((e) => e.confidence));
|
|
331
|
+
if (confidences.size > 1) {
|
|
332
|
+
conflicts.push(`${tag}: ${entries.map((e) => `${e.file}(${e.confidence})`).join(' vs ')}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (conflicts.length === 0) return { ok: true, detail: `${files.length} analyses, no conflicts` };
|
|
337
|
+
return { warn: true, detail: `${conflicts.length} tag conflicts: ${conflicts.slice(0, 2).join('; ')}` };
|
|
271
338
|
});
|
|
272
339
|
}
|
|
273
340
|
|
|
@@ -280,6 +347,8 @@ class HealthChecker {
|
|
|
280
347
|
this.checkDatabase();
|
|
281
348
|
this.checkOrphans();
|
|
282
349
|
this.checkFrontmatter();
|
|
350
|
+
this.checkStaleDeltas();
|
|
351
|
+
this.checkConflictingAnalyses();
|
|
283
352
|
return this;
|
|
284
353
|
}
|
|
285
354
|
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* mindlore uninstall — Remove Mindlore hooks, skills, and optionally project data.
|
|
6
|
+
*
|
|
7
|
+
* Usage: npx mindlore uninstall [--all]
|
|
8
|
+
*
|
|
9
|
+
* --all: also remove .mindlore/ project data (without flag, only hooks + skills)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const { homedir } = require('./lib/constants.cjs');
|
|
15
|
+
|
|
16
|
+
function log(msg) {
|
|
17
|
+
console.log(` ${msg}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ── Remove hooks from settings.json ────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
function removeHooks() {
|
|
23
|
+
const settingsPath = path.join(homedir(), '.claude', 'settings.json');
|
|
24
|
+
|
|
25
|
+
if (!fs.existsSync(settingsPath)) {
|
|
26
|
+
log('No settings.json found, skipping hooks');
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let settings;
|
|
31
|
+
try {
|
|
32
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
33
|
+
} catch (_err) {
|
|
34
|
+
log('Could not parse settings.json, skipping hooks');
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!settings.hooks) return 0;
|
|
39
|
+
|
|
40
|
+
let removed = 0;
|
|
41
|
+
for (const event of Object.keys(settings.hooks)) {
|
|
42
|
+
const entries = settings.hooks[event];
|
|
43
|
+
if (!Array.isArray(entries)) continue;
|
|
44
|
+
|
|
45
|
+
const filtered = entries.filter((entry) => {
|
|
46
|
+
const hooks = entry.hooks || [];
|
|
47
|
+
const hasMindlore = hooks.some(
|
|
48
|
+
(h) => (h.command || '').includes('mindlore-')
|
|
49
|
+
);
|
|
50
|
+
// Also check flat format (legacy)
|
|
51
|
+
const flatMindlore = (entry.command || '').includes('mindlore-');
|
|
52
|
+
|
|
53
|
+
if (hasMindlore || flatMindlore) {
|
|
54
|
+
removed++;
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
settings.hooks[event] = filtered;
|
|
61
|
+
|
|
62
|
+
// Clean up empty arrays
|
|
63
|
+
if (settings.hooks[event].length === 0) {
|
|
64
|
+
delete settings.hooks[event];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (removed > 0) {
|
|
69
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return removed;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── Remove skills from ~/.claude/skills/ ───────────────────────────────
|
|
76
|
+
|
|
77
|
+
function removeSkills() {
|
|
78
|
+
const skillsDir = path.join(homedir(), '.claude', 'skills');
|
|
79
|
+
if (!fs.existsSync(skillsDir)) return 0;
|
|
80
|
+
|
|
81
|
+
const mindloreSkills = fs
|
|
82
|
+
.readdirSync(skillsDir)
|
|
83
|
+
.filter((d) => d.startsWith('mindlore-'));
|
|
84
|
+
|
|
85
|
+
let removed = 0;
|
|
86
|
+
for (const skill of mindloreSkills) {
|
|
87
|
+
const skillPath = path.join(skillsDir, skill);
|
|
88
|
+
fs.rmSync(skillPath, { recursive: true, force: true });
|
|
89
|
+
removed++;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return removed;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── Remove SCHEMA.md from projectDocFiles ──────────────────────────────
|
|
96
|
+
|
|
97
|
+
function removeFromProjectDocs() {
|
|
98
|
+
const projectSettingsPath = path.join(process.cwd(), '.claude', 'settings.json');
|
|
99
|
+
if (!fs.existsSync(projectSettingsPath)) return false;
|
|
100
|
+
|
|
101
|
+
let settings;
|
|
102
|
+
try {
|
|
103
|
+
settings = JSON.parse(fs.readFileSync(projectSettingsPath, 'utf8'));
|
|
104
|
+
} catch (_err) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!settings.projectDocFiles) return false;
|
|
109
|
+
|
|
110
|
+
const before = settings.projectDocFiles.length;
|
|
111
|
+
settings.projectDocFiles = settings.projectDocFiles.filter(
|
|
112
|
+
(p) => !p.includes('mindlore')
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (settings.projectDocFiles.length < before) {
|
|
116
|
+
fs.writeFileSync(
|
|
117
|
+
projectSettingsPath,
|
|
118
|
+
JSON.stringify(settings, null, 2),
|
|
119
|
+
'utf8'
|
|
120
|
+
);
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── Remove .mindlore/ project data ─────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
function removeProjectData() {
|
|
129
|
+
const mindloreDir = path.join(process.cwd(), '.mindlore');
|
|
130
|
+
if (!fs.existsSync(mindloreDir)) {
|
|
131
|
+
log('No .mindlore/ directory in current project');
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
fs.rmSync(mindloreDir, { recursive: true, force: true });
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ── Main ───────────────────────────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
function main() {
|
|
142
|
+
const args = process.argv.slice(2);
|
|
143
|
+
const removeAll = args.includes('--all');
|
|
144
|
+
|
|
145
|
+
console.log('\n Mindlore — Uninstall\n');
|
|
146
|
+
|
|
147
|
+
// Hooks
|
|
148
|
+
const hooksRemoved = removeHooks();
|
|
149
|
+
log(
|
|
150
|
+
hooksRemoved > 0
|
|
151
|
+
? `Removed ${hooksRemoved} hooks from ~/.claude/settings.json`
|
|
152
|
+
: 'No hooks found'
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
// Skills
|
|
156
|
+
const skillsRemoved = removeSkills();
|
|
157
|
+
log(
|
|
158
|
+
skillsRemoved > 0
|
|
159
|
+
? `Removed ${skillsRemoved} skills from ~/.claude/skills/`
|
|
160
|
+
: 'No skills found'
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
// Project doc files
|
|
164
|
+
const docsRemoved = removeFromProjectDocs();
|
|
165
|
+
log(
|
|
166
|
+
docsRemoved
|
|
167
|
+
? 'Removed SCHEMA.md from project settings'
|
|
168
|
+
: 'No project doc references found'
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
// Project data (only with --all)
|
|
172
|
+
if (removeAll) {
|
|
173
|
+
const dataRemoved = removeProjectData();
|
|
174
|
+
log(
|
|
175
|
+
dataRemoved
|
|
176
|
+
? 'Removed .mindlore/ project data'
|
|
177
|
+
: 'No .mindlore/ directory found'
|
|
178
|
+
);
|
|
179
|
+
} else {
|
|
180
|
+
log('.mindlore/ project data kept (use --all to remove)');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
console.log('\n Done! Mindlore has been uninstalled.\n');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
main();
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Skill: Mindlore Decide
|
|
2
|
+
|
|
3
|
+
Record and list decisions in the `.mindlore/decisions/` directory.
|
|
4
|
+
|
|
5
|
+
## Trigger
|
|
6
|
+
|
|
7
|
+
User says `/mindlore-decide record` or `/mindlore-decide list`.
|
|
8
|
+
|
|
9
|
+
## Modes
|
|
10
|
+
|
|
11
|
+
### record
|
|
12
|
+
|
|
13
|
+
Record a new decision.
|
|
14
|
+
|
|
15
|
+
**Flow:**
|
|
16
|
+
1. Ask user (or extract from context): What was decided? What alternatives were considered? Why this choice?
|
|
17
|
+
2. Generate slug from decision title (kebab-case, max 5 words)
|
|
18
|
+
3. Check if a previous decision on same topic exists → set `supersedes` field
|
|
19
|
+
4. Write to `.mindlore/decisions/{slug}.md` with frontmatter:
|
|
20
|
+
|
|
21
|
+
```yaml
|
|
22
|
+
---
|
|
23
|
+
slug: use-fts5-over-vector
|
|
24
|
+
type: decision
|
|
25
|
+
title: Use FTS5 over vector search for v0.1
|
|
26
|
+
tags: [search, fts5, architecture]
|
|
27
|
+
date: 2026-04-11
|
|
28
|
+
supersedes: null
|
|
29
|
+
status: active
|
|
30
|
+
description: Chose FTS5 keyword search as primary engine, vector deferred to v0.4
|
|
31
|
+
---
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
5. Body structure:
|
|
35
|
+
```markdown
|
|
36
|
+
# {title}
|
|
37
|
+
|
|
38
|
+
## Context
|
|
39
|
+
Why this decision was needed.
|
|
40
|
+
|
|
41
|
+
## Alternatives Considered
|
|
42
|
+
1. **Option A** — pros/cons
|
|
43
|
+
2. **Option B** — pros/cons
|
|
44
|
+
|
|
45
|
+
## Decision
|
|
46
|
+
What was chosen and why.
|
|
47
|
+
|
|
48
|
+
## Consequences
|
|
49
|
+
What this means going forward.
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
6. Append to `log.md`: `| {date} | decide | {slug}.md |`
|
|
53
|
+
7. FTS5 auto-indexes via FileChanged hook
|
|
54
|
+
|
|
55
|
+
### list
|
|
56
|
+
|
|
57
|
+
List active decisions.
|
|
58
|
+
|
|
59
|
+
**Flow:**
|
|
60
|
+
1. Read all `.md` files in `.mindlore/decisions/`
|
|
61
|
+
2. Parse frontmatter, filter `status: active`
|
|
62
|
+
3. Display as table: slug, title, date, tags
|
|
63
|
+
4. Show supersedes chain if any (A → B → C)
|
|
64
|
+
|
|
65
|
+
## Rules
|
|
66
|
+
|
|
67
|
+
- Slug must be unique in decisions/
|
|
68
|
+
- `supersedes` field points to the slug of the replaced decision
|
|
69
|
+
- When a decision is superseded, update old one: `status: superseded`
|
|
70
|
+
- Tags should match domain topics for FTS5 discoverability
|
|
71
|
+
- Keep decision body concise — context + alternatives + choice + consequences
|
|
@@ -94,9 +94,20 @@ Only update the stats line: increment source count and total count.
|
|
|
94
94
|
N source, N analysis, N total
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
## Post-Ingest
|
|
97
|
+
## Post-Ingest Quality Gate
|
|
98
98
|
|
|
99
|
-
After ingest,
|
|
99
|
+
After every ingest, verify all 6 checkpoints before reporting success:
|
|
100
|
+
|
|
101
|
+
1. **raw/ file exists** — immutable capture written with frontmatter (slug, type, source_url)
|
|
102
|
+
2. **sources/ summary exists** — processed summary with full frontmatter (slug, type, title, tags, quality, description)
|
|
103
|
+
3. **INDEX.md updated** — stats line incremented, Recent section has new entry
|
|
104
|
+
4. **Domain updated** — if relevant domain exists, new finding added (max 1 domain per ingest)
|
|
105
|
+
5. **log.md entry** — append `| {date} | ingest | {slug}.md |`
|
|
106
|
+
6. **FTS5 indexed** — FileChanged hook auto-triggers, but verify: `node scripts/mindlore-fts5-search.cjs "{keyword}"` returns the new file
|
|
107
|
+
|
|
108
|
+
If any checkpoint fails, fix it before reporting "ingest complete". Do NOT skip steps.
|
|
109
|
+
|
|
110
|
+
Optional: run full health check for structural integrity:
|
|
100
111
|
```bash
|
|
101
112
|
node scripts/mindlore-health-check.cjs
|
|
102
113
|
```
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Skill: Mindlore Log
|
|
2
|
+
|
|
3
|
+
Session logging, pattern extraction, and wiki updates.
|
|
4
|
+
|
|
5
|
+
## Trigger
|
|
6
|
+
|
|
7
|
+
`/mindlore-log <mode>` where mode is `log`, `reflect`, `status`, or `save`.
|
|
8
|
+
|
|
9
|
+
## Modes
|
|
10
|
+
|
|
11
|
+
### log
|
|
12
|
+
|
|
13
|
+
Write a manual diary entry.
|
|
14
|
+
|
|
15
|
+
**Flow:**
|
|
16
|
+
1. User provides note/observation (or extract from conversation context)
|
|
17
|
+
2. Generate slug: `note-YYYY-MM-DD-HHmm`
|
|
18
|
+
3. Write to `.mindlore/diary/{slug}.md`:
|
|
19
|
+
|
|
20
|
+
```yaml
|
|
21
|
+
---
|
|
22
|
+
slug: note-2026-04-11-1530
|
|
23
|
+
type: diary
|
|
24
|
+
date: 2026-04-11
|
|
25
|
+
---
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
4. Body: user's note as-is
|
|
29
|
+
5. Append to `log.md`: `| {date} | log | {slug}.md |`
|
|
30
|
+
|
|
31
|
+
### reflect
|
|
32
|
+
|
|
33
|
+
Scan old deltas, extract patterns, write to learnings/.
|
|
34
|
+
|
|
35
|
+
**Flow (v0.2 — basic):**
|
|
36
|
+
1. Read all non-archived delta files in `diary/` (no `archived: true` frontmatter)
|
|
37
|
+
2. Present summary to user: "Found N unprocessed deltas spanning DATE1 to DATE2"
|
|
38
|
+
3. For each delta, extract: repeated topics, recurring decisions, common file changes
|
|
39
|
+
4. Propose learnings to user: "I found these patterns: ..."
|
|
40
|
+
5. User approves → write to `learnings/{topic}.md` (append if exists, create if not)
|
|
41
|
+
6. Format: `YAPMA:` / `BEST PRACTICE:` / `KRITIK:` prefixed rules
|
|
42
|
+
7. Mark processed deltas: add `archived: true` to their frontmatter
|
|
43
|
+
8. Append to `log.md`: `| {date} | reflect | {N} deltas processed, {M} learnings written |`
|
|
44
|
+
|
|
45
|
+
**Important:** Do NOT auto-extract patterns. Present findings, user approves. This is v0.2 basic mode — automated pattern extraction deferred to v0.2.1.
|
|
46
|
+
|
|
47
|
+
### status
|
|
48
|
+
|
|
49
|
+
Show recent session summary.
|
|
50
|
+
|
|
51
|
+
**Flow:**
|
|
52
|
+
1. Read last 5 delta files from `diary/` (sorted by date, newest first)
|
|
53
|
+
2. For each delta, extract: date, commits count, changed files count
|
|
54
|
+
3. Display as compact table
|
|
55
|
+
4. Show any open items (from delta "Yarım Kalan" sections if present)
|
|
56
|
+
5. Show total: "N sessions, M commits, K unique files changed"
|
|
57
|
+
|
|
58
|
+
### save
|
|
59
|
+
|
|
60
|
+
Structured delta + log.md append + domain wiki update.
|
|
61
|
+
|
|
62
|
+
**Flow:**
|
|
63
|
+
1. Gather current session context:
|
|
64
|
+
- Recent git commits (last 5)
|
|
65
|
+
- Changed files
|
|
66
|
+
- Decisions made (if decision-detector captured any)
|
|
67
|
+
2. Write structured delta to `diary/delta-YYYY-MM-DD-HHmm.md` (same format as session-end hook)
|
|
68
|
+
3. Append to `log.md`
|
|
69
|
+
4. Ask user: "Which domain pages should be updated with this session's findings?"
|
|
70
|
+
5. If user specifies domains → update those `.mindlore/domains/{slug}.md` pages with new findings
|
|
71
|
+
6. Max 2 domain updates per save (prevent sprawl)
|
|
72
|
+
|
|
73
|
+
## Rules
|
|
74
|
+
|
|
75
|
+
- Diary files are session-scoped (temporary), learnings are permanent
|
|
76
|
+
- reflect marks deltas as `archived: true` — they stay in diary/ but are not processed again
|
|
77
|
+
- Health check warns on deltas older than 30 days without `archived: true`
|
|
78
|
+
- learnings/ files are topic-based (one per topic), append-only
|
|
79
|
+
- save mode is the manual equivalent of what session-end hook does automatically
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Skill: Mindlore Query
|
|
2
|
+
|
|
3
|
+
Search, ask, analyze, and retrieve knowledge from `.mindlore/`.
|
|
4
|
+
|
|
5
|
+
## Trigger
|
|
6
|
+
|
|
7
|
+
`/mindlore-query <mode> [query]` where mode is `search`, `ask`, `stats`, or `brief`.
|
|
8
|
+
|
|
9
|
+
## Modes
|
|
10
|
+
|
|
11
|
+
### search
|
|
12
|
+
|
|
13
|
+
FTS5 keyword search with direct results.
|
|
14
|
+
|
|
15
|
+
**Flow:**
|
|
16
|
+
1. Parse user query into keywords (strip stop words)
|
|
17
|
+
2. Run FTS5 MATCH on `mindlore_fts` table
|
|
18
|
+
3. Return top 5 results (configurable) with: path, title, description, category, rank score
|
|
19
|
+
4. Display as table with snippet preview
|
|
20
|
+
5. If `--tags <tag>` flag provided: `WHERE tags MATCH '<tag>'` filter
|
|
21
|
+
|
|
22
|
+
**Output format:**
|
|
23
|
+
```
|
|
24
|
+
| # | Category | Title | Description | Score |
|
|
25
|
+
|---|----------|-------|-------------|-------|
|
|
26
|
+
| 1 | sources | React Hooks | useEffect cleanup patterns | -2.34 |
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### ask
|
|
30
|
+
|
|
31
|
+
Compounding query pipeline — knowledge grows with each answer.
|
|
32
|
+
|
|
33
|
+
**Flow:**
|
|
34
|
+
1. Parse user question
|
|
35
|
+
2. FTS5 search → find relevant files (sources + domains + analyses + insights — previous answers INCLUDED)
|
|
36
|
+
3. Read top 3-5 relevant files using ctx_execute_file if context-mode available, else Read
|
|
37
|
+
4. Synthesize answer from found knowledge
|
|
38
|
+
5. Cite sources: `[kaynak: sources/x.md]` format
|
|
39
|
+
6. Ask user: "Bu cevabı kaydetmemi ister misin?"
|
|
40
|
+
7. If yes → determine target:
|
|
41
|
+
- Short answer (<200 lines, 1-2 sources) → `insights/{slug}.md`
|
|
42
|
+
- Large synthesis (200+ lines, 3+ sources) → `analyses/{slug}.md`
|
|
43
|
+
8. Write with frontmatter:
|
|
44
|
+
|
|
45
|
+
```yaml
|
|
46
|
+
---
|
|
47
|
+
slug: react-hooks-cleanup-comparison
|
|
48
|
+
type: insight
|
|
49
|
+
title: React Hooks Cleanup Comparison
|
|
50
|
+
tags: [react, hooks, useEffect]
|
|
51
|
+
confidence: high
|
|
52
|
+
sources_used: [react-hooks, typescript-generics]
|
|
53
|
+
description: Comparison of cleanup patterns in useEffect vs useLayoutEffect
|
|
54
|
+
---
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
9. FTS5 auto-indexes via FileChanged hook → next query finds this answer
|
|
58
|
+
10. Update relevant domain page (max 1) with backlink if applicable
|
|
59
|
+
11. Append to `log.md`: `| {date} | query-ask | {slug}.md |`
|
|
60
|
+
|
|
61
|
+
**Compounding effect:** Step 2 searches ALL of `.mindlore/` including previous `insights/` and `analyses/`. Each saved answer enriches the next query.
|
|
62
|
+
|
|
63
|
+
**Error compounding prevention:**
|
|
64
|
+
- `confidence` field is REQUIRED on writebacks (high/medium/low)
|
|
65
|
+
- `sources_used` lists exact slugs — traceability
|
|
66
|
+
- Health check flags conflicting analyses on same topic (different confidence)
|
|
67
|
+
- User approval is the quality gate — low-quality answers are not saved
|
|
68
|
+
|
|
69
|
+
### stats
|
|
70
|
+
|
|
71
|
+
Knowledge base statistics.
|
|
72
|
+
|
|
73
|
+
**Flow:**
|
|
74
|
+
1. Count files per directory (9 directories)
|
|
75
|
+
2. Count FTS5 entries and file_hashes entries
|
|
76
|
+
3. Find most recent ingest (latest file by modified date per directory)
|
|
77
|
+
4. Count tags frequency (parse all frontmatter, aggregate tags)
|
|
78
|
+
5. Display summary:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
Mindlore Stats
|
|
82
|
+
─────────────
|
|
83
|
+
Total files: 47 (FTS5: 47 indexed)
|
|
84
|
+
- sources: 18
|
|
85
|
+
- domains: 5
|
|
86
|
+
- analyses: 6
|
|
87
|
+
- insights: 3
|
|
88
|
+
- connections: 2
|
|
89
|
+
- learnings: 4
|
|
90
|
+
- diary: 8
|
|
91
|
+
- decisions: 1
|
|
92
|
+
- raw: 0
|
|
93
|
+
|
|
94
|
+
Top tags: security (12), hooks (8), fts5 (6), testing (5)
|
|
95
|
+
Last ingest: 2026-04-11 (sources/react-hooks.md)
|
|
96
|
+
DB size: 1.2 MB
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### brief
|
|
100
|
+
|
|
101
|
+
Quick context on a topic — token-efficient (~50 tokens output).
|
|
102
|
+
|
|
103
|
+
**Flow:**
|
|
104
|
+
1. FTS5 search for topic
|
|
105
|
+
2. If domain page exists → read first 3 lines of body (after frontmatter)
|
|
106
|
+
3. If no domain → read description field from top FTS5 match
|
|
107
|
+
4. Return: title + description + "Read full: {path}" pointer
|
|
108
|
+
5. Do NOT read full file — this mode is for quick "do I need to open this?" decisions
|
|
109
|
+
|
|
110
|
+
**Output format:**
|
|
111
|
+
```
|
|
112
|
+
[Mindlore Brief: Security]
|
|
113
|
+
SSH hardening, firewall rules, audit checks. 5 sources, 2 analyses linked.
|
|
114
|
+
→ Read full: .mindlore/domains/security.md
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Rules
|
|
118
|
+
|
|
119
|
+
- All modes respect the SCHEMA.md writeback rules (Section 6: Wiki vs Diary)
|
|
120
|
+
- search and brief are read-only — no writes
|
|
121
|
+
- ask writes only with user approval
|
|
122
|
+
- stats is read-only
|
|
123
|
+
- Token strategy: prefer ctx_execute_file (if context-mode installed), fallback to Read
|
|
124
|
+
- Tags filter: `--tags security` works in search and ask modes
|
|
125
|
+
- Max results: search=5, ask=3-5 (for synthesis), brief=1
|