mindlore 0.6.7 → 0.6.8
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 +259 -259
- package/SCHEMA.md +292 -292
- package/dist/scripts/cc-memory-bulk-sync.d.ts.map +1 -1
- package/dist/scripts/cc-memory-bulk-sync.js +47 -42
- package/dist/scripts/cc-memory-bulk-sync.js.map +1 -1
- package/dist/scripts/cc-session-sync.d.ts.map +1 -1
- package/dist/scripts/cc-session-sync.js +58 -48
- package/dist/scripts/cc-session-sync.js.map +1 -1
- package/dist/scripts/init.js +8 -8
- package/dist/scripts/init.js.map +1 -1
- package/dist/scripts/lib/all-migrations.d.ts.map +1 -1
- package/dist/scripts/lib/all-migrations.js +4 -1
- package/dist/scripts/lib/all-migrations.js.map +1 -1
- package/dist/scripts/lib/consolidation.d.ts +4 -3
- package/dist/scripts/lib/consolidation.d.ts.map +1 -1
- package/dist/scripts/lib/consolidation.js +10 -10
- package/dist/scripts/lib/consolidation.js.map +1 -1
- package/dist/scripts/lib/constants.d.ts +1 -7
- package/dist/scripts/lib/constants.d.ts.map +1 -1
- package/dist/scripts/lib/constants.js +2 -9
- package/dist/scripts/lib/constants.js.map +1 -1
- package/dist/scripts/lib/db-helpers.d.ts +0 -15
- package/dist/scripts/lib/db-helpers.d.ts.map +1 -1
- package/dist/scripts/lib/db-helpers.js +1 -51
- package/dist/scripts/lib/db-helpers.js.map +1 -1
- package/dist/scripts/lib/decay.d.ts.map +1 -1
- package/dist/scripts/lib/decay.js +9 -9
- package/dist/scripts/lib/decay.js.map +1 -1
- package/dist/scripts/lib/episodes.js +23 -23
- package/dist/scripts/lib/migrations-v061.js +21 -21
- package/dist/scripts/lib/migrations-v062.js +11 -11
- package/dist/scripts/lib/migrations-v063.js +14 -14
- package/dist/scripts/lib/migrations-v067.js +11 -11
- package/dist/scripts/lib/migrations-v068.d.ts +3 -0
- package/dist/scripts/lib/migrations-v068.d.ts.map +1 -0
- package/dist/scripts/lib/migrations-v068.js +37 -0
- package/dist/scripts/lib/migrations-v068.js.map +1 -0
- package/dist/scripts/lib/migrations.d.ts.map +1 -1
- package/dist/scripts/lib/migrations.js +0 -15
- package/dist/scripts/lib/migrations.js.map +1 -1
- package/dist/scripts/lib/schema-version.js +6 -6
- package/dist/scripts/lib/search-cache.js +11 -11
- package/dist/scripts/lib/session-payload.d.ts.map +1 -1
- package/dist/scripts/lib/session-payload.js +7 -7
- package/dist/scripts/lib/session-payload.js.map +1 -1
- package/dist/scripts/lib/triage.js +3 -3
- package/dist/scripts/mindlore-backup.js +9 -9
- package/dist/scripts/mindlore-fts5-index.d.ts +1 -2
- package/dist/scripts/mindlore-fts5-index.d.ts.map +1 -1
- package/dist/scripts/mindlore-fts5-index.js +12 -64
- package/dist/scripts/mindlore-fts5-index.js.map +1 -1
- package/dist/scripts/mindlore-health-check.d.ts.map +1 -1
- package/dist/scripts/mindlore-health-check.js +0 -11
- package/dist/scripts/mindlore-health-check.js.map +1 -1
- package/dist/tests/cc-memory-bulk-sync.test.js +23 -0
- package/dist/tests/cc-memory-bulk-sync.test.js.map +1 -1
- package/dist/tests/cc-session-sync.test.js +25 -0
- package/dist/tests/cc-session-sync.test.js.map +1 -1
- package/dist/tests/compaction-snapshot.test.js +2 -2
- package/dist/tests/consolidation.test.js +5 -5
- package/dist/tests/consolidation.test.js.map +1 -1
- package/dist/tests/decay.test.js +9 -9
- package/dist/tests/diary.test.js +4 -4
- package/dist/tests/episode-kind-constant.test.d.ts +2 -0
- package/dist/tests/episode-kind-constant.test.d.ts.map +1 -0
- package/dist/tests/episode-kind-constant.test.js +28 -0
- package/dist/tests/episode-kind-constant.test.js.map +1 -0
- package/dist/tests/episodes-inject.test.js +9 -9
- package/dist/tests/fts5.test.js +66 -125
- package/dist/tests/fts5.test.js.map +1 -1
- package/dist/tests/globalSetup.d.ts +2 -0
- package/dist/tests/globalSetup.d.ts.map +1 -0
- package/dist/tests/globalSetup.js +36 -0
- package/dist/tests/globalSetup.js.map +1 -0
- package/dist/tests/helpers/db.d.ts +13 -5
- package/dist/tests/helpers/db.d.ts.map +1 -1
- package/dist/tests/helpers/db.js +60 -33
- package/dist/tests/helpers/db.js.map +1 -1
- package/dist/tests/lesson-graduation.test.js +11 -11
- package/dist/tests/lesson-graduation.test.js.map +1 -1
- package/dist/tests/migrations-v053.test.js +16 -16
- package/dist/tests/migrations-v061.test.js +10 -10
- package/dist/tests/migrations-v063.test.js +2 -2
- package/dist/tests/migrations-v068.test.d.ts +2 -0
- package/dist/tests/migrations-v068.test.d.ts.map +1 -0
- package/dist/tests/migrations-v068.test.js +53 -0
- package/dist/tests/migrations-v068.test.js.map +1 -0
- package/dist/tests/nomination-counts.test.d.ts +2 -0
- package/dist/tests/nomination-counts.test.d.ts.map +1 -0
- package/dist/tests/nomination-counts.test.js +51 -0
- package/dist/tests/nomination-counts.test.js.map +1 -0
- package/dist/tests/recall-telemetry.test.js +8 -8
- package/dist/tests/schema-version.test.js +3 -7
- package/dist/tests/schema-version.test.js.map +1 -1
- package/dist/tests/search-hook.test.js +2 -2
- package/dist/tests/sec-regression.test.js +0 -50
- package/dist/tests/sec-regression.test.js.map +1 -1
- package/dist/tests/session-end-cleanup.test.js +3 -20
- package/dist/tests/session-end-cleanup.test.js.map +1 -1
- package/dist/tests/session-focus.test.js +7 -30
- package/dist/tests/session-focus.test.js.map +1 -1
- package/dist/tests/session-payload.test.js +1 -1
- package/dist/tests/session-summary.test.js +1 -1
- package/hooks/lib/constants.cjs +15 -0
- package/hooks/lib/mindlore-common.cjs +974 -1042
- package/hooks/mindlore-cwd-changed.cjs +57 -57
- package/hooks/mindlore-decision-detector.cjs +54 -54
- package/hooks/mindlore-dont-repeat.cjs +222 -222
- package/hooks/mindlore-fts5-sync.cjs +97 -88
- package/hooks/mindlore-index.cjs +229 -229
- package/hooks/mindlore-model-router.cjs +54 -54
- package/hooks/mindlore-post-compact.cjs +69 -69
- package/hooks/mindlore-post-read.cjs +106 -106
- package/hooks/mindlore-pre-compact.cjs +154 -154
- package/hooks/mindlore-read-guard.cjs +105 -105
- package/hooks/mindlore-research-guard.cjs +176 -176
- package/hooks/mindlore-search.cjs +200 -200
- package/hooks/mindlore-session-end.cjs +509 -526
- package/hooks/mindlore-session-focus.cjs +256 -259
- package/package.json +75 -78
- package/plugin.json +1 -1
- package/skills/mindlore-diary/SKILL.md +85 -85
- package/skills/mindlore-evolve/SKILL.md +126 -126
- package/skills/mindlore-explore/SKILL.md +109 -109
- package/skills/mindlore-ingest/SKILL.md +195 -195
- package/skills/mindlore-maintain/SKILL.md +125 -125
- package/skills/mindlore-query/SKILL.md +151 -151
- package/skills/mindlore-reflect/SKILL.md +141 -141
- package/skills/mindlore-stats/SKILL.md +106 -106
- package/templates/INDEX.md +14 -14
- package/templates/SCHEMA.md +292 -292
- package/templates/config.json +1 -1
- package/templates/extraction/article.md +15 -15
- package/templates/extraction/changelog.md +15 -15
- package/templates/extraction/default.md +15 -15
- package/templates/extraction/docs.md +15 -15
- package/templates/extraction/github-repo.md +17 -17
- package/dist/scripts/lib/daemon.d.ts +0 -16
- package/dist/scripts/lib/daemon.d.ts.map +0 -1
- package/dist/scripts/lib/daemon.js +0 -133
- package/dist/scripts/lib/daemon.js.map +0 -1
- package/dist/scripts/lib/embedding.d.ts +0 -5
- package/dist/scripts/lib/embedding.d.ts.map +0 -1
- package/dist/scripts/lib/embedding.js +0 -44
- package/dist/scripts/lib/embedding.js.map +0 -1
- package/dist/scripts/mindlore-daemon.d.ts +0 -2
- package/dist/scripts/mindlore-daemon.d.ts.map +0 -1
- package/dist/scripts/mindlore-daemon.js +0 -117
- package/dist/scripts/mindlore-daemon.js.map +0 -1
- package/dist/tests/daemon-integration.test.d.ts +0 -2
- package/dist/tests/daemon-integration.test.d.ts.map +0 -1
- package/dist/tests/daemon-integration.test.js +0 -37
- package/dist/tests/daemon-integration.test.js.map +0 -1
- package/dist/tests/daemon.test.d.ts +0 -2
- package/dist/tests/daemon.test.d.ts.map +0 -1
- package/dist/tests/daemon.test.js +0 -187
- package/dist/tests/daemon.test.js.map +0 -1
- package/dist/tests/embedding-hf-integration.test.d.ts +0 -2
- package/dist/tests/embedding-hf-integration.test.d.ts.map +0 -1
- package/dist/tests/embedding-hf-integration.test.js +0 -52
- package/dist/tests/embedding-hf-integration.test.js.map +0 -1
- package/dist/tests/embedding.test.d.ts +0 -6
- package/dist/tests/embedding.test.d.ts.map +0 -1
- package/dist/tests/embedding.test.js +0 -71
- package/dist/tests/embedding.test.js.map +0 -1
- package/dist/tests/sqlite-vec-v12.test.d.ts +0 -2
- package/dist/tests/sqlite-vec-v12.test.d.ts.map +0 -1
- package/dist/tests/sqlite-vec-v12.test.js +0 -72
- package/dist/tests/sqlite-vec-v12.test.js.map +0 -1
|
@@ -1,57 +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 (global ~/.mindlore/ or none)
|
|
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, hookLog, withTelemetry } = 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
|
-
withTelemetry('mindlore-cwd-changed', main).catch(err => { hookLog('cwd-changed', 'error', err?.message ?? String(err)); });
|
|
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 (global ~/.mindlore/ or none)
|
|
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, hookLog, withTelemetry } = 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
|
+
withTelemetry('mindlore-cwd-changed', main).catch(err => { hookLog('cwd-changed', 'error', err?.message ?? String(err)); });
|
|
@@ -1,54 +1,54 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* mindlore-decision-detector — UserPromptSubmit hook
|
|
6
|
-
*
|
|
7
|
-
* Detects decision signals in user messages (TR + EN).
|
|
8
|
-
* Outputs a suggestion to record the decision via /mindlore-decide.
|
|
9
|
-
* Does NOT block (exit 0) — advisory only.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
const { findMindloreDir, readHookStdin, hookLog, withTelemetry } = require('./lib/mindlore-common.cjs');
|
|
13
|
-
|
|
14
|
-
const SIGNALS_TR = [
|
|
15
|
-
'karar verdik', 'karar verildi', 'kararlastirdik', 'kararlaştırdık',
|
|
16
|
-
'şunu seçtik', 'sunu sectik', 'bunu yapmayalım', 'bunu yapmayalim',
|
|
17
|
-
'yerine', 'tercih ettik', 'onaylandi', 'onaylandı', 'kesinleşti', 'kesinlesti',
|
|
18
|
-
'vazgeçtik', 'vazgectik', 'iptal ettik',
|
|
19
|
-
];
|
|
20
|
-
|
|
21
|
-
const SIGNALS_EN = [
|
|
22
|
-
'decided', 'decision made', "let's go with", 'lets go with',
|
|
23
|
-
"we'll use", 'well use', 'approved', 'settled on',
|
|
24
|
-
'going with', 'chosen', 'finalized', 'rejected',
|
|
25
|
-
];
|
|
26
|
-
|
|
27
|
-
function detectDecision(text) {
|
|
28
|
-
const lower = text.toLowerCase();
|
|
29
|
-
for (const signal of SIGNALS_TR) {
|
|
30
|
-
if (lower.includes(signal)) return signal;
|
|
31
|
-
}
|
|
32
|
-
for (const signal of SIGNALS_EN) {
|
|
33
|
-
if (lower.includes(signal)) return signal;
|
|
34
|
-
}
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function main() {
|
|
39
|
-
const baseDir = findMindloreDir();
|
|
40
|
-
if (!baseDir) return;
|
|
41
|
-
|
|
42
|
-
const userText = readHookStdin(['prompt', 'content', 'message']);
|
|
43
|
-
if (!userText || userText.length < 10) return;
|
|
44
|
-
|
|
45
|
-
const signal = detectDecision(userText);
|
|
46
|
-
if (signal) {
|
|
47
|
-
process.stdout.write(`[Mindlore: Karar sinyali tespit edildi ("${signal}") — /mindlore-decide record ile kaydetmek ister misin?]\n`);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
withTelemetry('mindlore-decision-detector', main).catch(err => {
|
|
52
|
-
hookLog('mindlore-decision-detector', 'error', err?.message ?? String(err));
|
|
53
|
-
process.exit(0);
|
|
54
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* mindlore-decision-detector — UserPromptSubmit hook
|
|
6
|
+
*
|
|
7
|
+
* Detects decision signals in user messages (TR + EN).
|
|
8
|
+
* Outputs a suggestion to record the decision via /mindlore-decide.
|
|
9
|
+
* Does NOT block (exit 0) — advisory only.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { findMindloreDir, readHookStdin, hookLog, withTelemetry } = require('./lib/mindlore-common.cjs');
|
|
13
|
+
|
|
14
|
+
const SIGNALS_TR = [
|
|
15
|
+
'karar verdik', 'karar verildi', 'kararlastirdik', 'kararlaştırdık',
|
|
16
|
+
'şunu seçtik', 'sunu sectik', 'bunu yapmayalım', 'bunu yapmayalim',
|
|
17
|
+
'yerine', 'tercih ettik', 'onaylandi', 'onaylandı', 'kesinleşti', 'kesinlesti',
|
|
18
|
+
'vazgeçtik', 'vazgectik', 'iptal ettik',
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const SIGNALS_EN = [
|
|
22
|
+
'decided', 'decision made', "let's go with", 'lets go with',
|
|
23
|
+
"we'll use", 'well use', 'approved', 'settled on',
|
|
24
|
+
'going with', 'chosen', 'finalized', 'rejected',
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
function detectDecision(text) {
|
|
28
|
+
const lower = text.toLowerCase();
|
|
29
|
+
for (const signal of SIGNALS_TR) {
|
|
30
|
+
if (lower.includes(signal)) return signal;
|
|
31
|
+
}
|
|
32
|
+
for (const signal of SIGNALS_EN) {
|
|
33
|
+
if (lower.includes(signal)) return signal;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function main() {
|
|
39
|
+
const baseDir = findMindloreDir();
|
|
40
|
+
if (!baseDir) return;
|
|
41
|
+
|
|
42
|
+
const userText = readHookStdin(['prompt', 'content', 'message']);
|
|
43
|
+
if (!userText || userText.length < 10) return;
|
|
44
|
+
|
|
45
|
+
const signal = detectDecision(userText);
|
|
46
|
+
if (signal) {
|
|
47
|
+
process.stdout.write(`[Mindlore: Karar sinyali tespit edildi ("${signal}") — /mindlore-decide record ile kaydetmek ister misin?]\n`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
withTelemetry('mindlore-decision-detector', main).catch(err => {
|
|
52
|
+
hookLog('mindlore-decision-detector', 'error', err?.message ?? String(err));
|
|
53
|
+
process.exit(0);
|
|
54
|
+
});
|
|
@@ -1,222 +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, getProjectName, hookLog, withTelemetrySync } = 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-${getProjectName()}.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
|
-
try { withTelemetrySync('mindlore-dont-repeat', main); } catch (err) { hookLog('dont-repeat', 'error', err?.message ?? String(err)); }
|
|
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, getProjectName, hookLog, withTelemetrySync } = 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-${getProjectName()}.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
|
+
try { withTelemetrySync('mindlore-dont-repeat', main); } catch (err) { hookLog('dont-repeat', 'error', err?.message ?? String(err)); }
|