mindlore 0.2.0 → 0.3.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 +15 -9
- package/SCHEMA.md +15 -2
- package/dist/scripts/init.d.ts +10 -0
- package/dist/scripts/init.d.ts.map +1 -0
- package/dist/scripts/init.js +406 -0
- package/dist/scripts/init.js.map +1 -0
- package/dist/scripts/lib/constants.d.ts +56 -0
- package/dist/scripts/lib/constants.d.ts.map +1 -0
- package/dist/scripts/lib/constants.js +131 -0
- package/dist/scripts/lib/constants.js.map +1 -0
- package/dist/scripts/lib/schemas.d.ts +93 -0
- package/dist/scripts/lib/schemas.d.ts.map +1 -0
- package/dist/scripts/lib/schemas.js +108 -0
- package/dist/scripts/lib/schemas.js.map +1 -0
- package/dist/scripts/mindlore-fts5-index.d.ts +9 -0
- package/dist/scripts/mindlore-fts5-index.d.ts.map +1 -0
- package/dist/scripts/mindlore-fts5-index.js +88 -0
- package/dist/scripts/mindlore-fts5-index.js.map +1 -0
- package/dist/scripts/mindlore-fts5-search.d.ts +10 -0
- package/dist/scripts/mindlore-fts5-search.d.ts.map +1 -0
- package/dist/scripts/mindlore-fts5-search.js +121 -0
- package/dist/scripts/mindlore-fts5-search.js.map +1 -0
- package/dist/scripts/mindlore-health-check.d.ts +10 -0
- package/dist/scripts/mindlore-health-check.d.ts.map +1 -0
- package/dist/scripts/mindlore-health-check.js +337 -0
- package/dist/scripts/mindlore-health-check.js.map +1 -0
- package/dist/scripts/uninstall.d.ts +10 -0
- package/dist/scripts/uninstall.d.ts.map +1 -0
- package/dist/scripts/uninstall.js +153 -0
- package/dist/scripts/uninstall.js.map +1 -0
- package/dist/tests/compounding.test.d.ts +8 -0
- package/dist/tests/compounding.test.d.ts.map +1 -0
- package/dist/tests/compounding.test.js +51 -0
- package/dist/tests/compounding.test.js.map +1 -0
- package/dist/tests/cwd-changed.test.d.ts +2 -0
- package/dist/tests/cwd-changed.test.d.ts.map +1 -0
- package/dist/tests/cwd-changed.test.js +62 -0
- package/dist/tests/cwd-changed.test.js.map +1 -0
- package/dist/tests/decision.test.d.ts +2 -0
- package/dist/tests/decision.test.d.ts.map +1 -0
- package/dist/tests/decision.test.js +61 -0
- package/dist/tests/decision.test.js.map +1 -0
- package/dist/tests/dedup.test.d.ts +2 -0
- package/dist/tests/dedup.test.d.ts.map +1 -0
- package/dist/tests/dedup.test.js +74 -0
- package/dist/tests/dedup.test.js.map +1 -0
- package/dist/tests/dont-repeat.test.d.ts +2 -0
- package/dist/tests/dont-repeat.test.d.ts.map +1 -0
- package/dist/tests/dont-repeat.test.js +100 -0
- package/dist/tests/dont-repeat.test.js.map +1 -0
- package/dist/tests/e2e-pipeline.test.d.ts +2 -0
- package/dist/tests/e2e-pipeline.test.d.ts.map +1 -0
- package/dist/tests/e2e-pipeline.test.js +220 -0
- package/dist/tests/e2e-pipeline.test.js.map +1 -0
- package/dist/tests/evolve.test.d.ts +2 -0
- package/dist/tests/evolve.test.d.ts.map +1 -0
- package/dist/tests/evolve.test.js +105 -0
- package/dist/tests/evolve.test.js.map +1 -0
- package/dist/tests/explore.test.d.ts +2 -0
- package/dist/tests/explore.test.d.ts.map +1 -0
- package/dist/tests/explore.test.js +146 -0
- package/dist/tests/explore.test.js.map +1 -0
- package/dist/tests/frontmatter.test.d.ts +2 -0
- package/dist/tests/frontmatter.test.d.ts.map +1 -0
- package/dist/tests/frontmatter.test.js +90 -0
- package/dist/tests/frontmatter.test.js.map +1 -0
- package/dist/tests/fts5.test.d.ts +2 -0
- package/dist/tests/fts5.test.d.ts.map +1 -0
- package/dist/tests/fts5.test.js +95 -0
- package/dist/tests/fts5.test.js.map +1 -0
- package/dist/tests/global-layer.test.d.ts +2 -0
- package/dist/tests/global-layer.test.d.ts.map +1 -0
- package/dist/tests/global-layer.test.js +152 -0
- package/dist/tests/global-layer.test.js.map +1 -0
- package/dist/tests/helpers/db.d.ts +20 -0
- package/dist/tests/helpers/db.d.ts.map +1 -0
- package/dist/tests/helpers/db.js +46 -0
- package/dist/tests/helpers/db.js.map +1 -0
- package/dist/tests/hook-smoke.test.d.ts +2 -0
- package/dist/tests/hook-smoke.test.d.ts.map +1 -0
- package/dist/tests/hook-smoke.test.js +58 -0
- package/dist/tests/hook-smoke.test.js.map +1 -0
- package/dist/tests/init.test.d.ts +2 -0
- package/dist/tests/init.test.d.ts.map +1 -0
- package/dist/tests/init.test.js +109 -0
- package/dist/tests/init.test.js.map +1 -0
- package/dist/tests/log.test.d.ts +2 -0
- package/dist/tests/log.test.d.ts.map +1 -0
- package/dist/tests/log.test.js +68 -0
- package/dist/tests/log.test.js.map +1 -0
- package/dist/tests/post-read.test.d.ts +2 -0
- package/dist/tests/post-read.test.d.ts.map +1 -0
- package/dist/tests/post-read.test.js +69 -0
- package/dist/tests/post-read.test.js.map +1 -0
- package/dist/tests/quality-populate.test.d.ts +2 -0
- package/dist/tests/quality-populate.test.d.ts.map +1 -0
- package/dist/tests/quality-populate.test.js +85 -0
- package/dist/tests/quality-populate.test.js.map +1 -0
- package/dist/tests/read-guard.test.d.ts +2 -0
- package/dist/tests/read-guard.test.d.ts.map +1 -0
- package/dist/tests/read-guard.test.js +69 -0
- package/dist/tests/read-guard.test.js.map +1 -0
- package/dist/tests/reflect.test.d.ts +2 -0
- package/dist/tests/reflect.test.d.ts.map +1 -0
- package/dist/tests/reflect.test.js +122 -0
- package/dist/tests/reflect.test.js.map +1 -0
- package/dist/tests/schemas.test.d.ts +2 -0
- package/dist/tests/schemas.test.d.ts.map +1 -0
- package/dist/tests/schemas.test.js +87 -0
- package/dist/tests/schemas.test.js.map +1 -0
- package/dist/tests/search-hook.test.d.ts +2 -0
- package/dist/tests/search-hook.test.d.ts.map +1 -0
- package/dist/tests/search-hook.test.js +108 -0
- package/dist/tests/search-hook.test.js.map +1 -0
- package/dist/tests/session-focus.test.d.ts +2 -0
- package/dist/tests/session-focus.test.d.ts.map +1 -0
- package/dist/tests/session-focus.test.js +71 -0
- package/dist/tests/session-focus.test.js.map +1 -0
- package/dist/tests/uninstall.test.d.ts +2 -0
- package/dist/tests/uninstall.test.d.ts.map +1 -0
- package/dist/tests/uninstall.test.js +98 -0
- package/dist/tests/uninstall.test.js.map +1 -0
- package/dist/tests/upgrade.test.d.ts +2 -0
- package/dist/tests/upgrade.test.d.ts.map +1 -0
- package/dist/tests/upgrade.test.js +91 -0
- package/dist/tests/upgrade.test.js.map +1 -0
- package/hooks/lib/mindlore-common.cjs +66 -5
- package/hooks/lib/types.d.ts +56 -0
- package/hooks/mindlore-cwd-changed.cjs +57 -0
- package/hooks/mindlore-dont-repeat.cjs +222 -0
- package/hooks/mindlore-fts5-sync.cjs +6 -9
- package/hooks/mindlore-index.cjs +3 -3
- package/hooks/mindlore-post-read.cjs +97 -0
- package/hooks/mindlore-read-guard.cjs +27 -4
- package/hooks/mindlore-search.cjs +73 -52
- package/hooks/mindlore-session-end.cjs +43 -1
- package/hooks/mindlore-session-focus.cjs +14 -0
- package/package.json +21 -8
- package/plugin.json +24 -1
- package/skills/mindlore-decide/SKILL.md +8 -0
- package/skills/mindlore-evolve/SKILL.md +81 -0
- package/skills/mindlore-explore/SKILL.md +84 -0
- package/skills/mindlore-health/SKILL.md +8 -0
- package/skills/mindlore-ingest/SKILL.md +19 -4
- package/skills/mindlore-log/SKILL.md +30 -13
- package/skills/mindlore-query/SKILL.md +8 -0
- package/templates/SCHEMA.md +15 -2
- package/scripts/init.cjs +0 -448
- package/scripts/lib/constants.cjs +0 -49
- package/scripts/mindlore-fts5-index.cjs +0 -112
- package/scripts/mindlore-fts5-search.cjs +0 -119
- package/scripts/mindlore-health-check.cjs +0 -389
- package/scripts/uninstall.cjs +0 -186
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* mindlore-dont-repeat — PreToolUse hook (matcher: "Write|Edit")
|
|
6
|
+
*
|
|
7
|
+
* Checks code being written against negative rules (DON'T, NEVER, AVOID, YAPMA, etc.)
|
|
8
|
+
* found in LESSONS files and Mindlore learnings/.
|
|
9
|
+
*
|
|
10
|
+
* Sources checked (in order):
|
|
11
|
+
* 1. ~/.claude/lessons/global.md (global rules)
|
|
12
|
+
* 2. ./LESSONS.md (project-level rules, if exists)
|
|
13
|
+
* 3. .mindlore/learnings/*.md (Mindlore learnings, if exists)
|
|
14
|
+
*
|
|
15
|
+
* Advisory only (exit 0) — does not block, injects additionalContext warning.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
const os = require('os');
|
|
21
|
+
const { findMindloreDir } = require('./lib/mindlore-common.cjs');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* File-persisted pattern cache — survives across process invocations.
|
|
25
|
+
* Cache file: .mindlore/diary/_pattern-cache.json
|
|
26
|
+
* Each entry keyed by source file path, stores mtimeMs + extracted patterns.
|
|
27
|
+
* On hit: stat only, no readFile+parse. On miss: read, parse, update cache.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
let cacheDirty = false;
|
|
31
|
+
|
|
32
|
+
function readCache(cachePath) {
|
|
33
|
+
if (!cachePath) return {};
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(fs.readFileSync(cachePath, 'utf8'));
|
|
36
|
+
} catch (_err) {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function writeCache(cachePath, cache) {
|
|
42
|
+
if (!cachePath || !cacheDirty) return;
|
|
43
|
+
try {
|
|
44
|
+
fs.writeFileSync(cachePath, JSON.stringify(cache), 'utf8');
|
|
45
|
+
} catch (_err) { /* write failure is non-fatal */ }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function loadPatterns(filePath, cache) {
|
|
49
|
+
try {
|
|
50
|
+
const stat = fs.statSync(filePath);
|
|
51
|
+
const mtimeMs = stat.mtimeMs;
|
|
52
|
+
const cached = cache[filePath];
|
|
53
|
+
if (cached && cached.mtimeMs === mtimeMs) return cached.patterns;
|
|
54
|
+
|
|
55
|
+
const patterns = extractNegativePatterns(fs.readFileSync(filePath, 'utf8'));
|
|
56
|
+
cache[filePath] = { mtimeMs, patterns };
|
|
57
|
+
cacheDirty = true;
|
|
58
|
+
return patterns;
|
|
59
|
+
} catch (_err) {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function extractNegativePatterns(content) {
|
|
65
|
+
const patterns = [];
|
|
66
|
+
const lines = content.split('\n');
|
|
67
|
+
|
|
68
|
+
for (const line of lines) {
|
|
69
|
+
const trimmed = line.trim();
|
|
70
|
+
// Multi-language: TR (YAPMA, KRITIK) + EN (DON'T, NEVER, AVOID, DO NOT)
|
|
71
|
+
const isNegativeRule = /^-\s*(YAPMA|KRITIK|DON'?T|NEVER|AVOID|DO NOT):/i.test(trimmed);
|
|
72
|
+
if (!isNegativeRule) continue;
|
|
73
|
+
|
|
74
|
+
// Extract backtick-quoted code patterns: `pattern`
|
|
75
|
+
const backtickMatches = trimmed.match(/`([^`]+)`/g);
|
|
76
|
+
if (backtickMatches) {
|
|
77
|
+
for (const match of backtickMatches) {
|
|
78
|
+
const pattern = match.slice(1, -1).trim();
|
|
79
|
+
// Skip short/generic patterns — too many false positives
|
|
80
|
+
if (pattern.length < 8) continue;
|
|
81
|
+
if (/^[^a-zA-Z0-9]+$/.test(pattern)) continue;
|
|
82
|
+
// Skip single words (too generic: "node", "bash", "any")
|
|
83
|
+
if (/^\w+$/.test(pattern) && pattern.length < 12) continue;
|
|
84
|
+
// Skip file extensions and paths
|
|
85
|
+
if (/^\.\w{1,5}$/.test(pattern)) continue;
|
|
86
|
+
if (pattern.startsWith('/') || pattern.startsWith('~')) continue;
|
|
87
|
+
if (pattern.includes('.md') || pattern.includes('.json')) continue;
|
|
88
|
+
// Skip common false-positive patterns
|
|
89
|
+
if (/^(node|bash|npm|git|process|require|import|export|const|let|var)$/i.test(pattern)) continue;
|
|
90
|
+
|
|
91
|
+
patterns.push({
|
|
92
|
+
pattern,
|
|
93
|
+
rule: trimmed.substring(0, 120),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Extract "quoted strings" as patterns
|
|
99
|
+
const quoteMatches = trimmed.match(/"([^"]+)"/g);
|
|
100
|
+
if (quoteMatches) {
|
|
101
|
+
for (const match of quoteMatches) {
|
|
102
|
+
const quoted = match.slice(1, -1).trim();
|
|
103
|
+
if (quoted.length < 4) continue;
|
|
104
|
+
patterns.push({
|
|
105
|
+
pattern: quoted,
|
|
106
|
+
rule: trimmed.substring(0, 120),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return patterns;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function checkContent(content, patterns) {
|
|
116
|
+
const matches = [];
|
|
117
|
+
for (const p of patterns) {
|
|
118
|
+
try {
|
|
119
|
+
const escaped = p.pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
120
|
+
const regex = new RegExp(escaped, 'i');
|
|
121
|
+
if (regex.test(content)) {
|
|
122
|
+
matches.push(p);
|
|
123
|
+
}
|
|
124
|
+
} catch { /* skip invalid patterns */ }
|
|
125
|
+
}
|
|
126
|
+
return matches;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function main() {
|
|
130
|
+
let input = '';
|
|
131
|
+
const stdinTimeout = setTimeout(() => process.exit(0), 3000);
|
|
132
|
+
process.stdin.setEncoding('utf8');
|
|
133
|
+
process.stdin.on('error', () => process.exit(0));
|
|
134
|
+
process.stdin.on('data', chunk => input += chunk);
|
|
135
|
+
process.stdin.on('end', () => {
|
|
136
|
+
clearTimeout(stdinTimeout);
|
|
137
|
+
try {
|
|
138
|
+
const data = JSON.parse(input || '{}');
|
|
139
|
+
const toolName = data.tool_name || '';
|
|
140
|
+
|
|
141
|
+
if (!['Write', 'Edit'].includes(toolName)) {
|
|
142
|
+
return process.exit(0);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const toolInput = data.tool_input || {};
|
|
146
|
+
const filePath = toolInput.file_path || '';
|
|
147
|
+
|
|
148
|
+
// Skip non-code files
|
|
149
|
+
if (!filePath) return process.exit(0);
|
|
150
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
151
|
+
const codeExts = ['.ts', '.tsx', '.js', '.jsx', '.cjs', '.mjs', '.py', '.go', '.rs', '.java', '.c', '.cpp', '.h', '.sh', '.yaml', '.yml'];
|
|
152
|
+
if (!codeExts.includes(ext)) return process.exit(0);
|
|
153
|
+
|
|
154
|
+
// Skip rule files themselves
|
|
155
|
+
const basename = path.basename(filePath);
|
|
156
|
+
if (basename === 'LESSONS.md' || basename === 'global.md' || basename === 'CLAUDE.md') {
|
|
157
|
+
return process.exit(0);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Collect content being written (skip old_string — that's code being removed, not added)
|
|
161
|
+
const allContent = [
|
|
162
|
+
toolInput.content || '',
|
|
163
|
+
toolInput.new_string || '',
|
|
164
|
+
].join('\n');
|
|
165
|
+
|
|
166
|
+
if (allContent.trim().length < 10) return process.exit(0);
|
|
167
|
+
|
|
168
|
+
// Load patterns from all sources (file-persisted mtime cache)
|
|
169
|
+
const mindloreDir = findMindloreDir();
|
|
170
|
+
const cachePath = mindloreDir ? path.join(mindloreDir, 'diary', '_pattern-cache.json') : null;
|
|
171
|
+
const cache = readCache(cachePath);
|
|
172
|
+
const allPatterns = [];
|
|
173
|
+
const cwd = process.cwd();
|
|
174
|
+
|
|
175
|
+
// 1. Global lessons
|
|
176
|
+
allPatterns.push(...loadPatterns(path.join(os.homedir(), '.claude', 'lessons', 'global.md'), cache));
|
|
177
|
+
|
|
178
|
+
// 2. Project LESSONS.md
|
|
179
|
+
allPatterns.push(...loadPatterns(path.join(cwd, 'LESSONS.md'), cache));
|
|
180
|
+
|
|
181
|
+
// 3. Mindlore learnings/ directory
|
|
182
|
+
if (mindloreDir) {
|
|
183
|
+
const learningsDir = path.join(mindloreDir, 'learnings');
|
|
184
|
+
try {
|
|
185
|
+
const files = fs.readdirSync(learningsDir).filter(f => f.endsWith('.md'));
|
|
186
|
+
for (const file of files) {
|
|
187
|
+
allPatterns.push(...loadPatterns(path.join(learningsDir, file), cache));
|
|
188
|
+
}
|
|
189
|
+
} catch (_err) { /* learnings/ doesn't exist yet */ }
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
writeCache(cachePath, cache);
|
|
193
|
+
|
|
194
|
+
if (allPatterns.length === 0) return process.exit(0);
|
|
195
|
+
|
|
196
|
+
// Check content against patterns
|
|
197
|
+
const matches = checkContent(allContent, allPatterns);
|
|
198
|
+
if (matches.length === 0) return process.exit(0);
|
|
199
|
+
|
|
200
|
+
// Build warning — max 3 matches shown
|
|
201
|
+
const shown = matches.slice(0, 3);
|
|
202
|
+
const warning = shown.map(m =>
|
|
203
|
+
` - Pattern: \`${m.pattern}\` → ${m.rule}`
|
|
204
|
+
).join('\n');
|
|
205
|
+
const extra = matches.length > 3 ? `\n ... and ${matches.length - 3} more` : '';
|
|
206
|
+
|
|
207
|
+
const msg = `[Mindlore: ${matches.length} dont-repeat rule violation detected]\n${warning}${extra}`;
|
|
208
|
+
|
|
209
|
+
process.stdout.write(JSON.stringify({
|
|
210
|
+
hookSpecificOutput: {
|
|
211
|
+
hookEventName: 'PreToolUse',
|
|
212
|
+
additionalContext: msg
|
|
213
|
+
}
|
|
214
|
+
}));
|
|
215
|
+
} catch {
|
|
216
|
+
// Silent fail
|
|
217
|
+
}
|
|
218
|
+
process.exit(0);
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
main();
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
const fs = require('fs');
|
|
15
15
|
const path = require('path');
|
|
16
|
-
const { MINDLORE_DIR, DB_NAME, sha256, openDatabase, getAllMdFiles, parseFrontmatter, extractFtsMetadata,
|
|
16
|
+
const { MINDLORE_DIR, DB_NAME, sha256, openDatabase, getAllMdFiles, parseFrontmatter, extractFtsMetadata, insertFtsRow, readHookStdin } = require('./lib/mindlore-common.cjs');
|
|
17
17
|
|
|
18
18
|
function main() {
|
|
19
19
|
const filePath = readHookStdin(['path', 'file_path']);
|
|
@@ -35,11 +35,10 @@ function main() {
|
|
|
35
35
|
if (!db) return;
|
|
36
36
|
|
|
37
37
|
const mdFiles = getAllMdFiles(baseDir);
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
|
|
40
40
|
const getHash = db.prepare('SELECT content_hash FROM file_hashes WHERE path = ?');
|
|
41
41
|
const deleteFts = db.prepare('DELETE FROM mindlore_fts WHERE path = ?');
|
|
42
|
-
const insertFts = db.prepare(SQL_FTS_INSERT);
|
|
43
42
|
const upsertHash = db.prepare(`
|
|
44
43
|
INSERT INTO file_hashes (path, content_hash, last_indexed)
|
|
45
44
|
VALUES (?, ?, ?)
|
|
@@ -60,11 +59,10 @@ function main() {
|
|
|
60
59
|
if (existing && existing.content_hash === hash) continue;
|
|
61
60
|
|
|
62
61
|
const { meta, body } = parseFrontmatter(content);
|
|
63
|
-
const { slug, description, type, category, title, tags, quality } = extractFtsMetadata(meta, body, file, baseDir);
|
|
62
|
+
const { slug, description, type, category, title, tags, quality, dateCaptured } = extractFtsMetadata(meta, body, file, baseDir);
|
|
64
63
|
deleteFts.run(file);
|
|
65
|
-
|
|
64
|
+
insertFtsRow(db, { path: file, slug, description, type, category, title, content: body, tags, quality, dateCaptured });
|
|
66
65
|
upsertHash.run(file, hash, now);
|
|
67
|
-
synced++;
|
|
68
66
|
}
|
|
69
67
|
});
|
|
70
68
|
transaction();
|
|
@@ -72,9 +70,8 @@ function main() {
|
|
|
72
70
|
db.close();
|
|
73
71
|
}
|
|
74
72
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
73
|
+
// FileChanged event stdout'u yutulur — log gerekiyorsa dosyaya yaz
|
|
74
|
+
// process.stdout.write kaldırıldı (kimse görmüyor)
|
|
78
75
|
}
|
|
79
76
|
|
|
80
77
|
main();
|
package/hooks/mindlore-index.cjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
const fs = require('fs');
|
|
12
12
|
const path = require('path');
|
|
13
|
-
const { MINDLORE_DIR, DB_NAME, SKIP_FILES, sha256, openDatabase, parseFrontmatter, extractFtsMetadata,
|
|
13
|
+
const { MINDLORE_DIR, DB_NAME, SKIP_FILES, sha256, openDatabase, parseFrontmatter, extractFtsMetadata, insertFtsRow, readHookStdin } = require('./lib/mindlore-common.cjs');
|
|
14
14
|
|
|
15
15
|
function main() {
|
|
16
16
|
const filePath = readHookStdin(['path', 'file_path']);
|
|
@@ -58,11 +58,11 @@ function main() {
|
|
|
58
58
|
|
|
59
59
|
// Parse frontmatter for rich FTS5 columns
|
|
60
60
|
const { meta, body } = parseFrontmatter(content);
|
|
61
|
-
const { slug, description, type, category, title, tags, quality } = extractFtsMetadata(meta, body, filePath, baseDir);
|
|
61
|
+
const { slug, description, type, category, title, tags, quality, dateCaptured } = extractFtsMetadata(meta, body, filePath, baseDir);
|
|
62
62
|
|
|
63
63
|
// Update FTS5
|
|
64
64
|
db.prepare('DELETE FROM mindlore_fts WHERE path = ?').run(filePath);
|
|
65
|
-
db
|
|
65
|
+
insertFtsRow(db, { path: filePath, slug, description, type, category, title, content: body, tags, quality, dateCaptured });
|
|
66
66
|
|
|
67
67
|
// Update hash
|
|
68
68
|
db.prepare(
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* mindlore-post-read — PostToolUse hook (matcher: "Read")
|
|
6
|
+
*
|
|
7
|
+
* After a file is read, estimate its token count
|
|
8
|
+
* and store in _session-reads.json for the read-guard to reference.
|
|
9
|
+
*
|
|
10
|
+
* Does NOT output anything (pure bookkeeping).
|
|
11
|
+
* PostToolUse stdout goes to debug log only — no inject needed.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const { findMindloreDir } = require('./lib/mindlore-common.cjs');
|
|
17
|
+
|
|
18
|
+
const CODE_EXTS = new Set(['.ts', '.tsx', '.js', '.jsx', '.py', '.rs', '.go', '.java', '.c', '.cpp', '.h', '.css', '.scss', '.sql', '.sh', '.yaml', '.yml', '.json', '.toml', '.xml', '.cjs', '.mjs']);
|
|
19
|
+
const PROSE_EXTS = new Set(['.md', '.txt', '.rst', '.adoc']);
|
|
20
|
+
|
|
21
|
+
function estimateTokens(charCount, ext) {
|
|
22
|
+
const ratio = CODE_EXTS.has(ext) ? 3.5 : PROSE_EXTS.has(ext) ? 4.0 : 3.75;
|
|
23
|
+
return Math.ceil(charCount / ratio);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function main() {
|
|
27
|
+
const baseDir = findMindloreDir();
|
|
28
|
+
if (!baseDir) return;
|
|
29
|
+
|
|
30
|
+
let input = '';
|
|
31
|
+
const stdinTimeout = setTimeout(() => process.exit(0), 3000);
|
|
32
|
+
process.stdin.setEncoding('utf8');
|
|
33
|
+
process.stdin.on('error', () => process.exit(0));
|
|
34
|
+
process.stdin.on('data', chunk => input += chunk);
|
|
35
|
+
process.stdin.on('end', () => {
|
|
36
|
+
clearTimeout(stdinTimeout);
|
|
37
|
+
try {
|
|
38
|
+
const data = JSON.parse(input || '{}');
|
|
39
|
+
const toolInput = data.tool_input || {};
|
|
40
|
+
const toolOutput = data.tool_output || {};
|
|
41
|
+
|
|
42
|
+
const filePath = toolInput.file_path || toolInput.path || '';
|
|
43
|
+
if (!filePath) return process.exit(0);
|
|
44
|
+
|
|
45
|
+
// Skip .mindlore/ internals
|
|
46
|
+
const resolved = path.resolve(filePath);
|
|
47
|
+
if (resolved.startsWith(path.resolve(baseDir))) return process.exit(0);
|
|
48
|
+
|
|
49
|
+
// Get content length from tool output or read file
|
|
50
|
+
let charCount = 0;
|
|
51
|
+
if (toolOutput.content) {
|
|
52
|
+
charCount = typeof toolOutput.content === 'string'
|
|
53
|
+
? toolOutput.content.length
|
|
54
|
+
: JSON.stringify(toolOutput.content).length;
|
|
55
|
+
} else {
|
|
56
|
+
// Fallback: read file size
|
|
57
|
+
try {
|
|
58
|
+
const stat = fs.statSync(resolved);
|
|
59
|
+
charCount = stat.size;
|
|
60
|
+
} catch { return process.exit(0); }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (charCount === 0) return process.exit(0);
|
|
64
|
+
|
|
65
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
66
|
+
const tokens = estimateTokens(charCount, ext);
|
|
67
|
+
|
|
68
|
+
// Update _session-reads.json with token info
|
|
69
|
+
const diaryDir = path.join(baseDir, 'diary');
|
|
70
|
+
const readsPath = path.join(diaryDir, '_session-reads.json');
|
|
71
|
+
let reads = {};
|
|
72
|
+
if (fs.existsSync(readsPath)) {
|
|
73
|
+
try { reads = JSON.parse(fs.readFileSync(readsPath, 'utf8')); } catch { reads = {}; }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const normalizedPath = path.resolve(filePath);
|
|
77
|
+
const key = normalizedPath;
|
|
78
|
+
|
|
79
|
+
if (typeof reads[key] === 'number') {
|
|
80
|
+
// Upgrade from old format (just count) to new format (object)
|
|
81
|
+
reads[key] = { count: reads[key], tokens, chars: charCount };
|
|
82
|
+
} else if (reads[key] && typeof reads[key] === 'object') {
|
|
83
|
+
reads[key].tokens = tokens;
|
|
84
|
+
reads[key].chars = charCount;
|
|
85
|
+
} else {
|
|
86
|
+
reads[key] = { count: 1, tokens, chars: charCount };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
fs.writeFileSync(readsPath, JSON.stringify(reads, null, 2), 'utf8');
|
|
90
|
+
} catch {
|
|
91
|
+
// Silent fail
|
|
92
|
+
}
|
|
93
|
+
process.exit(0);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
main();
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
/**
|
|
5
5
|
* mindlore-read-guard — PreToolUse hook (if: "Read")
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* Repeated-read detection: detects files read multiple times
|
|
8
8
|
* in the same session and emits a soft warning.
|
|
9
9
|
* Does NOT block (exit 0) — advisory only.
|
|
10
10
|
*
|
|
@@ -46,8 +46,24 @@ function main() {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
const normalizedPath = path.resolve(filePath);
|
|
49
|
-
const
|
|
50
|
-
|
|
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
|
+
}
|
|
51
67
|
|
|
52
68
|
// Write updated reads
|
|
53
69
|
fs.writeFileSync(readsPath, JSON.stringify(reads, null, 2), 'utf8');
|
|
@@ -55,7 +71,14 @@ function main() {
|
|
|
55
71
|
// Warn on repeated reads (2nd+ time)
|
|
56
72
|
if (count > 1) {
|
|
57
73
|
const basename = path.basename(filePath);
|
|
58
|
-
|
|
74
|
+
const tokenInfo = tokens > 0 ? ` (~${tokens} token)` : '';
|
|
75
|
+
const totalWaste = tokens > 0 ? ` Toplam tekrar: ~${tokens * (count - 1)} token.` : '';
|
|
76
|
+
process.stdout.write(JSON.stringify({
|
|
77
|
+
hookSpecificOutput: {
|
|
78
|
+
hookEventName: 'PreToolUse',
|
|
79
|
+
additionalContext: `[Mindlore: ${basename}${tokenInfo} bu session'da ${count}. kez okunuyor.${totalWaste} Değişiklik yoksa tekrar okumayı atlayabilirsin.]`
|
|
80
|
+
}
|
|
81
|
+
}));
|
|
59
82
|
}
|
|
60
83
|
}
|
|
61
84
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
const fs = require('fs');
|
|
12
12
|
const path = require('path');
|
|
13
|
-
const {
|
|
13
|
+
const { getAllDbs, requireDatabase, extractHeadings, readHookStdin } = require('./lib/mindlore-common.cjs');
|
|
14
14
|
|
|
15
15
|
const MAX_RESULTS = 3;
|
|
16
16
|
const MIN_QUERY_WORDS = 3;
|
|
@@ -59,30 +59,20 @@ function extractKeywords(text) {
|
|
|
59
59
|
return [...new Set(words)].slice(0, 8);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const baseDir =
|
|
67
|
-
if (!baseDir) return;
|
|
68
|
-
|
|
69
|
-
const dbPath = path.join(baseDir, DB_NAME);
|
|
70
|
-
if (!fs.existsSync(dbPath)) return;
|
|
71
|
-
|
|
72
|
-
const keywords = extractKeywords(userMessage);
|
|
73
|
-
if (keywords.length < MIN_QUERY_WORDS) return;
|
|
74
|
-
|
|
75
|
-
const Database = requireDatabase();
|
|
76
|
-
if (!Database) return;
|
|
77
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Search a single DB and return scored results with their baseDir.
|
|
64
|
+
*/
|
|
65
|
+
function searchDb(dbPath, keywords, Database) {
|
|
66
|
+
const baseDir = path.dirname(dbPath);
|
|
78
67
|
const db = new Database(dbPath, { readonly: true });
|
|
68
|
+
const results = [];
|
|
79
69
|
|
|
80
70
|
try {
|
|
81
|
-
// Per-keyword scoring (like old knowledge-search.cjs)
|
|
82
|
-
// Count how many keywords match each document
|
|
83
71
|
const allPaths = db.prepare('SELECT DISTINCT path FROM mindlore_fts').all();
|
|
84
|
-
const scores = [];
|
|
85
72
|
const matchStmt = db.prepare('SELECT rank FROM mindlore_fts WHERE path = ? AND mindlore_fts MATCH ?');
|
|
73
|
+
const metaStmt = db.prepare(
|
|
74
|
+
'SELECT slug, description, category, title, tags FROM mindlore_fts WHERE path = ?'
|
|
75
|
+
);
|
|
86
76
|
|
|
87
77
|
for (const row of allPaths) {
|
|
88
78
|
let hits = 0;
|
|
@@ -101,50 +91,81 @@ function main() {
|
|
|
101
91
|
}
|
|
102
92
|
|
|
103
93
|
if (hits >= MIN_KEYWORD_HITS) {
|
|
104
|
-
|
|
94
|
+
const meta = metaStmt.get(row.path) || {};
|
|
95
|
+
results.push({ path: row.path, hits, totalRank, baseDir, meta });
|
|
105
96
|
}
|
|
106
97
|
}
|
|
98
|
+
} catch (_err) {
|
|
99
|
+
// FTS5 query error — silently skip
|
|
100
|
+
} finally {
|
|
101
|
+
db.close();
|
|
102
|
+
}
|
|
107
103
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const relevant = scores.slice(0, MAX_RESULTS);
|
|
104
|
+
return results;
|
|
105
|
+
}
|
|
111
106
|
|
|
112
|
-
|
|
107
|
+
function main() {
|
|
108
|
+
const userMessage = readHookStdin(['prompt', 'content', 'message', 'query']);
|
|
109
|
+
if (!userMessage || userMessage.length < MIN_QUERY_WORDS) return;
|
|
113
110
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
'SELECT slug, description, category, title, tags FROM mindlore_fts WHERE path = ?'
|
|
117
|
-
);
|
|
111
|
+
const dbPaths = getAllDbs();
|
|
112
|
+
if (dbPaths.length === 0) return;
|
|
118
113
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const meta = metaStmt.get(r.path) || {};
|
|
122
|
-
const relativePath = path.relative(baseDir, r.path).replace(/\\/g, '/');
|
|
114
|
+
const keywords = extractKeywords(userMessage);
|
|
115
|
+
if (keywords.length < MIN_QUERY_WORDS) return;
|
|
123
116
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const content = fs.readFileSync(r.path, 'utf8');
|
|
127
|
-
headings = extractHeadings(content, 5);
|
|
128
|
-
}
|
|
117
|
+
const Database = requireDatabase();
|
|
118
|
+
if (!Database) return;
|
|
129
119
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
120
|
+
// Layered search: project DB first, global DB second
|
|
121
|
+
// Project results appear first in output (higher priority)
|
|
122
|
+
const allScores = [];
|
|
123
|
+
for (const dbPath of dbPaths) {
|
|
124
|
+
allScores.push(...searchDb(dbPath, keywords, Database));
|
|
125
|
+
}
|
|
133
126
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
127
|
+
// Sort: most keyword hits first, then best rank
|
|
128
|
+
allScores.sort((a, b) => b.hits - a.hits || a.totalRank - b.totalRank);
|
|
129
|
+
|
|
130
|
+
// Deduplicate by full path (project version wins — appears first in sort)
|
|
131
|
+
const seen = new Set();
|
|
132
|
+
const unique = [];
|
|
133
|
+
for (const r of allScores) {
|
|
134
|
+
const normalized = path.resolve(r.path);
|
|
135
|
+
if (!seen.has(normalized)) {
|
|
136
|
+
seen.add(normalized);
|
|
137
|
+
unique.push(r);
|
|
139
138
|
}
|
|
139
|
+
}
|
|
140
140
|
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
const relevant = unique.slice(0, MAX_RESULTS);
|
|
142
|
+
if (relevant.length === 0) return;
|
|
143
|
+
|
|
144
|
+
// Build rich inject output
|
|
145
|
+
const output = [];
|
|
146
|
+
for (const r of relevant) {
|
|
147
|
+
const meta = r.meta || {};
|
|
148
|
+
const relativePath = path.relative(r.baseDir, r.path).replace(/\\/g, '/');
|
|
149
|
+
|
|
150
|
+
let headings = [];
|
|
151
|
+
if (fs.existsSync(r.path)) {
|
|
152
|
+
const content = fs.readFileSync(r.path, 'utf8');
|
|
153
|
+
headings = extractHeadings(content, 5);
|
|
143
154
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
155
|
+
|
|
156
|
+
const category = meta.category || path.dirname(relativePath).split('/')[0];
|
|
157
|
+
const title = meta.title || meta.slug || path.basename(r.path, '.md');
|
|
158
|
+
const description = meta.description || '';
|
|
159
|
+
|
|
160
|
+
const headingStr = headings.length > 0 ? `\nBasliklar: ${headings.join(', ')}` : '';
|
|
161
|
+
const tagsStr = meta.tags ? `\nTags: ${meta.tags}` : '';
|
|
162
|
+
output.push(
|
|
163
|
+
`[Mindlore: ${category}/${title}] ${description}\nDosya: ${relativePath}${tagsStr}${headingStr}`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (output.length > 0) {
|
|
168
|
+
process.stdout.write(output.join('\n\n') + '\n');
|
|
148
169
|
}
|
|
149
170
|
}
|
|
150
171
|
|
|
@@ -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 } = require('./lib/mindlore-common.cjs');
|
|
15
|
+
const { findMindloreDir, globalDir } = require('./lib/mindlore-common.cjs');
|
|
16
16
|
|
|
17
17
|
function formatDate(date) {
|
|
18
18
|
const y = date.getFullYear();
|
|
@@ -129,6 +129,48 @@ function main() {
|
|
|
129
129
|
const logEntry = `| ${now.toISOString().slice(0, 10)} | session-end | delta-${dateStr}.md |\n`;
|
|
130
130
|
fs.appendFileSync(logPath, logEntry, 'utf8');
|
|
131
131
|
}
|
|
132
|
+
|
|
133
|
+
// Git auto-commit + push for global ~/.mindlore/ only
|
|
134
|
+
syncGlobalRepo();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Auto-commit and push ~/.mindlore/ if it has a .git directory.
|
|
139
|
+
* Only runs for the global scope — project .mindlore/ is in the project's own git.
|
|
140
|
+
* Push failure is graceful (offline support).
|
|
141
|
+
*/
|
|
142
|
+
function syncGlobalRepo() {
|
|
143
|
+
const gDir = globalDir();
|
|
144
|
+
const gitDir = path.join(gDir, '.git');
|
|
145
|
+
if (!fs.existsSync(gitDir)) return;
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
// Check for changes
|
|
149
|
+
const status = execSync('git status --porcelain', {
|
|
150
|
+
cwd: gDir,
|
|
151
|
+
encoding: 'utf8',
|
|
152
|
+
timeout: 5000,
|
|
153
|
+
}).trim();
|
|
154
|
+
|
|
155
|
+
if (!status) return; // nothing to commit
|
|
156
|
+
|
|
157
|
+
execSync('git add -A', { cwd: gDir, timeout: 5000, stdio: 'pipe' });
|
|
158
|
+
const now = new Date().toISOString().slice(0, 19);
|
|
159
|
+
execSync(`git commit -m "mindlore auto-sync ${now}"`, {
|
|
160
|
+
cwd: gDir,
|
|
161
|
+
timeout: 10000,
|
|
162
|
+
stdio: 'pipe',
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Push — graceful fail if no remote or offline
|
|
166
|
+
try {
|
|
167
|
+
execSync('git push', { cwd: gDir, timeout: 15000, stdio: 'pipe' });
|
|
168
|
+
} catch (_pushErr) {
|
|
169
|
+
// Offline or no remote — silently continue
|
|
170
|
+
}
|
|
171
|
+
} catch (_err) {
|
|
172
|
+
// Git not available or commit failed — silently continue
|
|
173
|
+
}
|
|
132
174
|
}
|
|
133
175
|
|
|
134
176
|
main();
|