@vheins/local-memory-mcp 0.1.3 → 0.1.5
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/dist/dashboard/dashboard.test.js +362 -0
- package/dist/dashboard/server.js +0 -0
- package/dist/mcp/client.test.js +130 -0
- package/dist/resources/index.test.js +96 -0
- package/dist/router.test.js +113 -0
- package/dist/server.js +0 -0
- package/dist/storage/sqlite.d.ts +95 -0
- package/dist/storage/sqlite.d.ts.map +1 -0
- package/dist/storage/sqlite.js +537 -0
- package/dist/storage/sqlite.js.map +1 -0
- package/dist/storage/sqlite.test.d.ts +2 -0
- package/dist/storage/sqlite.test.d.ts.map +1 -0
- package/dist/storage/sqlite.test.js +358 -0
- package/dist/storage/sqlite.test.js.map +1 -0
- package/dist/storage/vectors.stub.d.ts +12 -0
- package/dist/storage/vectors.stub.d.ts.map +1 -0
- package/dist/storage/vectors.stub.js +88 -0
- package/dist/storage/vectors.stub.js.map +1 -0
- package/dist/tools/memory.search.test.js +181 -0
- package/dist/utils/logger.test.js +84 -0
- package/dist/utils/normalize.test.js +159 -0
- package/dist/utils/query-expander.test.js +35 -0
- package/package.json +10 -3
- package/.kiro/specs/memory-mcp-optimization/.config.kiro +0 -1
- package/.vscode/tasks.json +0 -27
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Feature: memory-mcp-optimization, Property 11: createRouter() uses provided storage
|
|
2
|
+
import { describe, it, expect, vi } from "vitest";
|
|
3
|
+
import * as fc from "fast-check";
|
|
4
|
+
import { createRouter } from "./router.js";
|
|
5
|
+
/**
|
|
6
|
+
* Property 11: createRouter() menggunakan storage yang diberikan
|
|
7
|
+
* Validates: Requirements 10.1, 10.4
|
|
8
|
+
*
|
|
9
|
+
* For any mock SQLiteStore given to createRouter(mockDb, mockVectors),
|
|
10
|
+
* all tool operations run through the router SHALL use mockDb and not access the real DB.
|
|
11
|
+
*/
|
|
12
|
+
describe("createRouter() — Property 11: uses provided storage", () => {
|
|
13
|
+
function makeMockDb() {
|
|
14
|
+
return {
|
|
15
|
+
insert: vi.fn(),
|
|
16
|
+
update: vi.fn(),
|
|
17
|
+
delete: vi.fn(),
|
|
18
|
+
getById: vi.fn().mockReturnValue(null),
|
|
19
|
+
searchByRepo: vi.fn().mockReturnValue([]),
|
|
20
|
+
searchBySimilarity: vi.fn().mockReturnValue([]),
|
|
21
|
+
getRecentMemories: vi.fn().mockReturnValue([]),
|
|
22
|
+
getTotalCount: vi.fn().mockReturnValue(0),
|
|
23
|
+
getSummary: vi.fn().mockReturnValue(null),
|
|
24
|
+
upsertSummary: vi.fn(),
|
|
25
|
+
listRepos: vi.fn().mockReturnValue([]),
|
|
26
|
+
listRecent: vi.fn().mockReturnValue([]),
|
|
27
|
+
incrementHitCount: vi.fn(),
|
|
28
|
+
incrementRecallCount: vi.fn(),
|
|
29
|
+
getStats: vi.fn().mockReturnValue({ total: 0, byType: {}, unused: 0 }),
|
|
30
|
+
getAllMemoriesWithStats: vi.fn().mockReturnValue([]),
|
|
31
|
+
upsertVectorEmbedding: vi.fn(),
|
|
32
|
+
getVectorEmbedding: vi.fn().mockReturnValue(null),
|
|
33
|
+
archiveExpiredMemories: vi.fn().mockReturnValue(0),
|
|
34
|
+
logQuery: vi.fn(),
|
|
35
|
+
getRecentQueries: vi.fn().mockReturnValue([]),
|
|
36
|
+
close: vi.fn(),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function makeMockVectors() {
|
|
40
|
+
return {
|
|
41
|
+
upsert: vi.fn().mockResolvedValue(undefined),
|
|
42
|
+
remove: vi.fn().mockResolvedValue(undefined),
|
|
43
|
+
search: vi.fn().mockResolvedValue([]),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
it("memory-recap calls getRecentMemories on the provided mock db", async () => {
|
|
47
|
+
const mockDb = makeMockDb();
|
|
48
|
+
const mockVectors = makeMockVectors();
|
|
49
|
+
const router = createRouter(mockDb, mockVectors);
|
|
50
|
+
await router("tools/call", {
|
|
51
|
+
name: "memory-recap",
|
|
52
|
+
arguments: { repo: "test-repo", limit: 5 },
|
|
53
|
+
});
|
|
54
|
+
expect(mockDb.getRecentMemories).toHaveBeenCalledWith("test-repo", 5, 0);
|
|
55
|
+
expect(mockDb.getTotalCount).toHaveBeenCalledWith("test-repo");
|
|
56
|
+
});
|
|
57
|
+
it("memory-search calls searchBySimilarity on the provided mock db", async () => {
|
|
58
|
+
const mockDb = makeMockDb();
|
|
59
|
+
const mockVectors = makeMockVectors();
|
|
60
|
+
const router = createRouter(mockDb, mockVectors);
|
|
61
|
+
await router("tools/call", {
|
|
62
|
+
name: "memory-search",
|
|
63
|
+
arguments: { query: "test query", repo: "test-repo", limit: 5 },
|
|
64
|
+
});
|
|
65
|
+
expect(mockDb.searchBySimilarity).toHaveBeenCalled();
|
|
66
|
+
// Verify the first argument to searchBySimilarity contains the repo
|
|
67
|
+
const callArgs = mockDb.searchBySimilarity.mock.calls[0];
|
|
68
|
+
expect(callArgs[1]).toBe("test-repo");
|
|
69
|
+
});
|
|
70
|
+
it("property: for any repo string, memory-recap always uses the injected db", async () => {
|
|
71
|
+
await fc.assert(fc.asyncProperty(fc.string({ minLength: 1, maxLength: 50 }).filter((s) => s.trim().length > 0), fc.integer({ min: 1, max: 50 }), async (repo, limit) => {
|
|
72
|
+
const mockDb = makeMockDb();
|
|
73
|
+
const mockVectors = makeMockVectors();
|
|
74
|
+
const router = createRouter(mockDb, mockVectors);
|
|
75
|
+
await router("tools/call", {
|
|
76
|
+
name: "memory-recap",
|
|
77
|
+
arguments: { repo, limit },
|
|
78
|
+
});
|
|
79
|
+
// The mock db methods must have been called (not a real DB)
|
|
80
|
+
expect(mockDb.getRecentMemories).toHaveBeenCalled();
|
|
81
|
+
expect(mockDb.getTotalCount).toHaveBeenCalled();
|
|
82
|
+
}), { numRuns: 100 });
|
|
83
|
+
});
|
|
84
|
+
it("property: for any valid store args, memory-store uses the injected db", async () => {
|
|
85
|
+
await fc.assert(fc.asyncProperty(fc.record({
|
|
86
|
+
repo: fc.string({ minLength: 1, maxLength: 30 }).filter((s) => s.trim().length > 0),
|
|
87
|
+
content: fc.string({ minLength: 10, maxLength: 200 }),
|
|
88
|
+
importance: fc.integer({ min: 1, max: 5 }),
|
|
89
|
+
type: fc.constantFrom("code_fact", "decision", "mistake", "pattern"),
|
|
90
|
+
}), async ({ repo, content, importance, type }) => {
|
|
91
|
+
const mockDb = makeMockDb();
|
|
92
|
+
const mockVectors = makeMockVectors();
|
|
93
|
+
const router = createRouter(mockDb, mockVectors);
|
|
94
|
+
await router("tools/call", {
|
|
95
|
+
name: "memory-store",
|
|
96
|
+
arguments: { type, content, importance, scope: { repo } },
|
|
97
|
+
});
|
|
98
|
+
expect(mockDb.insert).toHaveBeenCalled();
|
|
99
|
+
}), { numRuns: 100 });
|
|
100
|
+
});
|
|
101
|
+
it("different router instances use their own injected db independently", () => {
|
|
102
|
+
const mockDb1 = makeMockDb();
|
|
103
|
+
const mockDb2 = makeMockDb();
|
|
104
|
+
const mockVectors = makeMockVectors();
|
|
105
|
+
const router1 = createRouter(mockDb1, mockVectors);
|
|
106
|
+
const router2 = createRouter(mockDb2, mockVectors);
|
|
107
|
+
// Both routers are distinct functions
|
|
108
|
+
expect(router1).not.toBe(router2);
|
|
109
|
+
// Each router closes over its own db
|
|
110
|
+
// (verified by the property tests above that each mock is called independently)
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
//# sourceMappingURL=router.test.js.map
|
package/dist/server.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { MemoryEntry } from "../types.js";
|
|
2
|
+
export declare class SQLiteStore {
|
|
3
|
+
private db;
|
|
4
|
+
constructor(dbPath?: string);
|
|
5
|
+
private migrate;
|
|
6
|
+
insert(entry: MemoryEntry): void;
|
|
7
|
+
update(id: string, updates: {
|
|
8
|
+
title?: string;
|
|
9
|
+
content?: string;
|
|
10
|
+
importance?: number;
|
|
11
|
+
}): void;
|
|
12
|
+
searchByRepo(repo: string, options?: {
|
|
13
|
+
types?: string[];
|
|
14
|
+
minImportance?: number;
|
|
15
|
+
limit?: number;
|
|
16
|
+
}): MemoryEntry[];
|
|
17
|
+
getById(id: string): MemoryEntry | null;
|
|
18
|
+
getByIdWithStats(id: string): (MemoryEntry & {
|
|
19
|
+
recall_rate: number;
|
|
20
|
+
}) | null;
|
|
21
|
+
listRecent(limit?: number): Array<{
|
|
22
|
+
id: string;
|
|
23
|
+
type: string;
|
|
24
|
+
repo: string;
|
|
25
|
+
}>;
|
|
26
|
+
getSummary(repo: string): {
|
|
27
|
+
summary: string;
|
|
28
|
+
updated_at: string;
|
|
29
|
+
} | null;
|
|
30
|
+
upsertSummary(repo: string, summary: string): void;
|
|
31
|
+
delete(id: string): void;
|
|
32
|
+
listRepos(): string[];
|
|
33
|
+
listRepoNavigation(): Array<{
|
|
34
|
+
repo: string;
|
|
35
|
+
memory_count: number;
|
|
36
|
+
last_updated_at: string | null;
|
|
37
|
+
}>;
|
|
38
|
+
incrementHitCount(id: string): void;
|
|
39
|
+
incrementRecallCount(id: string): void;
|
|
40
|
+
getStats(repo?: string): {
|
|
41
|
+
total: number;
|
|
42
|
+
byType: Record<string, number>;
|
|
43
|
+
unused: number;
|
|
44
|
+
};
|
|
45
|
+
getRecentMemories(repo: string, limit: number, offset?: number): MemoryEntry[];
|
|
46
|
+
getTotalCount(repo: string): number;
|
|
47
|
+
searchBySimilarity(query: string, repo: string, limit?: number): Array<MemoryEntry & {
|
|
48
|
+
similarity: number;
|
|
49
|
+
}>;
|
|
50
|
+
archiveExpiredMemories(): number;
|
|
51
|
+
getAllMemoriesWithStats(repo?: string): Array<MemoryEntry & {
|
|
52
|
+
hit_count: number;
|
|
53
|
+
recall_count: number;
|
|
54
|
+
recall_rate: number;
|
|
55
|
+
last_used_at: string | null;
|
|
56
|
+
}>;
|
|
57
|
+
listMemoriesForDashboard(options: {
|
|
58
|
+
repo: string;
|
|
59
|
+
type?: string;
|
|
60
|
+
search?: string;
|
|
61
|
+
minImportance?: number;
|
|
62
|
+
maxImportance?: number;
|
|
63
|
+
sortBy?: string;
|
|
64
|
+
sortOrder?: "asc" | "desc";
|
|
65
|
+
limit?: number;
|
|
66
|
+
offset?: number;
|
|
67
|
+
}): {
|
|
68
|
+
items: Array<MemoryEntry & {
|
|
69
|
+
hit_count: number;
|
|
70
|
+
recall_count: number;
|
|
71
|
+
recall_rate: number;
|
|
72
|
+
last_used_at: string | null;
|
|
73
|
+
}>;
|
|
74
|
+
total: number;
|
|
75
|
+
};
|
|
76
|
+
upsertVectorEmbedding(memoryId: string, vector: number[] | string[]): void;
|
|
77
|
+
getVectorEmbedding(memoryId: string): number[] | string[] | null;
|
|
78
|
+
close(): void;
|
|
79
|
+
private computeVector;
|
|
80
|
+
private cosineSimilarity;
|
|
81
|
+
private rowToMemoryEntry;
|
|
82
|
+
logAction(action: 'search' | 'read' | 'write' | 'update' | 'delete', repo: string, options?: {
|
|
83
|
+
query?: string;
|
|
84
|
+
memoryId?: string;
|
|
85
|
+
resultCount?: number;
|
|
86
|
+
}): void;
|
|
87
|
+
getRecentActions(repo?: string, limit?: number): Array<{
|
|
88
|
+
action: string;
|
|
89
|
+
query?: string;
|
|
90
|
+
memory_id?: string;
|
|
91
|
+
result_count?: number;
|
|
92
|
+
created_at: string;
|
|
93
|
+
}>;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=sqlite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/storage/sqlite.ts"],"names":[],"mappings":"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"}
|