@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.
- package/dist/cli.js +80 -23
- package/dist/index.js +80 -23
- package/dist/lib/active-learning.js +939 -787
- package/dist/lib/api-client.js +2527 -638
- package/dist/lib/auto-session.js +177 -196
- package/dist/lib/cli.js +34954 -128
- package/dist/lib/config.js +235 -201
- package/dist/lib/consolidation.js +374 -289
- package/dist/lib/context-assembly.js +1265 -838
- package/dist/lib/graph-expansion.js +139 -155
- package/dist/lib/http.js +1917 -130
- package/dist/lib/index.js +29525 -5
- package/dist/lib/lifecycle-maintenance.js +663 -79
- package/dist/lib/memory-cleanup.js +1316 -381
- package/dist/lib/onboard.js +2588 -32
- package/dist/lib/prompt-builder.js +438 -445
- package/dist/lib/remote.js +31733 -143
- package/dist/lib/server.js +29389 -3216
- package/dist/lib/skills.js +315 -132
- package/dist/lib/tui/agents.js +128 -107
- package/dist/lib/tui/docs.js +1590 -687
- package/dist/lib/tui/setup.js +5698 -804
- package/dist/lib/tui/theme.js +183 -86
- package/dist/lib/tui/writer.js +1149 -176
- package/package.json +2 -2
- package/src/api-client.ts +37 -1
- package/src/memory-cleanup.ts +92 -52
- package/src/server.ts +16 -1
- package/dist/lib/__tests__/active-learning.test.js +0 -386
- package/dist/lib/__tests__/agent-performance-profiles.test.js +0 -325
- package/dist/lib/__tests__/auto-session.test.js +0 -661
- package/dist/lib/__tests__/context-assembly.test.js +0 -362
- package/dist/lib/__tests__/graph-expansion.test.js +0 -150
- package/dist/lib/__tests__/integration-memory-crud.test.js +0 -797
- package/dist/lib/__tests__/integration-memory-system.test.js +0 -281
- package/dist/lib/__tests__/lifecycle-maintenance.test.js +0 -207
- package/dist/lib/__tests__/pattern-detection.test.js +0 -295
- package/dist/lib/__tests__/prompt-builder.test.js +0 -418
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gethmy/mcp",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.2",
|
|
4
4
|
"description": "MCP server for Harmony Kanban board - enables AI coding agents to manage your boards",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"bun": ">=1.0.0"
|
|
51
51
|
},
|
|
52
52
|
"scripts": {
|
|
53
|
-
"build": "bun build src/index.ts src/cli.ts --outdir dist --target node &&
|
|
53
|
+
"build": "bun build src/index.ts src/cli.ts --outdir dist --target node && bun build src/*.ts src/tui/*.ts --outdir dist/lib --root src --target node --external bun",
|
|
54
54
|
"build:bun": "bun build src/index.ts src/http.ts src/remote.ts src/cli.ts --outdir dist --target bun",
|
|
55
55
|
"serve:remote": "bun src/remote.ts",
|
|
56
56
|
"dev": "bun --watch src/index.ts",
|
package/src/api-client.ts
CHANGED
|
@@ -502,7 +502,6 @@ export class HarmonyApiClient {
|
|
|
502
502
|
phase?: string;
|
|
503
503
|
filesChanged?: number;
|
|
504
504
|
costCents?: number;
|
|
505
|
-
recentActions?: { action: string; ts: string }[];
|
|
506
505
|
},
|
|
507
506
|
): Promise<{ session: unknown; created: boolean }> {
|
|
508
507
|
return this.request("POST", `/cards/${cardId}/agent-context`, data);
|
|
@@ -518,6 +517,43 @@ export class HarmonyApiClient {
|
|
|
518
517
|
return this.request("DELETE", `/cards/${cardId}/agent-context`, data);
|
|
519
518
|
}
|
|
520
519
|
|
|
520
|
+
async flushActivityLog(
|
|
521
|
+
cardId: string,
|
|
522
|
+
data: {
|
|
523
|
+
sessionId: string;
|
|
524
|
+
entries: {
|
|
525
|
+
phase?: string;
|
|
526
|
+
eventType: string;
|
|
527
|
+
toolName?: string;
|
|
528
|
+
description: string;
|
|
529
|
+
metadata?: Record<string, unknown>;
|
|
530
|
+
createdAt?: string;
|
|
531
|
+
}[];
|
|
532
|
+
},
|
|
533
|
+
): Promise<{ inserted: number }> {
|
|
534
|
+
return this.request("POST", `/cards/${cardId}/agent-activity-log`, data);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
async getActivityLog(
|
|
538
|
+
cardId: string,
|
|
539
|
+
sessionId: string,
|
|
540
|
+
): Promise<{
|
|
541
|
+
entries: {
|
|
542
|
+
id: string;
|
|
543
|
+
phase: string | null;
|
|
544
|
+
eventType: string;
|
|
545
|
+
toolName: string | null;
|
|
546
|
+
description: string;
|
|
547
|
+
metadata: Record<string, unknown>;
|
|
548
|
+
createdAt: string;
|
|
549
|
+
}[];
|
|
550
|
+
}> {
|
|
551
|
+
return this.request(
|
|
552
|
+
"GET",
|
|
553
|
+
`/cards/${cardId}/agent-activity-log?sessionId=${sessionId}`,
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
|
|
521
557
|
async getAgentSession(
|
|
522
558
|
cardId: string,
|
|
523
559
|
options?: { includeEnded?: boolean },
|
package/src/memory-cleanup.ts
CHANGED
|
@@ -130,6 +130,11 @@ const ALL_STEPS: CleanupStep[] = [
|
|
|
130
130
|
"backfill",
|
|
131
131
|
];
|
|
132
132
|
|
|
133
|
+
const MS_PER_DAY = 1000 * 60 * 60 * 24;
|
|
134
|
+
const MAX_ENTITIES_FETCH = 200;
|
|
135
|
+
const DUPLICATE_SIMILARITY_THRESHOLD = 0.85;
|
|
136
|
+
const CONCURRENCY_LIMIT = 5;
|
|
137
|
+
|
|
133
138
|
// ---------------------------------------------------------------------------
|
|
134
139
|
// Main orchestrator
|
|
135
140
|
// ---------------------------------------------------------------------------
|
|
@@ -163,7 +168,7 @@ export async function runMemoryCleanup(
|
|
|
163
168
|
const listResult = await client.listMemoryEntities({
|
|
164
169
|
workspace_id: workspaceId,
|
|
165
170
|
project_id: projectId,
|
|
166
|
-
limit:
|
|
171
|
+
limit: MAX_ENTITIES_FETCH,
|
|
167
172
|
});
|
|
168
173
|
entities = (listResult.entities || []) as MemoryEntity[];
|
|
169
174
|
report.summary.totalEntities = entities.length;
|
|
@@ -186,8 +191,11 @@ export async function runMemoryCleanup(
|
|
|
186
191
|
try {
|
|
187
192
|
await client.deleteMemoryEntity(item.id);
|
|
188
193
|
report.steps.prune.pruned++;
|
|
189
|
-
} catch {
|
|
190
|
-
|
|
194
|
+
} catch (err) {
|
|
195
|
+
report.errors.push({
|
|
196
|
+
step: "prune",
|
|
197
|
+
message: `Failed to delete ${item.id}: ${(err as Error).message}`,
|
|
198
|
+
});
|
|
191
199
|
}
|
|
192
200
|
}
|
|
193
201
|
report.summary.actionsTaken += report.steps.prune.pruned;
|
|
@@ -237,8 +245,11 @@ export async function runMemoryCleanup(
|
|
|
237
245
|
try {
|
|
238
246
|
await client.deleteMemoryEntity(item.id);
|
|
239
247
|
report.steps.orphans.removed++;
|
|
240
|
-
} catch {
|
|
241
|
-
|
|
248
|
+
} catch (err) {
|
|
249
|
+
report.errors.push({
|
|
250
|
+
step: "orphans",
|
|
251
|
+
message: `Failed to delete ${item.id}: ${(err as Error).message}`,
|
|
252
|
+
});
|
|
242
253
|
}
|
|
243
254
|
}
|
|
244
255
|
report.summary.actionsTaken += report.steps.orphans.removed;
|
|
@@ -266,8 +277,11 @@ export async function runMemoryCleanup(
|
|
|
266
277
|
try {
|
|
267
278
|
await client.deleteMemoryEntity(pair.removeId);
|
|
268
279
|
report.steps.duplicates.resolved++;
|
|
269
|
-
} catch {
|
|
270
|
-
|
|
280
|
+
} catch (err) {
|
|
281
|
+
report.errors.push({
|
|
282
|
+
step: "duplicates",
|
|
283
|
+
message: `Failed to delete ${pair.removeId}: ${(err as Error).message}`,
|
|
284
|
+
});
|
|
271
285
|
}
|
|
272
286
|
}
|
|
273
287
|
report.summary.actionsTaken += report.steps.duplicates.resolved;
|
|
@@ -325,8 +339,7 @@ function runPruneStep(
|
|
|
325
339
|
const stale: PruneStepResult["items"] = [];
|
|
326
340
|
|
|
327
341
|
for (const entity of drafts) {
|
|
328
|
-
const ageDays =
|
|
329
|
-
(now - new Date(entity.created_at).getTime()) / (1000 * 60 * 60 * 24);
|
|
342
|
+
const ageDays = (now - new Date(entity.created_at).getTime()) / MS_PER_DAY;
|
|
330
343
|
if (ageDays < maxAgeDays) continue;
|
|
331
344
|
|
|
332
345
|
const lifecycle = evaluateLifecycle(entity);
|
|
@@ -353,31 +366,38 @@ async function runOrphanStep(
|
|
|
353
366
|
const candidates = entities.filter((e) => {
|
|
354
367
|
if (e.memory_tier === "reference") return false;
|
|
355
368
|
if (e.access_count >= 2) return false;
|
|
356
|
-
const ageDays =
|
|
357
|
-
(now - new Date(e.created_at).getTime()) / (1000 * 60 * 60 * 24);
|
|
369
|
+
const ageDays = (now - new Date(e.created_at).getTime()) / MS_PER_DAY;
|
|
358
370
|
return ageDays >= orphanAgeDays;
|
|
359
371
|
});
|
|
360
372
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
(
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
373
|
+
// Check relations in concurrent batches
|
|
374
|
+
for (let i = 0; i < candidates.length; i += CONCURRENCY_LIMIT) {
|
|
375
|
+
const batch = candidates.slice(i, i + CONCURRENCY_LIMIT);
|
|
376
|
+
const results = await Promise.allSettled(
|
|
377
|
+
batch.map(async (entity) => {
|
|
378
|
+
const related = await client.getRelatedEntities(entity.id);
|
|
379
|
+
const totalRelations =
|
|
380
|
+
(related.outgoing?.length || 0) + (related.incoming?.length || 0);
|
|
381
|
+
if (totalRelations > 0) return null;
|
|
382
|
+
|
|
383
|
+
const ageDays =
|
|
384
|
+
(now - new Date(entity.created_at).getTime()) / MS_PER_DAY;
|
|
385
|
+
return {
|
|
386
|
+
id: entity.id,
|
|
387
|
+
title: entity.title,
|
|
388
|
+
type: entity.type,
|
|
389
|
+
tier: entity.memory_tier,
|
|
390
|
+
ageDays: Math.round(ageDays),
|
|
391
|
+
accessCount: entity.access_count,
|
|
392
|
+
};
|
|
393
|
+
}),
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
for (const r of results) {
|
|
397
|
+
if (r.status === "fulfilled" && r.value) {
|
|
398
|
+
result.items.push(r.value);
|
|
399
|
+
result.orphansFound++;
|
|
400
|
+
}
|
|
381
401
|
}
|
|
382
402
|
}
|
|
383
403
|
|
|
@@ -398,28 +418,42 @@ async function runDuplicateStep(
|
|
|
398
418
|
|
|
399
419
|
const seenPairs = new Set<string>();
|
|
400
420
|
const flaggedForRemoval = new Set<string>();
|
|
421
|
+
const entityMap = new Map(entities.map((e) => [e.id, e]));
|
|
401
422
|
|
|
423
|
+
// Pre-fetch similarities in concurrent batches
|
|
424
|
+
type SimilarMatch = {
|
|
425
|
+
id: string;
|
|
426
|
+
type: string;
|
|
427
|
+
title: string;
|
|
428
|
+
content: string;
|
|
429
|
+
confidence: number;
|
|
430
|
+
};
|
|
431
|
+
const similarityMap = new Map<string, SimilarMatch[]>();
|
|
432
|
+
for (let i = 0; i < entities.length; i += CONCURRENCY_LIMIT) {
|
|
433
|
+
const batch = entities.slice(i, i + CONCURRENCY_LIMIT);
|
|
434
|
+
const results = await Promise.allSettled(
|
|
435
|
+
batch.map(async (entity) => {
|
|
436
|
+
const similar = await findSimilarEntities(
|
|
437
|
+
client,
|
|
438
|
+
entity.title,
|
|
439
|
+
entity.content,
|
|
440
|
+
workspaceId,
|
|
441
|
+
{ projectId, limit: 5, minRrfScore: 0.05, excludeIds: [entity.id] },
|
|
442
|
+
);
|
|
443
|
+
return { entityId: entity.id, similar };
|
|
444
|
+
}),
|
|
445
|
+
);
|
|
446
|
+
for (const r of results) {
|
|
447
|
+
if (r.status === "fulfilled") {
|
|
448
|
+
similarityMap.set(r.value.entityId, r.value.similar);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Process pairs sequentially (flaggedForRemoval creates dependencies)
|
|
402
454
|
for (const entity of entities) {
|
|
403
455
|
if (flaggedForRemoval.has(entity.id)) continue;
|
|
404
|
-
|
|
405
|
-
let similar: Array<{
|
|
406
|
-
id: string;
|
|
407
|
-
type: string;
|
|
408
|
-
title: string;
|
|
409
|
-
content: string;
|
|
410
|
-
confidence: number;
|
|
411
|
-
}>;
|
|
412
|
-
try {
|
|
413
|
-
similar = await findSimilarEntities(
|
|
414
|
-
client,
|
|
415
|
-
entity.title,
|
|
416
|
-
entity.content,
|
|
417
|
-
workspaceId,
|
|
418
|
-
{ projectId, limit: 5, minRrfScore: 0.05, excludeIds: [entity.id] },
|
|
419
|
-
);
|
|
420
|
-
} catch {
|
|
421
|
-
continue;
|
|
422
|
-
}
|
|
456
|
+
const similar = similarityMap.get(entity.id) || [];
|
|
423
457
|
|
|
424
458
|
for (const match of similar) {
|
|
425
459
|
if (flaggedForRemoval.has(match.id)) continue;
|
|
@@ -429,11 +463,11 @@ async function runDuplicateStep(
|
|
|
429
463
|
seenPairs.add(pairKey);
|
|
430
464
|
|
|
431
465
|
const sim = titleSimilarity(entity.title, match.title);
|
|
432
|
-
if (sim <
|
|
466
|
+
if (sim < DUPLICATE_SIMILARITY_THRESHOLD) continue;
|
|
433
467
|
|
|
434
468
|
// Keep the one with higher confidence, more accesses, or higher tier
|
|
435
469
|
const entityScore = entityQualityScore(entity);
|
|
436
|
-
const matchEntity =
|
|
470
|
+
const matchEntity = entityMap.get(match.id);
|
|
437
471
|
const matchScore = matchEntity
|
|
438
472
|
? entityQualityScore(matchEntity)
|
|
439
473
|
: match.confidence;
|
|
@@ -506,6 +540,12 @@ function generateHealthReport(report: CleanupReport): string {
|
|
|
506
540
|
"",
|
|
507
541
|
];
|
|
508
542
|
|
|
543
|
+
if (report.summary.totalEntities >= MAX_ENTITIES_FETCH) {
|
|
544
|
+
lines.push(
|
|
545
|
+
`> **Note:** Entity count hit the ${MAX_ENTITIES_FETCH} fetch limit. Some entities may not have been analyzed.\n`,
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
|
|
509
549
|
// Prune
|
|
510
550
|
if (report.steps.prune) {
|
|
511
551
|
const p = report.steps.prune;
|
package/src/server.ts
CHANGED
|
@@ -4024,9 +4024,24 @@ async function handleToolCall(
|
|
|
4024
4024
|
const projectId =
|
|
4025
4025
|
(args.projectId as string) || deps.getActiveProjectId() || undefined;
|
|
4026
4026
|
|
|
4027
|
+
const validSteps = [
|
|
4028
|
+
"prune",
|
|
4029
|
+
"consolidate",
|
|
4030
|
+
"orphans",
|
|
4031
|
+
"duplicates",
|
|
4032
|
+
"backfill",
|
|
4033
|
+
];
|
|
4034
|
+
const rawSteps = args.steps as string[] | undefined;
|
|
4035
|
+
const steps = rawSteps?.filter((s) => validSteps.includes(s));
|
|
4036
|
+
if (rawSteps && steps && steps.length < rawSteps.length) {
|
|
4037
|
+
const invalid = rawSteps.filter((s) => !validSteps.includes(s));
|
|
4038
|
+
// Will appear in report.errors via the healthReport
|
|
4039
|
+
console.warn(`Unknown cleanup steps ignored: ${invalid.join(", ")}`);
|
|
4040
|
+
}
|
|
4041
|
+
|
|
4027
4042
|
const report = await runMemoryCleanup(client, workspaceId, projectId, {
|
|
4028
4043
|
dryRun: args.dryRun as boolean | undefined,
|
|
4029
|
-
steps
|
|
4044
|
+
steps,
|
|
4030
4045
|
maxAgeDays: args.maxAgeDays as number | undefined,
|
|
4031
4046
|
minClusterSize: args.minClusterSize as number | undefined,
|
|
4032
4047
|
orphanAgeDays: args.orphanAgeDays as number | undefined,
|