@hir4ta/mneme 0.23.0 → 0.23.2

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.
@@ -0,0 +1,672 @@
1
+ #!/usr/bin/env node
2
+
3
+ // lib/search/prompt.ts
4
+ import * as fs6 from "node:fs";
5
+ import * as path6 from "node:path";
6
+
7
+ // lib/search/approved-rules.ts
8
+ import * as fs4 from "node:fs";
9
+ import * as path4 from "node:path";
10
+
11
+ // lib/search/helpers.ts
12
+ import * as fs3 from "node:fs";
13
+ import * as path3 from "node:path";
14
+
15
+ // lib/search/fuzzy.ts
16
+ import * as fs2 from "node:fs";
17
+ import * as path2 from "node:path";
18
+
19
+ // lib/utils.ts
20
+ import * as fs from "node:fs";
21
+ import * as path from "node:path";
22
+ function safeReadJson(filePath, fallback) {
23
+ try {
24
+ const content = fs.readFileSync(filePath, "utf-8");
25
+ return JSON.parse(content);
26
+ } catch {
27
+ return fallback;
28
+ }
29
+ }
30
+ function findJsonFiles(dir) {
31
+ const results = [];
32
+ if (!fs.existsSync(dir)) return results;
33
+ const items = fs.readdirSync(dir, { withFileTypes: true });
34
+ for (const item of items) {
35
+ const fullPath = path.join(dir, item.name);
36
+ if (item.isDirectory()) {
37
+ results.push(...findJsonFiles(fullPath));
38
+ } else if (item.name.endsWith(".json")) {
39
+ results.push(fullPath);
40
+ }
41
+ }
42
+ return results;
43
+ }
44
+
45
+ // lib/search/fuzzy.ts
46
+ function levenshtein(a, b) {
47
+ const matrix = [];
48
+ for (let i = 0; i <= a.length; i++) {
49
+ matrix[i] = [i];
50
+ }
51
+ for (let j = 0; j <= b.length; j++) {
52
+ matrix[0][j] = j;
53
+ }
54
+ for (let i = 1; i <= a.length; i++) {
55
+ for (let j = 1; j <= b.length; j++) {
56
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
57
+ matrix[i][j] = Math.min(
58
+ matrix[i - 1][j] + 1,
59
+ // deletion
60
+ matrix[i][j - 1] + 1,
61
+ // insertion
62
+ matrix[i - 1][j - 1] + cost
63
+ // substitution
64
+ );
65
+ }
66
+ }
67
+ return matrix[a.length][b.length];
68
+ }
69
+ function expandAliases(query, tags) {
70
+ const results = /* @__PURE__ */ new Set([query]);
71
+ const lowerQuery = query.toLowerCase();
72
+ for (const tag of tags) {
73
+ const allTerms = [tag.id, tag.label, ...tag.aliases].map(
74
+ (t) => t.toLowerCase()
75
+ );
76
+ if (allTerms.includes(lowerQuery)) {
77
+ results.add(tag.id);
78
+ results.add(tag.label);
79
+ for (const alias of tag.aliases) {
80
+ results.add(alias);
81
+ }
82
+ }
83
+ }
84
+ return Array.from(results);
85
+ }
86
+ function calculateSimilarity(text, query) {
87
+ const lowerText = text.toLowerCase();
88
+ const lowerQuery = query.toLowerCase();
89
+ if (lowerText === lowerQuery) return 10;
90
+ if (lowerText.includes(lowerQuery)) return 5;
91
+ if (lowerQuery.includes(lowerText)) return 3;
92
+ const distance = levenshtein(lowerText, lowerQuery);
93
+ if (distance <= 2) return 2;
94
+ if (distance <= 3) return 1;
95
+ return 0;
96
+ }
97
+ async function search(options) {
98
+ const {
99
+ query,
100
+ mnemeDir,
101
+ targets = ["sessions", "decisions"],
102
+ limit = 20,
103
+ timeout = 1e4
104
+ } = options;
105
+ const startTime = Date.now();
106
+ const results = [];
107
+ const tagsPath = path2.join(mnemeDir, "tags.json");
108
+ const tagsData = safeReadJson(tagsPath, { tags: [] });
109
+ const expandedQueries = expandAliases(query, tagsData.tags);
110
+ if (targets.includes("sessions")) {
111
+ const sessionsDir = path2.join(mnemeDir, "sessions");
112
+ if (fs2.existsSync(sessionsDir)) {
113
+ const files = findJsonFiles(sessionsDir);
114
+ for (const file of files) {
115
+ if (Date.now() - startTime > timeout) break;
116
+ const session = safeReadJson(file, {});
117
+ const score = scoreDocument(session, expandedQueries, [
118
+ "title",
119
+ "goal",
120
+ "tags"
121
+ ]);
122
+ if (score > 0) {
123
+ results.push({
124
+ type: "session",
125
+ id: session.id || path2.basename(file, ".json"),
126
+ score,
127
+ title: session.title || "Untitled",
128
+ highlights: []
129
+ });
130
+ }
131
+ }
132
+ }
133
+ }
134
+ if (targets.includes("decisions")) {
135
+ const decisionsDir = path2.join(mnemeDir, "decisions");
136
+ if (fs2.existsSync(decisionsDir)) {
137
+ const files = findJsonFiles(decisionsDir);
138
+ for (const file of files) {
139
+ if (Date.now() - startTime > timeout) break;
140
+ const decision = safeReadJson(file, {});
141
+ const score = scoreDocument(decision, expandedQueries, [
142
+ "title",
143
+ "decision",
144
+ "tags"
145
+ ]);
146
+ if (score > 0) {
147
+ results.push({
148
+ type: "decision",
149
+ id: decision.id || path2.basename(file, ".json"),
150
+ score,
151
+ title: decision.title || "Untitled",
152
+ highlights: []
153
+ });
154
+ }
155
+ }
156
+ }
157
+ }
158
+ if (targets.includes("patterns")) {
159
+ const patternsDir = path2.join(mnemeDir, "patterns");
160
+ if (fs2.existsSync(patternsDir)) {
161
+ const files = findJsonFiles(patternsDir);
162
+ for (const file of files) {
163
+ if (Date.now() - startTime > timeout) break;
164
+ const pattern = safeReadJson(file, {});
165
+ const patterns = pattern.patterns || [];
166
+ for (const p of patterns) {
167
+ const score = scoreDocument(p, expandedQueries, [
168
+ "description",
169
+ "errorPattern",
170
+ "tags"
171
+ ]);
172
+ if (score > 0) {
173
+ results.push({
174
+ type: "pattern",
175
+ id: `${path2.basename(file, ".json")}-${p.type || "unknown"}`,
176
+ score,
177
+ title: p.description || "Untitled pattern",
178
+ highlights: []
179
+ });
180
+ }
181
+ }
182
+ }
183
+ }
184
+ }
185
+ return results.sort((a, b) => b.score - a.score).slice(0, limit);
186
+ }
187
+ function scoreDocument(doc, queries, fields) {
188
+ let totalScore = 0;
189
+ for (const field of fields) {
190
+ const value = doc[field];
191
+ if (typeof value === "string") {
192
+ for (const q of queries) {
193
+ totalScore += calculateSimilarity(value, q);
194
+ }
195
+ } else if (Array.isArray(value)) {
196
+ for (const item of value) {
197
+ if (typeof item === "string") {
198
+ for (const q of queries) {
199
+ totalScore += calculateSimilarity(item, q);
200
+ }
201
+ }
202
+ }
203
+ }
204
+ }
205
+ return totalScore;
206
+ }
207
+ var isMain = process.argv[1]?.endsWith("fuzzy.js") || process.argv[1]?.endsWith("fuzzy.ts");
208
+ if (isMain && process.argv.length > 2) {
209
+ const args = process.argv.slice(2);
210
+ const queryIndex = args.indexOf("--query");
211
+ const query = queryIndex !== -1 ? args[queryIndex + 1] : "";
212
+ const mnemeDir = `${process.cwd()}/.mneme`;
213
+ if (!query) {
214
+ console.error(JSON.stringify({ success: false, error: "Missing --query" }));
215
+ process.exit(0);
216
+ }
217
+ search({ query, mnemeDir }).then((results) => {
218
+ console.log(JSON.stringify({ success: true, results }));
219
+ }).catch((error) => {
220
+ console.error(JSON.stringify({ success: false, error: String(error) }));
221
+ });
222
+ }
223
+
224
+ // lib/search/helpers.ts
225
+ function escapeRegex(value) {
226
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
227
+ }
228
+ function countMatches(text, pattern) {
229
+ const matches = text.match(new RegExp(pattern.source, "gi"));
230
+ return matches ? matches.length : 0;
231
+ }
232
+ function fieldScore(text, pattern, baseScore) {
233
+ if (!text) return 0;
234
+ const count = countMatches(text, pattern);
235
+ if (count === 0) return 0;
236
+ return baseScore + (count > 1 ? Math.log2(count) * 0.5 : 0);
237
+ }
238
+ function isFuzzyMatch(word, target, maxDistance = 2) {
239
+ if (word.length < 4) return false;
240
+ const distance = levenshtein(word.toLowerCase(), target.toLowerCase());
241
+ const threshold = Math.min(maxDistance, Math.floor(word.length / 3));
242
+ return distance <= threshold;
243
+ }
244
+ function loadTags(mnemeDir) {
245
+ const tagsPath = path3.join(mnemeDir, "tags.json");
246
+ if (!fs3.existsSync(tagsPath)) return null;
247
+ try {
248
+ return JSON.parse(fs3.readFileSync(tagsPath, "utf-8"));
249
+ } catch {
250
+ return null;
251
+ }
252
+ }
253
+ function expandKeywordsWithAliases(keywords, tags) {
254
+ if (!tags) return keywords;
255
+ const expanded = new Set(keywords.map((k) => k.toLowerCase()));
256
+ for (const keyword of keywords) {
257
+ const lowerKeyword = keyword.toLowerCase();
258
+ for (const tag of tags.tags) {
259
+ const matches = tag.id.toLowerCase() === lowerKeyword || tag.label.toLowerCase() === lowerKeyword || tag.aliases?.some((alias) => alias.toLowerCase() === lowerKeyword);
260
+ if (!matches) continue;
261
+ expanded.add(tag.id.toLowerCase());
262
+ expanded.add(tag.label.toLowerCase());
263
+ for (const alias of tag.aliases || []) {
264
+ expanded.add(alias.toLowerCase());
265
+ }
266
+ }
267
+ }
268
+ return Array.from(expanded);
269
+ }
270
+ function walkJsonFiles(dir, callback) {
271
+ if (!fs3.existsSync(dir)) return;
272
+ const entries = fs3.readdirSync(dir, { withFileTypes: true });
273
+ for (const entry of entries) {
274
+ const fullPath = path3.join(dir, entry.name);
275
+ if (entry.isDirectory()) {
276
+ walkJsonFiles(fullPath, callback);
277
+ continue;
278
+ }
279
+ if (entry.isFile() && entry.name.endsWith(".json")) {
280
+ callback(fullPath);
281
+ }
282
+ }
283
+ }
284
+
285
+ // lib/search/approved-rules.ts
286
+ function isApproved(status) {
287
+ if (typeof status !== "string") return false;
288
+ const s = status.toLowerCase();
289
+ return s === "approved" || s === "active";
290
+ }
291
+ function priorityBoost(priority) {
292
+ if (priority === "p0") return 2;
293
+ if (priority === "p1") return 1;
294
+ return 0;
295
+ }
296
+ function searchRuleFiles(mnemeDir, pattern) {
297
+ const results = [];
298
+ const rulesDir = path4.join(mnemeDir, "rules");
299
+ if (!fs4.existsSync(rulesDir)) return results;
300
+ for (const fileName of ["dev-rules.json", "review-guidelines.json"]) {
301
+ const filePath = path4.join(rulesDir, fileName);
302
+ if (!fs4.existsSync(filePath)) continue;
303
+ try {
304
+ const doc = JSON.parse(fs4.readFileSync(filePath, "utf-8"));
305
+ const items = doc.items || doc.rules || [];
306
+ for (const item of items) {
307
+ if (!isApproved(item.status)) continue;
308
+ let score = 0;
309
+ const matched = [];
310
+ const textScore = fieldScore(item.text, pattern, 4);
311
+ if (textScore > 0) {
312
+ score += textScore;
313
+ matched.push("text");
314
+ }
315
+ const keyScore = fieldScore(item.key, pattern, 3);
316
+ if (keyScore > 0) {
317
+ score += keyScore;
318
+ matched.push("key");
319
+ }
320
+ if (item.tags?.some((t) => pattern.test(t))) {
321
+ score += 1;
322
+ matched.push("tags");
323
+ }
324
+ score += priorityBoost(item.priority);
325
+ if (score > 0) {
326
+ results.push({
327
+ sourceType: "rule",
328
+ id: item.id || item.key || "",
329
+ title: item.text || item.key || "",
330
+ text: item.text || "",
331
+ priority: item.priority,
332
+ tags: item.tags || [],
333
+ score,
334
+ matchedFields: matched
335
+ });
336
+ }
337
+ }
338
+ } catch {
339
+ }
340
+ }
341
+ return results;
342
+ }
343
+ function searchDecisionFiles(mnemeDir, pattern) {
344
+ const results = [];
345
+ const decisionsDir = path4.join(mnemeDir, "decisions");
346
+ walkJsonFiles(decisionsDir, (filePath) => {
347
+ try {
348
+ const doc = JSON.parse(fs4.readFileSync(filePath, "utf-8"));
349
+ if (!isApproved(doc.status)) return;
350
+ let score = 0;
351
+ const matched = [];
352
+ const titleScore = fieldScore(doc.title, pattern, 3);
353
+ if (titleScore > 0) {
354
+ score += titleScore;
355
+ matched.push("title");
356
+ }
357
+ const decisionScore = fieldScore(doc.decision, pattern, 4);
358
+ if (decisionScore > 0) {
359
+ score += decisionScore;
360
+ matched.push("decision");
361
+ }
362
+ const reasoningScore = fieldScore(doc.reasoning, pattern, 2);
363
+ if (reasoningScore > 0) {
364
+ score += reasoningScore;
365
+ matched.push("reasoning");
366
+ }
367
+ if (doc.tags?.some((t) => pattern.test(t))) {
368
+ score += 1;
369
+ matched.push("tags");
370
+ }
371
+ if (score > 0) {
372
+ results.push({
373
+ sourceType: "decision",
374
+ id: doc.id || "",
375
+ title: doc.title || "",
376
+ text: doc.decision || doc.title || "",
377
+ tags: doc.tags || [],
378
+ score,
379
+ matchedFields: matched
380
+ });
381
+ }
382
+ } catch {
383
+ }
384
+ });
385
+ return results;
386
+ }
387
+ function searchPatternFiles(mnemeDir, pattern) {
388
+ const results = [];
389
+ const patternsDir = path4.join(mnemeDir, "patterns");
390
+ walkJsonFiles(patternsDir, (filePath) => {
391
+ try {
392
+ const doc = JSON.parse(fs4.readFileSync(filePath, "utf-8"));
393
+ const items = doc.items || doc.patterns || [];
394
+ for (const item of items) {
395
+ if (!isApproved(item.status)) continue;
396
+ let score = 0;
397
+ const matched = [];
398
+ const titleScore = fieldScore(item.title, pattern, 3);
399
+ if (titleScore > 0) {
400
+ score += titleScore;
401
+ matched.push("title");
402
+ }
403
+ const patternScore = fieldScore(item.pattern, pattern, 3);
404
+ if (patternScore > 0) {
405
+ score += patternScore;
406
+ matched.push("pattern");
407
+ }
408
+ if (item.tags?.some((t) => pattern.test(t))) {
409
+ score += 1;
410
+ matched.push("tags");
411
+ }
412
+ if (score > 0) {
413
+ results.push({
414
+ sourceType: "pattern",
415
+ id: item.id || "",
416
+ title: item.title || "",
417
+ text: item.pattern || item.title || "",
418
+ tags: item.tags || [],
419
+ score,
420
+ matchedFields: matched
421
+ });
422
+ }
423
+ }
424
+ } catch {
425
+ }
426
+ });
427
+ return results;
428
+ }
429
+ function searchApprovedRules(options) {
430
+ const { query, mnemeDir, limit = 5 } = options;
431
+ const keywords = query.toLowerCase().split(/\s+/).map((t) => t.trim()).filter((t) => t.length > 2);
432
+ if (keywords.length === 0) return [];
433
+ const expanded = expandKeywordsWithAliases(keywords, loadTags(mnemeDir));
434
+ const pattern = new RegExp(expanded.map(escapeRegex).join("|"), "i");
435
+ const results = [
436
+ ...searchRuleFiles(mnemeDir, pattern),
437
+ ...searchDecisionFiles(mnemeDir, pattern),
438
+ ...searchPatternFiles(mnemeDir, pattern)
439
+ ];
440
+ const seen = /* @__PURE__ */ new Set();
441
+ return results.sort((a, b) => b.score - a.score).filter((r) => {
442
+ if (seen.has(r.id)) return false;
443
+ seen.add(r.id);
444
+ return true;
445
+ }).slice(0, limit);
446
+ }
447
+
448
+ // lib/search/core.ts
449
+ import * as fs5 from "node:fs";
450
+ import * as path5 from "node:path";
451
+ function searchInteractions(keywords, projectPath, database, limit = 5) {
452
+ if (!database) return [];
453
+ try {
454
+ const stmt = database.prepare(`
455
+ SELECT
456
+ i.session_id,
457
+ i.content,
458
+ i.timestamp,
459
+ highlight(interactions_fts, 0, '[', ']') as content_highlight
460
+ FROM interactions_fts
461
+ JOIN interactions i ON interactions_fts.rowid = i.id
462
+ WHERE interactions_fts MATCH ?
463
+ AND i.project_path = ?
464
+ ORDER BY rank
465
+ LIMIT ?
466
+ `);
467
+ const rows = stmt.all(keywords.join(" OR "), projectPath, limit);
468
+ return rows.map((row) => ({
469
+ type: "interaction",
470
+ id: row.session_id,
471
+ title: `Interaction from ${row.timestamp}`,
472
+ snippet: (row.content_highlight || row.content).substring(0, 150),
473
+ score: 5,
474
+ matchedFields: ["content"]
475
+ }));
476
+ } catch {
477
+ try {
478
+ const clauses = keywords.map(() => "(content LIKE ? OR thinking LIKE ?)");
479
+ const sql = `
480
+ SELECT DISTINCT session_id, substr(content, 1, 120) as snippet, timestamp
481
+ FROM interactions
482
+ WHERE project_path = ?
483
+ AND (${clauses.join(" OR ")})
484
+ ORDER BY timestamp DESC
485
+ LIMIT ?
486
+ `;
487
+ const args = [projectPath];
488
+ for (const keyword of keywords) {
489
+ const pattern = `%${keyword}%`;
490
+ args.push(pattern, pattern);
491
+ }
492
+ args.push(limit);
493
+ const stmt = database.prepare(sql);
494
+ const rows = stmt.all(...args);
495
+ return rows.map((row) => ({
496
+ type: "interaction",
497
+ id: row.session_id,
498
+ title: `Interaction from ${row.timestamp}`,
499
+ snippet: row.snippet,
500
+ score: 3,
501
+ matchedFields: ["content"]
502
+ }));
503
+ } catch {
504
+ return [];
505
+ }
506
+ }
507
+ }
508
+ function searchSessions(mnemeDir, keywords, limit = 5) {
509
+ const sessionsDir = path5.join(mnemeDir, "sessions");
510
+ const results = [];
511
+ const pattern = new RegExp(keywords.map(escapeRegex).join("|"), "i");
512
+ walkJsonFiles(sessionsDir, (filePath) => {
513
+ try {
514
+ const session = JSON.parse(
515
+ fs5.readFileSync(filePath, "utf-8")
516
+ );
517
+ const title = session.title || session.summary?.title || "";
518
+ let score = 0;
519
+ const matchedFields = [];
520
+ const titleScore = fieldScore(title, pattern, 3);
521
+ if (titleScore > 0) {
522
+ score += titleScore;
523
+ matchedFields.push("title");
524
+ }
525
+ if (session.tags?.some((t) => pattern.test(t))) {
526
+ score += 1;
527
+ matchedFields.push("tags");
528
+ }
529
+ const goalScore = fieldScore(session.summary?.goal, pattern, 2);
530
+ if (goalScore > 0) {
531
+ score += goalScore;
532
+ matchedFields.push("summary.goal");
533
+ }
534
+ const descScore = fieldScore(session.summary?.description, pattern, 2);
535
+ if (descScore > 0) {
536
+ score += descScore;
537
+ matchedFields.push("summary.description");
538
+ }
539
+ if (session.discussions?.some(
540
+ (d) => pattern.test(d.topic || "") || pattern.test(d.decision || "")
541
+ )) {
542
+ score += 2;
543
+ matchedFields.push("discussions");
544
+ }
545
+ if (session.errors?.some(
546
+ (e) => pattern.test(e.error || "") || pattern.test(e.solution || "")
547
+ )) {
548
+ score += 2;
549
+ matchedFields.push("errors");
550
+ }
551
+ if (score === 0 && keywords.length <= 2) {
552
+ const titleWords = (title || "").toLowerCase().split(/\s+/);
553
+ const tagWords = session.tags || [];
554
+ for (const keyword of keywords) {
555
+ if (titleWords.some((w) => isFuzzyMatch(keyword, w))) {
556
+ score += 1;
557
+ matchedFields.push("title~fuzzy");
558
+ }
559
+ if (tagWords.some((t) => isFuzzyMatch(keyword, t))) {
560
+ score += 0.5;
561
+ matchedFields.push("tags~fuzzy");
562
+ }
563
+ }
564
+ }
565
+ if (score > 0) {
566
+ results.push({
567
+ type: "session",
568
+ id: session.id,
569
+ title: title || session.id,
570
+ snippet: session.summary?.description || session.summary?.goal || "",
571
+ score,
572
+ matchedFields
573
+ });
574
+ }
575
+ } catch {
576
+ }
577
+ });
578
+ return results.sort((a, b) => b.score - a.score).slice(0, limit);
579
+ }
580
+ function searchKnowledge(options) {
581
+ const {
582
+ query,
583
+ mnemeDir,
584
+ projectPath,
585
+ database = null,
586
+ types = ["session", "interaction"],
587
+ limit = 10,
588
+ offset = 0
589
+ } = options;
590
+ const keywords = query.toLowerCase().split(/\s+/).map((token) => token.trim()).filter((token) => token.length > 2);
591
+ if (keywords.length === 0) return [];
592
+ const expandedKeywords = expandKeywordsWithAliases(
593
+ keywords,
594
+ loadTags(mnemeDir)
595
+ );
596
+ const results = [];
597
+ const safeOffset = Math.max(0, offset);
598
+ const fetchLimit = Math.max(limit + safeOffset, limit, 10);
599
+ const normalizedTypes = new Set(types);
600
+ if (normalizedTypes.has("session")) {
601
+ results.push(...searchSessions(mnemeDir, expandedKeywords, fetchLimit));
602
+ }
603
+ if (normalizedTypes.has("interaction")) {
604
+ results.push(
605
+ ...searchInteractions(
606
+ expandedKeywords,
607
+ projectPath,
608
+ database,
609
+ fetchLimit
610
+ )
611
+ );
612
+ }
613
+ const seen = /* @__PURE__ */ new Set();
614
+ return results.sort((a, b) => b.score - a.score).filter((result) => {
615
+ const key = `${result.type}:${result.id}`;
616
+ if (seen.has(key)) return false;
617
+ seen.add(key);
618
+ return true;
619
+ }).slice(safeOffset, safeOffset + limit);
620
+ }
621
+
622
+ // lib/search/prompt.ts
623
+ var originalEmit = process.emit;
624
+ process.emit = (event, ...args) => {
625
+ 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")) {
626
+ return false;
627
+ }
628
+ return originalEmit.apply(process, [event, ...args]);
629
+ };
630
+ var { DatabaseSync } = await import("node:sqlite");
631
+ function getArg(args, name) {
632
+ const index = args.indexOf(`--${name}`);
633
+ return index === -1 ? void 0 : args[index + 1];
634
+ }
635
+ function main() {
636
+ const args = process.argv.slice(2);
637
+ const query = getArg(args, "query");
638
+ const projectPath = getArg(args, "project");
639
+ const limit = Number.parseInt(getArg(args, "limit") || "5", 10);
640
+ if (!query || !projectPath) {
641
+ console.log(
642
+ JSON.stringify({ success: false, error: "Missing required args" })
643
+ );
644
+ process.exit(1);
645
+ }
646
+ const mnemeDir = path6.join(projectPath, ".mneme");
647
+ const dbPath = path6.join(mnemeDir, "local.db");
648
+ let database = null;
649
+ try {
650
+ if (fs6.existsSync(dbPath)) {
651
+ database = new DatabaseSync(dbPath);
652
+ database.exec("PRAGMA journal_mode = WAL");
653
+ }
654
+ const results = searchKnowledge({
655
+ query,
656
+ mnemeDir,
657
+ projectPath,
658
+ database,
659
+ limit: Number.isFinite(limit) ? Math.max(1, Math.min(limit, 10)) : 5
660
+ });
661
+ const rules = searchApprovedRules({ query, mnemeDir, limit: 5 });
662
+ console.log(JSON.stringify({ success: true, results, rules }));
663
+ } catch (error) {
664
+ console.log(
665
+ JSON.stringify({ success: false, error: error.message })
666
+ );
667
+ process.exit(1);
668
+ } finally {
669
+ database?.close();
670
+ }
671
+ }
672
+ main();