mindlore 0.4.3 → 0.5.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 +2 -1
- package/dist/scripts/init.js +45 -3
- package/dist/scripts/init.js.map +1 -1
- package/dist/scripts/lib/constants.d.ts +15 -0
- package/dist/scripts/lib/constants.d.ts.map +1 -1
- package/dist/scripts/lib/constants.js +16 -1
- package/dist/scripts/lib/constants.js.map +1 -1
- package/dist/scripts/lib/db-helpers.d.ts +15 -0
- package/dist/scripts/lib/db-helpers.d.ts.map +1 -1
- package/dist/scripts/lib/db-helpers.js +51 -0
- package/dist/scripts/lib/db-helpers.js.map +1 -1
- package/dist/scripts/lib/embedding.d.ts +5 -0
- package/dist/scripts/lib/embedding.d.ts.map +1 -0
- package/dist/scripts/lib/embedding.js +44 -0
- package/dist/scripts/lib/embedding.js.map +1 -0
- package/dist/scripts/lib/hybrid-search.d.ts +62 -0
- package/dist/scripts/lib/hybrid-search.d.ts.map +1 -0
- package/dist/scripts/lib/hybrid-search.js +150 -0
- package/dist/scripts/lib/hybrid-search.js.map +1 -0
- package/dist/scripts/lib/migrations-v051.d.ts +3 -0
- package/dist/scripts/lib/migrations-v051.d.ts.map +1 -0
- package/dist/scripts/lib/migrations-v051.js +24 -0
- package/dist/scripts/lib/migrations-v051.js.map +1 -0
- package/dist/scripts/lib/migrations.d.ts +4 -0
- package/dist/scripts/lib/migrations.d.ts.map +1 -0
- package/dist/scripts/lib/migrations.js +40 -0
- package/dist/scripts/lib/migrations.js.map +1 -0
- package/dist/scripts/lib/privacy-filter.d.ts +3 -0
- package/dist/scripts/lib/privacy-filter.d.ts.map +1 -0
- package/dist/scripts/lib/privacy-filter.js +28 -0
- package/dist/scripts/lib/privacy-filter.js.map +1 -0
- package/dist/scripts/lib/schema-version.d.ts +13 -0
- package/dist/scripts/lib/schema-version.d.ts.map +1 -0
- package/dist/scripts/lib/schema-version.js +37 -0
- package/dist/scripts/lib/schema-version.js.map +1 -0
- package/dist/scripts/lib/similarity.d.ts +12 -0
- package/dist/scripts/lib/similarity.d.ts.map +1 -0
- package/dist/scripts/lib/similarity.js +64 -0
- package/dist/scripts/lib/similarity.js.map +1 -0
- package/dist/scripts/lib/synonym.d.ts +4 -0
- package/dist/scripts/lib/synonym.d.ts.map +1 -0
- package/dist/scripts/lib/synonym.js +37 -0
- package/dist/scripts/lib/synonym.js.map +1 -0
- package/dist/scripts/mindlore-fts5-index.d.ts +2 -1
- package/dist/scripts/mindlore-fts5-index.d.ts.map +1 -1
- package/dist/scripts/mindlore-fts5-index.js +71 -5
- package/dist/scripts/mindlore-fts5-index.js.map +1 -1
- package/dist/scripts/mindlore-fts5-search.d.ts +3 -2
- package/dist/scripts/mindlore-fts5-search.d.ts.map +1 -1
- package/dist/scripts/mindlore-fts5-search.js +89 -35
- package/dist/scripts/mindlore-fts5-search.js.map +1 -1
- package/dist/scripts/mindlore-health-check.js +105 -0
- package/dist/scripts/mindlore-health-check.js.map +1 -1
- package/dist/scripts/quality-populate.js +8 -4
- package/dist/scripts/quality-populate.js.map +1 -1
- package/dist/tests/cc-memory-sync.test.d.ts +2 -0
- package/dist/tests/cc-memory-sync.test.d.ts.map +1 -0
- package/dist/tests/cc-memory-sync.test.js +121 -0
- package/dist/tests/cc-memory-sync.test.js.map +1 -0
- package/dist/tests/embedding.test.d.ts +6 -0
- package/dist/tests/embedding.test.d.ts.map +1 -0
- package/dist/tests/embedding.test.js +71 -0
- package/dist/tests/embedding.test.js.map +1 -0
- package/dist/tests/episode-file.test.d.ts +2 -0
- package/dist/tests/episode-file.test.d.ts.map +1 -0
- package/dist/tests/episode-file.test.js +79 -0
- package/dist/tests/episode-file.test.js.map +1 -0
- package/dist/tests/fts5.test.js +82 -0
- package/dist/tests/fts5.test.js.map +1 -1
- package/dist/tests/helpers/db.d.ts +6 -0
- package/dist/tests/helpers/db.d.ts.map +1 -1
- package/dist/tests/helpers/db.js +29 -0
- package/dist/tests/helpers/db.js.map +1 -1
- package/dist/tests/hook-logging.test.d.ts +2 -0
- package/dist/tests/hook-logging.test.d.ts.map +1 -0
- package/dist/tests/hook-logging.test.js +108 -0
- package/dist/tests/hook-logging.test.js.map +1 -0
- package/dist/tests/hybrid-search.test.d.ts +2 -0
- package/dist/tests/hybrid-search.test.d.ts.map +1 -0
- package/dist/tests/hybrid-search.test.js +114 -0
- package/dist/tests/hybrid-search.test.js.map +1 -0
- package/dist/tests/index-cli-embed.test.d.ts +7 -0
- package/dist/tests/index-cli-embed.test.d.ts.map +1 -0
- package/dist/tests/index-cli-embed.test.js +128 -0
- package/dist/tests/index-cli-embed.test.js.map +1 -0
- package/dist/tests/privacy-filter.test.d.ts +2 -0
- package/dist/tests/privacy-filter.test.d.ts.map +1 -0
- package/dist/tests/privacy-filter.test.js +56 -0
- package/dist/tests/privacy-filter.test.js.map +1 -0
- package/dist/tests/schema-version.test.d.ts +2 -0
- package/dist/tests/schema-version.test.d.ts.map +1 -0
- package/dist/tests/schema-version.test.js +127 -0
- package/dist/tests/schema-version.test.js.map +1 -0
- package/dist/tests/search-cli-hybrid.test.d.ts +6 -0
- package/dist/tests/search-cli-hybrid.test.d.ts.map +1 -0
- package/dist/tests/search-cli-hybrid.test.js +103 -0
- package/dist/tests/search-cli-hybrid.test.js.map +1 -0
- package/dist/tests/search-hook.test.js +44 -0
- package/dist/tests/search-hook.test.js.map +1 -1
- package/dist/tests/similarity.test.d.ts +2 -0
- package/dist/tests/similarity.test.d.ts.map +1 -0
- package/dist/tests/similarity.test.js +61 -0
- package/dist/tests/similarity.test.js.map +1 -0
- package/dist/tests/synonym.test.d.ts +2 -0
- package/dist/tests/synonym.test.d.ts.map +1 -0
- package/dist/tests/synonym.test.js +47 -0
- package/dist/tests/synonym.test.js.map +1 -0
- package/dist/tests/token-budget.test.d.ts +2 -0
- package/dist/tests/token-budget.test.d.ts.map +1 -0
- package/dist/tests/token-budget.test.js +32 -0
- package/dist/tests/token-budget.test.js.map +1 -0
- package/hooks/lib/mindlore-common.cjs +120 -0
- package/hooks/mindlore-index.cjs +82 -2
- package/hooks/mindlore-search.cjs +102 -35
- package/hooks/mindlore-session-end.cjs +129 -39
- package/hooks/mindlore-session-focus.cjs +24 -3
- package/package.json +6 -4
- package/plugin.json +1 -1
- package/skills/mindlore-ingest/SKILL.md +7 -1
- package/templates/config.json +20 -1
|
@@ -10,11 +10,18 @@
|
|
|
10
10
|
|
|
11
11
|
const fs = require('fs');
|
|
12
12
|
const path = require('path');
|
|
13
|
-
const { getAllDbs, requireDatabase, extractHeadings, readHookStdin, extractKeywords } = require('./lib/mindlore-common.cjs');
|
|
13
|
+
const { getAllDbs, requireDatabase, extractHeadings, readHookStdin, extractKeywords, sanitizeKeyword, readConfig, loadSqliteVecCjs, hasVecTableCjs, hookLog } = require('./lib/mindlore-common.cjs');
|
|
14
14
|
|
|
15
15
|
const MAX_RESULTS = 3;
|
|
16
16
|
const MIN_QUERY_WORDS = 3;
|
|
17
|
-
|
|
17
|
+
|
|
18
|
+
// Try to load hybrid search module (built TS)
|
|
19
|
+
let hybridSearchMod;
|
|
20
|
+
try {
|
|
21
|
+
hybridSearchMod = require('../dist/scripts/lib/hybrid-search.js');
|
|
22
|
+
} catch (_err) {
|
|
23
|
+
// hybrid-search not built yet — pure FTS5 mode
|
|
24
|
+
}
|
|
18
25
|
|
|
19
26
|
/**
|
|
20
27
|
* Search a single DB and return scored results with their baseDir.
|
|
@@ -24,35 +31,79 @@ function searchDb(dbPath, keywords, Database) {
|
|
|
24
31
|
const db = new Database(dbPath, { readonly: true });
|
|
25
32
|
const results = [];
|
|
26
33
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
for (const row of allPaths) {
|
|
35
|
-
let hits = 0;
|
|
36
|
-
let totalRank = 0;
|
|
34
|
+
// v0.5.0: Try hybrid search with synonym expansion (no embedding — hooks are sync)
|
|
35
|
+
if (hybridSearchMod && loadSqliteVecCjs(db) && hasVecTableCjs(db)) {
|
|
36
|
+
try {
|
|
37
|
+
const config = readConfig(baseDir);
|
|
38
|
+
const synonyms = (config && config.synonyms) ? config.synonyms : {};
|
|
37
39
|
|
|
40
|
+
// Expand keywords with synonyms
|
|
41
|
+
const expandedTerms = keywords.slice();
|
|
38
42
|
for (const kw of keywords) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const r = matchStmt.get(row.path, '"' + sanitized + '"');
|
|
43
|
-
if (r) {
|
|
44
|
-
hits++;
|
|
45
|
-
totalRank += r.rank;
|
|
46
|
-
}
|
|
47
|
-
} catch (_err) {
|
|
48
|
-
// FTS5 query error for this keyword — skip
|
|
43
|
+
const lower = kw.toLowerCase();
|
|
44
|
+
if (synonyms[lower]) {
|
|
45
|
+
expandedTerms.push(...synonyms[lower]);
|
|
49
46
|
}
|
|
50
47
|
}
|
|
51
48
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
const fusedResults = hybridSearchMod.hybridSearch(db, expandedTerms.join(' '), {
|
|
50
|
+
maxResults: MAX_RESULTS,
|
|
51
|
+
project: path.basename(process.cwd()),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (fusedResults.length > 0) {
|
|
55
|
+
for (const r of fusedResults) {
|
|
56
|
+
const filePath = r.path || '';
|
|
57
|
+
let headings = [];
|
|
58
|
+
if (filePath && fs.existsSync(filePath)) {
|
|
59
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
60
|
+
headings = extractHeadings(content, 3);
|
|
61
|
+
}
|
|
62
|
+
results.push({
|
|
63
|
+
path: filePath,
|
|
64
|
+
slug: r.slug,
|
|
65
|
+
description: r.description || '',
|
|
66
|
+
category: r.category || '',
|
|
67
|
+
title: r.title || '',
|
|
68
|
+
tags: r.tags || '',
|
|
69
|
+
headings,
|
|
70
|
+
hits: 1,
|
|
71
|
+
rank: r.score,
|
|
72
|
+
baseDir,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
db.close();
|
|
76
|
+
return results;
|
|
55
77
|
}
|
|
78
|
+
} catch (_err) {
|
|
79
|
+
// Hybrid search failed — fall through to FTS5
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// FTS5-only fallback: OR-joined single query (replaces O(docs×keywords) nested loop)
|
|
84
|
+
try {
|
|
85
|
+
const sanitized = keywords.map(sanitizeKeyword).filter(Boolean);
|
|
86
|
+
if (sanitized.length === 0) { db.close(); return results; }
|
|
87
|
+
|
|
88
|
+
const ftsQuery = sanitized.join(' OR ');
|
|
89
|
+
const rows = db.prepare(
|
|
90
|
+
`SELECT path, slug, description, category, title, tags, rank
|
|
91
|
+
FROM mindlore_fts WHERE mindlore_fts MATCH ? ORDER BY rank LIMIT ?`
|
|
92
|
+
).all(ftsQuery, MAX_RESULTS * 2);
|
|
93
|
+
|
|
94
|
+
for (const r of rows) {
|
|
95
|
+
results.push({
|
|
96
|
+
path: r.path || '',
|
|
97
|
+
slug: r.slug,
|
|
98
|
+
description: r.description || '',
|
|
99
|
+
category: r.category || '',
|
|
100
|
+
title: r.title || '',
|
|
101
|
+
tags: r.tags || '',
|
|
102
|
+
headings: [], // populated later in main() after slicing
|
|
103
|
+
hits: sanitized.length,
|
|
104
|
+
rank: r.rank,
|
|
105
|
+
baseDir,
|
|
106
|
+
});
|
|
56
107
|
}
|
|
57
108
|
} catch (_err) {
|
|
58
109
|
// FTS5 query error — silently skip
|
|
@@ -69,7 +120,7 @@ function searchDb(dbPath, keywords, Database) {
|
|
|
69
120
|
*/
|
|
70
121
|
function searchEpisodesFts(db, keywords) {
|
|
71
122
|
try {
|
|
72
|
-
const ftsQuery = keywords.map(
|
|
123
|
+
const ftsQuery = keywords.map(sanitizeKeyword).filter(Boolean).join(' OR ');
|
|
73
124
|
const rows = db.prepare(
|
|
74
125
|
"SELECT title, category, slug, tags FROM mindlore_fts WHERE type = 'episode' AND mindlore_fts MATCH ? LIMIT 2"
|
|
75
126
|
).all(ftsQuery);
|
|
@@ -119,17 +170,32 @@ function main() {
|
|
|
119
170
|
const relevant = unique.slice(0, MAX_RESULTS);
|
|
120
171
|
if (relevant.length === 0) return;
|
|
121
172
|
|
|
173
|
+
// Populate headings only for final results (avoid reading extra files)
|
|
174
|
+
for (const r of relevant) {
|
|
175
|
+
if (r.path && r.headings.length === 0 && fs.existsSync(r.path)) {
|
|
176
|
+
try {
|
|
177
|
+
const content = fs.readFileSync(r.path, 'utf8');
|
|
178
|
+
r.headings = extractHeadings(content, 3);
|
|
179
|
+
} catch (_err) { /* skip */ }
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Token budget from config
|
|
184
|
+
const config = readConfig(path.dirname(dbPaths[0]));
|
|
185
|
+
const budget = (config && config.tokenBudget) || {};
|
|
186
|
+
// Defaults match DEFAULT_TOKEN_BUDGET in scripts/lib/constants.ts
|
|
187
|
+
const perResultChars = ((budget.perResult || 500) * 4); // ~4 chars/token
|
|
188
|
+
const totalChars = ((budget.searchResults || 1500) * 4);
|
|
189
|
+
|
|
122
190
|
// Build rich inject output
|
|
123
191
|
const output = [];
|
|
192
|
+
let totalUsed = 0;
|
|
124
193
|
for (const r of relevant) {
|
|
194
|
+
if (totalUsed >= totalChars) break;
|
|
125
195
|
const meta = r.meta || {};
|
|
126
196
|
const relativePath = path.relative(r.baseDir, r.path).replace(/\\/g, '/');
|
|
127
197
|
|
|
128
|
-
|
|
129
|
-
if (fs.existsSync(r.path)) {
|
|
130
|
-
const content = fs.readFileSync(r.path, 'utf8');
|
|
131
|
-
headings = extractHeadings(content, 5);
|
|
132
|
-
}
|
|
198
|
+
const headings = r.headings || [];
|
|
133
199
|
|
|
134
200
|
const category = meta.category || path.dirname(relativePath).split('/')[0];
|
|
135
201
|
const title = meta.title || meta.slug || path.basename(r.path, '.md');
|
|
@@ -137,9 +203,10 @@ function main() {
|
|
|
137
203
|
|
|
138
204
|
const headingStr = headings.length > 0 ? `\nBasliklar: ${headings.join(', ')}` : '';
|
|
139
205
|
const tagsStr = meta.tags ? `\nTags: ${meta.tags}` : '';
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
206
|
+
const entry = `[Mindlore: ${category}/${title}] ${description}\nDosya: ${relativePath}${tagsStr}${headingStr}`;
|
|
207
|
+
const truncated = entry.slice(0, perResultChars);
|
|
208
|
+
totalUsed += truncated.length;
|
|
209
|
+
output.push(truncated);
|
|
143
210
|
}
|
|
144
211
|
|
|
145
212
|
// v0.4.0: Search episode mirrors in FTS5 (reuses searchDb's DB path, no extra open)
|
|
@@ -162,4 +229,4 @@ function main() {
|
|
|
162
229
|
}
|
|
163
230
|
}
|
|
164
231
|
|
|
165
|
-
main();
|
|
232
|
+
try { main(); } catch (err) { hookLog('search', 'error', err?.message ?? String(err)); }
|
|
@@ -13,22 +13,48 @@ const fs = require('fs');
|
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const os = require('os');
|
|
15
15
|
const { execSync, spawn } = require('child_process');
|
|
16
|
-
const { findMindloreDir, globalDir, getProjectName, openDatabase, ensureEpisodesTable, hasEpisodesTable, insertBareEpisode, insertFtsRow } = require('./lib/mindlore-common.cjs');
|
|
16
|
+
const { findMindloreDir, globalDir, getProjectName, openDatabase, ensureEpisodesTable, hasEpisodesTable, insertBareEpisode, insertFtsRow, hookLog, SHARED_EXPORT_DIRS, resolveWin32Bin } = require('./lib/mindlore-common.cjs');
|
|
17
|
+
|
|
18
|
+
const EXPORT_DIRS = SHARED_EXPORT_DIRS;
|
|
17
19
|
|
|
18
20
|
// --worker mode: heavy ops run in detached child process (survives parent exit)
|
|
19
21
|
if (process.argv.includes('--worker')) {
|
|
22
|
+
hookLog('session-end', 'info', 'worker started, pid=' + process.pid);
|
|
20
23
|
const dataPath = process.argv[process.argv.indexOf('--worker') + 1];
|
|
24
|
+
let payload;
|
|
21
25
|
try {
|
|
22
26
|
const raw = fs.readFileSync(dataPath, 'utf8');
|
|
23
|
-
fs.unlinkSync(dataPath);
|
|
24
|
-
|
|
25
|
-
writeBareEpisode(baseDir, project, commits, changedFiles, reads);
|
|
26
|
-
syncObsidian(baseDir);
|
|
27
|
-
syncGlobalRepo();
|
|
27
|
+
fs.unlinkSync(dataPath);
|
|
28
|
+
payload = JSON.parse(raw);
|
|
28
29
|
} catch (_err) {
|
|
29
|
-
|
|
30
|
+
hookLog('session-end', 'error', 'payload read failed: ' + (_err?.message ?? _err));
|
|
31
|
+
process.exit(0);
|
|
30
32
|
}
|
|
31
|
-
|
|
33
|
+
const { baseDir, project, commits, changedFiles, reads } = payload;
|
|
34
|
+
|
|
35
|
+
async function safeRunAsync(fn, label) {
|
|
36
|
+
try {
|
|
37
|
+
await fn();
|
|
38
|
+
hookLog('session-end', 'info', label + ' OK');
|
|
39
|
+
} catch (e) {
|
|
40
|
+
hookLog('session-end', 'error', label + ' FAIL: ' + e?.message);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
(async () => {
|
|
45
|
+
// Episode writes share DB — run sequentially first
|
|
46
|
+
await safeRunAsync(() => writeBareEpisode(baseDir, project, commits, changedFiles, reads), 'episode');
|
|
47
|
+
await safeRunAsync(() => writeEpisodeFile(baseDir, project, commits, changedFiles, reads), 'episode-file');
|
|
48
|
+
|
|
49
|
+
// Obsidian + git-sync are independent — run in parallel
|
|
50
|
+
await Promise.allSettled([
|
|
51
|
+
safeRunAsync(() => syncObsidian(baseDir), 'obsidian'),
|
|
52
|
+
safeRunAsync(() => syncGlobalRepo(), 'git-sync'),
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
hookLog('session-end', 'info', 'worker done');
|
|
56
|
+
process.exit(0);
|
|
57
|
+
})();
|
|
32
58
|
}
|
|
33
59
|
|
|
34
60
|
function formatDate(date) {
|
|
@@ -47,9 +73,10 @@ function formatDate(date) {
|
|
|
47
73
|
function getRecentGitInfo() {
|
|
48
74
|
try {
|
|
49
75
|
// --name-only includes file names after each commit entry
|
|
50
|
-
const raw = execSync('git log --oneline -5 --name-only
|
|
76
|
+
const raw = execSync('git log --oneline -5 --name-only', {
|
|
51
77
|
encoding: 'utf8',
|
|
52
78
|
timeout: 5000,
|
|
79
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
53
80
|
}).trim();
|
|
54
81
|
if (!raw) return { commits: [], changedFiles: [] };
|
|
55
82
|
|
|
@@ -164,7 +191,11 @@ function main() {
|
|
|
164
191
|
const workerData = JSON.stringify({ baseDir, project, commits, changedFiles, reads });
|
|
165
192
|
const tmpFile = path.join(os.tmpdir(), `mindlore-worker-${Date.now()}.json`);
|
|
166
193
|
fs.writeFileSync(tmpFile, workerData, 'utf8');
|
|
167
|
-
|
|
194
|
+
// Use system node instead of process.execPath — CC's embedded Node
|
|
195
|
+
// may not work as a standalone binary for detached worker processes.
|
|
196
|
+
// Resolve full path to avoid shell:true deprecation warning on Windows.
|
|
197
|
+
const nodeBin = resolveWin32Bin('node');
|
|
198
|
+
const child = spawn(nodeBin, [__filename, '--worker', tmpFile], {
|
|
168
199
|
detached: true,
|
|
169
200
|
stdio: 'ignore',
|
|
170
201
|
cwd: process.cwd(),
|
|
@@ -173,6 +204,7 @@ function main() {
|
|
|
173
204
|
} catch (_err) {
|
|
174
205
|
// Fallback: run inline if spawn fails
|
|
175
206
|
writeBareEpisode(baseDir, project, commits, changedFiles, reads);
|
|
207
|
+
writeEpisodeFile(baseDir, project, commits, changedFiles, reads);
|
|
176
208
|
syncObsidian(baseDir);
|
|
177
209
|
syncGlobalRepo();
|
|
178
210
|
}
|
|
@@ -247,11 +279,60 @@ function writeBareEpisode(baseDir, project, commits, changedFiles, reads) {
|
|
|
247
279
|
|
|
248
280
|
db.close();
|
|
249
281
|
} catch (err) {
|
|
250
|
-
|
|
282
|
+
hookLog('session-end', 'error', `episode write failed: ${err?.message ?? err}`);
|
|
251
283
|
}
|
|
252
284
|
}
|
|
253
285
|
|
|
254
|
-
|
|
286
|
+
/**
|
|
287
|
+
* Write episode as .md file to diary/{project}/ for human-readable browsing.
|
|
288
|
+
* Complements the DB episode — same content, different medium.
|
|
289
|
+
*/
|
|
290
|
+
function writeEpisodeFile(baseDir, project, commits, changedFiles, reads) {
|
|
291
|
+
const projDir = path.join(baseDir, 'diary', project || 'unknown');
|
|
292
|
+
if (!fs.existsSync(projDir)) fs.mkdirSync(projDir, { recursive: true });
|
|
293
|
+
|
|
294
|
+
const now = new Date();
|
|
295
|
+
const ts = formatDate(now);
|
|
296
|
+
const filePath = path.join(projDir, `episode-${ts}.md`);
|
|
297
|
+
if (fs.existsSync(filePath)) return; // idempotent
|
|
298
|
+
|
|
299
|
+
const lines = [
|
|
300
|
+
'---',
|
|
301
|
+
`slug: episode-${ts}`,
|
|
302
|
+
'type: episode',
|
|
303
|
+
`date: ${now.toISOString().slice(0, 10)}`,
|
|
304
|
+
`project: ${project || 'unknown'}`,
|
|
305
|
+
'---',
|
|
306
|
+
'',
|
|
307
|
+
`# Episode — ${ts}`,
|
|
308
|
+
'',
|
|
309
|
+
];
|
|
310
|
+
|
|
311
|
+
if (commits.length > 0) {
|
|
312
|
+
lines.push('## Commits');
|
|
313
|
+
for (const c of commits) lines.push(`- ${c}`);
|
|
314
|
+
lines.push('');
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (changedFiles.length > 0) {
|
|
318
|
+
lines.push('## Changed Files');
|
|
319
|
+
for (const f of changedFiles) lines.push(`- ${f}`);
|
|
320
|
+
lines.push('');
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (reads) {
|
|
324
|
+
lines.push('## Read Stats');
|
|
325
|
+
lines.push(`- ${reads.count} files read, ${reads.repeats} repeated`);
|
|
326
|
+
lines.push('');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (commits.length === 0 && changedFiles.length === 0) {
|
|
330
|
+
lines.push('_Read-only session — no commits or file changes._');
|
|
331
|
+
lines.push('');
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
fs.writeFileSync(filePath, lines.join('\n'), 'utf8');
|
|
335
|
+
}
|
|
255
336
|
|
|
256
337
|
/**
|
|
257
338
|
* Load obsidian-helpers from compiled dist (single source of truth for wikilink conversion).
|
|
@@ -316,27 +397,40 @@ function syncObsidian(baseDir) {
|
|
|
316
397
|
if (!fs.existsSync(srcDir)) continue;
|
|
317
398
|
|
|
318
399
|
const destDir = path.join(destBase, dir);
|
|
319
|
-
|
|
400
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
320
401
|
|
|
321
402
|
for (const file of fs.readdirSync(srcDir).filter(f => f.endsWith('.md') && !f.startsWith('_'))) {
|
|
322
403
|
if (exportMdFile(path.join(srcDir, file), path.join(destDir, file), convertFn)) exported++;
|
|
323
404
|
}
|
|
405
|
+
|
|
406
|
+
// One-level subdirectories (e.g. diary/mindlore/)
|
|
407
|
+
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
408
|
+
if (!entry.isDirectory() || entry.name.startsWith('_') || entry.name.startsWith('.')) continue;
|
|
409
|
+
const subSrc = path.join(srcDir, entry.name);
|
|
410
|
+
const subDest = path.join(destDir, entry.name);
|
|
411
|
+
fs.mkdirSync(subDest, { recursive: true });
|
|
412
|
+
for (const file of fs.readdirSync(subSrc).filter(f => f.endsWith('.md') && !f.startsWith('_'))) {
|
|
413
|
+
if (exportMdFile(path.join(subSrc, file), path.join(subDest, file), convertFn)) exported++;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
324
416
|
}
|
|
325
417
|
|
|
326
418
|
for (const rootFile of ['INDEX.md', 'log.md']) {
|
|
327
419
|
const srcPath = path.join(baseDir, rootFile);
|
|
328
420
|
if (!fs.existsSync(srcPath)) continue;
|
|
329
|
-
|
|
421
|
+
fs.mkdirSync(destBase, { recursive: true });
|
|
330
422
|
if (exportMdFile(srcPath, path.join(destBase, rootFile), convertFn)) exported++;
|
|
331
423
|
}
|
|
332
424
|
|
|
425
|
+
hookLog('session-end', 'info', `obsidian exported=${exported}, dirs=${EXPORT_DIRS.length}, vault=${vaultPath}`);
|
|
333
426
|
if (exported > 0) {
|
|
334
427
|
config.obsidian.lastExport = new Date().toISOString();
|
|
335
428
|
config.obsidian.lastExportCount = exported;
|
|
336
429
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
|
|
337
430
|
}
|
|
338
431
|
} catch (err) {
|
|
339
|
-
|
|
432
|
+
hookLog('session-end', 'error', `obsidian internal: ${err?.message ?? err}`);
|
|
433
|
+
throw err; // re-throw so safeRun logs FAIL
|
|
340
434
|
}
|
|
341
435
|
}
|
|
342
436
|
|
|
@@ -345,38 +439,34 @@ function syncObsidian(baseDir) {
|
|
|
345
439
|
* Only runs for the global scope — project .mindlore/ is in the project's own git.
|
|
346
440
|
* Push failure is graceful (offline support).
|
|
347
441
|
*/
|
|
442
|
+
function resolveGitBin() {
|
|
443
|
+
return resolveWin32Bin('git');
|
|
444
|
+
}
|
|
445
|
+
|
|
348
446
|
function syncGlobalRepo() {
|
|
349
447
|
const gDir = globalDir();
|
|
350
448
|
const gitDir = path.join(gDir, '.git');
|
|
351
449
|
if (!fs.existsSync(gitDir)) return;
|
|
352
450
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
const status = execSync('git status --porcelain', {
|
|
356
|
-
cwd: gDir,
|
|
357
|
-
encoding: 'utf8',
|
|
358
|
-
timeout: 5000,
|
|
359
|
-
}).trim();
|
|
451
|
+
const git = resolveGitBin();
|
|
452
|
+
const execOpts = (timeout) => ({ cwd: gDir, encoding: 'utf8', timeout, stdio: 'pipe' });
|
|
360
453
|
|
|
361
|
-
|
|
454
|
+
// Check for changes
|
|
455
|
+
const status = execSync(`"${git}" status --porcelain`, execOpts(5000)).trim();
|
|
456
|
+
if (!status) return; // nothing to commit
|
|
362
457
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
cwd: gDir,
|
|
367
|
-
timeout: 10000,
|
|
368
|
-
stdio: 'pipe',
|
|
369
|
-
});
|
|
458
|
+
execSync(`"${git}" add *.md mindlore.db diary/ sources/ domains/ analyses/ decisions/ raw/ connections/ insights/ learnings/`, execOpts(10000));
|
|
459
|
+
const now = new Date().toISOString().slice(0, 19);
|
|
460
|
+
execSync(`"${git}" commit -m "mindlore auto-sync ${now}"`, execOpts(15000));
|
|
370
461
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
}
|
|
377
|
-
} catch (_err) {
|
|
378
|
-
// Git not available or commit failed — silently continue
|
|
462
|
+
// Push — graceful fail if no remote or offline
|
|
463
|
+
try {
|
|
464
|
+
execSync(`"${git}" push`, execOpts(15000));
|
|
465
|
+
} catch (_pushErr) {
|
|
466
|
+
hookLog('session-end', 'warn', 'git push failed (offline?): ' + (_pushErr?.message ?? '').slice(0, 100));
|
|
379
467
|
}
|
|
380
468
|
}
|
|
381
469
|
|
|
382
|
-
|
|
470
|
+
if (!process.argv.includes('--worker')) {
|
|
471
|
+
main();
|
|
472
|
+
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
const fs = require('fs');
|
|
12
12
|
const path = require('path');
|
|
13
|
-
const { findMindloreDir, readConfig, openDatabase, hasEpisodesTable, queryRecentEpisodes, querySupersededChains, formatSupersededChains, queryMultiSessionEpisodes, formatMultiSessionEpisodes, getAllMdFiles } = require('./lib/mindlore-common.cjs');
|
|
13
|
+
const { findMindloreDir, readConfig, openDatabase, hasEpisodesTable, queryRecentEpisodes, querySupersededChains, formatSupersededChains, queryMultiSessionEpisodes, formatMultiSessionEpisodes, getAllMdFiles, getRecentHookErrors, hookLog } = require('./lib/mindlore-common.cjs');
|
|
14
14
|
|
|
15
15
|
function main() {
|
|
16
16
|
const baseDir = findMindloreDir();
|
|
@@ -117,8 +117,29 @@ function main() {
|
|
|
117
117
|
}
|
|
118
118
|
} catch (_healthErr) { /* skip */ }
|
|
119
119
|
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
// Check for recent hook errors — inject warnings into CC context
|
|
121
|
+
try {
|
|
122
|
+
const errors = getRecentHookErrors();
|
|
123
|
+
if (errors.length > 0) {
|
|
124
|
+
const lines = errors.map(e => `- [${e.ts.slice(0, 19)}] **${e.hook}** (${e.level}): ${e.msg}`);
|
|
125
|
+
output.push(`[Mindlore Hook Alerts]\n${lines.join('\n')}`);
|
|
126
|
+
}
|
|
127
|
+
} catch (_hookLogErr) { /* skip */ }
|
|
128
|
+
|
|
129
|
+
hookLog('session-focus', 'info', 'session started');
|
|
130
|
+
|
|
131
|
+
// Token budget for session inject
|
|
132
|
+
// Defaults match DEFAULT_TOKEN_BUDGET in scripts/lib/constants.ts
|
|
133
|
+
const budgetConfig = config?.tokenBudget ?? {};
|
|
134
|
+
const maxInjectChars = (budgetConfig.sessionInject || 2000) * 4;
|
|
135
|
+
|
|
136
|
+
let joined = output.join('\n\n');
|
|
137
|
+
if (joined.length > maxInjectChars) {
|
|
138
|
+
joined = joined.slice(0, maxInjectChars) + '\n[...truncated by token budget]';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (joined.length > 0) {
|
|
142
|
+
process.stdout.write(joined + '\n');
|
|
122
143
|
}
|
|
123
144
|
}
|
|
124
145
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mindlore",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "AI-native knowledge system for Claude Code",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"node": ">=20.0.0"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"better-sqlite3": "^
|
|
46
|
+
"better-sqlite3": "^12.9.0",
|
|
47
47
|
"zod": "^4.3.6"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
@@ -52,9 +52,11 @@
|
|
|
52
52
|
"@types/node": "^25.6.0",
|
|
53
53
|
"@typescript-eslint/eslint-plugin": "^8.58.1",
|
|
54
54
|
"@typescript-eslint/parser": "^8.58.1",
|
|
55
|
-
"
|
|
56
|
-
"
|
|
55
|
+
"@xenova/transformers": "^2.17.2",
|
|
56
|
+
"eslint": "^10.2.0",
|
|
57
|
+
"globals": "^17.5.0",
|
|
57
58
|
"jest": "^29.7.0",
|
|
59
|
+
"sqlite-vec": "^0.1.9",
|
|
58
60
|
"ts-jest": "^29.4.9",
|
|
59
61
|
"typescript": "^6.0.2"
|
|
60
62
|
},
|
package/plugin.json
CHANGED
|
@@ -129,8 +129,14 @@ N source, N analysis, N total
|
|
|
129
129
|
|
|
130
130
|
## Post-Ingest Quality Gate
|
|
131
131
|
|
|
132
|
-
After every ingest, verify all
|
|
132
|
+
After every ingest, verify all 7 checkpoints before reporting success:
|
|
133
133
|
|
|
134
|
+
0. **Duplicate check** — Ingest öncesi mevcut DB'de benzer içerik ara:
|
|
135
|
+
```bash
|
|
136
|
+
node dist/scripts/lib/similarity.js "<title or first 100 chars>"
|
|
137
|
+
```
|
|
138
|
+
Eğer score > 0.7 olan sonuç varsa KULLANICIYA SOR: "Bu içerik '${slug}' ile benzer görünüyor. Yine de eklensin mi?"
|
|
139
|
+
Kullanıcı onaylarsa devam et, yoksa atla.
|
|
134
140
|
1. **raw/ file exists** — immutable capture written with frontmatter (slug, type, source_url)
|
|
135
141
|
2. **sources/ summary exists** — processed summary with full frontmatter (slug, type, title, tags, quality, description)
|
|
136
142
|
3. **INDEX.md updated** — stats line incremented, Recent section has new entry
|
package/templates/config.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.
|
|
2
|
+
"version": "0.5.1",
|
|
3
3
|
"models": {
|
|
4
4
|
"ingest": "haiku",
|
|
5
5
|
"evolve": "sonnet",
|
|
@@ -12,5 +12,24 @@
|
|
|
12
12
|
"session_focus": {
|
|
13
13
|
"max_episodes": 3,
|
|
14
14
|
"multi_session_days": 3
|
|
15
|
+
},
|
|
16
|
+
"synonyms": {
|
|
17
|
+
"auth": ["authentication", "login", "kimlik doğrulama"],
|
|
18
|
+
"güvenlik": ["security", "hardening", "sertleştirme"],
|
|
19
|
+
"db": ["database", "veritabanı", "sqlite"],
|
|
20
|
+
"api": ["endpoint", "rest", "graphql"],
|
|
21
|
+
"ui": ["frontend", "arayüz", "interface"],
|
|
22
|
+
"test": ["testing", "jest", "unit test"]
|
|
23
|
+
},
|
|
24
|
+
"hybrid": {
|
|
25
|
+
"enabled": true,
|
|
26
|
+
"ftsWeight": 0.4,
|
|
27
|
+
"vecWeight": 0.6,
|
|
28
|
+
"k": 60
|
|
29
|
+
},
|
|
30
|
+
"tokenBudget": {
|
|
31
|
+
"sessionInject": 2000,
|
|
32
|
+
"searchResults": 1500,
|
|
33
|
+
"perResult": 500
|
|
15
34
|
}
|
|
16
35
|
}
|