@vheins/local-memory-mcp 0.1.0 → 0.1.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.
Files changed (79) hide show
  1. package/.kiro/specs/memory-mcp-optimization/.config.kiro +1 -0
  2. package/.vscode/tasks.json +27 -0
  3. package/README.md +136 -0
  4. package/package.json +1 -5
  5. package/DASHBOARD.md +0 -129
  6. package/HYBRID_SEARCH.md +0 -204
  7. package/IMPLEMENTATION.md +0 -159
  8. package/dist/dashboard/dashboard.test.js +0 -362
  9. package/dist/mcp/client.test.js +0 -130
  10. package/dist/resources/index.test.js +0 -96
  11. package/dist/router.test.js +0 -113
  12. package/dist/storage/sqlite.d.ts +0 -95
  13. package/dist/storage/sqlite.d.ts.map +0 -1
  14. package/dist/storage/sqlite.js +0 -537
  15. package/dist/storage/sqlite.js.map +0 -1
  16. package/dist/storage/sqlite.test.d.ts +0 -2
  17. package/dist/storage/sqlite.test.d.ts.map +0 -1
  18. package/dist/storage/sqlite.test.js +0 -358
  19. package/dist/storage/sqlite.test.js.map +0 -1
  20. package/dist/storage/vectors.stub.d.ts +0 -12
  21. package/dist/storage/vectors.stub.d.ts.map +0 -1
  22. package/dist/storage/vectors.stub.js +0 -88
  23. package/dist/storage/vectors.stub.js.map +0 -1
  24. package/dist/tools/memory.search.test.js +0 -181
  25. package/dist/utils/logger.test.js +0 -84
  26. package/dist/utils/normalize.test.js +0 -159
  27. package/dist/utils/query-expander.test.js +0 -35
  28. package/docs/PRD.md +0 -199
  29. package/docs/PROMPT-agent.md +0 -139
  30. package/docs/SPEC-git-scope.md +0 -172
  31. package/docs/SPEC-heuristics.md +0 -199
  32. package/docs/SPEC-server.md +0 -243
  33. package/docs/SPEC-skeleton.md +0 -255
  34. package/docs/SPEC-sqlite-schema.md +0 -183
  35. package/docs/SPEC-tool-schema.md +0 -201
  36. package/docs/SPEC-vector-search.md +0 -198
  37. package/docs/TEST-scenarios.md +0 -179
  38. package/scripts/update-null-titles-ai.mjs +0 -272
  39. package/scripts/update-titles-batch.mjs +0 -71
  40. package/scripts/update-titles.mjs +0 -66
  41. package/seed-data.mjs +0 -151
  42. package/src/capabilities.ts +0 -22
  43. package/src/dashboard/dashboard.test.ts +0 -546
  44. package/src/dashboard/public/app.js +0 -1187
  45. package/src/dashboard/public/chart.js +0 -0
  46. package/src/dashboard/public/index.html +0 -967
  47. package/src/dashboard/server.ts +0 -347
  48. package/src/mcp/client.test.ts +0 -164
  49. package/src/mcp/client.ts +0 -212
  50. package/src/prompts/registry.ts +0 -89
  51. package/src/resources/index.test.ts +0 -132
  52. package/src/resources/index.ts +0 -113
  53. package/src/router.test.ts +0 -145
  54. package/src/router.ts +0 -80
  55. package/src/server.ts +0 -99
  56. package/src/storage/sqlite.test.ts +0 -504
  57. package/src/storage/sqlite.ts +0 -688
  58. package/src/storage/vectors.stub.ts +0 -101
  59. package/src/tools/memory.delete.ts +0 -37
  60. package/src/tools/memory.recap.ts +0 -61
  61. package/src/tools/memory.search.test.ts +0 -276
  62. package/src/tools/memory.search.ts +0 -244
  63. package/src/tools/memory.store.ts +0 -56
  64. package/src/tools/memory.summarize.ts +0 -23
  65. package/src/tools/memory.update.ts +0 -46
  66. package/src/tools/schemas.ts +0 -261
  67. package/src/types.ts +0 -36
  68. package/src/utils/git-scope.ts +0 -42
  69. package/src/utils/logger.test.ts +0 -125
  70. package/src/utils/logger.ts +0 -53
  71. package/src/utils/mcp-response.ts +0 -116
  72. package/src/utils/normalize.test.ts +0 -203
  73. package/src/utils/normalize.ts +0 -53
  74. package/src/utils/query-expander.test.ts +0 -40
  75. package/src/utils/query-expander.ts +0 -60
  76. package/storage/.gitkeep +0 -5
  77. package/test.sh +0 -48
  78. package/tsconfig.json +0 -21
  79. package/vitest.config.ts +0 -10
