@vheins/local-memory-mcp 0.4.12 → 0.5.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.
- package/README.md +21 -0
- package/bin/mcp-memory-server.js +1 -1
- package/dist/dashboard/public/assets/index-KxrQ6fPK.css +1 -0
- package/dist/dashboard/public/assets/index-Lu37nzOi.js +72 -0
- package/dist/dashboard/public/index.html +11 -1772
- package/dist/dashboard/server.js +115 -5
- package/dist/dashboard/server.js.map +1 -1
- package/dist/mcp/capabilities.d.ts +22 -0
- package/dist/mcp/capabilities.d.ts.map +1 -0
- package/dist/mcp/capabilities.js +38 -0
- package/dist/mcp/capabilities.js.map +1 -0
- package/dist/mcp/client.js +3 -3
- package/dist/mcp/client.js.map +1 -1
- package/dist/mcp/completion.d.ts +25 -0
- package/dist/mcp/completion.d.ts.map +1 -0
- package/dist/mcp/completion.js +127 -0
- package/dist/mcp/completion.js.map +1 -0
- package/dist/mcp/prompts/registry.d.ts +314 -0
- package/dist/mcp/prompts/registry.d.ts.map +1 -0
- package/dist/mcp/prompts/registry.js +936 -0
- package/dist/mcp/prompts/registry.js.map +1 -0
- package/dist/mcp/resources/index.d.ts +68 -0
- package/dist/mcp/resources/index.d.ts.map +1 -0
- package/dist/mcp/resources/index.js +323 -0
- package/dist/mcp/resources/index.js.map +1 -0
- package/dist/mcp/router.d.ts +14 -0
- package/dist/mcp/router.d.ts.map +1 -0
- package/dist/mcp/router.js +242 -0
- package/dist/mcp/router.js.map +1 -0
- package/dist/mcp/server.d.ts +3 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +338 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/storage/sqlite.d.ts +83 -0
- package/dist/mcp/storage/sqlite.d.ts.map +1 -0
- package/dist/mcp/storage/sqlite.js +1237 -0
- package/dist/mcp/storage/sqlite.js.map +1 -0
- package/dist/mcp/storage/vectors.d.ts +19 -0
- package/dist/mcp/storage/vectors.d.ts.map +1 -0
- package/dist/mcp/storage/vectors.js +74 -0
- package/dist/mcp/storage/vectors.js.map +1 -0
- package/dist/mcp/storage/vectors.stub.d.ts +12 -0
- package/dist/mcp/storage/vectors.stub.d.ts.map +1 -0
- package/dist/mcp/storage/vectors.stub.js +88 -0
- package/dist/mcp/storage/vectors.stub.js.map +1 -0
- package/dist/mcp/tests/client.test.d.ts +2 -0
- package/dist/mcp/tests/client.test.d.ts.map +1 -0
- package/dist/mcp/tests/client.test.js +130 -0
- package/dist/mcp/tests/client.test.js.map +1 -0
- package/dist/mcp/tests/dashboard.test.d.ts +2 -0
- package/dist/mcp/tests/dashboard.test.d.ts.map +1 -0
- package/dist/mcp/tests/dashboard.test.js +370 -0
- package/dist/mcp/tests/dashboard.test.js.map +1 -0
- package/dist/mcp/tests/e2e.test.d.ts +2 -0
- package/dist/mcp/tests/e2e.test.d.ts.map +1 -0
- package/dist/mcp/tests/e2e.test.js +250 -0
- package/dist/mcp/tests/e2e.test.js.map +1 -0
- package/dist/mcp/tests/index.test.d.ts +2 -0
- package/dist/mcp/tests/index.test.d.ts.map +1 -0
- package/dist/mcp/tests/index.test.js +185 -0
- package/dist/mcp/tests/index.test.js.map +1 -0
- package/dist/mcp/tests/logger.test.d.ts +2 -0
- package/dist/mcp/tests/logger.test.d.ts.map +1 -0
- package/dist/mcp/tests/logger.test.js +104 -0
- package/dist/mcp/tests/logger.test.js.map +1 -0
- package/dist/mcp/tests/memory.bulk.test.d.ts +2 -0
- package/dist/mcp/tests/memory.bulk.test.d.ts.map +1 -0
- package/dist/mcp/tests/memory.bulk.test.js +52 -0
- package/dist/mcp/tests/memory.bulk.test.js.map +1 -0
- package/dist/mcp/tests/memory.search.test.d.ts +2 -0
- package/dist/mcp/tests/memory.search.test.d.ts.map +1 -0
- package/dist/mcp/tests/memory.search.test.js +181 -0
- package/dist/mcp/tests/memory.search.test.js.map +1 -0
- package/dist/mcp/tests/normalize.test.d.ts +2 -0
- package/dist/mcp/tests/normalize.test.d.ts.map +1 -0
- package/dist/mcp/tests/normalize.test.js +181 -0
- package/dist/mcp/tests/normalize.test.js.map +1 -0
- package/dist/mcp/tests/query-expander.test.d.ts +2 -0
- package/dist/mcp/tests/query-expander.test.d.ts.map +1 -0
- package/dist/mcp/tests/query-expander.test.js +33 -0
- package/dist/mcp/tests/query-expander.test.js.map +1 -0
- package/dist/mcp/tests/router.test.d.ts +2 -0
- package/dist/mcp/tests/router.test.d.ts.map +1 -0
- package/dist/mcp/tests/router.test.js +470 -0
- package/dist/mcp/tests/router.test.js.map +1 -0
- package/dist/mcp/tests/sqlite.test.d.ts +2 -0
- package/dist/mcp/tests/sqlite.test.d.ts.map +1 -0
- package/dist/mcp/tests/sqlite.test.js +367 -0
- package/dist/mcp/tests/sqlite.test.js.map +1 -0
- package/dist/mcp/tests/tasks-search.test.d.ts +2 -0
- package/dist/mcp/tests/tasks-search.test.d.ts.map +1 -0
- package/dist/mcp/tests/tasks-search.test.js +154 -0
- package/dist/mcp/tests/tasks-search.test.js.map +1 -0
- package/dist/mcp/tests/tasks-transition.test.d.ts +2 -0
- package/dist/mcp/tests/tasks-transition.test.d.ts.map +1 -0
- package/dist/mcp/tests/tasks-transition.test.js +174 -0
- package/dist/mcp/tests/tasks-transition.test.js.map +1 -0
- package/dist/mcp/tests/tasks.bulk.test.d.ts +2 -0
- package/dist/mcp/tests/tasks.bulk.test.d.ts.map +1 -0
- package/dist/mcp/tests/tasks.bulk.test.js +254 -0
- package/dist/mcp/tests/tasks.bulk.test.js.map +1 -0
- package/dist/mcp/tests/tasks.e2e.test.d.ts +2 -0
- package/dist/mcp/tests/tasks.e2e.test.d.ts.map +1 -0
- package/dist/mcp/tests/tasks.e2e.test.js +289 -0
- package/dist/mcp/tests/tasks.e2e.test.js.map +1 -0
- package/dist/mcp/tests/tasks.pending-limit-refined.test.d.ts +2 -0
- package/dist/mcp/tests/tasks.pending-limit-refined.test.d.ts.map +1 -0
- package/dist/mcp/tests/tasks.pending-limit-refined.test.js +72 -0
- package/dist/mcp/tests/tasks.pending-limit-refined.test.js.map +1 -0
- package/dist/mcp/tests/v2-features.test.d.ts +2 -0
- package/dist/mcp/tests/v2-features.test.d.ts.map +1 -0
- package/dist/mcp/tests/v2-features.test.js +209 -0
- package/dist/mcp/tests/v2-features.test.js.map +1 -0
- package/dist/mcp/tools/memory.acknowledge.d.ts +4 -0
- package/dist/mcp/tools/memory.acknowledge.d.ts.map +1 -0
- package/dist/mcp/tools/memory.acknowledge.js +30 -0
- package/dist/mcp/tools/memory.acknowledge.js.map +1 -0
- package/dist/mcp/tools/memory.bulk-delete.d.ts +4 -0
- package/dist/mcp/tools/memory.bulk-delete.d.ts.map +1 -0
- package/dist/mcp/tools/memory.bulk-delete.js +39 -0
- package/dist/mcp/tools/memory.bulk-delete.js.map +1 -0
- package/dist/mcp/tools/memory.delete.d.ts +9 -0
- package/dist/mcp/tools/memory.delete.d.ts.map +1 -0
- package/dist/mcp/tools/memory.delete.js +39 -0
- package/dist/mcp/tools/memory.delete.js.map +1 -0
- package/dist/mcp/tools/memory.recap.d.ts +4 -0
- package/dist/mcp/tools/memory.recap.d.ts.map +1 -0
- package/dist/mcp/tools/memory.recap.js +90 -0
- package/dist/mcp/tools/memory.recap.js.map +1 -0
- package/dist/mcp/tools/memory.search.d.ts +5 -0
- package/dist/mcp/tools/memory.search.d.ts.map +1 -0
- package/dist/mcp/tools/memory.search.js +134 -0
- package/dist/mcp/tools/memory.search.js.map +1 -0
- package/dist/mcp/tools/memory.store.d.ts +5 -0
- package/dist/mcp/tools/memory.store.d.ts.map +1 -0
- package/dist/mcp/tools/memory.store.js +117 -0
- package/dist/mcp/tools/memory.store.js.map +1 -0
- package/dist/mcp/tools/memory.summarize.d.ts +4 -0
- package/dist/mcp/tools/memory.summarize.d.ts.map +1 -0
- package/dist/mcp/tools/memory.summarize.js +31 -0
- package/dist/mcp/tools/memory.summarize.js.map +1 -0
- package/dist/mcp/tools/memory.synthesize.d.ts +14 -0
- package/dist/mcp/tools/memory.synthesize.d.ts.map +1 -0
- package/dist/mcp/tools/memory.synthesize.js +228 -0
- package/dist/mcp/tools/memory.synthesize.js.map +1 -0
- package/dist/mcp/tools/memory.update.d.ts +5 -0
- package/dist/mcp/tools/memory.update.d.ts.map +1 -0
- package/dist/mcp/tools/memory.update.js +73 -0
- package/dist/mcp/tools/memory.update.js.map +1 -0
- package/dist/mcp/tools/schemas.d.ts +2762 -0
- package/dist/mcp/tools/schemas.d.ts.map +1 -0
- package/dist/mcp/tools/schemas.js +952 -0
- package/dist/mcp/tools/schemas.js.map +1 -0
- package/dist/mcp/tools/task.bulk-manage.d.ts +4 -0
- package/dist/mcp/tools/task.bulk-manage.d.ts.map +1 -0
- package/dist/mcp/tools/task.bulk-manage.js +146 -0
- package/dist/mcp/tools/task.bulk-manage.js.map +1 -0
- package/dist/mcp/tools/task.manage.d.ts +16 -0
- package/dist/mcp/tools/task.manage.d.ts.map +1 -0
- package/dist/mcp/tools/task.manage.js +395 -0
- package/dist/mcp/tools/task.manage.js.map +1 -0
- package/dist/mcp/types.d.ts +87 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +3 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/mcp/utils/completion.d.ts +2 -0
- package/dist/mcp/utils/completion.d.ts.map +1 -0
- package/dist/mcp/utils/completion.js +28 -0
- package/dist/mcp/utils/completion.js.map +1 -0
- package/dist/mcp/utils/git-scope.d.ts +8 -0
- package/dist/mcp/utils/git-scope.d.ts.map +1 -0
- package/dist/mcp/utils/git-scope.js +38 -0
- package/dist/mcp/utils/git-scope.js.map +1 -0
- package/dist/mcp/utils/logger.d.ts +25 -0
- package/dist/mcp/utils/logger.d.ts.map +1 -0
- package/dist/mcp/utils/logger.js +152 -0
- package/dist/mcp/utils/logger.js.map +1 -0
- package/dist/mcp/utils/mcp-response.d.ts +96 -0
- package/dist/mcp/utils/mcp-response.d.ts.map +1 -0
- package/dist/mcp/utils/mcp-response.js +131 -0
- package/dist/mcp/utils/mcp-response.js.map +1 -0
- package/dist/mcp/utils/normalize.d.ts +9 -0
- package/dist/mcp/utils/normalize.d.ts.map +1 -0
- package/dist/mcp/utils/normalize.js +62 -0
- package/dist/mcp/utils/normalize.js.map +1 -0
- package/dist/mcp/utils/pagination.d.ts +6 -0
- package/dist/mcp/utils/pagination.d.ts.map +1 -0
- package/dist/mcp/utils/pagination.js +32 -0
- package/dist/mcp/utils/pagination.js.map +1 -0
- package/dist/mcp/utils/query-expander.d.ts +2 -0
- package/dist/mcp/utils/query-expander.d.ts.map +1 -0
- package/dist/mcp/utils/query-expander.js +29 -0
- package/dist/mcp/utils/query-expander.js.map +1 -0
- package/dist/storage/sqlite.d.ts +3 -0
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +38 -9
- package/dist/storage/sqlite.js.map +1 -1
- package/package.json +17 -5
- package/dist/dashboard/public/app.js +0 -2779
- package/dist/dashboard/public/chart.js +0 -0
- package/dist/dashboard/public/screenshot.png +0 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
// Feature: Dashboard Filter & Export Property Tests
|
|
2
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
3
|
+
import * as fc from "fast-check";
|
|
4
|
+
describe("Property 12: Dashboard filter logic correctness", () => {
|
|
5
|
+
// Mock memory data generator
|
|
6
|
+
function generateMemories(count) {
|
|
7
|
+
const types = ["code_fact", "decision", "mistake", "pattern", "file_claim"];
|
|
8
|
+
return Array.from({ length: count }, (_, i) => ({
|
|
9
|
+
id: `test-${i}`,
|
|
10
|
+
type: types[i % types.length],
|
|
11
|
+
content: `Test memory content ${i} with some keywords`,
|
|
12
|
+
importance: (i % 5) + 1,
|
|
13
|
+
hit_count: i * 2,
|
|
14
|
+
recall_rate: i > 0 ? i / (i + 1) : 0,
|
|
15
|
+
created_at: new Date(Date.now() - i * 86400000).toISOString(),
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
// Filter function (same as in dashboard)
|
|
19
|
+
function applyFilters(memories, searchQuery, typeFilter, minImportance, maxImportance) {
|
|
20
|
+
let filtered = [...memories];
|
|
21
|
+
if (searchQuery) {
|
|
22
|
+
const q = searchQuery.toLowerCase();
|
|
23
|
+
filtered = filtered.filter((m) => m.content.toLowerCase().includes(q) ||
|
|
24
|
+
m.type.toLowerCase().includes(q));
|
|
25
|
+
}
|
|
26
|
+
if (typeFilter) {
|
|
27
|
+
filtered = filtered.filter((m) => m.type === typeFilter);
|
|
28
|
+
}
|
|
29
|
+
if (minImportance > 0) {
|
|
30
|
+
filtered = filtered.filter((m) => m.importance >= minImportance);
|
|
31
|
+
}
|
|
32
|
+
if (maxImportance < 5) {
|
|
33
|
+
filtered = filtered.filter((m) => m.importance <= maxImportance);
|
|
34
|
+
}
|
|
35
|
+
return filtered;
|
|
36
|
+
}
|
|
37
|
+
it("property: search filter returns subset containing search term", () => {
|
|
38
|
+
fc.assert(fc.property(fc.array(fc.record({
|
|
39
|
+
id: fc.uuid(),
|
|
40
|
+
type: fc.constantFrom("code_fact", "decision", "mistake", "pattern", "file_claim"),
|
|
41
|
+
content: fc.string({ minLength: 10, maxLength: 100 }),
|
|
42
|
+
importance: fc.integer({ min: 1, max: 5 }),
|
|
43
|
+
hit_count: fc.integer({ min: 0, max: 100 }),
|
|
44
|
+
})), fc.string({ minLength: 1, maxLength: 20 }), (memories, query) => {
|
|
45
|
+
if (memories.length === 0)
|
|
46
|
+
return true;
|
|
47
|
+
const filtered = applyFilters(memories, query, "", 0, 5);
|
|
48
|
+
// All results should contain query (if any returned)
|
|
49
|
+
for (const m of filtered) {
|
|
50
|
+
const containsQuery = m.content.toLowerCase().includes(query.toLowerCase()) ||
|
|
51
|
+
m.type.toLowerCase().includes(query.toLowerCase());
|
|
52
|
+
expect(containsQuery).toBe(true);
|
|
53
|
+
}
|
|
54
|
+
// Filtered should not be larger than original
|
|
55
|
+
expect(filtered.length).toBeLessThanOrEqual(memories.length);
|
|
56
|
+
}), { numRuns: 50 });
|
|
57
|
+
});
|
|
58
|
+
it("property: type filter returns only matching types", () => {
|
|
59
|
+
fc.assert(fc.property(fc.array(fc.record({
|
|
60
|
+
id: fc.uuid(),
|
|
61
|
+
type: fc.constantFrom("code_fact", "decision", "mistake", "pattern", "file_claim"),
|
|
62
|
+
content: fc.string({ minLength: 10, maxLength: 100 }),
|
|
63
|
+
importance: fc.integer({ min: 1, max: 5 }),
|
|
64
|
+
})), fc.constantFrom("", "code_fact", "decision", "mistake", "pattern", "file_claim"), (memories, typeFilter) => {
|
|
65
|
+
const filtered = applyFilters(memories, "", typeFilter, 0, 5);
|
|
66
|
+
if (typeFilter === "") {
|
|
67
|
+
// No filter should return all
|
|
68
|
+
expect(filtered.length).toBe(memories.length);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// All results should have matching type
|
|
72
|
+
for (const m of filtered) {
|
|
73
|
+
expect(m.type).toBe(typeFilter);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}), { numRuns: 50 });
|
|
77
|
+
});
|
|
78
|
+
it("property: importance range filter returns correct subset", () => {
|
|
79
|
+
fc.assert(fc.property(fc.array(fc.record({
|
|
80
|
+
id: fc.uuid(),
|
|
81
|
+
type: fc.constantFrom("code_fact", "decision", "mistake", "pattern", "file_claim"),
|
|
82
|
+
content: fc.string({ minLength: 10 }),
|
|
83
|
+
importance: fc.integer({ min: 1, max: 5 }),
|
|
84
|
+
})), fc.integer({ min: 0, max: 5 }), fc.integer({ min: 0, max: 5 }), (memories, minImp, maxImp) => {
|
|
85
|
+
const safeMin = Math.min(minImp, maxImp);
|
|
86
|
+
const safeMax = Math.max(minImp, maxImp);
|
|
87
|
+
const filtered = applyFilters(memories, "", "", safeMin, safeMax);
|
|
88
|
+
for (const m of filtered) {
|
|
89
|
+
expect(m.importance).toBeGreaterThanOrEqual(safeMin);
|
|
90
|
+
expect(m.importance).toBeLessThanOrEqual(safeMax);
|
|
91
|
+
}
|
|
92
|
+
}), { numRuns: 50 });
|
|
93
|
+
});
|
|
94
|
+
it("property: combination filters are additive (AND logic)", () => {
|
|
95
|
+
fc.assert(fc.property(fc.array(fc.record({
|
|
96
|
+
id: fc.uuid(),
|
|
97
|
+
type: fc.constantFrom("code_fact", "decision", "mistake", "pattern", "file_claim"),
|
|
98
|
+
content: fc.string({ minLength: 10 }),
|
|
99
|
+
importance: fc.integer({ min: 1, max: 5 }),
|
|
100
|
+
})), fc.string({ maxLength: 10 }), fc.constantFrom("", "code_fact", "decision"), fc.integer({ min: 1, max: 3 }), fc.integer({ min: 3, max: 5 }), (memories, search, type, minImp, maxImp) => {
|
|
101
|
+
const safeMin = Math.min(minImp, maxImp);
|
|
102
|
+
const safeMax = Math.max(minImp, maxImp);
|
|
103
|
+
const filtered = applyFilters(memories, search, type, safeMin, safeMax);
|
|
104
|
+
// Verify each result matches ALL criteria
|
|
105
|
+
for (const m of filtered) {
|
|
106
|
+
if (search) {
|
|
107
|
+
expect(m.content.toLowerCase().includes(search.toLowerCase()) ||
|
|
108
|
+
m.type.toLowerCase().includes(search.toLowerCase())).toBe(true);
|
|
109
|
+
}
|
|
110
|
+
if (type) {
|
|
111
|
+
expect(m.type).toBe(type);
|
|
112
|
+
}
|
|
113
|
+
expect(m.importance).toBeGreaterThanOrEqual(safeMin);
|
|
114
|
+
expect(m.importance).toBeLessThanOrEqual(safeMax);
|
|
115
|
+
}
|
|
116
|
+
}), { numRuns: 30 });
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
describe("Property 13: Pagination non-overlapping", () => {
|
|
120
|
+
function paginate(items, page, pageSize) {
|
|
121
|
+
const start = (page - 1) * pageSize;
|
|
122
|
+
return items.slice(start, start + pageSize);
|
|
123
|
+
}
|
|
124
|
+
it("property: different pages have no overlapping IDs", () => {
|
|
125
|
+
fc.assert(fc.property(fc.array(fc.uuid(), { minLength: 10, maxLength: 100 }), fc.integer({ min: 1, max: 10 }), (ids, pageSize) => {
|
|
126
|
+
if (pageSize < 1)
|
|
127
|
+
return true;
|
|
128
|
+
const page1 = paginate(ids, 1, pageSize);
|
|
129
|
+
const page2 = paginate(ids, 2, pageSize);
|
|
130
|
+
// No common IDs between pages
|
|
131
|
+
const overlap = page1.filter((id) => page2.includes(id));
|
|
132
|
+
expect(overlap.length).toBe(0);
|
|
133
|
+
}), { numRuns: 50 });
|
|
134
|
+
});
|
|
135
|
+
it("property: all items appear in exactly one page", () => {
|
|
136
|
+
fc.assert(fc.property(fc.array(fc.integer(), { minLength: 1, maxLength: 50 }), fc.integer({ min: 1, max: 10 }), (items, pageSize) => {
|
|
137
|
+
if (pageSize < 1)
|
|
138
|
+
return true;
|
|
139
|
+
if (items.length === 0)
|
|
140
|
+
return true;
|
|
141
|
+
const totalPages = Math.ceil(items.length / pageSize);
|
|
142
|
+
const allPages = [];
|
|
143
|
+
for (let page = 1; page <= totalPages; page++) {
|
|
144
|
+
const pageItems = paginate(items, page, pageSize);
|
|
145
|
+
allPages.push(...pageItems);
|
|
146
|
+
}
|
|
147
|
+
// Should have same total count
|
|
148
|
+
expect(allPages.length).toBe(items.length);
|
|
149
|
+
}), { numRuns: 30 });
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
describe("Property 14: Export format correctness", () => {
|
|
153
|
+
function exportToCsv(memories) {
|
|
154
|
+
const headers = ["id", "type", "content", "importance", "hit_count", "created_at", "agent", "model"];
|
|
155
|
+
const csvRows = [headers.join(",")];
|
|
156
|
+
for (const m of memories) {
|
|
157
|
+
const row = [
|
|
158
|
+
m.id,
|
|
159
|
+
m.type,
|
|
160
|
+
`"${m.content.replace(/"/g, '""')}"`,
|
|
161
|
+
m.importance,
|
|
162
|
+
m.hit_count,
|
|
163
|
+
m.created_at,
|
|
164
|
+
m.agent || 'unknown',
|
|
165
|
+
m.model || 'unknown'
|
|
166
|
+
];
|
|
167
|
+
csvRows.push(row.join(","));
|
|
168
|
+
}
|
|
169
|
+
return csvRows.join("\n");
|
|
170
|
+
}
|
|
171
|
+
function exportToJson(memories) {
|
|
172
|
+
return JSON.stringify(memories, null, 2);
|
|
173
|
+
}
|
|
174
|
+
it("property: CSV export contains all filtered data", () => {
|
|
175
|
+
fc.assert(fc.property(fc.array(fc.record({
|
|
176
|
+
id: fc.uuid(),
|
|
177
|
+
type: fc.constantFrom("code_fact", "decision", "mistake", "pattern", "file_claim"),
|
|
178
|
+
content: fc.string({ minLength: 5, maxLength: 50 }),
|
|
179
|
+
importance: fc.integer({ min: 1, max: 5 }),
|
|
180
|
+
hit_count: fc.integer({ min: 0, max: 20 }),
|
|
181
|
+
created_at: fc.constant("2026-04-02T10:00:00Z"),
|
|
182
|
+
agent: fc.string({ minLength: 3 }),
|
|
183
|
+
model: fc.string({ minLength: 3 }),
|
|
184
|
+
})), (memories) => {
|
|
185
|
+
if (memories.length === 0)
|
|
186
|
+
return;
|
|
187
|
+
const csv = exportToCsv(memories);
|
|
188
|
+
const lines = csv.split("\n");
|
|
189
|
+
// Header + data rows
|
|
190
|
+
expect(lines.length).toBe(memories.length + 1);
|
|
191
|
+
// Each row has 6 columns
|
|
192
|
+
for (let i = 1; i < lines.length; i++) {
|
|
193
|
+
const cols = lines[i].split(",");
|
|
194
|
+
expect(cols.length).toBeGreaterThanOrEqual(5);
|
|
195
|
+
}
|
|
196
|
+
}), { numRuns: 30, endOnFailure: true });
|
|
197
|
+
});
|
|
198
|
+
it("property: JSON export is valid parseable JSON", () => {
|
|
199
|
+
fc.assert(fc.property(fc.array(fc.record({
|
|
200
|
+
id: fc.uuid(),
|
|
201
|
+
type: fc.constantFrom("code_fact", "decision", "mistake", "pattern", "file_claim"),
|
|
202
|
+
content: fc.string({ minLength: 5 }),
|
|
203
|
+
importance: fc.integer({ min: 1, max: 5 }),
|
|
204
|
+
hit_count: fc.integer({ min: 0, max: 10 }),
|
|
205
|
+
created_at: fc.string(),
|
|
206
|
+
agent: fc.string({ minLength: 3 }),
|
|
207
|
+
model: fc.string({ minLength: 3 }),
|
|
208
|
+
})), (memories) => {
|
|
209
|
+
const json = exportToJson(memories);
|
|
210
|
+
const parsed = JSON.parse(json);
|
|
211
|
+
expect(Array.isArray(parsed)).toBe(true);
|
|
212
|
+
expect(parsed.length).toBe(memories.length);
|
|
213
|
+
}), { numRuns: 30 });
|
|
214
|
+
});
|
|
215
|
+
it("property: export respects filter (matches displayed data)", () => {
|
|
216
|
+
fc.assert(fc.property(fc.array(fc.record({
|
|
217
|
+
id: fc.uuid(),
|
|
218
|
+
type: fc.constantFrom("decision", "mistake", "code_fact", "pattern", "file_claim"),
|
|
219
|
+
content: fc.string({ minLength: 10 }),
|
|
220
|
+
importance: fc.integer({ min: 1, max: 5 }),
|
|
221
|
+
hit_count: fc.integer({ min: 0, max: 100 }),
|
|
222
|
+
created_at: fc.constant("2026-04-02T10:00:00Z"),
|
|
223
|
+
agent: fc.string({ minLength: 3 }),
|
|
224
|
+
model: fc.string({ minLength: 3 }),
|
|
225
|
+
})), fc.constantFrom("decision", "mistake", "code_fact", "pattern", "file_claim"), (memories, typeFilter) => {
|
|
226
|
+
// Apply filter (same as dashboard)
|
|
227
|
+
const filtered = memories.filter((m) => m.type === typeFilter);
|
|
228
|
+
const csv = exportToCsv(filtered);
|
|
229
|
+
const json = exportToJson(filtered);
|
|
230
|
+
// CSV should have filtered count + 1 header
|
|
231
|
+
expect(csv.split("\n").length).toBe(filtered.length + 1);
|
|
232
|
+
// JSON should parse to filtered array
|
|
233
|
+
const parsed = JSON.parse(json);
|
|
234
|
+
expect(parsed.length).toBe(filtered.length);
|
|
235
|
+
}), { numRuns: 30 });
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
describe("Property 15: localStorage preferences round-trip", () => {
|
|
239
|
+
// Mock localStorage for Node.js environment
|
|
240
|
+
let mockStorage = {};
|
|
241
|
+
beforeEach(() => {
|
|
242
|
+
mockStorage = {};
|
|
243
|
+
global.localStorage = {
|
|
244
|
+
getItem: (key) => mockStorage[key] || null,
|
|
245
|
+
setItem: (key, value) => { mockStorage[key] = value; },
|
|
246
|
+
removeItem: (key) => { delete mockStorage[key]; },
|
|
247
|
+
clear: () => { mockStorage = {}; },
|
|
248
|
+
get length() { return Object.keys(mockStorage).length; },
|
|
249
|
+
key: (i) => Object.keys(mockStorage)[i] || null,
|
|
250
|
+
};
|
|
251
|
+
});
|
|
252
|
+
afterEach(() => {
|
|
253
|
+
delete global.localStorage;
|
|
254
|
+
});
|
|
255
|
+
function savePrefs(prefs) {
|
|
256
|
+
localStorage.setItem("dashboard_prefs", JSON.stringify(prefs));
|
|
257
|
+
}
|
|
258
|
+
function loadPrefs() {
|
|
259
|
+
const stored = localStorage.getItem("dashboard_prefs");
|
|
260
|
+
return stored ? JSON.parse(stored) : null;
|
|
261
|
+
}
|
|
262
|
+
it("property: saved preferences can be loaded identically", () => {
|
|
263
|
+
fc.assert(fc.property(fc.record({
|
|
264
|
+
theme: fc.constantFrom("light", "dark"),
|
|
265
|
+
pageSize: fc.constantFrom(10, 25, 50, 100),
|
|
266
|
+
columnVisibility: fc.record({ id: fc.boolean(), type: fc.boolean(), content: fc.boolean() }),
|
|
267
|
+
columnWidths: fc.record({ id: fc.integer({ min: 50, max: 300 }), type: fc.integer({ min: 50, max: 300 }) }),
|
|
268
|
+
}), (prefs) => {
|
|
269
|
+
savePrefs(prefs);
|
|
270
|
+
const loaded = loadPrefs();
|
|
271
|
+
expect(loaded).not.toBeNull();
|
|
272
|
+
expect(loaded?.theme).toBe(prefs.theme);
|
|
273
|
+
expect(loaded?.pageSize).toBe(prefs.pageSize);
|
|
274
|
+
}), { numRuns: 30 });
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
describe("Property 16: Recent Queries feature", () => {
|
|
278
|
+
let mockStorage = {};
|
|
279
|
+
beforeEach(() => {
|
|
280
|
+
mockStorage = {};
|
|
281
|
+
global.localStorage = {
|
|
282
|
+
getItem: (key) => mockStorage[key] || null,
|
|
283
|
+
setItem: (key, value) => { mockStorage[key] = value; },
|
|
284
|
+
removeItem: (key) => { delete mockStorage[key]; },
|
|
285
|
+
clear: () => { mockStorage = {}; },
|
|
286
|
+
get length() { return Object.keys(mockStorage).length; },
|
|
287
|
+
key: (i) => Object.keys(mockStorage)[i] || null,
|
|
288
|
+
};
|
|
289
|
+
});
|
|
290
|
+
afterEach(() => {
|
|
291
|
+
delete global.localStorage;
|
|
292
|
+
});
|
|
293
|
+
// Simulate recent queries stored in localStorage (fallback)
|
|
294
|
+
function getRecentQueriesFromStorage() {
|
|
295
|
+
const stored = localStorage.getItem("recentQueries");
|
|
296
|
+
return stored ? JSON.parse(stored) : [];
|
|
297
|
+
}
|
|
298
|
+
function addRecentQueryToStorage(query) {
|
|
299
|
+
if (!query.trim())
|
|
300
|
+
return;
|
|
301
|
+
const queries = getRecentQueriesFromStorage();
|
|
302
|
+
const filtered = queries.filter(q => q !== query);
|
|
303
|
+
filtered.unshift(query);
|
|
304
|
+
const limited = filtered.slice(0, 20);
|
|
305
|
+
localStorage.setItem("recentQueries", JSON.stringify(limited));
|
|
306
|
+
}
|
|
307
|
+
it("property: adding query moves it to front and removes duplicates", () => {
|
|
308
|
+
addRecentQueryToStorage("database");
|
|
309
|
+
addRecentQueryToStorage("authentication");
|
|
310
|
+
addRecentQueryToStorage("database"); // duplicate - should move to front
|
|
311
|
+
const queries = getRecentQueriesFromStorage();
|
|
312
|
+
expect(queries[0]).toBe("database");
|
|
313
|
+
expect(queries[1]).toBe("authentication");
|
|
314
|
+
expect(queries.length).toBe(2);
|
|
315
|
+
});
|
|
316
|
+
it("property: recent queries are limited to 20", () => {
|
|
317
|
+
for (let i = 0; i < 25; i++) {
|
|
318
|
+
addRecentQueryToStorage(`query-${i}`);
|
|
319
|
+
}
|
|
320
|
+
const queries = getRecentQueriesFromStorage();
|
|
321
|
+
expect(queries.length).toBe(20);
|
|
322
|
+
expect(queries[0]).toBe("query-24");
|
|
323
|
+
expect(queries[19]).toBe("query-5");
|
|
324
|
+
});
|
|
325
|
+
it("property: empty query is not added", () => {
|
|
326
|
+
addRecentQueryToStorage("valid-query");
|
|
327
|
+
addRecentQueryToStorage("");
|
|
328
|
+
addRecentQueryToStorage(" ");
|
|
329
|
+
const queries = getRecentQueriesFromStorage();
|
|
330
|
+
expect(queries.length).toBe(1);
|
|
331
|
+
expect(queries[0]).toBe("valid-query");
|
|
332
|
+
});
|
|
333
|
+
it("property: queries maintain insertion order with unique constraint", () => {
|
|
334
|
+
addRecentQueryToStorage("first");
|
|
335
|
+
addRecentQueryToStorage("second");
|
|
336
|
+
addRecentQueryToStorage("third");
|
|
337
|
+
addRecentQueryToStorage("first"); // move to front
|
|
338
|
+
addRecentQueryToStorage("second"); // move to front
|
|
339
|
+
const queries = getRecentQueriesFromStorage();
|
|
340
|
+
expect(queries).toEqual(["second", "first", "third"]);
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
describe("Property 17: Recent Queries API response format", () => {
|
|
344
|
+
function parseRecentQueriesResponse(json) {
|
|
345
|
+
const parsed = JSON.parse(json);
|
|
346
|
+
if (!Array.isArray(parsed.queries)) {
|
|
347
|
+
throw new Error("Invalid response: queries must be an array");
|
|
348
|
+
}
|
|
349
|
+
return parsed;
|
|
350
|
+
}
|
|
351
|
+
it("property: valid API response parses correctly", () => {
|
|
352
|
+
const apiResponse = JSON.stringify({
|
|
353
|
+
queries: ["database", "authentication", "user management"]
|
|
354
|
+
});
|
|
355
|
+
const result = parseRecentQueriesResponse(apiResponse);
|
|
356
|
+
expect(result.queries).toHaveLength(3);
|
|
357
|
+
expect(result.queries[0]).toBe("database");
|
|
358
|
+
});
|
|
359
|
+
it("property: empty queries array is valid", () => {
|
|
360
|
+
const apiResponse = JSON.stringify({ queries: [] });
|
|
361
|
+
const result = parseRecentQueriesResponse(apiResponse);
|
|
362
|
+
expect(result.queries).toHaveLength(0);
|
|
363
|
+
});
|
|
364
|
+
it("property: malformed response throws error", () => {
|
|
365
|
+
expect(() => parseRecentQueriesResponse("not json")).toThrow();
|
|
366
|
+
expect(() => parseRecentQueriesResponse("{}")).toThrow();
|
|
367
|
+
expect(() => parseRecentQueriesResponse('{"results": []}')).toThrow();
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
//# sourceMappingURL=dashboard.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard.test.js","sourceRoot":"","sources":["../../../src/mcp/tests/dashboard.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"e2e.test.d.ts","sourceRoot":"","sources":["../../../src/mcp/tests/e2e.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
2
|
+
import { createRouter } from "../router.js";
|
|
3
|
+
import { SQLiteStore } from "../storage/sqlite.js";
|
|
4
|
+
import { RealVectorStore } from "../storage/vectors.js";
|
|
5
|
+
// Global configuration for heavy AI tests
|
|
6
|
+
vi.setConfig({ testTimeout: 90000 });
|
|
7
|
+
describe("MCP Local Memory - High-Complexity E2E Scenarios", () => {
|
|
8
|
+
let db;
|
|
9
|
+
let vectors;
|
|
10
|
+
let router;
|
|
11
|
+
const REPO = "enterprise-app-v2";
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
db = new SQLiteStore(":memory:");
|
|
14
|
+
vectors = new RealVectorStore(db);
|
|
15
|
+
router = createRouter(db, vectors);
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* SCENARIO 1: Semantic Precision & Ranking
|
|
19
|
+
* Test if the system can distinguish between closely related topics.
|
|
20
|
+
*/
|
|
21
|
+
it("should maintain high precision ranking among related but distinct technical concepts", async () => {
|
|
22
|
+
const memories = [
|
|
23
|
+
{ title: "Primary Database", content: "We use SQLite for local persistence to ensure local-first capability." },
|
|
24
|
+
{ title: "Cache Layer", content: "Redis is used for session management and fast lookups." },
|
|
25
|
+
{ title: "Database Migration", content: "Run 'npm run migrate' to update the SQLite schema safely." },
|
|
26
|
+
{ title: "Backup Policy", content: "Hourly snapshots of the .db file are stored in the backups folder." },
|
|
27
|
+
{ title: "External API Integration", content: "The system connects to Postgres only for reporting services." }
|
|
28
|
+
];
|
|
29
|
+
for (const m of memories) {
|
|
30
|
+
await router("tools/call", {
|
|
31
|
+
name: "memory-store",
|
|
32
|
+
arguments: {
|
|
33
|
+
type: "decision",
|
|
34
|
+
title: m.title,
|
|
35
|
+
content: m.content,
|
|
36
|
+
importance: 4,
|
|
37
|
+
scope: { repo: REPO },
|
|
38
|
+
agent: "test-agent",
|
|
39
|
+
model: "test-model"
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
// QUERY: "How do I update the database?"
|
|
44
|
+
// EXPECT: "Database Migration" should be Top Match, not "Primary Database"
|
|
45
|
+
const searchRes = await router("tools/call", {
|
|
46
|
+
name: "memory-search",
|
|
47
|
+
arguments: { query: "How do I update the database schema?", repo: REPO }
|
|
48
|
+
});
|
|
49
|
+
const textOutput = searchRes.content[0].text;
|
|
50
|
+
expect(textOutput).toContain("Database Migration");
|
|
51
|
+
// Validate JSON ordering
|
|
52
|
+
const results = searchRes.data.results;
|
|
53
|
+
expect(results[0].title).toBe("Database Migration");
|
|
54
|
+
expect(results.length).toBeGreaterThan(1); // Should find related db facts too but lower
|
|
55
|
+
});
|
|
56
|
+
/**
|
|
57
|
+
* SCENARIO 2: The Correction & Learning Loop
|
|
58
|
+
* Agent makes a mistake -> Stores it -> Learns -> Replaces it with a pattern.
|
|
59
|
+
*/
|
|
60
|
+
it("should guide the agent away from past mistakes using supersedes and status", async () => {
|
|
61
|
+
// 1. Store a mistake
|
|
62
|
+
const mistakeRes = await router("tools/call", {
|
|
63
|
+
name: "memory-store",
|
|
64
|
+
arguments: {
|
|
65
|
+
type: "mistake",
|
|
66
|
+
title: "Large File Upload Failure",
|
|
67
|
+
content: "Don't use fs.readFileSync for large files, it causes OOM errors.",
|
|
68
|
+
importance: 5,
|
|
69
|
+
scope: { repo: REPO },
|
|
70
|
+
agent: "test-agent",
|
|
71
|
+
model: "test-model"
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
const mistakeId = mistakeRes.data.id;
|
|
75
|
+
// 2. Store the correct pattern that replaces the mistake
|
|
76
|
+
await router("tools/call", {
|
|
77
|
+
name: "memory-store",
|
|
78
|
+
arguments: {
|
|
79
|
+
type: "pattern",
|
|
80
|
+
title: "Streaming File Upload",
|
|
81
|
+
content: "Always use streams (fs.createReadStream) for files > 10MB.",
|
|
82
|
+
importance: 5,
|
|
83
|
+
scope: { repo: REPO },
|
|
84
|
+
agent: "test-agent",
|
|
85
|
+
model: "test-model",
|
|
86
|
+
supersedes: mistakeId
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
// 3. Search for "file upload"
|
|
90
|
+
const searchRes = await router("tools/call", {
|
|
91
|
+
name: "memory-search",
|
|
92
|
+
arguments: { query: "How to handle file uploads?", repo: REPO }
|
|
93
|
+
});
|
|
94
|
+
// EXPECT: Mistake is archived and NOT in search results by default
|
|
95
|
+
expect(searchRes.content[0].text).toContain("Streaming File Upload");
|
|
96
|
+
expect(searchRes.content[0].text).not.toContain("Large File Upload Failure");
|
|
97
|
+
const results = searchRes.data.results;
|
|
98
|
+
expect(results.some((r) => r.id === mistakeId)).toBe(false);
|
|
99
|
+
// 4. Audit: Verify we can still find the mistake if we EXPLICITLY ask for archived
|
|
100
|
+
const auditRes = await router("tools/call", {
|
|
101
|
+
name: "memory-search",
|
|
102
|
+
arguments: { query: "file upload", repo: REPO, include_archived: true }
|
|
103
|
+
});
|
|
104
|
+
expect(auditRes.data.results.some((r) => r.id === mistakeId)).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
/**
|
|
107
|
+
* SCENARIO 3: Workspace-Aware Context Nuance
|
|
108
|
+
* Boost based on folder depth and language extensions.
|
|
109
|
+
*/
|
|
110
|
+
it("should provide relevant boost when working in deep subdirectories", async () => {
|
|
111
|
+
// Memory A: Global
|
|
112
|
+
await router("tools/call", {
|
|
113
|
+
name: "memory-store",
|
|
114
|
+
arguments: {
|
|
115
|
+
type: "code_fact",
|
|
116
|
+
title: "Global Logging",
|
|
117
|
+
content: "Use logger.info for all modules.",
|
|
118
|
+
importance: 2,
|
|
119
|
+
scope: { repo: REPO },
|
|
120
|
+
agent: "test-agent",
|
|
121
|
+
model: "test-model"
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
// Memory B: Auth Specific
|
|
125
|
+
await router("tools/call", {
|
|
126
|
+
name: "memory-store",
|
|
127
|
+
arguments: {
|
|
128
|
+
type: "code_fact",
|
|
129
|
+
title: "Auth Security Audit",
|
|
130
|
+
content: "Auth module requires PII masking in logs.",
|
|
131
|
+
importance: 5,
|
|
132
|
+
scope: { repo: REPO, folder: "src/auth" },
|
|
133
|
+
agent: "test-agent",
|
|
134
|
+
model: "test-model"
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
// Working in a deep auth file
|
|
138
|
+
const searchRes = await router("tools/call", {
|
|
139
|
+
name: "memory-search",
|
|
140
|
+
arguments: {
|
|
141
|
+
query: "How to log data?",
|
|
142
|
+
repo: REPO,
|
|
143
|
+
current_file_path: "src/auth/services/ldap/provider.ts"
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
// EXPECT: Auth Specific memory should be the Top Match despite the query being generic "log"
|
|
147
|
+
expect(searchRes.content[0].text).toContain("Auth Security Audit");
|
|
148
|
+
});
|
|
149
|
+
/**
|
|
150
|
+
* SCENARIO 4: Bulk Operations & Pagination Integrity
|
|
151
|
+
*/
|
|
152
|
+
it("should handle large volumes of memories and maintain pagination integrity", async () => {
|
|
153
|
+
// Store 15 memories about different microservices
|
|
154
|
+
for (let i = 1; i <= 15; i++) {
|
|
155
|
+
await router("tools/call", {
|
|
156
|
+
name: "memory-store",
|
|
157
|
+
arguments: {
|
|
158
|
+
type: "code_fact",
|
|
159
|
+
title: `Service ${i} Specs`,
|
|
160
|
+
content: `Documentation for microservice number ${i} in our mesh network.`,
|
|
161
|
+
importance: 3,
|
|
162
|
+
scope: { repo: "cloud-infra" },
|
|
163
|
+
agent: "test-agent",
|
|
164
|
+
model: "test-model"
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
// Call memory-recap (which uses pagination)
|
|
169
|
+
const recapRes = await router("tools/call", {
|
|
170
|
+
name: "memory-recap",
|
|
171
|
+
arguments: { repo: "cloud-infra", limit: 5, offset: 0 }
|
|
172
|
+
});
|
|
173
|
+
// Based on memory-recap implementation
|
|
174
|
+
expect(recapRes.content[0].text).toContain("cloud-infra");
|
|
175
|
+
// Check Resource Pagination via URI
|
|
176
|
+
const resourceRes = await router("resources/read", {
|
|
177
|
+
uri: "memory://index?repo=cloud-infra"
|
|
178
|
+
});
|
|
179
|
+
const entries = JSON.parse(resourceRes.contents[0].text);
|
|
180
|
+
expect(entries.length).toBeLessThanOrEqual(20); // Default limit in resource.read is 20
|
|
181
|
+
});
|
|
182
|
+
/**
|
|
183
|
+
* SCENARIO 5: Semantic Conflict Denial (Deep Overlap)
|
|
184
|
+
*/
|
|
185
|
+
it("should strictly deny near-duplicate decisions to prevent prompt bloat", async () => {
|
|
186
|
+
const originalContent = "We use TailwindCSS for styling all components.";
|
|
187
|
+
await router("tools/call", {
|
|
188
|
+
name: "memory-store",
|
|
189
|
+
arguments: {
|
|
190
|
+
type: "decision",
|
|
191
|
+
title: "Styling Standard",
|
|
192
|
+
content: originalContent,
|
|
193
|
+
importance: 3,
|
|
194
|
+
scope: { repo: REPO },
|
|
195
|
+
agent: "test-agent",
|
|
196
|
+
model: "test-model"
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
// Try to store almost the same thing with a different title
|
|
200
|
+
const duplicateRes = await router("tools/call", {
|
|
201
|
+
name: "memory-store",
|
|
202
|
+
arguments: {
|
|
203
|
+
type: "decision",
|
|
204
|
+
title: "CSS Rule",
|
|
205
|
+
content: "Use Tailwind CSS for styling our UI components.", // Subtly different but semantically identical
|
|
206
|
+
importance: 3,
|
|
207
|
+
scope: { repo: REPO },
|
|
208
|
+
agent: "test-agent",
|
|
209
|
+
model: "test-model"
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
expect(duplicateRes.content[0].text).toContain("conflict");
|
|
213
|
+
expect(duplicateRes.content[0].text).toContain("Hint:");
|
|
214
|
+
expect(duplicateRes.content[0].text).toContain("memory-delete");
|
|
215
|
+
expect(db.getTotalCount(REPO)).toBe(1); // Should still be 1
|
|
216
|
+
});
|
|
217
|
+
/**
|
|
218
|
+
* SCENARIO 6: Cross-Project Knowledge Sharing (Tech-Stack Affinity)
|
|
219
|
+
* Repository A has Filament knowledge. Repository B (new) should access it via tags.
|
|
220
|
+
*/
|
|
221
|
+
it("should share knowledge across projects using tech-stack affinity tags", async () => {
|
|
222
|
+
// 1. Store Filament best practice in Repo A
|
|
223
|
+
await router("tools/call", {
|
|
224
|
+
name: "memory-store",
|
|
225
|
+
arguments: {
|
|
226
|
+
type: "pattern",
|
|
227
|
+
title: "Filament Custom Action",
|
|
228
|
+
content: "Always use ->requiresConfirmation() for destructive actions in Filament.",
|
|
229
|
+
importance: 5,
|
|
230
|
+
scope: { repo: "project-a" },
|
|
231
|
+
agent: "test-agent",
|
|
232
|
+
model: "test-model",
|
|
233
|
+
tags: ["filament", "laravel"]
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
// 2. Search in Repo B (which is empty) with "filament" tag
|
|
237
|
+
const searchRes = await router("tools/call", {
|
|
238
|
+
name: "memory-search",
|
|
239
|
+
arguments: {
|
|
240
|
+
query: "how to make safe actions?",
|
|
241
|
+
repo: "project-b",
|
|
242
|
+
current_tags: ["filament"]
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
// EXPECT: Should find the "Filament Custom Action" from Project A
|
|
246
|
+
expect(searchRes.content[0].text).toContain("Filament Custom Action");
|
|
247
|
+
expect(searchRes.data.results[0].scope.repo).toBe("project-a");
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
//# sourceMappingURL=e2e.test.js.map
|