@gethmy/mcp 2.3.1 → 2.3.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 (34) hide show
  1. package/dist/lib/active-learning.js +939 -787
  2. package/dist/lib/api-client.js +2527 -644
  3. package/dist/lib/auto-session.js +177 -196
  4. package/dist/lib/cli.js +34954 -128
  5. package/dist/lib/config.js +235 -201
  6. package/dist/lib/consolidation.js +374 -289
  7. package/dist/lib/context-assembly.js +1265 -838
  8. package/dist/lib/graph-expansion.js +139 -155
  9. package/dist/lib/http.js +1917 -130
  10. package/dist/lib/index.js +29525 -5
  11. package/dist/lib/lifecycle-maintenance.js +663 -79
  12. package/dist/lib/memory-cleanup.js +1315 -409
  13. package/dist/lib/onboard.js +2588 -32
  14. package/dist/lib/prompt-builder.js +438 -445
  15. package/dist/lib/remote.js +31733 -143
  16. package/dist/lib/server.js +29388 -3229
  17. package/dist/lib/skills.js +315 -132
  18. package/dist/lib/tui/agents.js +128 -107
  19. package/dist/lib/tui/docs.js +1590 -687
  20. package/dist/lib/tui/setup.js +5698 -804
  21. package/dist/lib/tui/theme.js +183 -86
  22. package/dist/lib/tui/writer.js +1149 -176
  23. package/package.json +2 -2
  24. package/src/memory-cleanup.ts +2 -4
  25. package/dist/lib/__tests__/active-learning.test.js +0 -386
  26. package/dist/lib/__tests__/agent-performance-profiles.test.js +0 -325
  27. package/dist/lib/__tests__/auto-session.test.js +0 -661
  28. package/dist/lib/__tests__/context-assembly.test.js +0 -362
  29. package/dist/lib/__tests__/graph-expansion.test.js +0 -150
  30. package/dist/lib/__tests__/integration-memory-crud.test.js +0 -797
  31. package/dist/lib/__tests__/integration-memory-system.test.js +0 -281
  32. package/dist/lib/__tests__/lifecycle-maintenance.test.js +0 -207
  33. package/dist/lib/__tests__/pattern-detection.test.js +0 -295
  34. package/dist/lib/__tests__/prompt-builder.test.js +0 -418