@@ -1,181 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import * as fc from "fast-check";
3
- // ─── Property 2: Recap conditional ───────────────────────────────────────────
4
- // Feature: memory-mcp-optimization, Property 2: Recap conditional
5
- // We test the weight constants directly by importing them from the module.
6
- // For Property 2 we need to verify the conditional recap call logic.
7
- // We do this by extracting the logic into a testable helper and verifying
8
- // the branching behaviour with fast-check.
9
- /**
10
- * Mirrors the weight selection logic from memory.search.ts.
11
- * Returns the weights object that would be used given whether vector results
12
- * are present or not.
13
- */
14
- function selectWeights(vectorResultsEmpty) {
15
- if (vectorResultsEmpty) {
16
- return { similarity: 0.85, importance: 0.15 };
17
- }
18
- return { similarity: 0.6, vector: 0.3, importance: 0.1 };
19
- }
20
- /**
21
- * Mirrors the recap-call decision from memory.search.ts.
22
- * Returns true if handleMemoryRecap should be called.
23
- */
24
- function shouldCallRecap(includeRecap) {
25
- return includeRecap === true;
26
- }
27
- // ─── Property 2 tests ────────────────────────────────────────────────────────
28
- describe("Property 2: Recap dipanggil jika dan hanya jika includeRecap = true", () => {
29
- // Feature: memory-mcp-optimization, Property 2: Recap conditional
30
- // Validates: Requirements 2.1, 2.2, 2.3
31
- it("should NOT call recap when includeRecap is false or absent", () => {
32
- fc.assert(fc.property(fc.oneof(fc.constant(false), fc.constant(undefined)), (includeRecap) => {
33
- expect(shouldCallRecap(includeRecap)).toBe(false);
34
- }), { numRuns: 100 });
35
- });
36
- it("should call recap when includeRecap is true", () => {
37
- fc.assert(fc.property(fc.constant(true), (includeRecap) => {
38
- expect(shouldCallRecap(includeRecap)).toBe(true);
39
- }), { numRuns: 100 });
40
- });
41
- it("recap is called if and only if includeRecap === true (combined property)", () => {
42
- fc.assert(fc.property(fc.oneof(fc.constant(true), fc.constant(false), fc.constant(undefined)), (includeRecap) => {
43
- const called = shouldCallRecap(includeRecap);
44
- // The iff condition: called ↔ (includeRecap === true)
45
- expect(called).toBe(includeRecap === true);
46
- }), { numRuns: 200 });
47
- });
48
- });
49
- // ─── Property 3: Hybrid score weights sum to 1.0 ─────────────────────────────
50
- describe("Property 3: Total bobot Hybrid Score selalu = 1.0", () => {
51
- // Feature: memory-mcp-optimization, Property 3: Hybrid score weights sum to 1.0
52
- // Validates: Requirements 3.1, 3.2, 3.4
53
- it("weights sum to 1.0 when vector store is empty (no vector results)", () => {
54
- fc.assert(fc.property(fc.constant(true), // vectorResultsEmpty = true
55
- (vectorResultsEmpty) => {
56
- const weights = selectWeights(vectorResultsEmpty);
57
- const sum = weights.similarity + (weights.vector ?? 0) + weights.importance;
58
- expect(Math.abs(sum - 1.0)).toBeLessThan(1e-10);
59
- }), { numRuns: 100 });
60
- });
61
- it("weights sum to 1.0 when vector store is active (has vector results)", () => {
62
- fc.assert(fc.property(fc.constant(false), // vectorResultsEmpty = false
63
- (vectorResultsEmpty) => {
64
- const weights = selectWeights(vectorResultsEmpty);
65
- const sum = weights.similarity + (weights.vector ?? 0) + weights.importance;
66
- expect(Math.abs(sum - 1.0)).toBeLessThan(1e-10);
67
- }), { numRuns: 100 });
68
- });
69
- it("weights always sum to 1.0 for both vector conditions", () => {
70
- fc.assert(fc.property(fc.boolean(), // arbitrary vectorResultsEmpty flag
71
- (vectorResultsEmpty) => {
72
- const weights = selectWeights(vectorResultsEmpty);
73
- const sum = weights.similarity + (weights.vector ?? 0) + weights.importance;
74
- expect(Math.abs(sum - 1.0)).toBeLessThan(1e-10);
75
- }), { numRuns: 200 });
76
- });
77
- it("vector-active weights are exactly 0.6 + 0.3 + 0.1 = 1.0", () => {
78
- const weights = selectWeights(false);
79
- expect(weights.similarity).toBe(0.6);
80
- expect(weights.vector).toBe(0.3);
81
- expect(weights.importance).toBe(0.1);
82
- expect(weights.similarity + (weights.vector ?? 0) + weights.importance).toBeCloseTo(1.0, 10);
83
- });
84
- it("vector-empty weights are exactly 0.85 + 0.15 = 1.0", () => {
85
- const weights = selectWeights(true);
86
- expect(weights.similarity).toBe(0.85);
87
- expect(weights.vector).toBeUndefined();
88
- expect(weights.importance).toBe(0.15);
89
- expect(weights.similarity + weights.importance).toBe(1.0);
90
- });
91
- });
92
- // ─── Prompt parameter tests ─────────────────────────────────────────────────
93
- describe("Property: prompt parameter in memory-search", () => {
94
- it("prompt is optional - search works without prompt", () => {
95
- const params = {
96
- query: "database orm",
97
- repo: "test-repo",
98
- limit: 5
99
- };
100
- // If prompt is not provided, searchQuery should equal query
101
- const hasPrompt = 'prompt' in params && params.prompt;
102
- const searchQuery = hasPrompt
103
- ? `${params.query} ${params.prompt}`
104
- : params.query;
105
- expect(searchQuery).toBe("database orm");
106
- });
107
- it("prompt is combined with query when provided", () => {
108
- const params = {
109
- query: "database orm",
110
- prompt: "I need user authentication",
111
- repo: "test-repo",
112
- limit: 5
113
- };
114
- const searchQuery = params.prompt
115
- ? `${params.query} ${params.prompt}`
116
- : params.query;
117
- expect(searchQuery).toBe("database orm I need user authentication");
118
- });
119
- it("prompt can be empty string - treated as no prompt (empty string is falsy)", () => {
120
- const params = {
121
- query: "database orm",
122
- prompt: "",
123
- repo: "test-repo",
124
- limit: 5
125
- };
126
- // Empty string is falsy in JS, so it uses just query
127
- const searchQuery = params.prompt
128
- ? `${params.query} ${params.prompt}`
129
- : params.query;
130
- expect(searchQuery).toBe("database orm");
131
- });
132
- it("matchReason includes prompt when provided", () => {
133
- const query = "database orm";
134
- const prompt = "user authentication context";
135
- const matchReason = prompt
136
- ? `Results ranked by relevance to "${query}" with context: ${prompt}`
137
- : `Results ranked by relevance to "${query}"`;
138
- expect(matchReason).toContain("database orm");
139
- expect(matchReason).toContain("user authentication context");
140
- expect(matchReason).toContain("with context:");
141
- });
142
- it("matchReason does not include prompt when not provided", () => {
143
- const query = "database orm";
144
- const prompt = undefined;
145
- const matchReason = prompt
146
- ? `Results ranked by relevance to "${query}" with context: ${prompt}`
147
- : `Results ranked by relevance to "${query}"`;
148
- expect(matchReason).toBe(`Results ranked by relevance to "${query}"`);
149
- expect(matchReason).not.toContain("with context:");
150
- });
151
- it("property: searchQuery combines query and prompt consistently", () => {
152
- fc.assert(fc.property(fc.string({ minLength: 3, maxLength: 50 }), fc.string({ minLength: 1, maxLength: 100 }), (query, prompt) => {
153
- const searchQuery = `${query} ${prompt}`;
154
- // Search query should always start with the original query
155
- expect(searchQuery.startsWith(query)).toBe(true);
156
- // Search query should contain the prompt
157
- expect(searchQuery.includes(prompt)).toBe(true);
158
- // There should be exactly one space between query and prompt
159
- expect(searchQuery).toBe(`${query} ${prompt}`);
160
- }), { numRuns: 50 });
161
- });
162
- it("property: matchReason format is correct with and without prompt", () => {
163
- fc.assert(fc.property(fc.string({ minLength: 1, maxLength: 30 }), fc.option(fc.string({ minLength: 1, maxLength: 50 })), (query, prompt) => {
164
- const matchReason = prompt
165
- ? `Results ranked by relevance to "${query}" with context: ${prompt}`
166
- : `Results ranked by relevance to "${query}"`;
167
- // Should always contain the query
168
- expect(matchReason).toContain(query);
169
- if (prompt) {
170
- // Should contain "with context:" when prompt exists
171
- expect(matchReason).toContain("with context:");
172
- expect(matchReason).toContain(prompt);
173
- }
174
- else {
175
- // Should NOT contain "with context:" when prompt is absent
176
- expect(matchReason).not.toContain("with context:");
177
- }
178
- }), { numRuns: 50 });
179
- });
180
- });
181
- //# sourceMappingURL=memory.search.test.js.map
@@ -1,84 +0,0 @@
1
- // Feature: memory-mcp-optimization
2
- // Property 20: StructuredLogger output adalah JSON valid dengan field wajib
3
- // Validates: Requirements 21.1, 21.2
4
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
5
- import * as fc from "fast-check";
6
- describe("Property 20: StructuredLogger output adalah JSON valid dengan field wajib", () => {
7
- let stderrOutput = [];
8
- let stderrSpy;
9
- beforeEach(() => {
10
- stderrOutput = [];
11
- stderrSpy = vi.spyOn(process.stderr, "write").mockImplementation((chunk) => {
12
- stderrOutput.push(typeof chunk === "string" ? chunk : chunk.toString());
13
- return true;
14
- });
15
- // Reset LOG_LEVEL to ensure all levels are logged
16
- process.env.LOG_LEVEL = "debug";
17
- });
18
- afterEach(() => {
19
- stderrSpy.mockRestore();
20
- delete process.env.LOG_LEVEL;
21
- });
22
- it("each log call produces valid JSON output with required fields", async () => {
23
- // Re-import logger after setting LOG_LEVEL
24
- const { logger } = await import("./logger.js?t=" + Date.now());
25
- fc.assert(fc.property(fc.oneof(fc.constant("debug"), fc.constant("info"), fc.constant("warn"), fc.constant("error")), fc.string({ minLength: 1, maxLength: 100 }), (level, message) => {
26
- stderrOutput = [];
27
- logger[level](message);
28
- expect(stderrOutput.length).toBeGreaterThanOrEqual(1);
29
- const lastOutput = stderrOutput[stderrOutput.length - 1];
30
- // Must be parseable JSON
31
- let parsed;
32
- expect(() => {
33
- parsed = JSON.parse(lastOutput.trim());
34
- }).not.toThrow();
35
- parsed = JSON.parse(lastOutput.trim());
36
- // Must have required fields
37
- expect(parsed).toHaveProperty("level");
38
- expect(parsed).toHaveProperty("timestamp");
39
- expect(parsed).toHaveProperty("message");
40
- // level must match
41
- expect(parsed.level).toBe(level);
42
- // message must match
43
- expect(parsed.message).toBe(message);
44
- // timestamp must be ISO 8601
45
- expect(typeof parsed.timestamp).toBe("string");
46
- const ts = new Date(parsed.timestamp);
47
- expect(isNaN(ts.getTime())).toBe(false);
48
- }), { numRuns: 100 });
49
- });
50
- it("log output with context includes context field", async () => {
51
- const { logger } = await import("./logger.js?t=" + Date.now() + "ctx");
52
- stderrOutput = [];
53
- logger.info("test message", { key: "value", count: 42 });
54
- expect(stderrOutput.length).toBeGreaterThanOrEqual(1);
55
- const lastOutput = stderrOutput[stderrOutput.length - 1];
56
- const parsed = JSON.parse(lastOutput.trim());
57
- expect(parsed).toHaveProperty("context");
58
- expect(parsed.context).toEqual({ key: "value", count: 42 });
59
- });
60
- it("log output without context does not include context field", async () => {
61
- const { logger } = await import("./logger.js?t=" + Date.now() + "noctx");
62
- stderrOutput = [];
63
- logger.info("no context message");
64
- expect(stderrOutput.length).toBeGreaterThanOrEqual(1);
65
- const lastOutput = stderrOutput[stderrOutput.length - 1];
66
- const parsed = JSON.parse(lastOutput.trim());
67
- expect(parsed).not.toHaveProperty("context");
68
- });
69
- it("all four log levels produce valid JSON with correct level field", async () => {
70
- const { logger } = await import("./logger.js?t=" + Date.now() + "levels");
71
- const levels = ["debug", "info", "warn", "error"];
72
- for (const level of levels) {
73
- stderrOutput = [];
74
- logger[level](`test ${level} message`);
75
- expect(stderrOutput.length).toBeGreaterThanOrEqual(1);
76
- const lastOutput = stderrOutput[stderrOutput.length - 1];
77
- const parsed = JSON.parse(lastOutput.trim());
78
- expect(parsed.level).toBe(level);
79
- expect(parsed.message).toBe(`test ${level} message`);
80
- expect(typeof parsed.timestamp).toBe("string");
81
- }
82
- });
83
- });
84
- //# sourceMappingURL=logger.test.js.map
@@ -1,159 +0,0 @@
1
- // Feature: memory-mcp-optimization
2
- // Unit + Property tests for normalize(), tokenize(), and STOPWORDS
3
- // Requirements: 5.4, 5.5, 17.3, 17.4, 17.7
4
- import { describe, it, expect } from "vitest";
5
- import * as fc from "fast-check";
6
- import { normalize, tokenize, STOPWORDS } from "./normalize.js";
7
- // ─── Unit Tests: normalize() ─────────────────────────────────────────────────
8
- describe("normalize() — unit tests", () => {
9
- it("converts text to lowercase", () => {
10
- expect(normalize("Hello World")).toBe("hello world");
11
- expect(normalize("TYPESCRIPT")).toBe("typescript");
12
- expect(normalize("CamelCase")).toBe("camelcase");
13
- });
14
- it("trims leading and trailing whitespace", () => {
15
- expect(normalize(" hello ")).toBe("hello");
16
- expect(normalize("\t text \n")).toBe("text");
17
- });
18
- it("replaces special characters with spaces", () => {
19
- const result = normalize("hello!world@test#value");
20
- // Special chars replaced by spaces, then collapsed
21
- expect(result).not.toContain("!");
22
- expect(result).not.toContain("@");
23
- expect(result).not.toContain("#");
24
- });
25
- it("collapses multiple spaces into one", () => {
26
- expect(normalize("hello world")).toBe("hello world");
27
- expect(normalize("a b c")).toBe("a b c");
28
- });
29
- it("handles empty string", () => {
30
- expect(normalize("")).toBe("");
31
- });
32
- it("handles string with only special characters", () => {
33
- expect(normalize("!!!@@@###")).toBe("");
34
- });
35
- it("preserves alphanumeric characters", () => {
36
- expect(normalize("abc123")).toBe("abc123");
37
- });
38
- });
39
- // ─── Unit Tests: tokenize() ──────────────────────────────────────────────────
40
- describe("tokenize() — unit tests", () => {
41
- it("removes stopwords from output", () => {
42
- const tokens = tokenize("the quick brown fox");
43
- expect(tokens).not.toContain("the");
44
- expect(tokens).toContain("quick");
45
- expect(tokens).toContain("brown");
46
- expect(tokens).toContain("fox");
47
- });
48
- it("removes short words (length <= 0 after normalize)", () => {
49
- // tokenize filters tokens with length > 0 and not in STOPWORDS
50
- const tokens = tokenize("a b c hello");
51
- // "a", "b", "c" are stopwords or single chars
52
- expect(tokens).toContain("hello");
53
- });
54
- it("returns empty array for all-stopword input", () => {
55
- const tokens = tokenize("the and or but");
56
- expect(tokens).toEqual([]);
57
- });
58
- it("handles empty string", () => {
59
- expect(tokenize("")).toEqual([]);
60
- });
61
- it("normalizes before tokenizing (lowercase, trim)", () => {
62
- const tokens1 = tokenize("TypeScript");
63
- const tokens2 = tokenize("typescript");
64
- expect(tokens1).toEqual(tokens2);
65
- });
66
- it("removes Indonesian stopwords", () => {
67
- const tokens = tokenize("yang dan di ke dari ini itu coding");
68
- expect(tokens).not.toContain("yang");
69
- expect(tokens).not.toContain("dan");
70
- expect(tokens).toContain("coding");
71
- });
72
- });
73
- // ─── Property 4: Tokenisasi konsisten antara SQLiteStore dan StubVectorStore ──
74
- // Feature: memory-mcp-optimization, Property 4: Tokenisasi konsisten
75
- // Validates: Requirements 4.4, 5.2, 5.3
76
- describe("Property 4: Tokenisasi konsisten antara SQLiteStore dan StubVectorStore", () => {
77
- it("tokenize() is deterministic — same input always produces same output", () => {
78
- fc.assert(fc.property(fc.string({ minLength: 0, maxLength: 100 }), (text) => {
79
- // Both SQLiteStore and StubVectorStore use tokenize() from normalize.ts
80
- // So testing tokenize() determinism validates consistency between them
81
- const result1 = tokenize(text);
82
- const result2 = tokenize(text);
83
- expect(result1).toEqual(result2);
84
- }), { numRuns: 200 });
85
- });
86
- it("tokenize() output is a subset of normalize() tokens", () => {
87
- fc.assert(fc.property(fc.string({ minLength: 0, maxLength: 100 }), (text) => {
88
- const normalized = normalize(text);
89
- const allTokens = normalized.split(" ").filter((t) => t.length > 0);
90
- const filtered = tokenize(text);
91
- // Every token in filtered must appear in allTokens
92
- for (const token of filtered) {
93
- expect(allTokens).toContain(token);
94
- }
95
- }), { numRuns: 200 });
96
- });
97
- });
98
- // ─── Property 5: normalize() idempoten ganda ─────────────────────────────────
99
- // Feature: memory-mcp-optimization, Property 5: normalize() idempoten ganda
100
- // Validates: Requirements 5.4, 5.5
101
- describe("Property 5: normalize() idempoten ganda", () => {
102
- it("normalize(normalize(text)) === normalize(text) for 100+ random inputs", () => {
103
- fc.assert(fc.property(fc.string({ minLength: 0, maxLength: 200 }), (text) => {
104
- const once = normalize(text);
105
- const twice = normalize(once);
106
- expect(twice).toBe(once);
107
- }), { numRuns: 200 });
108
- });
109
- it("normalize is idempotent for specific edge cases", () => {
110
- const cases = [
111
- "",
112
- " ",
113
- "Hello World!",
114
- "UPPERCASE",
115
- "already normalized",
116
- "multiple spaces",
117
- "special!@#chars",
118
- "123numbers456",
119
- ];
120
- for (const text of cases) {
121
- const once = normalize(text);
122
- const twice = normalize(once);
123
- expect(twice).toBe(once);
124
- }
125
- });
126
- });
127
- // ─── Property 21: Stopword list tidak mengandung duplikat ────────────────────
128
- // Feature: memory-mcp-optimization, Property 21: Stopword no duplicates
129
- // Validates: Requirement 17.4
130
- describe("Property 21: Stopword list tidak mengandung duplikat", () => {
131
- it("STOPWORDS has no duplicate entries", () => {
132
- // Since STOPWORDS is a Set, duplicates are automatically removed.
133
- // We verify the source array (before Set construction) has no duplicates
134
- // by checking that Set size equals the number of unique entries.
135
- // The Set itself guarantees uniqueness, so we verify the exported Set is consistent.
136
- const stopwordsArray = Array.from(STOPWORDS);
137
- const uniqueSet = new Set(stopwordsArray);
138
- expect(uniqueSet.size).toBe(stopwordsArray.length);
139
- });
140
- it("all STOPWORDS entries are lowercase strings", () => {
141
- for (const word of STOPWORDS) {
142
- expect(typeof word).toBe("string");
143
- expect(word).toBe(word.toLowerCase());
144
- }
145
- });
146
- it("STOPWORDS is non-empty", () => {
147
- expect(STOPWORDS.size).toBeGreaterThan(0);
148
- });
149
- it("property: no string appears more than once in STOPWORDS", () => {
150
- // This is a structural property — verified once (not random inputs needed)
151
- // since STOPWORDS is a constant Set
152
- const seen = new Set();
153
- for (const word of STOPWORDS) {
154
- expect(seen.has(word)).toBe(false);
155
- seen.add(word);
156
- }
157
- });
158
- });
159
- //# sourceMappingURL=normalize.test.js.map
@@ -1,35 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { expandQuery } from "../utils/query-expander.js";
3
- describe("expandQuery", () => {
4
- it("returns original query when no prompt provided", () => {
5
- expect(expandQuery("database")).toBe("database");
6
- expect(expandQuery("api endpoint")).toBe("api endpoint");
7
- });
8
- it("expands query with prompt keywords", () => {
9
- const result = expandQuery("database", "user authentication implementation");
10
- expect(result).toContain("database");
11
- expect(result).toContain("user");
12
- expect(result).toContain("auth");
13
- });
14
- it("expands known keywords", () => {
15
- const result = expandQuery("auth", "login system");
16
- expect(result).toContain("auth");
17
- expect(result).toContain("login");
18
- expect(result).toContain("password");
19
- });
20
- it("limits to 10 keywords", () => {
21
- const result = expandQuery("api", "building a rest endpoint with controller and route handling");
22
- const words = result.split(" ");
23
- expect(words.length).toBeLessThanOrEqual(10);
24
- });
25
- it("removes duplicates", () => {
26
- const result = expandQuery("database", "database query optimization");
27
- const words = result.split(" ");
28
- const unique = new Set(words);
29
- expect(words.length).toBe(unique.size);
30
- });
31
- it("handles empty prompt gracefully", () => {
32
- expect(expandQuery("test", "")).toBe("test");
33
- });
34
- });
35
- //# sourceMappingURL=query-expander.test.js.map
package/docs/PRD.md DELETED
@@ -1,199 +0,0 @@
1
- # PRD – MCP Local Memory Service
2
-
3
- ## TL;DR
4
-
5
- Membangun **MCP Local Memory Service** berbasis Node.js yang menyediakan *long-term, high-signal memory* untuk coding copilot & antigravity agent. Sistem ini menyimpan keputusan, fakta kode, kesalahan, dan pola penting menggunakan SQLite + vector similarity search, sehingga agent tetap konsisten, tidak mengulang kesalahan, dan tahan terhadap typo / variasi query.
6
-
7
- ---
8
-
9
- ## Problem Statement
10
-
11
- Coding copilot dan agent saat ini:
12
- - Mudah kehilangan konteks keputusan sebelumnya
13
- - Mengulang kesalahan yang sama
14
- - Overfit ke prompt terakhir (*gravity problem*)
15
- - Lemah terhadap typo atau phrasing berbeda
16
-
17
- Tanpa memory terstruktur, agent terasa "pintar sesaat" tapi tidak reliable untuk penggunaan jangka panjang.
18
-
19
- ---
20
-
21
- ## Goals
22
-
23
- ### Business Goals
24
- - Meningkatkan kualitas dan konsistensi output coding copilot
25
- - Mengurangi user correction berulang
26
- - Menjadikan agent usable untuk long-running projects
27
-
28
- ### User Goals
29
- - Agent mengingat keputusan & constraint penting
30
- - Agent tidak mengulang kesalahan lama
31
- - Agent tetap relevan walau prompt berubah atau typo
32
-
33
- ### Non-Goals
34
- - Bukan chat history logger
35
- - Bukan audit trail
36
- - Tidak mendukung distributed / cloud sync (local-first)
37
-
38
- ---
39
-
40
- ## Target Users
41
- - Developer menggunakan coding copilot lokal
42
- - Power user / engineer yang menginginkan agent “punya ingatan”
43
- - Pengguna MCP-based agent system
44
-
45
- ---
46
-
47
- ## User Stories
48
- 1. **Sebagai developer**, saya ingin agent mengingat keputusan arsitektur agar tidak perlu menjelaskannya berulang kali.
49
- 2. **Sebagai developer**, saya ingin agent tidak mengulang kesalahan yang sudah saya koreksi sebelumnya.
50
- 3. **Sebagai developer**, saya ingin agent tetap menemukan memory relevan walau saya salah ketik atau pakai istilah berbeda.
51
- 4. **Sebagai agent**, saya ingin memory yang ringkas dan relevan agar tidak membanjiri prompt.
52
-
53
- ---
54
-
55
- ## Memory Types (Core Concept)
56
-
57
- ### 1. code_fact
58
- Fakta stabil tentang codebase.
59
- *Contoh:*
60
- - Project pakai clean architecture
61
- - Semua API pakai zod validation
62
-
63
- ### 2. decision
64
- Keputusan desain atau arsitektur.
65
- *Contoh:*
66
- - Tidak menggunakan ORM
67
- - Menggunakan raw SQL demi performa
68
-
69
- ### 3. mistake
70
- Kesalahan yang tidak boleh diulang.
71
- *Contoh:*
72
- - Jangan pakai default export
73
- - Jangan gunakan `any` di domain layer
74
-
75
- ### 4. pattern
76
- Pola kode atau konvensi yang sering dipakai.
77
- *Contoh:*
78
- - Struktur handler
79
- - Pola error handling
80
-
81
- ---
82
-
83
- ## User Experience (End-to-End Flow)
84
-
85
- ### 1. Normal Interaction
86
- 1. User meminta agent generate / modify code
87
- 2. Agent membaca project summary (jika ada)
88
- 3. Agent mencari memory relevan via semantic search
89
- 4. Memory terpilih diinjeksi ke prompt
90
- 5. Agent menghasilkan code sesuai constraint
91
-
92
- ### 2. Memory Creation Flow
93
- 1. User memberi constraint / koreksi penting
94
- 2. Agent mendeteksi sinyal *memory-worthy*
95
- 3. Agent menyimpan memory via MCP tool
96
- 4. Agent (opsional) update project summary
97
-
98
- ---
99
-
100
- ## Narrative (Executive Story)
101
-
102
- Bayangkan seorang developer bekerja di codebase selama berminggu-minggu. Ia sudah menetapkan aturan: arsitektur tertentu, larangan tertentu, dan pola kode yang disepakati.
103
-
104
- Tanpa memory, setiap hari agent kembali seperti "intern baru" — pintar, tapi lupa segalanya.
105
-
106
- Dengan **MCP Local Memory Service**, agent berubah menjadi rekan tim yang belajar:
107
- - Mengingat keputusan
108
- - Menghindari kesalahan lama
109
- - Berpikir di level arsitektur
110
-
111
- Ini bukan soal membuat agent lebih pintar — tapi membuatnya lebih dapat dipercaya.
112
-
113
- ---
114
-
115
- ## Functional Requirements
116
-
117
- ### Memory Storage
118
- - Menyimpan memory bertipe: `code_fact`, `decision`, `mistake`, `pattern`
119
- - Mendukung metadata scope (repo, language, folder)
120
- - Mendukung importance score (1–5)
121
-
122
- ### Memory Retrieval
123
- - Semantic similarity search (vector-based)
124
- - Tahan typo & variasi bahasa
125
- - Mendukung filtering by type, scope, importance
126
-
127
- ### Memory Summary
128
- - Ringkasan high-level per repo
129
- - Digunakan sebagai *antigravity anchor*
130
-
131
- ---
132
-
133
- ## MCP Interface
134
-
135
- ### Resources
136
- - `memory://entries`
137
- - `memory://summary`
138
-
139
- ### Tools
140
- - `memory.search`
141
- - `memory.store`
142
- - `memory.summarize`
143
-
144
- Agent tidak mengetahui detail database atau vector engine.
145
-
146
- ---
147
-
148
- ## Success Metrics
149
-
150
- ### Quantitative
151
- - Penurunan jumlah user correction berulang
152
- - Peningkatan reuse memory dalam prompt
153
- - Latency memory search < 100ms (local)
154
-
155
- ### Qualitative
156
- - Agent terasa konsisten
157
- - Agent tidak mengulang kesalahan lama
158
- - User trust meningkat
159
-
160
- ---
161
-
162
- ## Technical Considerations
163
- - Node.js MCP server
164
- - SQLite sebagai primary storage
165
- - Vector similarity via sqlite extension atau embedded index
166
- - Local embedding model (via Ollama / llama.cpp)
167
- - Normalization layer untuk typo handling
168
-
169
- ---
170
-
171
- ## Milestones & Sequencing
172
- 1. MCP contract & interface definition (XX weeks)
173
- 2. SQLite schema + vector indexing (XX weeks)
174
- 3. Memory search & ranking logic (XX weeks)
175
- 4. Agent prompt integration (XX weeks)
176
- 5. Heuristic-based auto memory rules (XX weeks)
177
-
178
- ---
179
-
180
- ## Risks & Mitigations
181
- - **Risk: Memory pollution**
182
- - *Mitigasi:* strict store rules + importance threshold
183
- - **Risk: Prompt bloat**
184
- - *Mitigasi:* limit memory injection + summary usage
185
- - **Risk: Overfitting memory**
186
- - *Mitigasi:* relevance threshold + scope filtering
187
-
188
- ---
189
-
190
- ## Open Questions
191
- - Perlu manual memory deletion UI?
192
- - Perlu memory versioning?
193
- - Perlu cross-repo inference di masa depan?
194
-
195
- ---
196
-
197
- ## Final Take
198
- Produk ini bukan tentang database atau vector search.
199
- Ini tentang mendisiplinkan ingatan agent agar ia benar-benar berguna untuk kerja nyata.