mindlore 0.2.1 → 0.3.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 +14 -11
- package/SCHEMA.md +15 -2
- package/dist/scripts/init.d.ts +1 -1
- package/dist/scripts/init.js +133 -68
- package/dist/scripts/init.js.map +1 -1
- package/dist/scripts/lib/constants.d.ts +34 -0
- package/dist/scripts/lib/constants.d.ts.map +1 -1
- package/dist/scripts/lib/constants.js +95 -1
- package/dist/scripts/lib/constants.js.map +1 -1
- 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.js +3 -4
- package/dist/scripts/mindlore-fts5-index.js.map +1 -1
- package/dist/scripts/mindlore-fts5-search.d.ts +1 -1
- package/dist/scripts/mindlore-fts5-search.js +87 -74
- package/dist/scripts/mindlore-fts5-search.js.map +1 -1
- package/dist/scripts/quality-populate.d.ts +11 -0
- package/dist/scripts/quality-populate.d.ts.map +1 -0
- package/dist/scripts/quality-populate.js +86 -0
- package/dist/scripts/quality-populate.js.map +1 -0
- package/dist/scripts/uninstall.d.ts +1 -1
- package/dist/scripts/uninstall.js +27 -17
- package/dist/scripts/uninstall.js.map +1 -1
- package/dist/tests/compounding.test.js +2 -2
- package/dist/tests/compounding.test.js.map +1 -1
- 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/dedup.test.js +4 -4
- package/dist/tests/dedup.test.js.map +1 -1
- 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/fts5.test.js +7 -7
- package/dist/tests/fts5.test.js.map +1 -1
- 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 +14 -1
- package/dist/tests/helpers/db.d.ts.map +1 -1
- package/dist/tests/helpers/db.js +3 -3
- package/dist/tests/helpers/db.js.map +1 -1
- package/dist/tests/hook-smoke.test.js +2 -2
- package/dist/tests/hook-smoke.test.js.map +1 -1
- package/dist/tests/init.test.js +83 -0
- package/dist/tests/init.test.js.map +1 -1
- package/dist/tests/model-router.test.d.ts +2 -0
- package/dist/tests/model-router.test.d.ts.map +1 -0
- package/dist/tests/model-router.test.js +146 -0
- package/dist/tests/model-router.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 +158 -0
- package/dist/tests/quality-populate.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.js +3 -3
- package/dist/tests/search-hook.test.js.map +1 -1
- 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 +89 -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-model-router.cjs +54 -0
- 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 +5 -3
- package/plugin.json +29 -1
- package/skills/mindlore-decide/SKILL.md +8 -0
- package/skills/mindlore-evolve/SKILL.md +99 -0
- package/skills/mindlore-explore/SKILL.md +104 -0
- package/skills/mindlore-health/SKILL.md +8 -0
- package/skills/mindlore-ingest/SKILL.md +65 -32
- package/skills/mindlore-log/SKILL.md +30 -13
- package/skills/mindlore-query/SKILL.md +8 -0
- package/templates/SCHEMA.md +15 -2
- package/templates/config.json +9 -0
|
@@ -14,16 +14,52 @@ const MINDLORE_DIR = '.mindlore';
|
|
|
14
14
|
const DB_NAME = 'mindlore.db';
|
|
15
15
|
const SKIP_FILES = new Set(['INDEX.md', 'SCHEMA.md', 'log.md']);
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Compute global .mindlore/ path at call time.
|
|
19
|
+
* Separate function so os.homedir() is evaluated lazily (testable).
|
|
20
|
+
*/
|
|
21
|
+
function globalDir() {
|
|
22
|
+
return path.join(os.homedir(), MINDLORE_DIR);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Convenience export — snapshot at load time for simple references.
|
|
26
|
+
const GLOBAL_MINDLORE_DIR = globalDir();
|
|
27
|
+
|
|
17
28
|
function findMindloreDir() {
|
|
18
29
|
const projectDir = path.join(process.cwd(), MINDLORE_DIR);
|
|
19
30
|
if (fs.existsSync(projectDir)) return projectDir;
|
|
20
31
|
|
|
21
|
-
const
|
|
22
|
-
if (fs.existsSync(
|
|
32
|
+
const gDir = globalDir();
|
|
33
|
+
if (fs.existsSync(gDir)) return gDir;
|
|
23
34
|
|
|
24
35
|
return null;
|
|
25
36
|
}
|
|
26
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Always returns a .mindlore/ path — project if exists, otherwise global.
|
|
40
|
+
* Unlike findMindloreDir, never returns null.
|
|
41
|
+
*/
|
|
42
|
+
function getActiveMindloreDir() {
|
|
43
|
+
const projectDir = path.join(process.cwd(), MINDLORE_DIR);
|
|
44
|
+
if (fs.existsSync(projectDir)) return projectDir;
|
|
45
|
+
return globalDir();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Return all existing mindlore DB paths (project first, global second).
|
|
50
|
+
* Used for layered search: project results ranked higher.
|
|
51
|
+
*/
|
|
52
|
+
function getAllDbs() {
|
|
53
|
+
const dbs = [];
|
|
54
|
+
const projectDb = path.join(process.cwd(), MINDLORE_DIR, DB_NAME);
|
|
55
|
+
const gDb = path.join(globalDir(), DB_NAME);
|
|
56
|
+
|
|
57
|
+
if (fs.existsSync(projectDb)) dbs.push(projectDb);
|
|
58
|
+
if (fs.existsSync(gDb) && gDb !== projectDb) dbs.push(gDb);
|
|
59
|
+
|
|
60
|
+
return dbs;
|
|
61
|
+
}
|
|
62
|
+
|
|
27
63
|
function getLatestDelta(diaryDir) {
|
|
28
64
|
if (!fs.existsSync(diaryDir)) return null;
|
|
29
65
|
|
|
@@ -91,17 +127,37 @@ function extractFtsMetadata(meta, body, filePath, baseDir) {
|
|
|
91
127
|
tags = Array.isArray(meta.tags) ? meta.tags.join(', ') : String(meta.tags);
|
|
92
128
|
}
|
|
93
129
|
const quality = meta.quality !== undefined && meta.quality !== null ? meta.quality : null;
|
|
94
|
-
|
|
130
|
+
const dateCaptured = meta.date_captured || meta.date || null;
|
|
131
|
+
return { slug, description, type, category, title, tags, quality, dateCaptured };
|
|
95
132
|
}
|
|
96
133
|
|
|
97
134
|
/**
|
|
98
135
|
* Shared SQL constants to prevent drift across indexing paths.
|
|
99
136
|
*/
|
|
100
137
|
const SQL_FTS_CREATE =
|
|
101
|
-
"CREATE VIRTUAL TABLE IF NOT EXISTS mindlore_fts USING fts5(path UNINDEXED, slug, description, type UNINDEXED, category, title, content, tags, quality UNINDEXED, tokenize='porter unicode61')";
|
|
138
|
+
"CREATE VIRTUAL TABLE IF NOT EXISTS mindlore_fts USING fts5(path UNINDEXED, slug, description, type UNINDEXED, category, title, content, tags, quality UNINDEXED, date_captured UNINDEXED, tokenize='porter unicode61')";
|
|
102
139
|
|
|
103
140
|
const SQL_FTS_INSERT =
|
|
104
|
-
'INSERT INTO mindlore_fts (path, slug, description, type, category, title, content, tags, quality) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)';
|
|
141
|
+
'INSERT INTO mindlore_fts (path, slug, description, type, category, title, content, tags, quality, date_captured) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Insert a row into FTS5 using an object parameter (replaces positional args).
|
|
145
|
+
*/
|
|
146
|
+
function insertFtsRow(db, entry) {
|
|
147
|
+
const stmt = db.prepare(SQL_FTS_INSERT);
|
|
148
|
+
stmt.run(
|
|
149
|
+
entry.path || '',
|
|
150
|
+
entry.slug || '',
|
|
151
|
+
entry.description || '',
|
|
152
|
+
entry.type || '',
|
|
153
|
+
entry.category || '',
|
|
154
|
+
entry.title || '',
|
|
155
|
+
entry.content || '',
|
|
156
|
+
entry.tags || '',
|
|
157
|
+
entry.quality || null,
|
|
158
|
+
entry.dateCaptured || null,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
105
161
|
|
|
106
162
|
/**
|
|
107
163
|
* Extract headings (h1-h3) from markdown content.
|
|
@@ -177,11 +233,36 @@ function readHookStdin(fields) {
|
|
|
177
233
|
return input;
|
|
178
234
|
}
|
|
179
235
|
|
|
236
|
+
/**
|
|
237
|
+
* Read .mindlore/config.json and return parsed object.
|
|
238
|
+
* Returns null if file doesn't exist or is invalid JSON.
|
|
239
|
+
*/
|
|
240
|
+
function readConfig(mindloreDir) {
|
|
241
|
+
if (!mindloreDir) return null;
|
|
242
|
+
const configPath = path.join(mindloreDir, 'config.json');
|
|
243
|
+
try {
|
|
244
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
245
|
+
} catch (_err) {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const DEFAULT_MODELS = {
|
|
251
|
+
ingest: 'haiku',
|
|
252
|
+
evolve: 'sonnet',
|
|
253
|
+
explore: 'sonnet',
|
|
254
|
+
default: 'haiku',
|
|
255
|
+
};
|
|
256
|
+
|
|
180
257
|
module.exports = {
|
|
181
258
|
MINDLORE_DIR,
|
|
259
|
+
GLOBAL_MINDLORE_DIR,
|
|
260
|
+
globalDir,
|
|
182
261
|
DB_NAME,
|
|
183
262
|
SKIP_FILES,
|
|
184
263
|
findMindloreDir,
|
|
264
|
+
getActiveMindloreDir,
|
|
265
|
+
getAllDbs,
|
|
185
266
|
getLatestDelta,
|
|
186
267
|
sha256,
|
|
187
268
|
parseFrontmatter,
|
|
@@ -189,8 +270,11 @@ module.exports = {
|
|
|
189
270
|
readHookStdin,
|
|
190
271
|
SQL_FTS_CREATE,
|
|
191
272
|
SQL_FTS_INSERT,
|
|
273
|
+
insertFtsRow,
|
|
192
274
|
extractHeadings,
|
|
193
275
|
requireDatabase,
|
|
194
276
|
openDatabase,
|
|
195
277
|
getAllMdFiles,
|
|
278
|
+
readConfig,
|
|
279
|
+
DEFAULT_MODELS,
|
|
196
280
|
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type declarations for mindlore-common.cjs hook functions.
|
|
3
|
+
* Use with JSDoc @type imports in .cjs hook files.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface FtsMetadata {
|
|
7
|
+
slug: string;
|
|
8
|
+
description: string;
|
|
9
|
+
type: string;
|
|
10
|
+
category: string;
|
|
11
|
+
title: string;
|
|
12
|
+
tags: string;
|
|
13
|
+
quality: string | null;
|
|
14
|
+
dateCaptured: string | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface FtsEntry {
|
|
18
|
+
path: string;
|
|
19
|
+
slug?: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
type?: string;
|
|
22
|
+
category?: string;
|
|
23
|
+
title?: string;
|
|
24
|
+
content?: string;
|
|
25
|
+
tags?: string;
|
|
26
|
+
quality?: string | null;
|
|
27
|
+
dateCaptured?: string | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ParsedFrontmatter {
|
|
31
|
+
meta: Record<string, string | string[]>;
|
|
32
|
+
body: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface MindloreCommon {
|
|
36
|
+
MINDLORE_DIR: string;
|
|
37
|
+
GLOBAL_MINDLORE_DIR: string;
|
|
38
|
+
DB_NAME: string;
|
|
39
|
+
SKIP_FILES: Set<string>;
|
|
40
|
+
globalDir(): string;
|
|
41
|
+
findMindloreDir(): string | null;
|
|
42
|
+
getActiveMindloreDir(): string;
|
|
43
|
+
getAllDbs(): string[];
|
|
44
|
+
getLatestDelta(diaryDir: string): string | null;
|
|
45
|
+
sha256(content: string): string;
|
|
46
|
+
parseFrontmatter(content: string): ParsedFrontmatter;
|
|
47
|
+
extractFtsMetadata(meta: Record<string, string>, body: string, filePath: string, baseDir: string): FtsMetadata;
|
|
48
|
+
insertFtsRow(db: import('better-sqlite3').Database, entry: FtsEntry): void;
|
|
49
|
+
readHookStdin(fields: string[]): string;
|
|
50
|
+
extractHeadings(content: string, max: number): string[];
|
|
51
|
+
requireDatabase(): typeof import('better-sqlite3') | null;
|
|
52
|
+
openDatabase(dbPath: string, opts?: { readonly?: boolean }): import('better-sqlite3').Database | null;
|
|
53
|
+
getAllMdFiles(dir: string, skip?: Set<string>): string[];
|
|
54
|
+
SQL_FTS_CREATE: string;
|
|
55
|
+
SQL_FTS_INSERT: string;
|
|
56
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* mindlore-cwd-changed — CwdChanged hook
|
|
6
|
+
*
|
|
7
|
+
* Fires when user changes working directory.
|
|
8
|
+
* CwdChanged has NO inject to Claude — stdout is swallowed, stderr shown to user.
|
|
9
|
+
*
|
|
10
|
+
* Side effects:
|
|
11
|
+
* 1. Detect scope (project .mindlore/ vs global ~/.mindlore/)
|
|
12
|
+
* 2. Write scope state to .mindlore/diary/_scope.json for session-focus to read
|
|
13
|
+
* 3. Show user-facing message via stderr
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const { findMindloreDir, globalDir } = require('./lib/mindlore-common.cjs');
|
|
19
|
+
|
|
20
|
+
function main() {
|
|
21
|
+
const cwd = process.cwd();
|
|
22
|
+
const activeDir = findMindloreDir();
|
|
23
|
+
const scope = !activeDir ? 'none' : activeDir.startsWith(globalDir()) ? 'global' : 'project';
|
|
24
|
+
|
|
25
|
+
if (activeDir) {
|
|
26
|
+
const diaryDir = path.join(activeDir, 'diary');
|
|
27
|
+
if (!fs.existsSync(diaryDir)) {
|
|
28
|
+
fs.mkdirSync(diaryDir, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Dirty-check: skip write if scope hasn't changed
|
|
32
|
+
const scopePath = path.join(diaryDir, '_scope.json');
|
|
33
|
+
if (fs.existsSync(scopePath)) {
|
|
34
|
+
try {
|
|
35
|
+
const existing = JSON.parse(fs.readFileSync(scopePath, 'utf8'));
|
|
36
|
+
if (existing.cwd === cwd && existing.scope === scope) return;
|
|
37
|
+
} catch (_err) {
|
|
38
|
+
// corrupt file — overwrite
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fs.writeFileSync(scopePath, JSON.stringify({
|
|
43
|
+
scope,
|
|
44
|
+
dir: activeDir,
|
|
45
|
+
cwd,
|
|
46
|
+
timestamp: new Date().toISOString(),
|
|
47
|
+
}, null, 2), 'utf8');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (scope === 'none') {
|
|
51
|
+
process.stderr.write(`[Mindlore] Bu projede mindlore kurulu degil. npx mindlore init calistirin.\n`);
|
|
52
|
+
} else {
|
|
53
|
+
process.stderr.write(`[Mindlore scope: ${scope}] ${activeDir}\n`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
main();
|
|
@@ -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,54 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* mindlore-model-router — PreToolUse (Agent) hook
|
|
5
|
+
* Overrides model for [mindlore:SKILL] marked Agent spawns.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const { findMindloreDir, readConfig, DEFAULT_MODELS } = require('./lib/mindlore-common.cjs');
|
|
10
|
+
|
|
11
|
+
const SKILL_KEYS = Object.keys(DEFAULT_MODELS).filter((k) => k !== 'default');
|
|
12
|
+
const MARKER_REGEX = new RegExp(`\\[mindlore:(${SKILL_KEYS.join('|')})\\]`);
|
|
13
|
+
|
|
14
|
+
function main() {
|
|
15
|
+
const mindloreDir = findMindloreDir();
|
|
16
|
+
if (!mindloreDir) return;
|
|
17
|
+
|
|
18
|
+
let input;
|
|
19
|
+
try {
|
|
20
|
+
const raw = fs.readFileSync(0, 'utf8').trim();
|
|
21
|
+
if (!raw) return;
|
|
22
|
+
input = JSON.parse(raw);
|
|
23
|
+
} catch (_err) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const toolName = input.tool_name || '';
|
|
28
|
+
if (toolName !== 'Agent') return;
|
|
29
|
+
|
|
30
|
+
const toolInput = input.tool_input || {};
|
|
31
|
+
const prompt = toolInput.prompt || '';
|
|
32
|
+
const match = prompt.match(MARKER_REGEX);
|
|
33
|
+
if (!match) return;
|
|
34
|
+
|
|
35
|
+
const skill = match[1];
|
|
36
|
+
|
|
37
|
+
// Resolve model: config.json → config default → hardcoded
|
|
38
|
+
const config = readConfig(mindloreDir);
|
|
39
|
+
const models = (config && config.models) || {};
|
|
40
|
+
const model = models[skill] || models.default || DEFAULT_MODELS[skill] || DEFAULT_MODELS.default;
|
|
41
|
+
|
|
42
|
+
const updatedInput = { ...toolInput, model: model };
|
|
43
|
+
|
|
44
|
+
const output = {
|
|
45
|
+
hookSpecificOutput: {
|
|
46
|
+
hookEventName: 'PreToolUse',
|
|
47
|
+
updatedInput: updatedInput,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
process.stdout.write(JSON.stringify(output));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
main();
|