@@ -1,295 +0,0 @@
1
- /**
2
- * Unit tests for the pattern detection logic (detectAndCreatePatterns).
3
- *
4
- * Run with: bun test packages/mcp-server/src/__tests__/pattern-detection.test.ts
5
- */
6
- import { describe, expect, mock, test } from "bun:test";
7
- // Mock config before importing
8
- mock.module("../config.js", () => ({
9
- getActiveWorkspaceId: () => "ws-test",
10
- getActiveProjectId: () => "proj-test",
11
- isConfigured: () => true,
12
- loadConfig: () => { },
13
- }));
14
- // Mock graph-expansion so autoExpandGraph calls don't interfere with pattern tests
15
- mock.module("../graph-expansion.js", () => ({
16
- autoExpandGraph: mock(async () => ({ relationsCreated: 0 })),
17
- }));
18
- const { detectAndCreatePatterns } = await import("../active-learning.js");
19
- function makeFullMockClient(opts) {
20
- const createdEntities = [];
21
- const createdRelations = [];
22
- let nextId = 100;
23
- return {
24
- createdEntities,
25
- createdRelations,
26
- getMemoryEntity: mock(async (_id) => ({
27
- entity: opts.entity ?? { id: _id, type: "lesson" },
28
- })),
29
- searchMemoryEntities: mock(async () => ({
30
- entities: opts.searchResults ?? [],
31
- count: opts.searchResults?.length ?? 0,
32
- })),
33
- listMemoryEntities: mock(async () => ({
34
- entities: opts.existingPatterns ?? [],
35
- count: opts.existingPatterns?.length ?? 0,
36
- })),
37
- createMemoryEntity: mock(async (data) => {
38
- const entity = { id: `pattern-${nextId++}`, ...data };
39
- createdEntities.push(entity);
40
- return { entity };
41
- }),
42
- updateMemoryEntity: mock(async (id, updates) => ({
43
- entity: { id, ...updates },
44
- })),
45
- createMemoryRelation: mock(async (data) => {
46
- createdRelations.push(data);
47
- return { relation: { id: `rel-${nextId++}`, ...data } };
48
- }),
49
- };
50
- }
51
- function makeSession(overrides = {}) {
52
- return {
53
- cardId: "card-1",
54
- cardTitle: "Implement feature X",
55
- cardLabels: ["backend", "api"],
56
- agentIdentifier: "claude-code",
57
- agentName: "Claude Code",
58
- status: "completed",
59
- ...overrides,
60
- };
61
- }
62
- // ---- Tests ----
63
- describe("detectAndCreatePatterns", () => {
64
- describe("threshold gating", () => {
65
- test("does nothing when fewer than 3 similar entities exist", async () => {
66
- const client = makeFullMockClient({
67
- entity: { id: "new-1", type: "lesson" },
68
- searchResults: [
69
- { id: "old-1", type: "lesson" },
70
- { id: "old-2", type: "lesson" },
71
- ],
72
- });
73
- await detectAndCreatePatterns(client, ["new-1"], makeSession(), "ws-test");
74
- expect(client.createMemoryEntity).not.toHaveBeenCalled();
75
- expect(client.createMemoryRelation).not.toHaveBeenCalled();
76
- });
77
- test("creates pattern when exactly 3 similar entities exist", async () => {
78
- const client = makeFullMockClient({
79
- entity: { id: "new-1", type: "lesson" },
80
- searchResults: [
81
- { id: "old-1", type: "lesson" },
82
- { id: "old-2", type: "lesson" },
83
- { id: "old-3", type: "lesson" },
84
- ],
85
- });
86
- await detectAndCreatePatterns(client, ["new-1"], makeSession(), "ws-test");
87
- expect(client.createMemoryEntity).toHaveBeenCalledTimes(1);
88
- const created = client.createdEntities[0];
89
- expect(created.type).toBe("pattern");
90
- expect(created.memory_tier).toBe("reference");
91
- expect(created.confidence).toBe(0.75);
92
- });
93
- test("creates pattern when more than 3 similar entities exist", async () => {
94
- const client = makeFullMockClient({
95
- entity: { id: "new-1", type: "error" },
96
- searchResults: Array.from({ length: 10 }, (_, i) => ({
97
- id: `old-${i}`,
98
- type: "error",
99
- })),
100
- });
101
- await detectAndCreatePatterns(client, ["new-1"], makeSession(), "ws-test");
102
- expect(client.createMemoryEntity).toHaveBeenCalledTimes(1);
103
- });
104
- });
105
- describe("newly-created entities are excluded from match count", () => {
106
- test("newEntityIds are excluded from the existing count", async () => {
107
- // 3 search results but 2 of them are in newEntityIds → only 1 truly existing
108
- const client = makeFullMockClient({
109
- entity: { id: "new-1", type: "lesson" },
110
- searchResults: [
111
- { id: "new-2", type: "lesson" }, // also new - should be excluded
112
- { id: "new-3", type: "lesson" }, // also new - should be excluded
113
- { id: "old-1", type: "lesson" }, // truly existing
114
- ],
115
- });
116
- await detectAndCreatePatterns(client, ["new-1", "new-2", "new-3"], makeSession(), "ws-test");
117
- // Only 1 truly existing → below threshold → no pattern
118
- expect(client.createMemoryEntity).not.toHaveBeenCalled();
119
- });
120
- });
121
- describe("existing pattern update", () => {
122
- test("updates existing pattern entity instead of creating a new one", async () => {
123
- const client = makeFullMockClient({
124
- entity: { id: "new-1", type: "lesson" },
125
- searchResults: Array.from({ length: 5 }, (_, i) => ({
126
- id: `old-${i}`,
127
- type: "lesson",
128
- })),
129
- existingPatterns: [{ id: "existing-pattern", type: "pattern" }],
130
- });
131
- await detectAndCreatePatterns(client, ["new-1"], makeSession(), "ws-test");
132
- // No new pattern entity created
133
- expect(client.createMemoryEntity).not.toHaveBeenCalled();
134
- // Existing one was updated
135
- expect(client.updateMemoryEntity).toHaveBeenCalledWith("existing-pattern", expect.objectContaining({
136
- content: expect.stringContaining("Recurring pattern"),
137
- metadata: expect.objectContaining({
138
- pattern_count: expect.any(Number),
139
- }),
140
- }));
141
- });
142
- test("creates relation from existing pattern to new entity", async () => {
143
- const client = makeFullMockClient({
144
- entity: { id: "new-1", type: "lesson" },
145
- searchResults: Array.from({ length: 4 }, (_, i) => ({
146
- id: `old-${i}`,
147
- type: "lesson",
148
- })),
149
- existingPatterns: [{ id: "existing-pattern", type: "pattern" }],
150
- });
151
- await detectAndCreatePatterns(client, ["new-1"], makeSession(), "ws-test");
152
- const relationsFromPattern = client.createdRelations.filter((r) => r.source_id === "existing-pattern");
153
- expect(relationsFromPattern.length).toBeGreaterThan(0);
154
- const targetIds = relationsFromPattern.map((r) => r.target_id);
155
- expect(targetIds).toContain("new-1");
156
- });
157
- });
158
- describe("new pattern creation", () => {
159
- test("pattern entity has correct metadata", async () => {
160
- const session = makeSession({ cardLabels: ["backend", "api"] });
161
- const client = makeFullMockClient({
162
- entity: { id: "new-1", type: "error" },
163
- searchResults: Array.from({ length: 4 }, (_, i) => ({
164
- id: `old-${i}`,
165
- type: "error",
166
- })),
167
- });
168
- await detectAndCreatePatterns(client, ["new-1"], session, "ws-test", "proj-test");
169
- const pattern = client.createdEntities[0];
170
- expect(pattern.type).toBe("pattern");
171
- expect(pattern.memory_tier).toBe("reference");
172
- expect(pattern.confidence).toBe(0.75);
173
- expect(pattern.workspace_id).toBe("ws-test");
174
- expect(pattern.project_id).toBe("proj-test");
175
- const meta = pattern.metadata;
176
- expect(meta.source).toBe("pattern_detection");
177
- expect(meta.pattern_type).toBe("error");
178
- });
179
- test("pattern title references the entity type", async () => {
180
- const client = makeFullMockClient({
181
- entity: { id: "new-1", type: "solution" },
182
- searchResults: Array.from({ length: 3 }, (_, i) => ({
183
- id: `old-${i}`,
184
- type: "solution",
185
- })),
186
- });
187
- await detectAndCreatePatterns(client, ["new-1"], makeSession({ cardLabels: ["backend", "api"] }), "ws-test");
188
- const pattern = client.createdEntities[0];
189
- expect(pattern.title).toContain("Pattern:");
190
- expect(pattern.title).toContain("solution");
191
- });
192
- test("creates relates_to relations from pattern to source entities", async () => {
193
- const client = makeFullMockClient({
194
- entity: { id: "new-1", type: "lesson" },
195
- searchResults: [
196
- { id: "old-1", type: "lesson" },
197
- { id: "old-2", type: "lesson" },
198
- { id: "old-3", type: "lesson" },
199
- ],
200
- });
201
- await detectAndCreatePatterns(client, ["new-1"], makeSession(), "ws-test");
202
- // Relations should link pattern → new entity + up to 4 existing
203
- const relations = client.createdRelations;
204
- expect(relations.length).toBeGreaterThan(0);
205
- for (const rel of relations) {
206
- expect(rel.relation_type).toBe("relates_to");
207
- expect(rel.confidence).toBe(0.75);
208
- }
209
- const targetIds = relations.map((r) => r.target_id);
210
- expect(targetIds).toContain("new-1");
211
- });
212
- });
213
- describe("error handling", () => {
214
- test("is non-fatal when getMemoryEntity throws", async () => {
215
- const client = {
216
- getMemoryEntity: mock(async () => {
217
- throw new Error("Not found");
218
- }),
219
- searchMemoryEntities: mock(async () => ({ entities: [], count: 0 })),
220
- listMemoryEntities: mock(async () => ({ entities: [], count: 0 })),
221
- createMemoryEntity: mock(async () => ({ entity: { id: "x" } })),
222
- updateMemoryEntity: mock(async () => ({ entity: {} })),
223
- createMemoryRelation: mock(async () => ({ relation: {} })),
224
- };
225
- // Should not throw
226
- const result = await detectAndCreatePatterns(client, ["new-1"], makeSession(), "ws-test");
227
- expect(result).toEqual([]);
228
- });
229
- test("skips entity if type is missing", async () => {
230
- const client = makeFullMockClient({
231
- // entity with no type
232
- entity: undefined,
233
- searchResults: [],
234
- });
235
- // Override getMemoryEntity to return entity without type
236
- client.getMemoryEntity = mock(async () => ({
237
- entity: { id: "new-1" },
238
- }));
239
- await detectAndCreatePatterns(client, ["new-1"], makeSession(), "ws-test");
240
- expect(client.createMemoryEntity).not.toHaveBeenCalled();
241
- });
242
- test("duplicate relation failures do not abort remaining relations", async () => {
243
- let relCount = 0;
244
- const client = makeFullMockClient({
245
- entity: { id: "new-1", type: "lesson" },
246
- searchResults: [
247
- { id: "old-1", type: "lesson" },
248
- { id: "old-2", type: "lesson" },
249
- { id: "old-3", type: "lesson" },
250
- ],
251
- });
252
- // First relation call throws 409, rest succeed
253
- client.createMemoryRelation = mock(async (data) => {
254
- relCount++;
255
- if (relCount === 1)
256
- throw Object.assign(new Error("Conflict"), { status: 409 });
257
- client.createdRelations.push(data);
258
- return { relation: {} };
259
- });
260
- // Should not throw
261
- await detectAndCreatePatterns(client, ["new-1"], makeSession(), "ws-test");
262
- // Pattern was still created despite relation error
263
- expect(client.createdEntities).toHaveLength(1);
264
- });
265
- });
266
- describe("multiple new entities in one session", () => {
267
- test("processes each new entity independently", async () => {
268
- let callCount = 0;
269
- const entityTypes = ["lesson", "error"];
270
- const client = {
271
- getMemoryEntity: mock(async (id) => ({
272
- entity: { id, type: entityTypes[callCount++ % 2] ?? "lesson" },
273
- })),
274
- searchMemoryEntities: mock(async () => ({
275
- entities: Array.from({ length: 4 }, (_, i) => ({
276
- id: `old-${i}`,
277
- type: "lesson",
278
- })),
279
- count: 4,
280
- })),
281
- listMemoryEntities: mock(async () => ({ entities: [], count: 0 })),
282
- createMemoryEntity: mock(async (data) => ({
283
- entity: { id: `pat-${Math.random()}`, ...data },
284
- })),
285
- updateMemoryEntity: mock(async () => ({ entity: {} })),
286
- createMemoryRelation: mock(async () => ({ relation: {} })),
287
- createdEntities: [],
288
- createdRelations: [],
289
- };
290
- await detectAndCreatePatterns(client, ["new-1", "new-2"], makeSession(), "ws-test");
291
- // Both new entities processed → 2 pattern entities potentially created
292
- expect(client.getMemoryEntity).toHaveBeenCalledTimes(2);
293
- });
294
- });
295
- });