@hir4ta/mneme 0.24.0 → 0.24.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/.claude-plugin/plugin.json +1 -1
- package/README.ja.md +3 -4
- package/README.md +3 -4
- package/dist/lib/save/index.js +94 -13
- package/dist/lib/search/prompt.js +225 -8
- package/dist/lib/session/finalize.js +104 -18
- package/dist/lib/session/init.js +12 -9
- package/dist/public/assets/index-BL-68Hbg.css +1 -0
- package/dist/public/assets/index-rwYM2mwM.js +382 -0
- package/dist/public/assets/{react-force-graph-2d-BRZ1ditM.js → react-force-graph-2d-n2R24ZBV.js} +1 -1
- package/dist/public/index.html +2 -2
- package/dist/server.js +6 -3
- package/dist/servers/db-server.js +41 -41
- package/dist/servers/search-server.js +11 -11
- package/hooks/user-prompt-submit.sh +36 -5
- package/package.json +1 -1
- package/servers/db/save.ts +2 -2
- package/servers/db/session-summary.ts +37 -9
- package/servers/db/tools.ts +6 -5
- package/servers/db/utils.ts +6 -0
- package/servers/db/validate-sources.ts +361 -0
- package/servers/db-server.ts +2 -0
- package/skills/harvest/SKILL.md +3 -3
- package/skills/init-mneme/SKILL.md +11 -85
- package/skills/save/SKILL.md +19 -7
- package/skills/search/SKILL.md +19 -0
- package/skills/using-mneme/SKILL.md +11 -0
- package/dist/public/assets/index-B1iHg48P.css +0 -1
- package/dist/public/assets/index-BzMjX28q.js +0 -377
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mneme",
|
|
3
3
|
"description": "A plugin that provides long-term memory for Claude Code. It automatically saves context lost during auto-compact, offering features for session restoration, recording technical decisions, and learning developer patterns.",
|
|
4
|
-
"version": "0.24.
|
|
4
|
+
"version": "0.24.1",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "hir4ta"
|
|
7
7
|
},
|
package/README.ja.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# mneme
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|

