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,176 +1,176 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* mindlore-research-guard — PreToolUse (Agent) hook
|
|
6
|
-
*
|
|
7
|
-
* Before spawning a researcher agent, checks FTS5 for existing knowledge.
|
|
8
|
-
* - High quality + recent (30 days) match → exit 2 (block)
|
|
9
|
-
* - Old or low quality match → exit 0 with warning (additionalContext)
|
|
10
|
-
* - No match → silent pass
|
|
11
|
-
*
|
|
12
|
-
* Prevents redundant web research when knowledge already exists in DB.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
const fs = require('fs');
|
|
16
|
-
const path = require('path');
|
|
17
|
-
const { getAllDbs, requireDatabase, extractKeywords, sanitizeKeyword, hookLog, withTelemetrySync } = require('./lib/mindlore-common.cjs');
|
|
18
|
-
|
|
19
|
-
// Keywords that signal a research/web-search intent in agent prompts
|
|
20
|
-
// Note: entries with dots/stars are regex patterns, rest are literals
|
|
21
|
-
const RESEARCH_SIGNALS = [
|
|
22
|
-
'research', 'araştır', 'arastir', 'investigate', 'search for',
|
|
23
|
-
'web search', 'websearch', 'webfetch', 'fetch.*url', 'look up',
|
|
24
|
-
'find out', 'check.*docs', 'documentation.*for',
|
|
25
|
-
];
|
|
26
|
-
const RESEARCH_REGEX = new RegExp(RESEARCH_SIGNALS.join('|'), 'i');
|
|
27
|
-
|
|
28
|
-
// Exclude ingest/internal operations (they intentionally fetch URLs)
|
|
29
|
-
const EXCLUDE_REGEX = /\[mindlore:|\bmindlore-ingest\b|ingest.*url|save.*raw|\[research-override\]/i;
|
|
30
|
-
|
|
31
|
-
const MAX_AGE_DAYS = 30;
|
|
32
|
-
|
|
33
|
-
function isRecent(dateStr) {
|
|
34
|
-
if (!dateStr) return false;
|
|
35
|
-
const d = new Date(dateStr);
|
|
36
|
-
if (isNaN(d.getTime())) return false;
|
|
37
|
-
const diff = (Date.now() - d.getTime()) / (1000 * 60 * 60 * 24);
|
|
38
|
-
return diff <= MAX_AGE_DAYS;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Search FTS5 using a single OR query instead of per-path×keyword loop.
|
|
43
|
-
* Returns top matches with quality and date from FTS5 columns (no file I/O).
|
|
44
|
-
*/
|
|
45
|
-
function searchDbs(keywords) {
|
|
46
|
-
const Database = requireDatabase();
|
|
47
|
-
if (!Database) return [];
|
|
48
|
-
|
|
49
|
-
const sanitized = keywords.map(sanitizeKeyword).filter(Boolean);
|
|
50
|
-
if (sanitized.length === 0) return [];
|
|
51
|
-
|
|
52
|
-
const matchQuery = sanitized.join(' OR ');
|
|
53
|
-
const dbPaths = getAllDbs();
|
|
54
|
-
const results = [];
|
|
55
|
-
|
|
56
|
-
for (const dbPath of dbPaths) {
|
|
57
|
-
try {
|
|
58
|
-
const db = new Database(dbPath, { readonly: true });
|
|
59
|
-
|
|
60
|
-
// Single FTS5 query — O(1) instead of O(paths × keywords)
|
|
61
|
-
const rows = db.prepare(
|
|
62
|
-
`SELECT path, slug, title, description, quality, date_captured, rank
|
|
63
|
-
FROM mindlore_fts
|
|
64
|
-
WHERE mindlore_fts MATCH ?
|
|
65
|
-
ORDER BY rank
|
|
66
|
-
LIMIT 10`
|
|
67
|
-
).all(matchQuery);
|
|
68
|
-
|
|
69
|
-
for (const row of rows) {
|
|
70
|
-
const quality = (row.quality || 'medium').toLowerCase();
|
|
71
|
-
const date_captured = row.date_captured || null;
|
|
72
|
-
|
|
73
|
-
results.push({
|
|
74
|
-
slug: row.slug || path.basename(row.path, '.md'),
|
|
75
|
-
title: row.title || row.description || row.slug || '',
|
|
76
|
-
quality,
|
|
77
|
-
date_captured,
|
|
78
|
-
recent: isRecent(date_captured),
|
|
79
|
-
rank: row.rank,
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
db.close();
|
|
84
|
-
} catch (_err) { /* db open or query failed */ }
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Sort by rank (lower = better match in FTS5)
|
|
88
|
-
results.sort((a, b) => a.rank - b.rank);
|
|
89
|
-
return results.slice(0, 5);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function main() {
|
|
93
|
-
let input;
|
|
94
|
-
try {
|
|
95
|
-
const raw = fs.readFileSync(0, 'utf8').trim();
|
|
96
|
-
if (!raw) return;
|
|
97
|
-
input = JSON.parse(raw);
|
|
98
|
-
} catch (_err) {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const toolName = input.tool_name || '';
|
|
103
|
-
if (toolName !== 'Agent') return;
|
|
104
|
-
|
|
105
|
-
const toolInput = input.tool_input || {};
|
|
106
|
-
|
|
107
|
-
// Only block agents with web access — let local-only agents pass
|
|
108
|
-
const WEB_CAPABLE_TYPES = ['researcher', 'general-purpose'];
|
|
109
|
-
const LOCAL_ONLY_TYPES = [
|
|
110
|
-
'Explore', 'coder', 'code-reviewer', 'Plan',
|
|
111
|
-
'bug-analyzer', 'security-reviewer', 'contrarian',
|
|
112
|
-
'scope-guardian', 'quality-gate', 'test-runner',
|
|
113
|
-
];
|
|
114
|
-
const subagentType = toolInput.subagent_type || '';
|
|
115
|
-
const description = (toolInput.description || '').toLowerCase();
|
|
116
|
-
|
|
117
|
-
// Known local-only agent → always pass
|
|
118
|
-
if (LOCAL_ONLY_TYPES.includes(subagentType)) return;
|
|
119
|
-
|
|
120
|
-
// Known web-capable agent → continue to FTS5 check
|
|
121
|
-
// Unknown or empty subagent_type → check description for research intent
|
|
122
|
-
if (subagentType && !WEB_CAPABLE_TYPES.includes(subagentType)) return;
|
|
123
|
-
|
|
124
|
-
// If no subagent_type, check description for web research intent (reuse RESEARCH_REGEX)
|
|
125
|
-
if (!subagentType && !RESEARCH_REGEX.test(description)) return;
|
|
126
|
-
|
|
127
|
-
const prompt = (toolInput.prompt || '') + ' ' + (toolInput.description || '');
|
|
128
|
-
|
|
129
|
-
// Skip mindlore internal operations and explicit overrides
|
|
130
|
-
if (EXCLUDE_REGEX.test(prompt)) return;
|
|
131
|
-
|
|
132
|
-
// If subagent_type is a known research type, skip prompt-level regex check
|
|
133
|
-
// Otherwise require research signals in the prompt text
|
|
134
|
-
const isKnownResearchType = WEB_CAPABLE_TYPES.includes(subagentType);
|
|
135
|
-
if (!isKnownResearchType && !RESEARCH_REGEX.test(prompt)) return;
|
|
136
|
-
|
|
137
|
-
const keywords = extractKeywords(prompt, 10);
|
|
138
|
-
if (keywords.length < 2) return;
|
|
139
|
-
|
|
140
|
-
const matches = searchDbs(keywords);
|
|
141
|
-
if (matches.length === 0) return;
|
|
142
|
-
|
|
143
|
-
// Prevents false positives like "claude-code-repo" matching on generic words
|
|
144
|
-
const lcKeywords = keywords.map((k) => k.toLowerCase());
|
|
145
|
-
const relevantMatches = matches.filter((m) => {
|
|
146
|
-
const haystack = `${m.slug} ${m.title}`.toLowerCase();
|
|
147
|
-
const overlap = lcKeywords.filter((k) => haystack.includes(k));
|
|
148
|
-
return overlap.length >= 2;
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
if (relevantMatches.length === 0) return;
|
|
152
|
-
|
|
153
|
-
// Check for high-quality recent matches among relevant ones
|
|
154
|
-
const strongMatches = relevantMatches.filter((m) => m.quality === 'high' && m.recent);
|
|
155
|
-
|
|
156
|
-
if (strongMatches.length > 0) {
|
|
157
|
-
const slugList = strongMatches.map((m) => ` - ${m.slug} (${m.title})`).join('\n');
|
|
158
|
-
const msg = `[mindlore-research-guard] BLOK: Bu konuda guncel, yuksek kaliteli bilgi DB'de zaten var.\n` +
|
|
159
|
-
`Once mevcut bilgiyi oku:\n${slugList}\n` +
|
|
160
|
-
`Eger bilgi yetersizse, prompt'a "[research-override]" ekleyerek tekrar dene.`;
|
|
161
|
-
process.stderr.write(msg);
|
|
162
|
-
process.exit(2);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// WARN: relevant but old or low-quality matches exist
|
|
166
|
-
const slugList = relevantMatches.map((m) => `${m.slug} (${m.quality}, ${m.date_captured || 'tarih yok'})`).join(', ');
|
|
167
|
-
const output = {
|
|
168
|
-
hookSpecificOutput: {
|
|
169
|
-
hookEventName: 'PreToolUse',
|
|
170
|
-
additionalContext: `[mindlore-research-guard] DB'de ilgili bilgi var ama eski/dusuk kalite: ${slugList}. Guncelleme gerekebilir — arastirma sonrasi DB'yi guncelle.`,
|
|
171
|
-
},
|
|
172
|
-
};
|
|
173
|
-
process.stdout.write(JSON.stringify(output));
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
try { withTelemetrySync('mindlore-research-guard', main); } catch (err) { hookLog('research-guard', 'error', err?.message ?? String(err)); }
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* mindlore-research-guard — PreToolUse (Agent) hook
|
|
6
|
+
*
|
|
7
|
+
* Before spawning a researcher agent, checks FTS5 for existing knowledge.
|
|
8
|
+
* - High quality + recent (30 days) match → exit 2 (block)
|
|
9
|
+
* - Old or low quality match → exit 0 with warning (additionalContext)
|
|
10
|
+
* - No match → silent pass
|
|
11
|
+
*
|
|
12
|
+
* Prevents redundant web research when knowledge already exists in DB.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const { getAllDbs, requireDatabase, extractKeywords, sanitizeKeyword, hookLog, withTelemetrySync } = require('./lib/mindlore-common.cjs');
|
|
18
|
+
|
|
19
|
+
// Keywords that signal a research/web-search intent in agent prompts
|
|
20
|
+
// Note: entries with dots/stars are regex patterns, rest are literals
|
|
21
|
+
const RESEARCH_SIGNALS = [
|
|
22
|
+
'research', 'araştır', 'arastir', 'investigate', 'search for',
|
|
23
|
+
'web search', 'websearch', 'webfetch', 'fetch.*url', 'look up',
|
|
24
|
+
'find out', 'check.*docs', 'documentation.*for',
|
|
25
|
+
];
|
|
26
|
+
const RESEARCH_REGEX = new RegExp(RESEARCH_SIGNALS.join('|'), 'i');
|
|
27
|
+
|
|
28
|
+
// Exclude ingest/internal operations (they intentionally fetch URLs)
|
|
29
|
+
const EXCLUDE_REGEX = /\[mindlore:|\bmindlore-ingest\b|ingest.*url|save.*raw|\[research-override\]/i;
|
|
30
|
+
|
|
31
|
+
const MAX_AGE_DAYS = 30;
|
|
32
|
+
|
|
33
|
+
function isRecent(dateStr) {
|
|
34
|
+
if (!dateStr) return false;
|
|
35
|
+
const d = new Date(dateStr);
|
|
36
|
+
if (isNaN(d.getTime())) return false;
|
|
37
|
+
const diff = (Date.now() - d.getTime()) / (1000 * 60 * 60 * 24);
|
|
38
|
+
return diff <= MAX_AGE_DAYS;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Search FTS5 using a single OR query instead of per-path×keyword loop.
|
|
43
|
+
* Returns top matches with quality and date from FTS5 columns (no file I/O).
|
|
44
|
+
*/
|
|
45
|
+
function searchDbs(keywords) {
|
|
46
|
+
const Database = requireDatabase();
|
|
47
|
+
if (!Database) return [];
|
|
48
|
+
|
|
49
|
+
const sanitized = keywords.map(sanitizeKeyword).filter(Boolean);
|
|
50
|
+
if (sanitized.length === 0) return [];
|
|
51
|
+
|
|
52
|
+
const matchQuery = sanitized.join(' OR ');
|
|
53
|
+
const dbPaths = getAllDbs();
|
|
54
|
+
const results = [];
|
|
55
|
+
|
|
56
|
+
for (const dbPath of dbPaths) {
|
|
57
|
+
try {
|
|
58
|
+
const db = new Database(dbPath, { readonly: true });
|
|
59
|
+
|
|
60
|
+
// Single FTS5 query — O(1) instead of O(paths × keywords)
|
|
61
|
+
const rows = db.prepare(
|
|
62
|
+
`SELECT path, slug, title, description, quality, date_captured, rank
|
|
63
|
+
FROM mindlore_fts
|
|
64
|
+
WHERE mindlore_fts MATCH ?
|
|
65
|
+
ORDER BY rank
|
|
66
|
+
LIMIT 10`
|
|
67
|
+
).all(matchQuery);
|
|
68
|
+
|
|
69
|
+
for (const row of rows) {
|
|
70
|
+
const quality = (row.quality || 'medium').toLowerCase();
|
|
71
|
+
const date_captured = row.date_captured || null;
|
|
72
|
+
|
|
73
|
+
results.push({
|
|
74
|
+
slug: row.slug || path.basename(row.path, '.md'),
|
|
75
|
+
title: row.title || row.description || row.slug || '',
|
|
76
|
+
quality,
|
|
77
|
+
date_captured,
|
|
78
|
+
recent: isRecent(date_captured),
|
|
79
|
+
rank: row.rank,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
db.close();
|
|
84
|
+
} catch (_err) { /* db open or query failed */ }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Sort by rank (lower = better match in FTS5)
|
|
88
|
+
results.sort((a, b) => a.rank - b.rank);
|
|
89
|
+
return results.slice(0, 5);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function main() {
|
|
93
|
+
let input;
|
|
94
|
+
try {
|
|
95
|
+
const raw = fs.readFileSync(0, 'utf8').trim();
|
|
96
|
+
if (!raw) return;
|
|
97
|
+
input = JSON.parse(raw);
|
|
98
|
+
} catch (_err) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const toolName = input.tool_name || '';
|
|
103
|
+
if (toolName !== 'Agent') return;
|
|
104
|
+
|
|
105
|
+
const toolInput = input.tool_input || {};
|
|
106
|
+
|
|
107
|
+
// Only block agents with web access — let local-only agents pass
|
|
108
|
+
const WEB_CAPABLE_TYPES = ['researcher', 'general-purpose'];
|
|
109
|
+
const LOCAL_ONLY_TYPES = [
|
|
110
|
+
'Explore', 'coder', 'code-reviewer', 'Plan',
|
|
111
|
+
'bug-analyzer', 'security-reviewer', 'contrarian',
|
|
112
|
+
'scope-guardian', 'quality-gate', 'test-runner',
|
|
113
|
+
];
|
|
114
|
+
const subagentType = toolInput.subagent_type || '';
|
|
115
|
+
const description = (toolInput.description || '').toLowerCase();
|
|
116
|
+
|
|
117
|
+
// Known local-only agent → always pass
|
|
118
|
+
if (LOCAL_ONLY_TYPES.includes(subagentType)) return;
|
|
119
|
+
|
|
120
|
+
// Known web-capable agent → continue to FTS5 check
|
|
121
|
+
// Unknown or empty subagent_type → check description for research intent
|
|
122
|
+
if (subagentType && !WEB_CAPABLE_TYPES.includes(subagentType)) return;
|
|
123
|
+
|
|
124
|
+
// If no subagent_type, check description for web research intent (reuse RESEARCH_REGEX)
|
|
125
|
+
if (!subagentType && !RESEARCH_REGEX.test(description)) return;
|
|
126
|
+
|
|
127
|
+
const prompt = (toolInput.prompt || '') + ' ' + (toolInput.description || '');
|
|
128
|
+
|
|
129
|
+
// Skip mindlore internal operations and explicit overrides
|
|
130
|
+
if (EXCLUDE_REGEX.test(prompt)) return;
|
|
131
|
+
|
|
132
|
+
// If subagent_type is a known research type, skip prompt-level regex check
|
|
133
|
+
// Otherwise require research signals in the prompt text
|
|
134
|
+
const isKnownResearchType = WEB_CAPABLE_TYPES.includes(subagentType);
|
|
135
|
+
if (!isKnownResearchType && !RESEARCH_REGEX.test(prompt)) return;
|
|
136
|
+
|
|
137
|
+
const keywords = extractKeywords(prompt, 10);
|
|
138
|
+
if (keywords.length < 2) return;
|
|
139
|
+
|
|
140
|
+
const matches = searchDbs(keywords);
|
|
141
|
+
if (matches.length === 0) return;
|
|
142
|
+
|
|
143
|
+
// Prevents false positives like "claude-code-repo" matching on generic words
|
|
144
|
+
const lcKeywords = keywords.map((k) => k.toLowerCase());
|
|
145
|
+
const relevantMatches = matches.filter((m) => {
|
|
146
|
+
const haystack = `${m.slug} ${m.title}`.toLowerCase();
|
|
147
|
+
const overlap = lcKeywords.filter((k) => haystack.includes(k));
|
|
148
|
+
return overlap.length >= 2;
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
if (relevantMatches.length === 0) return;
|
|
152
|
+
|
|
153
|
+
// Check for high-quality recent matches among relevant ones
|
|
154
|
+
const strongMatches = relevantMatches.filter((m) => m.quality === 'high' && m.recent);
|
|
155
|
+
|
|
156
|
+
if (strongMatches.length > 0) {
|
|
157
|
+
const slugList = strongMatches.map((m) => ` - ${m.slug} (${m.title})`).join('\n');
|
|
158
|
+
const msg = `[mindlore-research-guard] BLOK: Bu konuda guncel, yuksek kaliteli bilgi DB'de zaten var.\n` +
|
|
159
|
+
`Once mevcut bilgiyi oku:\n${slugList}\n` +
|
|
160
|
+
`Eger bilgi yetersizse, prompt'a "[research-override]" ekleyerek tekrar dene.`;
|
|
161
|
+
process.stderr.write(msg);
|
|
162
|
+
process.exit(2);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// WARN: relevant but old or low-quality matches exist
|
|
166
|
+
const slugList = relevantMatches.map((m) => `${m.slug} (${m.quality}, ${m.date_captured || 'tarih yok'})`).join(', ');
|
|
167
|
+
const output = {
|
|
168
|
+
hookSpecificOutput: {
|
|
169
|
+
hookEventName: 'PreToolUse',
|
|
170
|
+
additionalContext: `[mindlore-research-guard] DB'de ilgili bilgi var ama eski/dusuk kalite: ${slugList}. Guncelleme gerekebilir — arastirma sonrasi DB'yi guncelle.`,
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
process.stdout.write(JSON.stringify(output));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
try { withTelemetrySync('mindlore-research-guard', main); } catch (err) { hookLog('research-guard', 'error', err?.message ?? String(err)); }
|