@gethmy/mcp 2.3.0 → 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 (38) hide show
  1. package/dist/cli.js +80 -23
  2. package/dist/index.js +80 -23
  3. package/dist/lib/active-learning.js +939 -787
  4. package/dist/lib/api-client.js +2527 -638
  5. package/dist/lib/auto-session.js +177 -196
  6. package/dist/lib/cli.js +34954 -128
  7. package/dist/lib/config.js +235 -201
  8. package/dist/lib/consolidation.js +374 -289
  9. package/dist/lib/context-assembly.js +1265 -838
  10. package/dist/lib/graph-expansion.js +139 -155
  11. package/dist/lib/http.js +1917 -130
  12. package/dist/lib/index.js +29525 -5
  13. package/dist/lib/lifecycle-maintenance.js +663 -79
  14. package/dist/lib/memory-cleanup.js +1316 -381
  15. package/dist/lib/onboard.js +2588 -32
  16. package/dist/lib/prompt-builder.js +438 -445
  17. package/dist/lib/remote.js +31733 -143
  18. package/dist/lib/server.js +29389 -3216
  19. package/dist/lib/skills.js +315 -132
  20. package/dist/lib/tui/agents.js +128 -107
  21. package/dist/lib/tui/docs.js +1590 -687
  22. package/dist/lib/tui/setup.js +5698 -804
  23. package/dist/lib/tui/theme.js +183 -86
  24. package/dist/lib/tui/writer.js +1149 -176
  25. package/package.json +2 -2
  26. package/src/api-client.ts +37 -1
  27. package/src/memory-cleanup.ts +92 -52
  28. package/src/server.ts +16 -1
  29. package/dist/lib/__tests__/active-learning.test.js +0 -386
  30. package/dist/lib/__tests__/agent-performance-profiles.test.js +0 -325
  31. package/dist/lib/__tests__/auto-session.test.js +0 -661
  32. package/dist/lib/__tests__/context-assembly.test.js +0 -362
  33. package/dist/lib/__tests__/graph-expansion.test.js +0 -150
  34. package/dist/lib/__tests__/integration-memory-crud.test.js +0 -797
  35. package/dist/lib/__tests__/integration-memory-system.test.js +0 -281
  36. package/dist/lib/__tests__/lifecycle-maintenance.test.js +0 -207
  37. package/dist/lib/__tests__/pattern-detection.test.js +0 -295
  38. 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
- });