|
|
5
5
|
[](https://www.npmjs.com/package/@hir4ta/mneme)
|
|
6
6
|
[](https://github.com/hir4ta/mneme/blob/main/LICENSE)
|
|
@@ -127,9 +127,8 @@ implement → save → approve rules
|
|
|
127
127
|
```
|
|
128
128
|
|
|
129
129
|
1. **implement**: コードを実装
|
|
130
|
-
2. **save**:
|
|
131
|
-
3. **
|
|
132
|
-
4. **approve rules**: 生成された開発ルールをインラインで確認・承認/却下
|
|
130
|
+
2. **save**: 元データを抽出して開発ルール候補を生成(バリデーションはMCP経由で自動実行)
|
|
131
|
+
3. **approve rules**: 生成された開発ルールをインラインで確認・承認/却下
|
|
133
132
|
|
|
134
133
|
ランタイム詳細(Hook分岐、未保存終了、Auto-Compact)は以下:
|
|
135
134
|
- `docs/mneme-runtime-flow.md`
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# mneme
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|

|
|
5
5
|
[](https://www.npmjs.com/package/@hir4ta/mneme)
|
|
6
6
|
[](https://github.com/hir4ta/mneme/blob/main/LICENSE)
|
|
@@ -127,9 +127,8 @@ implement → save → approve rules
|
|
|
127
127
|
```
|
|
128
128
|
|
|
129
129
|
1. **implement**: Write code
|
|
130
|
-
2. **save**: Extract source knowledge and generate development rule candidates
|
|
131
|
-
3. **
|
|
132
|
-
4. **approve rules**: Review and approve/reject generated development rules inline
|
|
130
|
+
2. **save**: Extract source knowledge and generate development rule candidates (validation runs automatically via MCP)
|
|
131
|
+
3. **approve rules**: Review and approve/reject generated development rules inline
|
|
133
132
|
|
|
134
133
|
Detailed runtime flow (hooks, uncommitted policy, auto-compact path):
|
|
135
134
|
- `docs/mneme-runtime-flow.md`
|
package/dist/lib/save/index.js
CHANGED
|
@@ -56,13 +56,13 @@ async function getGitInfo(projectPath) {
|
|
|
56
56
|
return { owner, repository, repositoryUrl, repositoryRoot };
|
|
57
57
|
}
|
|
58
58
|
function resolveMnemeSessionId(projectPath, claudeSessionId) {
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
`${shortId}.json`
|
|
59
|
+
const sessionLinksDir = path.join(projectPath, ".mneme", "session-links");
|
|
60
|
+
const fullPath = path.join(sessionLinksDir, `${claudeSessionId}.json`);
|
|
61
|
+
const shortPath = path.join(
|
|
62
|
+
sessionLinksDir,
|
|
63
|
+
`${claudeSessionId.slice(0, 8)}.json`
|
|
65
64
|
);
|
|
65
|
+
const sessionLinkPath = fs.existsSync(fullPath) ? fullPath : shortPath;
|
|
66
66
|
if (fs.existsSync(sessionLinkPath)) {
|
|
67
67
|
try {
|
|
68
68
|
const link = JSON.parse(fs.readFileSync(sessionLinkPath, "utf8"));
|
|
@@ -72,24 +72,29 @@ function resolveMnemeSessionId(projectPath, claudeSessionId) {
|
|
|
72
72
|
} catch {
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
|
-
return
|
|
75
|
+
return claudeSessionId;
|
|
76
76
|
}
|
|
77
77
|
function findSessionFileById(projectPath, mnemeSessionId) {
|
|
78
78
|
const sessionsDir = path.join(projectPath, ".mneme", "sessions");
|
|
79
|
-
const
|
|
79
|
+
const searchDirFor = (dir, fileName) => {
|
|
80
80
|
if (!fs.existsSync(dir)) return null;
|
|
81
81
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
82
82
|
const fullPath = path.join(dir, entry.name);
|
|
83
83
|
if (entry.isDirectory()) {
|
|
84
|
-
const
|
|
85
|
-
if (
|
|
86
|
-
} else if (entry.name ===
|
|
84
|
+
const result2 = searchDirFor(fullPath, fileName);
|
|
85
|
+
if (result2) return result2;
|
|
86
|
+
} else if (entry.name === fileName) {
|
|
87
87
|
return fullPath;
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
return null;
|
|
91
91
|
};
|
|
92
|
-
|
|
92
|
+
const result = searchDirFor(sessionsDir, `${mnemeSessionId}.json`);
|
|
93
|
+
if (result) return result;
|
|
94
|
+
if (mnemeSessionId.length > 8) {
|
|
95
|
+
return searchDirFor(sessionsDir, `${mnemeSessionId.slice(0, 8)}.json`);
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
93
98
|
}
|
|
94
99
|
function hasSessionSummary(sessionFile) {
|
|
95
100
|
if (!sessionFile) return false;
|
|
@@ -181,12 +186,19 @@ function cleanupStaleUncommittedSessions(projectPath, graceDays) {
|
|
|
181
186
|
} catch {
|
|
182
187
|
}
|
|
183
188
|
}
|
|
184
|
-
const
|
|
189
|
+
const fullLinkPath = path2.join(
|
|
190
|
+
projectPath,
|
|
191
|
+
".mneme",
|
|
192
|
+
"session-links",
|
|
193
|
+
`${row.claude_session_id}.json`
|
|
194
|
+
);
|
|
195
|
+
const shortLinkPath = path2.join(
|
|
185
196
|
projectPath,
|
|
186
197
|
".mneme",
|
|
187
198
|
"session-links",
|
|
188
199
|
`${row.claude_session_id.slice(0, 8)}.json`
|
|
189
200
|
);
|
|
201
|
+
const linkPath = fs2.existsSync(fullLinkPath) ? fullLinkPath : shortLinkPath;
|
|
190
202
|
if (fs2.existsSync(linkPath)) {
|
|
191
203
|
try {
|
|
192
204
|
fs2.unlinkSync(linkPath);
|
|
@@ -322,6 +334,24 @@ function migrateDatabase(db) {
|
|
|
322
334
|
`);
|
|
323
335
|
console.error("[mneme] Migrated: created session_save_state table");
|
|
324
336
|
}
|
|
337
|
+
try {
|
|
338
|
+
db.exec("SELECT 1 FROM file_index LIMIT 1");
|
|
339
|
+
} catch {
|
|
340
|
+
db.exec(`
|
|
341
|
+
CREATE TABLE IF NOT EXISTS file_index (
|
|
342
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
343
|
+
session_id TEXT NOT NULL,
|
|
344
|
+
project_path TEXT NOT NULL,
|
|
345
|
+
file_path TEXT NOT NULL,
|
|
346
|
+
tool_name TEXT,
|
|
347
|
+
timestamp TEXT NOT NULL,
|
|
348
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
349
|
+
);
|
|
350
|
+
CREATE INDEX IF NOT EXISTS idx_file_index_session ON file_index(session_id);
|
|
351
|
+
CREATE INDEX IF NOT EXISTS idx_file_index_project_file ON file_index(project_path, file_path);
|
|
352
|
+
`);
|
|
353
|
+
console.error("[mneme] Migrated: created file_index table");
|
|
354
|
+
}
|
|
325
355
|
}
|
|
326
356
|
function initDatabase(dbPath) {
|
|
327
357
|
const mnemeDir = path3.dirname(dbPath);
|
|
@@ -568,6 +598,52 @@ async function parseTranscriptIncremental(transcriptPath, lastSavedLine) {
|
|
|
568
598
|
|
|
569
599
|
// lib/save/index.ts
|
|
570
600
|
var { DatabaseSync: DatabaseSync3 } = await import("node:sqlite");
|
|
601
|
+
function normalizeFilePath(absPath, projectPath) {
|
|
602
|
+
if (!absPath.startsWith(projectPath)) return null;
|
|
603
|
+
return absPath.slice(projectPath.length).replace(/^\//, "");
|
|
604
|
+
}
|
|
605
|
+
var IGNORED_PREFIXES = [
|
|
606
|
+
"node_modules/",
|
|
607
|
+
"dist/",
|
|
608
|
+
".git/",
|
|
609
|
+
".mneme/",
|
|
610
|
+
".claude/"
|
|
611
|
+
];
|
|
612
|
+
var IGNORED_FILES = ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"];
|
|
613
|
+
function isIgnoredPath(relativePath) {
|
|
614
|
+
return IGNORED_PREFIXES.some((p) => relativePath.startsWith(p)) || IGNORED_FILES.includes(relativePath);
|
|
615
|
+
}
|
|
616
|
+
function indexFilePaths(fileIndexStmt, interaction, mnemeSessionId, projectPath) {
|
|
617
|
+
const seen = /* @__PURE__ */ new Set();
|
|
618
|
+
const add = (absPath, toolName) => {
|
|
619
|
+
const normalized = normalizeFilePath(absPath, projectPath);
|
|
620
|
+
if (!normalized || isIgnoredPath(normalized) || seen.has(normalized))
|
|
621
|
+
return;
|
|
622
|
+
seen.add(normalized);
|
|
623
|
+
try {
|
|
624
|
+
fileIndexStmt.run(
|
|
625
|
+
mnemeSessionId,
|
|
626
|
+
projectPath,
|
|
627
|
+
normalized,
|
|
628
|
+
toolName,
|
|
629
|
+
interaction.timestamp
|
|
630
|
+
);
|
|
631
|
+
} catch {
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
for (const td of interaction.toolDetails) {
|
|
635
|
+
if (typeof td.detail === "string" && td.detail.startsWith("/")) {
|
|
636
|
+
add(td.detail, td.name);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
if (interaction.toolResults) {
|
|
640
|
+
for (const tr of interaction.toolResults) {
|
|
641
|
+
if (tr.filePath?.startsWith("/")) {
|
|
642
|
+
add(tr.filePath, tr.toolName || "");
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
571
647
|
async function incrementalSave(claudeSessionId, transcriptPath, projectPath) {
|
|
572
648
|
if (!claudeSessionId || !transcriptPath || !projectPath) {
|
|
573
649
|
return {
|
|
@@ -613,6 +689,10 @@ async function incrementalSave(claudeSessionId, transcriptPath, projectPath) {
|
|
|
613
689
|
owner, role, content, thinking, tool_calls, timestamp, is_compact_summary
|
|
614
690
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
615
691
|
`);
|
|
692
|
+
const fileIndexStmt = db.prepare(`
|
|
693
|
+
INSERT INTO file_index (session_id, project_path, file_path, tool_name, timestamp)
|
|
694
|
+
VALUES (?, ?, ?, ?, ?)
|
|
695
|
+
`);
|
|
616
696
|
let insertedCount = 0;
|
|
617
697
|
let lastTimestamp = saveState.lastSavedTimestamp || "";
|
|
618
698
|
for (const interaction of interactions) {
|
|
@@ -676,6 +756,7 @@ async function incrementalSave(claudeSessionId, transcriptPath, projectPath) {
|
|
|
676
756
|
);
|
|
677
757
|
insertedCount++;
|
|
678
758
|
}
|
|
759
|
+
indexFilePaths(fileIndexStmt, interaction, mnemeSessionId, projectPath);
|
|
679
760
|
lastTimestamp = interaction.timestamp;
|
|
680
761
|
} catch (error) {
|
|
681
762
|
console.error(`[mneme] Error inserting interaction: ${error}`);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// lib/search/prompt.ts
|
|
4
|
-
import * as
|
|
5
|
-
import * as
|
|
4
|
+
import * as fs7 from "node:fs";
|
|
5
|
+
import * as path7 from "node:path";
|
|
6
6
|
|
|
7
7
|
// lib/search/approved-rules.ts
|
|
8
8
|
import * as fs4 from "node:fs";
|
|
@@ -282,6 +282,158 @@ function walkJsonFiles(dir, callback) {
|
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
+
// lib/search/stopwords.ts
|
|
286
|
+
var ENGLISH_STOPWORDS = /* @__PURE__ */ new Set([
|
|
287
|
+
// Articles & determiners
|
|
288
|
+
"the",
|
|
289
|
+
"this",
|
|
290
|
+
"that",
|
|
291
|
+
"these",
|
|
292
|
+
"those",
|
|
293
|
+
"some",
|
|
294
|
+
"any",
|
|
295
|
+
"all",
|
|
296
|
+
"each",
|
|
297
|
+
"every",
|
|
298
|
+
"both",
|
|
299
|
+
"other",
|
|
300
|
+
// Pronouns
|
|
301
|
+
"you",
|
|
302
|
+
"your",
|
|
303
|
+
"its",
|
|
304
|
+
"his",
|
|
305
|
+
"her",
|
|
306
|
+
"our",
|
|
307
|
+
"their",
|
|
308
|
+
"them",
|
|
309
|
+
"they",
|
|
310
|
+
"who",
|
|
311
|
+
"whom",
|
|
312
|
+
"which",
|
|
313
|
+
// Prepositions & conjunctions
|
|
314
|
+
"for",
|
|
315
|
+
"with",
|
|
316
|
+
"from",
|
|
317
|
+
"into",
|
|
318
|
+
"about",
|
|
319
|
+
"between",
|
|
320
|
+
"through",
|
|
321
|
+
"during",
|
|
322
|
+
"before",
|
|
323
|
+
"after",
|
|
324
|
+
"above",
|
|
325
|
+
"below",
|
|
326
|
+
"under",
|
|
327
|
+
"and",
|
|
328
|
+
"but",
|
|
329
|
+
"nor",
|
|
330
|
+
"not",
|
|
331
|
+
"than",
|
|
332
|
+
"then",
|
|
333
|
+
"also",
|
|
334
|
+
"once",
|
|
335
|
+
"again",
|
|
336
|
+
"further",
|
|
337
|
+
"such",
|
|
338
|
+
"same",
|
|
339
|
+
"few",
|
|
340
|
+
"more",
|
|
341
|
+
"most",
|
|
342
|
+
"too",
|
|
343
|
+
"own",
|
|
344
|
+
// Common verbs (non-technical)
|
|
345
|
+
"are",
|
|
346
|
+
"was",
|
|
347
|
+
"were",
|
|
348
|
+
"been",
|
|
349
|
+
"being",
|
|
350
|
+
"have",
|
|
351
|
+
"has",
|
|
352
|
+
"had",
|
|
353
|
+
"does",
|
|
354
|
+
"did",
|
|
355
|
+
"will",
|
|
356
|
+
"would",
|
|
357
|
+
"could",
|
|
358
|
+
"should",
|
|
359
|
+
"shall",
|
|
360
|
+
"might",
|
|
361
|
+
"must",
|
|
362
|
+
"can",
|
|
363
|
+
// Prompt noise
|
|
364
|
+
"please",
|
|
365
|
+
"help",
|
|
366
|
+
"want",
|
|
367
|
+
"need",
|
|
368
|
+
"like",
|
|
369
|
+
"just",
|
|
370
|
+
"only",
|
|
371
|
+
"very",
|
|
372
|
+
"really",
|
|
373
|
+
"here",
|
|
374
|
+
"there",
|
|
375
|
+
"what",
|
|
376
|
+
"when",
|
|
377
|
+
"where",
|
|
378
|
+
"how",
|
|
379
|
+
"why",
|
|
380
|
+
"let",
|
|
381
|
+
"make",
|
|
382
|
+
"way",
|
|
383
|
+
"tell"
|
|
384
|
+
]);
|
|
385
|
+
var JAPANESE_STOPWORDS = /* @__PURE__ */ new Set([
|
|
386
|
+
// Common prompt phrases (split on whitespace, so these appear as tokens)
|
|
387
|
+
"\u304F\u3060\u3055\u3044",
|
|
388
|
+
"\u306B\u3064\u3044\u3066",
|
|
389
|
+
"\u3064\u3044\u3066",
|
|
390
|
+
"\u3057\u307E\u3059",
|
|
391
|
+
"\u3057\u3066\u3044\u308B",
|
|
392
|
+
"\u3057\u3066\u308B",
|
|
393
|
+
"\u3067\u304D\u308B",
|
|
394
|
+
"\u3067\u304D\u307E\u3059",
|
|
395
|
+
"\u3067\u3059\u304B",
|
|
396
|
+
"\u3042\u308A\u307E\u3059\u304B",
|
|
397
|
+
"\u3042\u308A\u307E\u305B\u3093",
|
|
398
|
+
"\u3042\u308A\u307E\u3057\u305F",
|
|
399
|
+
"\u3067\u3059\u304C",
|
|
400
|
+
"\u3067\u3059\u3051\u3069",
|
|
401
|
+
"\u3067\u3059\u306E\u3067",
|
|
402
|
+
"\u3067\u3059\u304B\u3089",
|
|
403
|
+
"\u3067\u3059\u306D",
|
|
404
|
+
"\u3067\u3059\u3088",
|
|
405
|
+
"\u3057\u307E\u3057\u3087\u3046",
|
|
406
|
+
"\u3057\u3088\u3046",
|
|
407
|
+
"\u3057\u305F\u3044",
|
|
408
|
+
"\u3057\u305F\u3044\u3067\u3059",
|
|
409
|
+
"\u307B\u3057\u3044",
|
|
410
|
+
"\u307B\u3057\u3044\u3067\u3059",
|
|
411
|
+
"\u3042\u308A\u304C\u3068\u3046",
|
|
412
|
+
"\u304A\u306D\u304C\u3044",
|
|
413
|
+
"\u304A\u9858\u3044",
|
|
414
|
+
"\u6559\u3048\u3066",
|
|
415
|
+
"\u898B\u305B\u3066",
|
|
416
|
+
"\u3084\u3063\u3066",
|
|
417
|
+
"\u3069\u3046\u3084\u3063\u3066",
|
|
418
|
+
"\u306A\u305C",
|
|
419
|
+
"\u3069\u3046",
|
|
420
|
+
"\u3069\u306E",
|
|
421
|
+
"\u305D\u306E",
|
|
422
|
+
"\u3053\u306E",
|
|
423
|
+
"\u3042\u306E",
|
|
424
|
+
"\u305D\u308C",
|
|
425
|
+
"\u3053\u308C",
|
|
426
|
+
"\u3042\u308C"
|
|
427
|
+
]);
|
|
428
|
+
var ALL_STOPWORDS = /* @__PURE__ */ new Set([...ENGLISH_STOPWORDS, ...JAPANESE_STOPWORDS]);
|
|
429
|
+
function isStopword(token) {
|
|
430
|
+
return ALL_STOPWORDS.has(token.toLowerCase());
|
|
431
|
+
}
|
|
432
|
+
function removeStopwords(tokens) {
|
|
433
|
+
const filtered = tokens.filter((t) => !isStopword(t));
|
|
434
|
+
return filtered.length > 0 ? filtered : tokens;
|
|
435
|
+
}
|
|
436
|
+
|
|
285
437
|
// lib/search/approved-rules.ts
|
|
286
438
|
function isApproved(status) {
|
|
287
439
|
if (typeof status !== "string") return false;
|
|
@@ -428,7 +580,9 @@ function searchPatternFiles(mnemeDir, pattern) {
|
|
|
428
580
|
}
|
|
429
581
|
function searchApprovedRules(options) {
|
|
430
582
|
const { query, mnemeDir, limit = 5 } = options;
|
|
431
|
-
const keywords =
|
|
583
|
+
const keywords = removeStopwords(
|
|
584
|
+
query.toLowerCase().split(/\s+/).map((t) => t.trim()).filter((t) => t.length > 2)
|
|
585
|
+
);
|
|
432
586
|
if (keywords.length === 0) return [];
|
|
433
587
|
const expanded = expandKeywordsWithAliases(keywords, loadTags(mnemeDir));
|
|
434
588
|
const pattern = new RegExp(expanded.map(escapeRegex).join("|"), "i");
|
|
@@ -563,6 +717,14 @@ function searchSessions(mnemeDir, keywords, limit = 5, detail = "compact") {
|
|
|
563
717
|
score += 2;
|
|
564
718
|
matchedFields.push("errors");
|
|
565
719
|
}
|
|
720
|
+
if (session.technologies?.some((t) => pattern.test(t))) {
|
|
721
|
+
score += 1.5;
|
|
722
|
+
matchedFields.push("technologies");
|
|
723
|
+
}
|
|
724
|
+
if (session.filesModified?.some((f) => pattern.test(f.path))) {
|
|
725
|
+
score += 1;
|
|
726
|
+
matchedFields.push("filesModified");
|
|
727
|
+
}
|
|
566
728
|
if (score === 0 && keywords.length <= 2) {
|
|
567
729
|
const titleWords = (title || "").toLowerCase().split(/\s+/);
|
|
568
730
|
const tagWords = session.tags || [];
|
|
@@ -613,7 +775,9 @@ function searchKnowledge(options) {
|
|
|
613
775
|
offset = 0,
|
|
614
776
|
detail = "compact"
|
|
615
777
|
} = options;
|
|
616
|
-
const keywords =
|
|
778
|
+
const keywords = removeStopwords(
|
|
779
|
+
query.toLowerCase().split(/\s+/).map((token) => token.trim()).filter((token) => token.length > 2)
|
|
780
|
+
);
|
|
617
781
|
if (keywords.length === 0) return [];
|
|
618
782
|
const expandedKeywords = expandKeywordsWithAliases(
|
|
619
783
|
keywords,
|
|
@@ -648,6 +812,55 @@ function searchKnowledge(options) {
|
|
|
648
812
|
}).slice(safeOffset, safeOffset + limit);
|
|
649
813
|
}
|
|
650
814
|
|
|
815
|
+
// lib/search/file-search.ts
|
|
816
|
+
import * as fs6 from "node:fs";
|
|
817
|
+
import * as path6 from "node:path";
|
|
818
|
+
function getSessionTitle(mnemeDir, sessionId) {
|
|
819
|
+
const sessionsDir = path6.join(mnemeDir, "sessions");
|
|
820
|
+
let title = sessionId;
|
|
821
|
+
walkJsonFiles(sessionsDir, (filePath) => {
|
|
822
|
+
if (!path6.basename(filePath, ".json").startsWith(sessionId)) return;
|
|
823
|
+
try {
|
|
824
|
+
const data = JSON.parse(fs6.readFileSync(filePath, "utf-8"));
|
|
825
|
+
if (data.title) title = data.title;
|
|
826
|
+
} catch {
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
return title;
|
|
830
|
+
}
|
|
831
|
+
function searchByFiles(database, projectPath, filePaths, mnemeDir, limit = 3) {
|
|
832
|
+
if (filePaths.length === 0) return [];
|
|
833
|
+
try {
|
|
834
|
+
const placeholders = filePaths.map(() => "?").join(",");
|
|
835
|
+
const rows = database.prepare(
|
|
836
|
+
`SELECT session_id, file_path, COUNT(*) as cnt
|
|
837
|
+
FROM file_index
|
|
838
|
+
WHERE project_path = ? AND file_path IN (${placeholders})
|
|
839
|
+
GROUP BY session_id, file_path`
|
|
840
|
+
).all(projectPath, ...filePaths);
|
|
841
|
+
const sessionMap = /* @__PURE__ */ new Map();
|
|
842
|
+
for (const row of rows) {
|
|
843
|
+
const entry = sessionMap.get(row.session_id) || {
|
|
844
|
+
files: /* @__PURE__ */ new Set(),
|
|
845
|
+
count: 0
|
|
846
|
+
};
|
|
847
|
+
entry.files.add(row.file_path);
|
|
848
|
+
entry.count += row.cnt;
|
|
849
|
+
sessionMap.set(row.session_id, entry);
|
|
850
|
+
}
|
|
851
|
+
return [...sessionMap.entries()].sort(
|
|
852
|
+
(a, b) => b[1].files.size - a[1].files.size || b[1].count - a[1].count
|
|
853
|
+
).slice(0, limit).map(([sessionId, data]) => ({
|
|
854
|
+
sessionId,
|
|
855
|
+
title: getSessionTitle(mnemeDir, sessionId),
|
|
856
|
+
matchedFiles: [...data.files],
|
|
857
|
+
fileCount: data.count
|
|
858
|
+
}));
|
|
859
|
+
} catch {
|
|
860
|
+
return [];
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
651
864
|
// lib/search/prompt.ts
|
|
652
865
|
var originalEmit = process.emit;
|
|
653
866
|
process.emit = (event, ...args) => {
|
|
@@ -666,17 +879,18 @@ function main() {
|
|
|
666
879
|
const query = getArg(args, "query");
|
|
667
880
|
const projectPath = getArg(args, "project");
|
|
668
881
|
const limit = Number.parseInt(getArg(args, "limit") || "5", 10);
|
|
882
|
+
const files = getArg(args, "files");
|
|
669
883
|
if (!query || !projectPath) {
|
|
670
884
|
console.log(
|
|
671
885
|
JSON.stringify({ success: false, error: "Missing required args" })
|
|
672
886
|
);
|
|
673
887
|
process.exit(1);
|
|
674
888
|
}
|
|
675
|
-
const mnemeDir =
|
|
676
|
-
const dbPath =
|
|
889
|
+
const mnemeDir = path7.join(projectPath, ".mneme");
|
|
890
|
+
const dbPath = path7.join(mnemeDir, "local.db");
|
|
677
891
|
let database = null;
|
|
678
892
|
try {
|
|
679
|
-
if (
|
|
893
|
+
if (fs7.existsSync(dbPath)) {
|
|
680
894
|
database = new DatabaseSync(dbPath);
|
|
681
895
|
database.exec("PRAGMA journal_mode = WAL");
|
|
682
896
|
}
|
|
@@ -688,7 +902,10 @@ function main() {
|
|
|
688
902
|
limit: Number.isFinite(limit) ? Math.max(1, Math.min(limit, 10)) : 5
|
|
689
903
|
});
|
|
690
904
|
const rules = searchApprovedRules({ query, mnemeDir, limit: 5 });
|
|
691
|
-
|
|
905
|
+
const fileRecommendations = files && database ? searchByFiles(database, projectPath, files.split(","), mnemeDir) : [];
|
|
906
|
+
console.log(
|
|
907
|
+
JSON.stringify({ success: true, results, rules, fileRecommendations })
|
|
908
|
+
);
|
|
692
909
|
} catch (error) {
|
|
693
910
|
console.log(
|
|
694
911
|
JSON.stringify({ success: false, error: error.message })
|