@hir4ta/mneme 0.20.2 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/.claude-plugin/plugin.json +2 -5
  2. package/README.ja.md +45 -283
  3. package/README.md +48 -280
  4. package/dist/lib/db.js +7 -5
  5. package/dist/lib/incremental-save.js +122 -28
  6. package/dist/lib/prompt-search.js +570 -0
  7. package/dist/lib/search-core.js +516 -0
  8. package/dist/lib/session-finalize.js +983 -0
  9. package/dist/lib/session-init.js +397 -0
  10. package/dist/lib/suppress-sqlite-warning.js +8 -0
  11. package/dist/public/assets/index-Bvl_IrPy.css +1 -0
  12. package/dist/public/assets/index-k5JYSPV6.js +351 -0
  13. package/dist/public/assets/{react-force-graph-2d-CGnpkwRw.js → react-force-graph-2d-Dlcfvz01.js} +1 -1
  14. package/dist/public/index.html +2 -2
  15. package/dist/server.js +565 -37
  16. package/dist/servers/db-server.js +1301 -98
  17. package/dist/servers/search-server.js +613 -333
  18. package/hooks/hooks.json +1 -0
  19. package/hooks/lib/common.sh +55 -0
  20. package/hooks/post-tool-use.sh +52 -58
  21. package/hooks/pre-compact.sh +30 -42
  22. package/hooks/session-end.sh +30 -142
  23. package/hooks/session-start.sh +32 -337
  24. package/hooks/stop.sh +31 -42
  25. package/hooks/user-prompt-submit.sh +58 -212
  26. package/package.json +10 -3
  27. package/scripts/export-weekly-knowledge-html.ts +906 -0
  28. package/scripts/search-benchmark.queries.json +78 -0
  29. package/scripts/search-benchmark.ts +120 -0
  30. package/scripts/validate-source-artifacts.mjs +378 -0
  31. package/servers/db-server.ts +995 -65
  32. package/servers/search-server.ts +117 -528
  33. package/skills/harvest/SKILL.md +78 -0
  34. package/skills/init-mneme/{skill.md → SKILL.md} +7 -1
  35. package/skills/resume/{skill.md → SKILL.md} +24 -9
  36. package/skills/save/SKILL.md +131 -0
  37. package/skills/search/SKILL.md +76 -0
  38. package/skills/using-mneme/SKILL.md +38 -0
  39. package/tsconfig.tsbuildinfo +1 -0
  40. package/dist/public/assets/index-CeHiZXwl.js +0 -345
  41. package/dist/public/assets/index-t_srr1OD.css +0 -1
  42. package/learn_claude_code/figma_exports/claude_code_map.svg +0 -107
  43. package/learn_claude_code/figma_exports/claude_code_whiteboard.excalidraw +0 -2578
  44. package/skills/AGENTS.override.md +0 -5
  45. package/skills/harvest/skill.md +0 -295
  46. package/skills/plan/skill.md +0 -422
  47. package/skills/report/skill.md +0 -74
  48. package/skills/review/skill.md +0 -419
  49. package/skills/save/skill.md +0 -496
  50. package/skills/search/skill.md +0 -175
  51. package/skills/using-mneme/skill.md +0 -185
@@ -0,0 +1,570 @@
1
+ #!/usr/bin/env node
2
+
3
+ // lib/prompt-search.ts
4
+ import * as fs4 from "node:fs";
5
+ import * as path4 from "node:path";
6
+
7
+ // lib/search-core.ts
8
+ import * as fs3 from "node:fs";
9
+ import * as path3 from "node:path";
10
+
11
+ // lib/fuzzy-search.ts
12
+ import * as fs2 from "node:fs";
13
+ import * as path2 from "node:path";
14
+
15
+ // lib/utils.ts
16
+ import * as fs from "node:fs";
17
+ import * as path from "node:path";
18
+ function safeReadJson(filePath, fallback) {
19
+ try {
20
+ const content = fs.readFileSync(filePath, "utf-8");
21
+ return JSON.parse(content);
22
+ } catch {
23
+ return fallback;
24
+ }
25
+ }
26
+ function findJsonFiles(dir) {
27
+ const results = [];
28
+ if (!fs.existsSync(dir)) return results;
29
+ const items = fs.readdirSync(dir, { withFileTypes: true });
30
+ for (const item of items) {
31
+ const fullPath = path.join(dir, item.name);
32
+ if (item.isDirectory()) {
33
+ results.push(...findJsonFiles(fullPath));
34
+ } else if (item.name.endsWith(".json")) {
35
+ results.push(fullPath);
36
+ }
37
+ }
38
+ return results;
39
+ }
40
+
41
+ // lib/fuzzy-search.ts
42
+ function levenshtein(a, b) {
43
+ const matrix = [];
44
+ for (let i = 0; i <= a.length; i++) {
45
+ matrix[i] = [i];
46
+ }
47
+ for (let j = 0; j <= b.length; j++) {
48
+ matrix[0][j] = j;
49
+ }
50
+ for (let i = 1; i <= a.length; i++) {
51
+ for (let j = 1; j <= b.length; j++) {
52
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
53
+ matrix[i][j] = Math.min(
54
+ matrix[i - 1][j] + 1,
55
+ // deletion
56
+ matrix[i][j - 1] + 1,
57
+ // insertion
58
+ matrix[i - 1][j - 1] + cost
59
+ // substitution
60
+ );
61
+ }
62
+ }
63
+ return matrix[a.length][b.length];
64
+ }
65
+ function expandAliases(query, tags) {
66
+ const results = /* @__PURE__ */ new Set([query]);
67
+ const lowerQuery = query.toLowerCase();
68
+ for (const tag of tags) {
69
+ const allTerms = [tag.id, tag.label, ...tag.aliases].map(
70
+ (t) => t.toLowerCase()
71
+ );
72
+ if (allTerms.includes(lowerQuery)) {
73
+ results.add(tag.id);
74
+ results.add(tag.label);
75
+ for (const alias of tag.aliases) {
76
+ results.add(alias);
77
+ }
78
+ }
79
+ }
80
+ return Array.from(results);
81
+ }
82
+ function calculateSimilarity(text, query) {
83
+ const lowerText = text.toLowerCase();
84
+ const lowerQuery = query.toLowerCase();
85
+ if (lowerText === lowerQuery) return 10;
86
+ if (lowerText.includes(lowerQuery)) return 5;
87
+ if (lowerQuery.includes(lowerText)) return 3;
88
+ const distance = levenshtein(lowerText, lowerQuery);
89
+ if (distance <= 2) return 2;
90
+ if (distance <= 3) return 1;
91
+ return 0;
92
+ }
93
+ async function search(options) {
94
+ const {
95
+ query,
96
+ mnemeDir,
97
+ targets = ["sessions", "decisions"],
98
+ limit = 20,
99
+ timeout = 1e4
100
+ } = options;
101
+ const startTime = Date.now();
102
+ const results = [];
103
+ const tagsPath = path2.join(mnemeDir, "tags.json");
104
+ const tagsData = safeReadJson(tagsPath, { tags: [] });
105
+ const expandedQueries = expandAliases(query, tagsData.tags);
106
+ if (targets.includes("sessions")) {
107
+ const sessionsDir = path2.join(mnemeDir, "sessions");
108
+ if (fs2.existsSync(sessionsDir)) {
109
+ const files = findJsonFiles(sessionsDir);
110
+ for (const file of files) {
111
+ if (Date.now() - startTime > timeout) break;
112
+ const session = safeReadJson(file, {});
113
+ const score = scoreDocument(session, expandedQueries, [
114
+ "title",
115
+ "goal",
116
+ "tags"
117
+ ]);
118
+ if (score > 0) {
119
+ results.push({
120
+ type: "session",
121
+ id: session.id || path2.basename(file, ".json"),
122
+ score,
123
+ title: session.title || "Untitled",
124
+ highlights: []
125
+ });
126
+ }
127
+ }
128
+ }
129
+ }
130
+ if (targets.includes("decisions")) {
131
+ const decisionsDir = path2.join(mnemeDir, "decisions");
132
+ if (fs2.existsSync(decisionsDir)) {
133
+ const files = findJsonFiles(decisionsDir);
134
+ for (const file of files) {
135
+ if (Date.now() - startTime > timeout) break;
136
+ const decision = safeReadJson(file, {});
137
+ const score = scoreDocument(decision, expandedQueries, [
138
+ "title",
139
+ "decision",
140
+ "tags"
141
+ ]);
142
+ if (score > 0) {
143
+ results.push({
144
+ type: "decision",
145
+ id: decision.id || path2.basename(file, ".json"),
146
+ score,
147
+ title: decision.title || "Untitled",
148
+ highlights: []
149
+ });
150
+ }
151
+ }
152
+ }
153
+ }
154
+ if (targets.includes("patterns")) {
155
+ const patternsDir = path2.join(mnemeDir, "patterns");
156
+ if (fs2.existsSync(patternsDir)) {
157
+ const files = findJsonFiles(patternsDir);
158
+ for (const file of files) {
159
+ if (Date.now() - startTime > timeout) break;
160
+ const pattern = safeReadJson(file, {});
161
+ const patterns = pattern.patterns || [];
162
+ for (const p of patterns) {
163
+ const score = scoreDocument(p, expandedQueries, [
164
+ "description",
165
+ "errorPattern",
166
+ "tags"
167
+ ]);
168
+ if (score > 0) {
169
+ results.push({
170
+ type: "pattern",
171
+ id: `${path2.basename(file, ".json")}-${p.type || "unknown"}`,
172
+ score,
173
+ title: p.description || "Untitled pattern",
174
+ highlights: []
175
+ });
176
+ }
177
+ }
178
+ }
179
+ }
180
+ }
181
+ return results.sort((a, b) => b.score - a.score).slice(0, limit);
182
+ }
183
+ function scoreDocument(doc, queries, fields) {
184
+ let totalScore = 0;
185
+ for (const field of fields) {
186
+ const value = doc[field];
187
+ if (typeof value === "string") {
188
+ for (const q of queries) {
189
+ totalScore += calculateSimilarity(value, q);
190
+ }
191
+ } else if (Array.isArray(value)) {
192
+ for (const item of value) {
193
+ if (typeof item === "string") {
194
+ for (const q of queries) {
195
+ totalScore += calculateSimilarity(item, q);
196
+ }
197
+ }
198
+ }
199
+ }
200
+ }
201
+ return totalScore;
202
+ }
203
+ var isMain = process.argv[1]?.endsWith("fuzzy-search.js") || process.argv[1]?.endsWith("fuzzy-search.ts");
204
+ if (isMain && process.argv.length > 2) {
205
+ const args = process.argv.slice(2);
206
+ const queryIndex = args.indexOf("--query");
207
+ const query = queryIndex !== -1 ? args[queryIndex + 1] : "";
208
+ const mnemeDir = `${process.cwd()}/.mneme`;
209
+ if (!query) {
210
+ console.error(JSON.stringify({ success: false, error: "Missing --query" }));
211
+ process.exit(0);
212
+ }
213
+ search({ query, mnemeDir }).then((results) => {
214
+ console.log(JSON.stringify({ success: true, results }));
215
+ }).catch((error) => {
216
+ console.error(JSON.stringify({ success: false, error: String(error) }));
217
+ });
218
+ }
219
+
220
+ // lib/search-core.ts
221
+ function escapeRegex(value) {
222
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
223
+ }
224
+ function countMatches(text, pattern) {
225
+ const matches = text.match(new RegExp(pattern.source, "gi"));
226
+ return matches ? matches.length : 0;
227
+ }
228
+ function fieldScore(text, pattern, baseScore) {
229
+ if (!text) return 0;
230
+ const count = countMatches(text, pattern);
231
+ if (count === 0) return 0;
232
+ return baseScore + (count > 1 ? Math.log2(count) * 0.5 : 0);
233
+ }
234
+ function isFuzzyMatch(word, target, maxDistance = 2) {
235
+ if (word.length < 4) return false;
236
+ const distance = levenshtein(word.toLowerCase(), target.toLowerCase());
237
+ const threshold = Math.min(maxDistance, Math.floor(word.length / 3));
238
+ return distance <= threshold;
239
+ }
240
+ function loadTags(mnemeDir) {
241
+ const tagsPath = path3.join(mnemeDir, "tags.json");
242
+ if (!fs3.existsSync(tagsPath)) return null;
243
+ try {
244
+ return JSON.parse(fs3.readFileSync(tagsPath, "utf-8"));
245
+ } catch {
246
+ return null;
247
+ }
248
+ }
249
+ function expandKeywordsWithAliases(keywords, tags) {
250
+ if (!tags) return keywords;
251
+ const expanded = new Set(keywords.map((k) => k.toLowerCase()));
252
+ for (const keyword of keywords) {
253
+ const lowerKeyword = keyword.toLowerCase();
254
+ for (const tag of tags.tags) {
255
+ const matches = tag.id.toLowerCase() === lowerKeyword || tag.label.toLowerCase() === lowerKeyword || tag.aliases?.some((alias) => alias.toLowerCase() === lowerKeyword);
256
+ if (!matches) continue;
257
+ expanded.add(tag.id.toLowerCase());
258
+ expanded.add(tag.label.toLowerCase());
259
+ for (const alias of tag.aliases || []) {
260
+ expanded.add(alias.toLowerCase());
261
+ }
262
+ }
263
+ }
264
+ return Array.from(expanded);
265
+ }
266
+ function searchInteractions(keywords, projectPath, database, limit = 5) {
267
+ if (!database) return [];
268
+ try {
269
+ const stmt = database.prepare(`
270
+ SELECT
271
+ i.session_id,
272
+ i.content,
273
+ i.timestamp,
274
+ highlight(interactions_fts, 0, '[', ']') as content_highlight
275
+ FROM interactions_fts
276
+ JOIN interactions i ON interactions_fts.rowid = i.id
277
+ WHERE interactions_fts MATCH ?
278
+ AND i.project_path = ?
279
+ ORDER BY rank
280
+ LIMIT ?
281
+ `);
282
+ const rows = stmt.all(keywords.join(" OR "), projectPath, limit);
283
+ return rows.map((row) => ({
284
+ type: "interaction",
285
+ id: row.session_id,
286
+ title: `Interaction from ${row.timestamp}`,
287
+ snippet: (row.content_highlight || row.content).substring(0, 150),
288
+ score: 5,
289
+ matchedFields: ["content"]
290
+ }));
291
+ } catch {
292
+ try {
293
+ const clauses = keywords.map(() => "(content LIKE ? OR thinking LIKE ?)");
294
+ const sql = `
295
+ SELECT DISTINCT session_id, substr(content, 1, 120) as snippet, timestamp
296
+ FROM interactions
297
+ WHERE project_path = ?
298
+ AND (${clauses.join(" OR ")})
299
+ ORDER BY timestamp DESC
300
+ LIMIT ?
301
+ `;
302
+ const args = [projectPath];
303
+ for (const keyword of keywords) {
304
+ const pattern = `%${keyword}%`;
305
+ args.push(pattern, pattern);
306
+ }
307
+ args.push(limit);
308
+ const stmt = database.prepare(sql);
309
+ const rows = stmt.all(...args);
310
+ return rows.map((row) => ({
311
+ type: "interaction",
312
+ id: row.session_id,
313
+ title: `Interaction from ${row.timestamp}`,
314
+ snippet: row.snippet,
315
+ score: 3,
316
+ matchedFields: ["content"]
317
+ }));
318
+ } catch {
319
+ return [];
320
+ }
321
+ }
322
+ }
323
+ function walkJsonFiles(dir, callback) {
324
+ if (!fs3.existsSync(dir)) return;
325
+ const entries = fs3.readdirSync(dir, { withFileTypes: true });
326
+ for (const entry of entries) {
327
+ const fullPath = path3.join(dir, entry.name);
328
+ if (entry.isDirectory()) {
329
+ walkJsonFiles(fullPath, callback);
330
+ continue;
331
+ }
332
+ if (entry.isFile() && entry.name.endsWith(".json")) {
333
+ callback(fullPath);
334
+ }
335
+ }
336
+ }
337
+ function searchSessions(mnemeDir, keywords, limit = 5) {
338
+ const sessionsDir = path3.join(mnemeDir, "sessions");
339
+ const results = [];
340
+ const pattern = new RegExp(keywords.map(escapeRegex).join("|"), "i");
341
+ walkJsonFiles(sessionsDir, (filePath) => {
342
+ try {
343
+ const session = JSON.parse(
344
+ fs3.readFileSync(filePath, "utf-8")
345
+ );
346
+ const title = session.title || session.summary?.title || "";
347
+ let score = 0;
348
+ const matchedFields = [];
349
+ const titleScore = fieldScore(title, pattern, 3);
350
+ if (titleScore > 0) {
351
+ score += titleScore;
352
+ matchedFields.push("title");
353
+ }
354
+ if (session.tags?.some((t) => pattern.test(t))) {
355
+ score += 1;
356
+ matchedFields.push("tags");
357
+ }
358
+ const goalScore = fieldScore(session.summary?.goal, pattern, 2);
359
+ if (goalScore > 0) {
360
+ score += goalScore;
361
+ matchedFields.push("summary.goal");
362
+ }
363
+ const descScore = fieldScore(session.summary?.description, pattern, 2);
364
+ if (descScore > 0) {
365
+ score += descScore;
366
+ matchedFields.push("summary.description");
367
+ }
368
+ if (session.discussions?.some(
369
+ (d) => pattern.test(d.topic || "") || pattern.test(d.decision || "")
370
+ )) {
371
+ score += 2;
372
+ matchedFields.push("discussions");
373
+ }
374
+ if (session.errors?.some(
375
+ (e) => pattern.test(e.error || "") || pattern.test(e.solution || "")
376
+ )) {
377
+ score += 2;
378
+ matchedFields.push("errors");
379
+ }
380
+ if (score === 0 && keywords.length <= 2) {
381
+ const titleWords = (title || "").toLowerCase().split(/\s+/);
382
+ const tagWords = session.tags || [];
383
+ for (const keyword of keywords) {
384
+ if (titleWords.some((w) => isFuzzyMatch(keyword, w))) {
385
+ score += 1;
386
+ matchedFields.push("title~fuzzy");
387
+ }
388
+ if (tagWords.some((t) => isFuzzyMatch(keyword, t))) {
389
+ score += 0.5;
390
+ matchedFields.push("tags~fuzzy");
391
+ }
392
+ }
393
+ }
394
+ if (score > 0) {
395
+ results.push({
396
+ type: "session",
397
+ id: session.id,
398
+ title: title || session.id,
399
+ snippet: session.summary?.description || session.summary?.goal || "",
400
+ score,
401
+ matchedFields
402
+ });
403
+ }
404
+ } catch {
405
+ }
406
+ });
407
+ return results.sort((a, b) => b.score - a.score).slice(0, limit);
408
+ }
409
+ function searchUnits(mnemeDir, keywords, limit = 5) {
410
+ const unitsPath = path3.join(mnemeDir, "units", "units.json");
411
+ const results = [];
412
+ const pattern = new RegExp(keywords.map(escapeRegex).join("|"), "i");
413
+ if (!fs3.existsSync(unitsPath)) return results;
414
+ try {
415
+ const cards = JSON.parse(fs3.readFileSync(unitsPath, "utf-8"));
416
+ const items = (cards.items || []).filter(
417
+ (item) => item.status === "approved"
418
+ );
419
+ for (const item of items) {
420
+ let score = 0;
421
+ const matchedFields = [];
422
+ const titleScore = fieldScore(item.title, pattern, 3);
423
+ if (titleScore > 0) {
424
+ score += titleScore;
425
+ matchedFields.push("title");
426
+ }
427
+ const summaryScore = fieldScore(item.summary, pattern, 2);
428
+ if (summaryScore > 0) {
429
+ score += summaryScore;
430
+ matchedFields.push("summary");
431
+ }
432
+ if (item.tags?.some((tag) => pattern.test(tag))) {
433
+ score += 1;
434
+ matchedFields.push("tags");
435
+ }
436
+ if (item.sourceType && pattern.test(item.sourceType)) {
437
+ score += 1;
438
+ matchedFields.push("sourceType");
439
+ }
440
+ if (score === 0 && keywords.length <= 2) {
441
+ const titleWords = (item.title || "").toLowerCase().split(/\s+/);
442
+ const tagWords = item.tags || [];
443
+ for (const keyword of keywords) {
444
+ if (titleWords.some((w) => isFuzzyMatch(keyword, w))) {
445
+ score += 1;
446
+ matchedFields.push("title~fuzzy");
447
+ }
448
+ if (tagWords.some((t) => isFuzzyMatch(keyword, t))) {
449
+ score += 0.5;
450
+ matchedFields.push("tags~fuzzy");
451
+ }
452
+ }
453
+ }
454
+ if (score > 0) {
455
+ results.push({
456
+ type: "unit",
457
+ id: item.id,
458
+ title: item.title || item.id,
459
+ snippet: item.summary || "",
460
+ score,
461
+ matchedFields
462
+ });
463
+ }
464
+ }
465
+ } catch {
466
+ }
467
+ return results.sort((a, b) => b.score - a.score).slice(0, limit);
468
+ }
469
+ function normalizeRequestedTypes(types) {
470
+ const normalized = /* @__PURE__ */ new Set();
471
+ for (const type of types) {
472
+ normalized.add(type);
473
+ }
474
+ return normalized;
475
+ }
476
+ function searchKnowledge(options) {
477
+ const {
478
+ query,
479
+ mnemeDir,
480
+ projectPath,
481
+ database = null,
482
+ types = ["session", "unit", "interaction"],
483
+ limit = 10,
484
+ offset = 0
485
+ } = options;
486
+ const keywords = query.toLowerCase().split(/\s+/).map((token) => token.trim()).filter((token) => token.length > 2);
487
+ if (keywords.length === 0) return [];
488
+ const expandedKeywords = expandKeywordsWithAliases(
489
+ keywords,
490
+ loadTags(mnemeDir)
491
+ );
492
+ const results = [];
493
+ const safeOffset = Math.max(0, offset);
494
+ const fetchLimit = Math.max(limit + safeOffset, limit, 10);
495
+ const normalizedTypes = normalizeRequestedTypes(types);
496
+ if (normalizedTypes.has("session")) {
497
+ results.push(...searchSessions(mnemeDir, expandedKeywords, fetchLimit));
498
+ }
499
+ if (normalizedTypes.has("unit")) {
500
+ results.push(...searchUnits(mnemeDir, expandedKeywords, fetchLimit));
501
+ }
502
+ if (normalizedTypes.has("interaction")) {
503
+ results.push(
504
+ ...searchInteractions(
505
+ expandedKeywords,
506
+ projectPath,
507
+ database,
508
+ fetchLimit
509
+ )
510
+ );
511
+ }
512
+ const seen = /* @__PURE__ */ new Set();
513
+ return results.sort((a, b) => b.score - a.score).filter((result) => {
514
+ const key = `${result.type}:${result.id}`;
515
+ if (seen.has(key)) return false;
516
+ seen.add(key);
517
+ return true;
518
+ }).slice(safeOffset, safeOffset + limit);
519
+ }
520
+
521
+ // lib/prompt-search.ts
522
+ var originalEmit = process.emit;
523
+ process.emit = (event, ...args) => {
524
+ if (event === "warning" && typeof args[0] === "object" && args[0] !== null && "name" in args[0] && args[0].name === "ExperimentalWarning" && "message" in args[0] && typeof args[0].message === "string" && args[0].message.includes("SQLite")) {
525
+ return false;
526
+ }
527
+ return originalEmit.apply(process, [event, ...args]);
528
+ };
529
+ var { DatabaseSync } = await import("node:sqlite");
530
+ function getArg(args, name) {
531
+ const index = args.indexOf(`--${name}`);
532
+ return index === -1 ? void 0 : args[index + 1];
533
+ }
534
+ function main() {
535
+ const args = process.argv.slice(2);
536
+ const query = getArg(args, "query");
537
+ const projectPath = getArg(args, "project");
538
+ const limit = Number.parseInt(getArg(args, "limit") || "5", 10);
539
+ if (!query || !projectPath) {
540
+ console.log(
541
+ JSON.stringify({ success: false, error: "Missing required args" })
542
+ );
543
+ process.exit(1);
544
+ }
545
+ const mnemeDir = path4.join(projectPath, ".mneme");
546
+ const dbPath = path4.join(mnemeDir, "local.db");
547
+ let database = null;
548
+ try {
549
+ if (fs4.existsSync(dbPath)) {
550
+ database = new DatabaseSync(dbPath);
551
+ database.exec("PRAGMA journal_mode = WAL");
552
+ }
553
+ const results = searchKnowledge({
554
+ query,
555
+ mnemeDir,
556
+ projectPath,
557
+ database,
558
+ limit: Number.isFinite(limit) ? Math.max(1, Math.min(limit, 10)) : 5
559
+ });
560
+ console.log(JSON.stringify({ success: true, results }));
561
+ } catch (error) {
562
+ console.log(
563
+ JSON.stringify({ success: false, error: error.message })
564
+ );
565
+ process.exit(1);
566
+ } finally {
567
+ database?.close();
568
+ }
569
+ }
570
+ main();