@gethmy/mcp 2.2.3 → 2.3.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/dist/cli.js +780 -352
- package/dist/index.js +744 -351
- package/dist/lib/active-learning.js +73 -129
- package/dist/lib/consolidation.js +71 -11
- package/dist/lib/context-assembly.js +287 -30
- package/dist/lib/memory-cleanup.js +426 -0
- package/dist/lib/prompt-builder.js +5 -1
- package/dist/lib/server.js +63 -0
- package/dist/lib/skills.js +25 -1
- package/dist/lib/tui/setup.js +11 -0
- package/package.json +1 -1
- package/src/active-learning.ts +83 -145
- package/src/consolidation.ts +81 -12
- package/src/context-assembly.ts +342 -30
- package/src/memory-cleanup.ts +616 -0
- package/src/prompt-builder.ts +13 -1
- package/src/server.ts +74 -0
- package/src/skills.ts +25 -1
- package/src/tui/setup.ts +11 -0
package/dist/cli.js
CHANGED
|
@@ -9163,6 +9163,7 @@ __export(exports_context_assembly, {
|
|
|
9163
9163
|
mapToContextEntity: () => mapToContextEntity,
|
|
9164
9164
|
getSessionAssemblyId: () => getSessionAssemblyId,
|
|
9165
9165
|
getCachedManifest: () => getCachedManifest,
|
|
9166
|
+
expandQuery: () => expandQuery,
|
|
9166
9167
|
computeRelevanceScore: () => computeRelevanceScore,
|
|
9167
9168
|
cacheManifest: () => cacheManifest,
|
|
9168
9169
|
assembleContext: () => assembleContext
|
|
@@ -9170,6 +9171,29 @@ __export(exports_context_assembly, {
|
|
|
9170
9171
|
function estimateTokens(text) {
|
|
9171
9172
|
return Math.ceil(text.length / 4);
|
|
9172
9173
|
}
|
|
9174
|
+
function passesQualityGate(entity) {
|
|
9175
|
+
const content = entity.content.trim();
|
|
9176
|
+
if (content.length < 50)
|
|
9177
|
+
return false;
|
|
9178
|
+
const normalizedTitle = entity.title.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim();
|
|
9179
|
+
const normalizedContent = content.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim();
|
|
9180
|
+
if (normalizedContent.length < normalizedTitle.length * 1.5) {
|
|
9181
|
+
return false;
|
|
9182
|
+
}
|
|
9183
|
+
if (entity.type === "pattern" && /recurring .+ \(\d+ instances\)/i.test(entity.title)) {
|
|
9184
|
+
const lines = content.split(`
|
|
9185
|
+
`).filter((l) => l.trim().length > 0);
|
|
9186
|
+
const bulletLines = lines.filter((l) => l.trim().startsWith("- "));
|
|
9187
|
+
if (bulletLines.length > lines.length * 0.6)
|
|
9188
|
+
return false;
|
|
9189
|
+
}
|
|
9190
|
+
if (entity.type === "procedure") {
|
|
9191
|
+
const stepCount = (content.match(/^\d+\.\s/gm) || []).length;
|
|
9192
|
+
if (stepCount < 3)
|
|
9193
|
+
return false;
|
|
9194
|
+
}
|
|
9195
|
+
return true;
|
|
9196
|
+
}
|
|
9173
9197
|
function generateAssemblyId() {
|
|
9174
9198
|
return `ctx_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
9175
9199
|
}
|
|
@@ -9201,7 +9225,45 @@ function truncateContent(content, maxTokens) {
|
|
|
9201
9225
|
}
|
|
9202
9226
|
return { text: result, truncated: true };
|
|
9203
9227
|
}
|
|
9204
|
-
function
|
|
9228
|
+
function escapeRegex2(str) {
|
|
9229
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
9230
|
+
}
|
|
9231
|
+
function expandQuery(taskContext) {
|
|
9232
|
+
const queries = [taskContext];
|
|
9233
|
+
const lowerQueries = [taskContext.toLowerCase()];
|
|
9234
|
+
const words = taskContext.toLowerCase().split(/\W+/).filter((w) => w.length > 2);
|
|
9235
|
+
const expandableWords = words.filter((w) => QUERY_SYNONYMS[w]);
|
|
9236
|
+
for (const word of expandableWords) {
|
|
9237
|
+
const synonyms = QUERY_SYNONYMS[word];
|
|
9238
|
+
if (!synonyms)
|
|
9239
|
+
continue;
|
|
9240
|
+
const variation = taskContext.replace(new RegExp(`\\b${escapeRegex2(word)}\\b`, "gi"), synonyms[0]);
|
|
9241
|
+
const lowerVariation = variation.toLowerCase();
|
|
9242
|
+
if (lowerVariation !== taskContext.toLowerCase() && !lowerQueries.includes(lowerVariation)) {
|
|
9243
|
+
queries.push(variation);
|
|
9244
|
+
lowerQueries.push(lowerVariation);
|
|
9245
|
+
}
|
|
9246
|
+
if (queries.length >= MAX_QUERY_VARIATIONS)
|
|
9247
|
+
break;
|
|
9248
|
+
}
|
|
9249
|
+
if (words.length >= 3) {
|
|
9250
|
+
const keyPhrases = words.filter((w) => ![
|
|
9251
|
+
"the",
|
|
9252
|
+
"and",
|
|
9253
|
+
"for",
|
|
9254
|
+
"with",
|
|
9255
|
+
"this",
|
|
9256
|
+
"that",
|
|
9257
|
+
"from",
|
|
9258
|
+
"into"
|
|
9259
|
+
].includes(w)).slice(0, 4).join(" ");
|
|
9260
|
+
if (!lowerQueries.includes(keyPhrases)) {
|
|
9261
|
+
queries.push(keyPhrases);
|
|
9262
|
+
}
|
|
9263
|
+
}
|
|
9264
|
+
return queries.slice(0, MAX_QUERY_VARIATIONS);
|
|
9265
|
+
}
|
|
9266
|
+
function computeRelevanceScore(entity, taskContext, cardLabels, graphRelations) {
|
|
9205
9267
|
const reasons = [];
|
|
9206
9268
|
let score = 0;
|
|
9207
9269
|
const hasRrfScore = entity.rrf_score !== undefined && entity.rrf_score > 0;
|
|
@@ -9260,7 +9322,23 @@ function computeRelevanceScore(entity, taskContext, cardLabels) {
|
|
|
9260
9322
|
score += 0.1;
|
|
9261
9323
|
reasons.push("procedure_boost");
|
|
9262
9324
|
}
|
|
9263
|
-
|
|
9325
|
+
if (graphRelations && graphRelations.length > 0) {
|
|
9326
|
+
const entityRelations = graphRelations.filter((r) => r.source_id === entity.id || r.target_id === entity.id);
|
|
9327
|
+
if (entityRelations.length > 0) {
|
|
9328
|
+
let bestBonus = 0;
|
|
9329
|
+
let bestRelType = "";
|
|
9330
|
+
for (const rel of entityRelations) {
|
|
9331
|
+
const bonus = RELATION_BONUSES[rel.relation_type] ?? 0.1;
|
|
9332
|
+
if (bonus > bestBonus) {
|
|
9333
|
+
bestBonus = bonus;
|
|
9334
|
+
bestRelType = rel.relation_type;
|
|
9335
|
+
}
|
|
9336
|
+
}
|
|
9337
|
+
score += bestBonus;
|
|
9338
|
+
reasons.push(`graph_walk(${bestRelType})`);
|
|
9339
|
+
}
|
|
9340
|
+
}
|
|
9341
|
+
score = Math.max(0, Math.min(score, 1));
|
|
9264
9342
|
const tierWeight = TIER_WEIGHTS[entity.memory_tier];
|
|
9265
9343
|
score *= tierWeight;
|
|
9266
9344
|
return { score, reasons };
|
|
@@ -9272,7 +9350,11 @@ async function assembleContext(options) {
|
|
|
9272
9350
|
taskContext,
|
|
9273
9351
|
cardLabels = [],
|
|
9274
9352
|
tokenBudget = DEFAULT_TOKEN_BUDGET,
|
|
9275
|
-
client: client2
|
|
9353
|
+
client: client2,
|
|
9354
|
+
graphWalkEnabled = true,
|
|
9355
|
+
queryExpansionEnabled = true,
|
|
9356
|
+
enableLlmReranking = false,
|
|
9357
|
+
rerankFn
|
|
9276
9358
|
} = options;
|
|
9277
9359
|
const assemblyId = generateAssemblyId();
|
|
9278
9360
|
const manifest = {
|
|
@@ -9288,13 +9370,26 @@ async function assembleContext(options) {
|
|
|
9288
9370
|
reference: { count: 0, tokens: 0 }
|
|
9289
9371
|
}
|
|
9290
9372
|
};
|
|
9291
|
-
|
|
9292
|
-
|
|
9293
|
-
|
|
9294
|
-
|
|
9295
|
-
|
|
9373
|
+
const candidates = [];
|
|
9374
|
+
const queries = queryExpansionEnabled ? expandQuery(taskContext) : [taskContext];
|
|
9375
|
+
const searchResults = await Promise.allSettled(queries.map((query) => client2.searchMemoryEntities(workspaceId, query, {
|
|
9376
|
+
project_id: projectId,
|
|
9377
|
+
limit: 30
|
|
9378
|
+
})));
|
|
9379
|
+
const candidateIds = new Set;
|
|
9380
|
+
for (const result of searchResults) {
|
|
9381
|
+
if (result.status !== "fulfilled")
|
|
9382
|
+
continue;
|
|
9383
|
+
if (result.value.entities?.length > 0) {
|
|
9384
|
+
for (const raw of result.value.entities) {
|
|
9385
|
+
const entity = mapToContextEntity(raw);
|
|
9386
|
+
if (!candidateIds.has(entity.id)) {
|
|
9387
|
+
candidateIds.add(entity.id);
|
|
9388
|
+
candidates.push(entity);
|
|
9389
|
+
}
|
|
9390
|
+
}
|
|
9296
9391
|
}
|
|
9297
|
-
}
|
|
9392
|
+
}
|
|
9298
9393
|
if (candidates.length < 10 && projectId) {
|
|
9299
9394
|
try {
|
|
9300
9395
|
const listResult = await client2.listMemoryEntities({
|
|
@@ -9303,9 +9398,13 @@ async function assembleContext(options) {
|
|
|
9303
9398
|
limit: 30
|
|
9304
9399
|
});
|
|
9305
9400
|
if (listResult.entities?.length > 0) {
|
|
9306
|
-
const
|
|
9307
|
-
|
|
9308
|
-
|
|
9401
|
+
for (const raw of listResult.entities) {
|
|
9402
|
+
const entity = mapToContextEntity(raw);
|
|
9403
|
+
if (!candidateIds.has(entity.id)) {
|
|
9404
|
+
candidateIds.add(entity.id);
|
|
9405
|
+
candidates.push(entity);
|
|
9406
|
+
}
|
|
9407
|
+
}
|
|
9309
9408
|
}
|
|
9310
9409
|
} catch {}
|
|
9311
9410
|
}
|
|
@@ -9317,9 +9416,33 @@ async function assembleContext(options) {
|
|
|
9317
9416
|
limit: 20
|
|
9318
9417
|
});
|
|
9319
9418
|
if (wsResult.entities?.length > 0) {
|
|
9320
|
-
const
|
|
9321
|
-
|
|
9322
|
-
|
|
9419
|
+
for (const raw of wsResult.entities) {
|
|
9420
|
+
const entity = mapToContextEntity(raw);
|
|
9421
|
+
if (!candidateIds.has(entity.id)) {
|
|
9422
|
+
candidateIds.add(entity.id);
|
|
9423
|
+
candidates.push(entity);
|
|
9424
|
+
}
|
|
9425
|
+
}
|
|
9426
|
+
}
|
|
9427
|
+
} catch {}
|
|
9428
|
+
}
|
|
9429
|
+
let graphRelations = [];
|
|
9430
|
+
if (graphWalkEnabled && candidates.length > 0) {
|
|
9431
|
+
try {
|
|
9432
|
+
const seedCandidates = [...candidates].sort((a, b) => (b.rrf_score ?? 0) - (a.rrf_score ?? 0)).slice(0, GRAPH_WALK_SEED_COUNT);
|
|
9433
|
+
const seedIds = seedCandidates.map((c) => c.id);
|
|
9434
|
+
const walkResult = await discoverRelatedContext(client2, seedIds, GRAPH_WALK_MAX_DEPTH, GRAPH_WALK_MAX_ENTITIES, GRAPH_WALK_MIN_CONFIDENCE);
|
|
9435
|
+
graphRelations = walkResult.relations;
|
|
9436
|
+
const newEntityIds = walkResult.entities.filter((e) => !candidateIds.has(e.id)).map((e) => e.id);
|
|
9437
|
+
if (newEntityIds.length > 0) {
|
|
9438
|
+
const fetchResults = await Promise.allSettled(newEntityIds.map((id) => client2.getMemoryEntity(id)));
|
|
9439
|
+
for (const result of fetchResults) {
|
|
9440
|
+
if (result.status !== "fulfilled" || !result.value.entity)
|
|
9441
|
+
continue;
|
|
9442
|
+
const mapped = mapToContextEntity(result.value.entity);
|
|
9443
|
+
candidateIds.add(mapped.id);
|
|
9444
|
+
candidates.push(mapped);
|
|
9445
|
+
}
|
|
9323
9446
|
}
|
|
9324
9447
|
} catch {}
|
|
9325
9448
|
}
|
|
@@ -9330,11 +9453,52 @@ async function assembleContext(options) {
|
|
|
9330
9453
|
memories: []
|
|
9331
9454
|
};
|
|
9332
9455
|
}
|
|
9333
|
-
const
|
|
9334
|
-
|
|
9456
|
+
const qualityCandidates = candidates.filter((entity) => {
|
|
9457
|
+
if (passesQualityGate(entity))
|
|
9458
|
+
return true;
|
|
9459
|
+
manifest.excluded.push({
|
|
9460
|
+
entityId: entity.id,
|
|
9461
|
+
title: entity.title,
|
|
9462
|
+
type: entity.type,
|
|
9463
|
+
tier: entity.memory_tier,
|
|
9464
|
+
relevanceScore: 0,
|
|
9465
|
+
reason: "failed_quality_gate"
|
|
9466
|
+
});
|
|
9467
|
+
return false;
|
|
9468
|
+
});
|
|
9469
|
+
if (qualityCandidates.length === 0) {
|
|
9470
|
+
return {
|
|
9471
|
+
context: "",
|
|
9472
|
+
manifest,
|
|
9473
|
+
memories: []
|
|
9474
|
+
};
|
|
9475
|
+
}
|
|
9476
|
+
const scored = qualityCandidates.map((entity) => {
|
|
9477
|
+
const { score, reasons } = computeRelevanceScore(entity, taskContext, cardLabels, graphRelations.length > 0 ? graphRelations : undefined);
|
|
9335
9478
|
return { entity, score, reasons };
|
|
9336
9479
|
});
|
|
9337
9480
|
scored.sort((a, b) => b.score - a.score);
|
|
9481
|
+
if (enableLlmReranking && rerankFn && scored.length >= RERANK_MIN_CANDIDATES) {
|
|
9482
|
+
const topN = scored.slice(0, RERANK_TOP_N);
|
|
9483
|
+
const scoreRange = topN[0].score - topN[topN.length - 1].score;
|
|
9484
|
+
if (scoreRange <= RERANK_CLUSTER_THRESHOLD) {
|
|
9485
|
+
try {
|
|
9486
|
+
const rerankCandidates = topN.map((s) => ({
|
|
9487
|
+
id: s.entity.id,
|
|
9488
|
+
title: s.entity.title,
|
|
9489
|
+
snippet: s.entity.content.slice(0, 200)
|
|
9490
|
+
}));
|
|
9491
|
+
const rerankedIds = await rerankFn(taskContext, rerankCandidates);
|
|
9492
|
+
const idOrder = new Map(rerankedIds.map((id, i) => [id, i]));
|
|
9493
|
+
topN.sort((a, b) => {
|
|
9494
|
+
const aIdx = idOrder.get(a.entity.id) ?? 999;
|
|
9495
|
+
const bIdx = idOrder.get(b.entity.id) ?? 999;
|
|
9496
|
+
return aIdx - bIdx;
|
|
9497
|
+
});
|
|
9498
|
+
scored.splice(0, topN.length, ...topN);
|
|
9499
|
+
} catch {}
|
|
9500
|
+
}
|
|
9501
|
+
}
|
|
9338
9502
|
const procedureBudget = Math.floor(tokenBudget * PROCEDURE_BUDGET_FRACTION);
|
|
9339
9503
|
const remainingBudget = tokenBudget - procedureBudget;
|
|
9340
9504
|
const tierBudgets = {
|
|
@@ -9646,7 +9810,7 @@ async function recordContextFeedback(client2, cardId, sessionStatus, progressPer
|
|
|
9646
9810
|
sessionAssemblyMap.delete(cardId);
|
|
9647
9811
|
return { adjusted };
|
|
9648
9812
|
}
|
|
9649
|
-
var DEFAULT_TOKEN_BUDGET = 4000, MAX_TOKENS_PER_ENTITY = 500, MIN_RELEVANCE_THRESHOLD = 0.
|
|
9813
|
+
var DEFAULT_TOKEN_BUDGET = 4000, MAX_TOKENS_PER_ENTITY = 500, MIN_RELEVANCE_THRESHOLD = 0.15, TIER_WEIGHTS, PROCEDURE_BUDGET_FRACTION = 0.15, TIER_BUDGET_ALLOCATION, MIN_REFERENCE_SLOTS = 1, GRAPH_WALK_MAX_DEPTH = 1, GRAPH_WALK_MAX_ENTITIES = 10, GRAPH_WALK_MIN_CONFIDENCE = 0.5, GRAPH_WALK_SEED_COUNT = 5, MAX_QUERY_VARIATIONS = 4, RERANK_CLUSTER_THRESHOLD = 0.05, RERANK_TOP_N = 10, RERANK_MIN_CANDIDATES = 5, RELATION_BONUSES, QUERY_SYNONYMS, manifestCache, MAX_CACHE_SIZE = 50, sessionAssemblyMap, MAX_SESSION_MAP_SIZE = 100;
|
|
9650
9814
|
var init_context_assembly = __esm(() => {
|
|
9651
9815
|
init_dist();
|
|
9652
9816
|
TIER_WEIGHTS = {
|
|
@@ -9659,6 +9823,33 @@ var init_context_assembly = __esm(() => {
|
|
|
9659
9823
|
episode: 0.3,
|
|
9660
9824
|
draft: 0.1
|
|
9661
9825
|
};
|
|
9826
|
+
RELATION_BONUSES = {
|
|
9827
|
+
depends_on: 0.15,
|
|
9828
|
+
resolved_by: 0.2,
|
|
9829
|
+
relates_to: 0.1,
|
|
9830
|
+
implements: 0.15,
|
|
9831
|
+
blocks: 0.15,
|
|
9832
|
+
references: 0.1,
|
|
9833
|
+
extends: 0.1,
|
|
9834
|
+
caused_by: 0.15
|
|
9835
|
+
};
|
|
9836
|
+
QUERY_SYNONYMS = {
|
|
9837
|
+
auth: ["authentication", "authorization", "session"],
|
|
9838
|
+
authentication: ["auth", "session", "sign-in"],
|
|
9839
|
+
login: ["sign-in", "authentication", "session"],
|
|
9840
|
+
bug: ["error", "issue", "defect", "problem"],
|
|
9841
|
+
error: ["exception", "failure", "issue"],
|
|
9842
|
+
fix: ["resolve", "patch", "repair", "correct"],
|
|
9843
|
+
deploy: ["deployment", "release", "ship", "publish"],
|
|
9844
|
+
test: ["testing", "spec", "assertion", "verify"],
|
|
9845
|
+
config: ["configuration", "settings", "setup"],
|
|
9846
|
+
db: ["database", "storage", "persistence"],
|
|
9847
|
+
database: ["storage", "persistence", "data store"],
|
|
9848
|
+
api: ["endpoint", "route", "service"],
|
|
9849
|
+
ui: ["frontend", "component", "view"],
|
|
9850
|
+
perf: ["performance", "speed", "latency"],
|
|
9851
|
+
performance: ["speed", "latency", "optimization"]
|
|
9852
|
+
};
|
|
9662
9853
|
manifestCache = new Map;
|
|
9663
9854
|
sessionAssemblyMap = new Map;
|
|
9664
9855
|
});
|
|
@@ -9787,7 +9978,11 @@ ${card.description}`);
|
|
|
9787
9978
|
roleFraming.focus.forEach((f) => {
|
|
9788
9979
|
sections.push(`- ${f}`);
|
|
9789
9980
|
});
|
|
9790
|
-
sections.push(`- **Memory:**
|
|
9981
|
+
sections.push(`- **Memory:** Store reusable knowledge via \`harmony_remember\`. Only store what a future agent couldn't easily discover from the code itself, applies beyond this specific card, and includes a "because" (not just what, but why).`);
|
|
9982
|
+
sections.push(` - GOOD: "BoardContext card state must use moveCard action, never direct setState — optimistic updates depend on action ordering"`);
|
|
9983
|
+
sections.push(` - GOOD: "Mobile bottom bar is 64px, overlaps fixed-position drawers — always add pb-16 to drawer content"`);
|
|
9984
|
+
sections.push(` - BAD: "Fixed the login button" (no reusable knowledge — the fix is in the code)`);
|
|
9985
|
+
sections.push(` - BAD: "Completed card #42" (ephemeral, auto-tracked by session)`);
|
|
9791
9986
|
sections.push(`
|
|
9792
9987
|
## Suggested Outputs`);
|
|
9793
9988
|
roleFraming.outputSuggestions.forEach((s) => {
|
|
@@ -26383,13 +26578,13 @@ function levenshteinSimilarity(a, b) {
|
|
|
26383
26578
|
const maxLen = Math.max(sa.length, sb.length);
|
|
26384
26579
|
return 1 - matrix[sa.length][sb.length] / maxLen;
|
|
26385
26580
|
}
|
|
26386
|
-
async function extractMidSessionLearnings(
|
|
26581
|
+
async function extractMidSessionLearnings(_client, ctx) {
|
|
26387
26582
|
const workspaceId = getActiveWorkspaceId();
|
|
26388
26583
|
if (!workspaceId)
|
|
26389
26584
|
return { count: 0, entityIds: [] };
|
|
26390
|
-
const
|
|
26585
|
+
const _projectId = getActiveProjectId() || undefined;
|
|
26391
26586
|
const now = Date.now();
|
|
26392
|
-
const
|
|
26587
|
+
const _entityIds = [];
|
|
26393
26588
|
const history = sessionTaskHistory.get(ctx.cardId);
|
|
26394
26589
|
if (ctx.currentTask) {
|
|
26395
26590
|
const previousTask = history?.lastTask || "";
|
|
@@ -26422,81 +26617,22 @@ async function extractMidSessionLearnings(client2, ctx) {
|
|
|
26422
26617
|
}
|
|
26423
26618
|
}
|
|
26424
26619
|
if (ctx.status === "blocked" && ctx.blockers?.length) {
|
|
26425
|
-
for (const blocker of ctx.blockers) {
|
|
26426
|
-
try {
|
|
26427
|
-
const result = await client2.createMemoryEntity({
|
|
26428
|
-
workspace_id: workspaceId,
|
|
26429
|
-
project_id: projectId,
|
|
26430
|
-
type: "error",
|
|
26431
|
-
scope: "project",
|
|
26432
|
-
memory_tier: "draft",
|
|
26433
|
-
title: `Blocker (mid-session): ${blocker.slice(0, 100)}`,
|
|
26434
|
-
content: `Encountered while working on "${ctx.cardTitle}":
|
|
26435
|
-
|
|
26436
|
-
${blocker}
|
|
26437
|
-
|
|
26438
|
-
Agent: ${ctx.agentName}
|
|
26439
|
-
Progress: ${ctx.progressPercent ?? "unknown"}%`,
|
|
26440
|
-
confidence: 0.5,
|
|
26441
|
-
tags: ["auto-extracted", "blocker", "mid-session"],
|
|
26442
|
-
metadata: {
|
|
26443
|
-
source: "mid_session",
|
|
26444
|
-
card_id: ctx.cardId
|
|
26445
|
-
},
|
|
26446
|
-
agent_identifier: ctx.agentIdentifier
|
|
26447
|
-
});
|
|
26448
|
-
const entity = result.entity;
|
|
26449
|
-
if (entity?.id)
|
|
26450
|
-
entityIds.push(entity.id);
|
|
26451
|
-
} catch {}
|
|
26452
|
-
}
|
|
26453
26620
|
sessionTaskHistory.set(ctx.cardId, {
|
|
26454
26621
|
lastTask: ctx.currentTask || "",
|
|
26455
26622
|
lastExtractionAt: now,
|
|
26456
26623
|
steps: history?.steps || []
|
|
26457
26624
|
});
|
|
26458
|
-
return { count:
|
|
26625
|
+
return { count: 0, entityIds: [] };
|
|
26459
26626
|
}
|
|
26460
26627
|
if (ctx.currentTask) {
|
|
26461
|
-
const previousTask = history?.lastTask || "";
|
|
26462
|
-
const similarity = levenshteinSimilarity(previousTask, ctx.currentTask);
|
|
26463
|
-
if (similarity < 0.6 && previousTask.length > 0) {
|
|
26464
|
-
try {
|
|
26465
|
-
const result = await client2.createMemoryEntity({
|
|
26466
|
-
workspace_id: workspaceId,
|
|
26467
|
-
project_id: projectId,
|
|
26468
|
-
type: "context",
|
|
26469
|
-
scope: "project",
|
|
26470
|
-
memory_tier: "draft",
|
|
26471
|
-
title: `Task transition: ${ctx.cardTitle}`,
|
|
26472
|
-
content: `Agent transitioned tasks on "${ctx.cardTitle}".
|
|
26473
|
-
|
|
26474
|
-
Previous: ${previousTask}
|
|
26475
|
-
Current: ${ctx.currentTask}
|
|
26476
|
-
Progress: ${ctx.progressPercent ?? "unknown"}%`,
|
|
26477
|
-
confidence: 0.5,
|
|
26478
|
-
tags: ["auto-extracted", "task-transition", "mid-session"],
|
|
26479
|
-
metadata: {
|
|
26480
|
-
source: "mid_session",
|
|
26481
|
-
card_id: ctx.cardId,
|
|
26482
|
-
previous_task: previousTask,
|
|
26483
|
-
current_task: ctx.currentTask
|
|
26484
|
-
},
|
|
26485
|
-
agent_identifier: ctx.agentIdentifier
|
|
26486
|
-
});
|
|
26487
|
-
const entity = result.entity;
|
|
26488
|
-
if (entity?.id)
|
|
26489
|
-
entityIds.push(entity.id);
|
|
26490
|
-
} catch {}
|
|
26491
|
-
}
|
|
26492
26628
|
const currentHistory = sessionTaskHistory.get(ctx.cardId);
|
|
26493
26629
|
sessionTaskHistory.set(ctx.cardId, {
|
|
26494
26630
|
lastTask: ctx.currentTask,
|
|
26495
|
-
lastExtractionAt:
|
|
26631
|
+
lastExtractionAt: currentHistory?.lastExtractionAt ?? 0,
|
|
26496
26632
|
steps: currentHistory?.steps || []
|
|
26497
26633
|
});
|
|
26498
26634
|
}
|
|
26499
|
-
return { count:
|
|
26635
|
+
return { count: 0, entityIds: [] };
|
|
26500
26636
|
}
|
|
26501
26637
|
function clearMidSessionTracking(cardId) {
|
|
26502
26638
|
sessionTaskHistory.delete(cardId);
|
|
@@ -26680,49 +26816,60 @@ async function extractLearnings(client2, session) {
|
|
|
26680
26816
|
Related: ${relatedEntityTitles.map((t) => `[[${t}]]`).join(", ")}` : "";
|
|
26681
26817
|
if (session.blockers && session.blockers.length > 0) {
|
|
26682
26818
|
for (const blocker of session.blockers) {
|
|
26683
|
-
|
|
26684
|
-
|
|
26685
|
-
|
|
26819
|
+
if (blocker.length < 80)
|
|
26820
|
+
continue;
|
|
26821
|
+
let isDuplicate = false;
|
|
26822
|
+
try {
|
|
26823
|
+
const similar = await findSimilarEntities(client2, blocker.slice(0, 200), blocker, workspaceId, { projectId, limit: 3, minRrfScore: 0.05 });
|
|
26824
|
+
isDuplicate = similar.some((e) => e.type === "error" && (e.rrf_score ?? 0) >= 0.06);
|
|
26825
|
+
} catch {}
|
|
26826
|
+
if (!isDuplicate) {
|
|
26827
|
+
learnings.push({
|
|
26828
|
+
title: `Blocker: ${blocker.slice(0, 100)}`,
|
|
26829
|
+
content: `Encountered while working on "${session.cardTitle}":
|
|
26686
26830
|
|
|
26687
26831
|
${blocker}
|
|
26688
26832
|
|
|
26689
26833
|
Agent: ${session.agentName}
|
|
26690
26834
|
Session status: ${session.status}`,
|
|
26691
|
-
|
|
26692
|
-
|
|
26693
|
-
|
|
26694
|
-
|
|
26695
|
-
|
|
26696
|
-
|
|
26697
|
-
|
|
26698
|
-
|
|
26699
|
-
|
|
26835
|
+
type: "error",
|
|
26836
|
+
tier: "episode",
|
|
26837
|
+
confidence: 0.6,
|
|
26838
|
+
tags: [
|
|
26839
|
+
"auto-extracted",
|
|
26840
|
+
"blocker",
|
|
26841
|
+
...session.cardLabels.slice(0, 3)
|
|
26842
|
+
],
|
|
26843
|
+
metadata: {
|
|
26844
|
+
source: "active_learning",
|
|
26845
|
+
card_id: session.cardId
|
|
26846
|
+
}
|
|
26847
|
+
});
|
|
26848
|
+
}
|
|
26700
26849
|
}
|
|
26701
26850
|
}
|
|
26702
|
-
|
|
26703
|
-
if (session.status === "completed" && hasMeaningfulContent) {
|
|
26851
|
+
if (session.status === "paused" && (session.blockers?.length ?? 0) > 0) {
|
|
26704
26852
|
const durationInfo = session.sessionDurationMs ? `
|
|
26705
26853
|
Duration: ${Math.round(session.sessionDurationMs / 60000)} minutes` : "";
|
|
26706
26854
|
learnings.push({
|
|
26707
|
-
title: `
|
|
26855
|
+
title: `Paused: ${session.cardTitle}`,
|
|
26708
26856
|
content: [
|
|
26709
|
-
`
|
|
26710
|
-
session.currentTask ? `
|
|
26857
|
+
`Paused work on "${session.cardTitle}".`,
|
|
26858
|
+
session.currentTask ? `Last task: ${session.currentTask}` : "",
|
|
26711
26859
|
session.progressPercent !== undefined ? `Progress: ${session.progressPercent}%` : "",
|
|
26712
26860
|
durationInfo,
|
|
26713
|
-
session.
|
|
26714
|
-
session.blockers?.length ? `Blockers encountered: ${session.blockers.join("; ")}` : "",
|
|
26861
|
+
session.blockers?.length ? `Blockers: ${session.blockers.join("; ")}` : "",
|
|
26715
26862
|
`
|
|
26716
26863
|
Agent: ${session.agentName}`,
|
|
26717
26864
|
wikiLinksLine
|
|
26718
26865
|
].filter(Boolean).join(`
|
|
26719
26866
|
`),
|
|
26720
26867
|
type: "lesson",
|
|
26721
|
-
tier: "
|
|
26722
|
-
confidence: 0.
|
|
26868
|
+
tier: "draft",
|
|
26869
|
+
confidence: 0.6,
|
|
26723
26870
|
tags: [
|
|
26724
26871
|
"auto-extracted",
|
|
26725
|
-
"session-
|
|
26872
|
+
"session-paused",
|
|
26726
26873
|
...session.cardLabels.slice(0, 3)
|
|
26727
26874
|
],
|
|
26728
26875
|
metadata: {
|
|
@@ -26731,35 +26878,14 @@ Agent: ${session.agentName}`,
|
|
|
26731
26878
|
}
|
|
26732
26879
|
});
|
|
26733
26880
|
}
|
|
26734
|
-
const hasBugLabel = session.cardLabels.some((l) => ["bug", "fix", "hotfix", "defect", "error"].includes(l.toLowerCase()));
|
|
26735
|
-
if (hasBugLabel && session.status === "completed") {
|
|
26736
|
-
learnings.push({
|
|
26737
|
-
title: `Solution: ${session.cardTitle}`,
|
|
26738
|
-
content: [
|
|
26739
|
-
`Resolved bug: "${session.cardTitle}"`,
|
|
26740
|
-
session.currentTask ? `
|
|
26741
|
-
Approach: ${session.currentTask}` : "",
|
|
26742
|
-
`
|
|
26743
|
-
Agent: ${session.agentName}`,
|
|
26744
|
-
wikiLinksLine
|
|
26745
|
-
].filter(Boolean).join(`
|
|
26746
|
-
`),
|
|
26747
|
-
type: "solution",
|
|
26748
|
-
tier: "reference",
|
|
26749
|
-
confidence: 0.8,
|
|
26750
|
-
tags: ["auto-extracted", "bug-fix", ...session.cardLabels.slice(0, 3)],
|
|
26751
|
-
metadata: {
|
|
26752
|
-
source: "active_learning",
|
|
26753
|
-
card_id: session.cardId,
|
|
26754
|
-
auto_confidence: true
|
|
26755
|
-
}
|
|
26756
|
-
});
|
|
26757
|
-
}
|
|
26758
26881
|
const entityIds = [];
|
|
26759
26882
|
const stepHistory = sessionTaskHistory.get(session.cardId);
|
|
26760
|
-
const
|
|
26883
|
+
const MIN_PROCEDURE_STEPS = 5;
|
|
26884
|
+
const MIN_PROCEDURE_DURATION_MS = 10 * 60 * 1000;
|
|
26885
|
+
const hasEnoughSteps = stepHistory && stepHistory.steps.length >= MIN_PROCEDURE_STEPS;
|
|
26886
|
+
const hasMinDuration = (session.sessionDurationMs ?? 0) >= MIN_PROCEDURE_DURATION_MS;
|
|
26761
26887
|
const isSuccessful = session.status === "completed" && (session.progressPercent === undefined || session.progressPercent >= 85) && !session.blockers?.length;
|
|
26762
|
-
if (isSuccessful && hasEnoughSteps) {
|
|
26888
|
+
if (isSuccessful && hasEnoughSteps && hasMinDuration) {
|
|
26763
26889
|
const procedureResult = await extractOrReinforceProcedure(client2, session, stepHistory.steps, workspaceId, projectId, wikiLinksLine);
|
|
26764
26890
|
if (procedureResult) {
|
|
26765
26891
|
if (procedureResult.mode === "created") {
|
|
@@ -26803,218 +26929,9 @@ Agent: ${session.agentName}`,
|
|
|
26803
26929
|
if (createdPairs.length >= 2) {
|
|
26804
26930
|
linkSessionEntities(client2, createdPairs, workspaceId, projectId).catch(() => {});
|
|
26805
26931
|
}
|
|
26806
|
-
if (entityIds.length > 0) {
|
|
26807
|
-
detectAndCreatePatterns(client2, entityIds, session, workspaceId, projectId).catch(() => {});
|
|
26808
|
-
}
|
|
26809
|
-
if (createdPairs.length > 0) {
|
|
26810
|
-
detectCausalPatterns(client2, createdPairs, session, workspaceId, projectId).catch(() => {});
|
|
26811
|
-
}
|
|
26812
26932
|
clearMidSessionTracking(session.cardId);
|
|
26813
26933
|
return { count: entityIds.length, entityIds };
|
|
26814
26934
|
}
|
|
26815
|
-
var PATTERN_THRESHOLD = 3;
|
|
26816
|
-
async function detectAndCreatePatterns(client2, newEntityIds, session, workspaceId, projectId) {
|
|
26817
|
-
const patternEntityIds = [];
|
|
26818
|
-
for (const newEntityId of newEntityIds) {
|
|
26819
|
-
try {
|
|
26820
|
-
const { entity: rawEntity } = await client2.getMemoryEntity(newEntityId);
|
|
26821
|
-
const entity = rawEntity;
|
|
26822
|
-
if (!entity?.type)
|
|
26823
|
-
continue;
|
|
26824
|
-
const similar = await findSimilarEntities(client2, entity.title, entity.content, workspaceId, { projectId, limit: 30, minRrfScore: 0.01 });
|
|
26825
|
-
const existing = similar.filter((c) => !newEntityIds.includes(c.id) && c.type === entity.type);
|
|
26826
|
-
if (existing.length < PATTERN_THRESHOLD)
|
|
26827
|
-
continue;
|
|
26828
|
-
const memberTitles = [
|
|
26829
|
-
entity.title,
|
|
26830
|
-
...existing.slice(0, 4).map((e) => e.title)
|
|
26831
|
-
];
|
|
26832
|
-
const patternTitle = `Pattern: recurring ${entity.type} (${existing.length + 1} instances)`;
|
|
26833
|
-
const { entities: existingPatterns } = await client2.listMemoryEntities({
|
|
26834
|
-
workspace_id: workspaceId,
|
|
26835
|
-
project_id: projectId,
|
|
26836
|
-
type: "pattern",
|
|
26837
|
-
limit: 10
|
|
26838
|
-
});
|
|
26839
|
-
const matchingPattern = existingPatterns.find((p) => p.metadata?.pattern_type === entity.type);
|
|
26840
|
-
let patternId = null;
|
|
26841
|
-
if (matchingPattern) {
|
|
26842
|
-
patternId = matchingPattern.id;
|
|
26843
|
-
await client2.updateMemoryEntity(patternId, {
|
|
26844
|
-
content: `Recurring pattern: ${entity.type} entities appearing ${existing.length + 1} times.
|
|
26845
|
-
|
|
26846
|
-
Members:
|
|
26847
|
-
${memberTitles.map((t) => `- ${t}`).join(`
|
|
26848
|
-
`)}
|
|
26849
|
-
|
|
26850
|
-
Last updated: ${new Date().toISOString()}`,
|
|
26851
|
-
metadata: {
|
|
26852
|
-
pattern_count: existing.length + 1,
|
|
26853
|
-
pattern_type: entity.type,
|
|
26854
|
-
last_updated: new Date().toISOString()
|
|
26855
|
-
}
|
|
26856
|
-
});
|
|
26857
|
-
} else {
|
|
26858
|
-
const result = await client2.createMemoryEntity({
|
|
26859
|
-
workspace_id: workspaceId,
|
|
26860
|
-
project_id: projectId,
|
|
26861
|
-
type: "pattern",
|
|
26862
|
-
scope: "project",
|
|
26863
|
-
memory_tier: "reference",
|
|
26864
|
-
title: patternTitle,
|
|
26865
|
-
content: `Recurring pattern: ${entity.type} entities detected ${existing.length + 1} times.
|
|
26866
|
-
|
|
26867
|
-
Members:
|
|
26868
|
-
${memberTitles.map((t) => `- ${t}`).join(`
|
|
26869
|
-
`)}`,
|
|
26870
|
-
confidence: 0.75,
|
|
26871
|
-
tags: ["auto-extracted", "pattern", entity.type],
|
|
26872
|
-
metadata: {
|
|
26873
|
-
source: "pattern_detection",
|
|
26874
|
-
pattern_type: entity.type,
|
|
26875
|
-
pattern_count: existing.length + 1
|
|
26876
|
-
},
|
|
26877
|
-
agent_identifier: session.agentIdentifier
|
|
26878
|
-
});
|
|
26879
|
-
const created = result.entity;
|
|
26880
|
-
if (created?.id) {
|
|
26881
|
-
patternId = created.id;
|
|
26882
|
-
patternEntityIds.push(patternId);
|
|
26883
|
-
}
|
|
26884
|
-
}
|
|
26885
|
-
if (!patternId)
|
|
26886
|
-
continue;
|
|
26887
|
-
const toLink = [newEntityId, ...existing.slice(0, 4).map((e) => e.id)];
|
|
26888
|
-
for (const sourceId of toLink) {
|
|
26889
|
-
try {
|
|
26890
|
-
await client2.createMemoryRelation({
|
|
26891
|
-
source_id: sourceId,
|
|
26892
|
-
target_id: patternId,
|
|
26893
|
-
relation_type: "part_of",
|
|
26894
|
-
confidence: 0.75
|
|
26895
|
-
});
|
|
26896
|
-
} catch {}
|
|
26897
|
-
}
|
|
26898
|
-
} catch {}
|
|
26899
|
-
}
|
|
26900
|
-
return patternEntityIds;
|
|
26901
|
-
}
|
|
26902
|
-
var CAUSAL_PATTERN_THRESHOLD = 3;
|
|
26903
|
-
async function detectCausalPatterns(client2, createdPairs, session, workspaceId, projectId) {
|
|
26904
|
-
const patternIds = [];
|
|
26905
|
-
const errors3 = createdPairs.filter((p) => p.learning.type === "error");
|
|
26906
|
-
const solutions = createdPairs.filter((p) => p.learning.type === "solution");
|
|
26907
|
-
if (errors3.length === 0 || solutions.length === 0)
|
|
26908
|
-
return patternIds;
|
|
26909
|
-
for (const errorPair of errors3) {
|
|
26910
|
-
try {
|
|
26911
|
-
const similarErrors = await findSimilarEntities(client2, errorPair.learning.title, errorPair.learning.content, workspaceId, {
|
|
26912
|
-
projectId,
|
|
26913
|
-
limit: 20,
|
|
26914
|
-
minRrfScore: 0.03,
|
|
26915
|
-
excludeIds: createdPairs.map((p) => p.id),
|
|
26916
|
-
type: "error"
|
|
26917
|
-
});
|
|
26918
|
-
const resolvedErrors = [];
|
|
26919
|
-
for (const similar of similarErrors.slice(0, 10)) {
|
|
26920
|
-
try {
|
|
26921
|
-
const { outgoing } = await client2.getRelatedEntities(similar.id);
|
|
26922
|
-
const resolvedByRel = outgoing.find((r) => r.relation_type === "resolved_by");
|
|
26923
|
-
if (resolvedByRel) {
|
|
26924
|
-
resolvedErrors.push({
|
|
26925
|
-
errorId: similar.id,
|
|
26926
|
-
errorTitle: similar.title,
|
|
26927
|
-
solutionTitle: resolvedByRel.target_title || "unknown"
|
|
26928
|
-
});
|
|
26929
|
-
}
|
|
26930
|
-
} catch {}
|
|
26931
|
-
}
|
|
26932
|
-
if (resolvedErrors.length + 1 < CAUSAL_PATTERN_THRESHOLD)
|
|
26933
|
-
continue;
|
|
26934
|
-
const { entities: existingPatterns } = await client2.listMemoryEntities({
|
|
26935
|
-
workspace_id: workspaceId,
|
|
26936
|
-
project_id: projectId,
|
|
26937
|
-
type: "pattern",
|
|
26938
|
-
limit: 10
|
|
26939
|
-
});
|
|
26940
|
-
const matchingPattern = existingPatterns.find((p) => p.metadata?.pattern_chain_type === "error_resolved_by_solution");
|
|
26941
|
-
if (matchingPattern) {
|
|
26942
|
-
await client2.updateMemoryEntity(matchingPattern.id, {
|
|
26943
|
-
content: [
|
|
26944
|
-
`Recurring error→solution chain detected (${resolvedErrors.length + 1} instances).`,
|
|
26945
|
-
"",
|
|
26946
|
-
"## Error→Solution Pairs",
|
|
26947
|
-
`- ${errorPair.learning.title} → ${solutions[0].learning.title}`,
|
|
26948
|
-
...resolvedErrors.slice(0, 5).map((r) => `- ${r.errorTitle} → ${r.solutionTitle}`),
|
|
26949
|
-
"",
|
|
26950
|
-
`Last updated: ${new Date().toISOString()}`
|
|
26951
|
-
].join(`
|
|
26952
|
-
`),
|
|
26953
|
-
metadata: {
|
|
26954
|
-
pattern_chain_type: "error_resolved_by_solution",
|
|
26955
|
-
pattern_count: resolvedErrors.length + 1,
|
|
26956
|
-
last_updated: new Date().toISOString()
|
|
26957
|
-
}
|
|
26958
|
-
});
|
|
26959
|
-
for (const pair of [errorPair, solutions[0]]) {
|
|
26960
|
-
try {
|
|
26961
|
-
await client2.createMemoryRelation({
|
|
26962
|
-
source_id: pair.id,
|
|
26963
|
-
target_id: matchingPattern.id,
|
|
26964
|
-
relation_type: "part_of",
|
|
26965
|
-
confidence: 0.75
|
|
26966
|
-
});
|
|
26967
|
-
} catch {}
|
|
26968
|
-
}
|
|
26969
|
-
} else {
|
|
26970
|
-
const result = await client2.createMemoryEntity({
|
|
26971
|
-
workspace_id: workspaceId,
|
|
26972
|
-
project_id: projectId,
|
|
26973
|
-
type: "pattern",
|
|
26974
|
-
scope: "project",
|
|
26975
|
-
memory_tier: "reference",
|
|
26976
|
-
title: `Pattern: recurring error→solution chain (${resolvedErrors.length + 1} instances)`,
|
|
26977
|
-
content: [
|
|
26978
|
-
`Recurring error→solution chain detected across ${resolvedErrors.length + 1} sessions.`,
|
|
26979
|
-
"",
|
|
26980
|
-
"## Error→Solution Pairs",
|
|
26981
|
-
`- ${errorPair.learning.title} → ${solutions[0].learning.title}`,
|
|
26982
|
-
...resolvedErrors.slice(0, 5).map((r) => `- ${r.errorTitle} → ${r.solutionTitle}`)
|
|
26983
|
-
].join(`
|
|
26984
|
-
`),
|
|
26985
|
-
confidence: 0.8,
|
|
26986
|
-
tags: ["auto-extracted", "pattern", "causal-chain"],
|
|
26987
|
-
metadata: {
|
|
26988
|
-
source: "causal_pattern_detection",
|
|
26989
|
-
pattern_chain_type: "error_resolved_by_solution",
|
|
26990
|
-
pattern_count: resolvedErrors.length + 1
|
|
26991
|
-
},
|
|
26992
|
-
agent_identifier: session.agentIdentifier
|
|
26993
|
-
});
|
|
26994
|
-
const created = result.entity;
|
|
26995
|
-
if (created?.id) {
|
|
26996
|
-
patternIds.push(created.id);
|
|
26997
|
-
const memberIds = [
|
|
26998
|
-
errorPair.id,
|
|
26999
|
-
solutions[0].id,
|
|
27000
|
-
...resolvedErrors.slice(0, 4).map((r) => r.errorId)
|
|
27001
|
-
];
|
|
27002
|
-
for (const memberId of memberIds) {
|
|
27003
|
-
try {
|
|
27004
|
-
await client2.createMemoryRelation({
|
|
27005
|
-
source_id: memberId,
|
|
27006
|
-
target_id: created.id,
|
|
27007
|
-
relation_type: "part_of",
|
|
27008
|
-
confidence: 0.75
|
|
27009
|
-
});
|
|
27010
|
-
} catch {}
|
|
27011
|
-
}
|
|
27012
|
-
}
|
|
27013
|
-
}
|
|
27014
|
-
} catch {}
|
|
27015
|
-
}
|
|
27016
|
-
return patternIds;
|
|
27017
|
-
}
|
|
27018
26935
|
async function detectContradictions(client2, entityId, entityType, title, content, tags, workspaceId, projectId) {
|
|
27019
26936
|
if (!CONTRADICTION_TYPES.has(entityType))
|
|
27020
26937
|
return [];
|
|
@@ -27827,7 +27744,7 @@ async function autoEndSession(client3, cardId, status) {
|
|
|
27827
27744
|
// src/consolidation.ts
|
|
27828
27745
|
async function consolidateMemories(client3, workspaceId, projectId, options) {
|
|
27829
27746
|
const dryRun = options?.dryRun !== false;
|
|
27830
|
-
const minClusterSize = options?.minClusterSize ??
|
|
27747
|
+
const minClusterSize = options?.minClusterSize ?? 3;
|
|
27831
27748
|
const result = {
|
|
27832
27749
|
consolidated: 0,
|
|
27833
27750
|
clustersFound: 0,
|
|
@@ -27892,12 +27809,7 @@ async function consolidateMemories(client3, workspaceId, projectId, options) {
|
|
|
27892
27809
|
result.clustersFound++;
|
|
27893
27810
|
const mergedTitle = deriveClusterTitle(cluster, type);
|
|
27894
27811
|
const memberTitles = cluster.map((e) => e.title);
|
|
27895
|
-
const mergedContent =
|
|
27896
|
-
`Consolidated from ${cluster.length} ${type} memories:
|
|
27897
|
-
`,
|
|
27898
|
-
...cluster.map((e) => `- **${e.title}**: ${e.content.slice(0, 200)}`)
|
|
27899
|
-
].join(`
|
|
27900
|
-
`);
|
|
27812
|
+
const mergedContent = synthesizeClusterContent(cluster, type);
|
|
27901
27813
|
const maxConfidence = Math.max(...cluster.map((e) => e.confidence));
|
|
27902
27814
|
const allTags = [...new Set(cluster.flatMap((e) => e.tags || []))];
|
|
27903
27815
|
const detail = {
|
|
@@ -27959,6 +27871,60 @@ async function consolidateMemories(client3, workspaceId, projectId, options) {
|
|
|
27959
27871
|
}
|
|
27960
27872
|
return result;
|
|
27961
27873
|
}
|
|
27874
|
+
function synthesizeClusterContent(cluster, type) {
|
|
27875
|
+
const SKIP_PATTERNS = [
|
|
27876
|
+
/^##\s/,
|
|
27877
|
+
/^Agent:/,
|
|
27878
|
+
/^Duration:/,
|
|
27879
|
+
/^Labels:/,
|
|
27880
|
+
/^Progress:/,
|
|
27881
|
+
/^Session status:/,
|
|
27882
|
+
/^Completed at/,
|
|
27883
|
+
/^Final state:/,
|
|
27884
|
+
/^Related:/,
|
|
27885
|
+
/^When working on:/,
|
|
27886
|
+
/^\d+\.\s+.+\(\d+%,\s*\+\d+%\)/,
|
|
27887
|
+
/^Last updated:/,
|
|
27888
|
+
/^Recurring pattern:/,
|
|
27889
|
+
/^Consolidated from/
|
|
27890
|
+
];
|
|
27891
|
+
const seenLines = new Set;
|
|
27892
|
+
const knowledgeLines = [];
|
|
27893
|
+
for (const entity of cluster) {
|
|
27894
|
+
const lines = entity.content.split(`
|
|
27895
|
+
`).map((l) => l.trim());
|
|
27896
|
+
for (const line of lines) {
|
|
27897
|
+
if (!line || line.length < 20)
|
|
27898
|
+
continue;
|
|
27899
|
+
if (SKIP_PATTERNS.some((p) => p.test(line)))
|
|
27900
|
+
continue;
|
|
27901
|
+
const normalized = line.toLowerCase().replace(/[*_`#[\]]/g, "").trim();
|
|
27902
|
+
if (seenLines.has(normalized))
|
|
27903
|
+
continue;
|
|
27904
|
+
seenLines.add(normalized);
|
|
27905
|
+
knowledgeLines.push(line);
|
|
27906
|
+
}
|
|
27907
|
+
}
|
|
27908
|
+
if (knowledgeLines.length === 0) {
|
|
27909
|
+
return `${cluster.length} related ${type} entities consolidated. Original titles:
|
|
27910
|
+
${cluster.map((e) => `- ${e.title}`).join(`
|
|
27911
|
+
`)}`;
|
|
27912
|
+
}
|
|
27913
|
+
const MAX_CHARS = 1600;
|
|
27914
|
+
const result = [
|
|
27915
|
+
`Consolidated knowledge from ${cluster.length} ${type} entities:
|
|
27916
|
+
`
|
|
27917
|
+
];
|
|
27918
|
+
let charCount = result[0].length;
|
|
27919
|
+
for (const line of knowledgeLines) {
|
|
27920
|
+
if (charCount + line.length + 3 > MAX_CHARS)
|
|
27921
|
+
break;
|
|
27922
|
+
result.push(`- ${line}`);
|
|
27923
|
+
charCount += line.length + 3;
|
|
27924
|
+
}
|
|
27925
|
+
return result.join(`
|
|
27926
|
+
`);
|
|
27927
|
+
}
|
|
27962
27928
|
function deriveClusterTitle(cluster, type) {
|
|
27963
27929
|
const stopWords = new Set([
|
|
27964
27930
|
"the",
|
|
@@ -28013,9 +27979,9 @@ function deriveClusterTitle(cluster, type) {
|
|
|
28013
27979
|
wordCounts.set(word, (wordCounts.get(word) || 0) + 1);
|
|
28014
27980
|
}
|
|
28015
27981
|
}
|
|
28016
|
-
const topWords = [...wordCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0,
|
|
28017
|
-
const suffix = topWords.length > 0 ? topWords.join("
|
|
28018
|
-
return
|
|
27982
|
+
const topWords = [...wordCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 4).map(([word]) => word[0].toUpperCase() + word.slice(1));
|
|
27983
|
+
const suffix = topWords.length > 0 ? topWords.join(" / ") : "Various";
|
|
27984
|
+
return `${type[0].toUpperCase() + type.slice(1)}: ${suffix}`;
|
|
28019
27985
|
}
|
|
28020
27986
|
|
|
28021
27987
|
// src/server.ts
|
|
@@ -28091,6 +28057,371 @@ async function runLifecycleMaintenance(client3, workspaceId, projectId) {
|
|
|
28091
28057
|
return result;
|
|
28092
28058
|
}
|
|
28093
28059
|
|
|
28060
|
+
// src/memory-cleanup.ts
|
|
28061
|
+
init_dist();
|
|
28062
|
+
var ALL_STEPS = [
|
|
28063
|
+
"prune",
|
|
28064
|
+
"consolidate",
|
|
28065
|
+
"orphans",
|
|
28066
|
+
"duplicates",
|
|
28067
|
+
"backfill"
|
|
28068
|
+
];
|
|
28069
|
+
async function runMemoryCleanup(client3, workspaceId, projectId, options) {
|
|
28070
|
+
const dryRun = options?.dryRun !== false;
|
|
28071
|
+
const steps = options?.steps ?? ALL_STEPS;
|
|
28072
|
+
const maxAgeDays = options?.maxAgeDays ?? 30;
|
|
28073
|
+
const minClusterSize = options?.minClusterSize ?? 3;
|
|
28074
|
+
const orphanAgeDays = options?.orphanAgeDays ?? 14;
|
|
28075
|
+
const report = {
|
|
28076
|
+
success: true,
|
|
28077
|
+
dryRun,
|
|
28078
|
+
timestamp: new Date().toISOString(),
|
|
28079
|
+
workspace: { id: workspaceId, projectId },
|
|
28080
|
+
summary: { totalEntities: 0, issuesFound: 0, actionsTaken: 0 },
|
|
28081
|
+
steps: {},
|
|
28082
|
+
errors: [],
|
|
28083
|
+
healthReport: ""
|
|
28084
|
+
};
|
|
28085
|
+
let entities = [];
|
|
28086
|
+
try {
|
|
28087
|
+
const listResult = await client3.listMemoryEntities({
|
|
28088
|
+
workspace_id: workspaceId,
|
|
28089
|
+
project_id: projectId,
|
|
28090
|
+
limit: 200
|
|
28091
|
+
});
|
|
28092
|
+
entities = listResult.entities || [];
|
|
28093
|
+
report.summary.totalEntities = entities.length;
|
|
28094
|
+
} catch (err) {
|
|
28095
|
+
report.errors.push({
|
|
28096
|
+
step: "init",
|
|
28097
|
+
message: `Failed to fetch entities: ${err.message}`
|
|
28098
|
+
});
|
|
28099
|
+
report.success = false;
|
|
28100
|
+
report.healthReport = generateHealthReport(report);
|
|
28101
|
+
return report;
|
|
28102
|
+
}
|
|
28103
|
+
if (steps.includes("prune")) {
|
|
28104
|
+
try {
|
|
28105
|
+
report.steps.prune = runPruneStep(entities, maxAgeDays);
|
|
28106
|
+
if (!dryRun) {
|
|
28107
|
+
for (const item of report.steps.prune.items) {
|
|
28108
|
+
try {
|
|
28109
|
+
await client3.deleteMemoryEntity(item.id);
|
|
28110
|
+
report.steps.prune.pruned++;
|
|
28111
|
+
} catch {}
|
|
28112
|
+
}
|
|
28113
|
+
report.summary.actionsTaken += report.steps.prune.pruned;
|
|
28114
|
+
}
|
|
28115
|
+
report.summary.issuesFound += report.steps.prune.staleDraftsFound;
|
|
28116
|
+
} catch (err) {
|
|
28117
|
+
report.errors.push({
|
|
28118
|
+
step: "prune",
|
|
28119
|
+
message: err.message
|
|
28120
|
+
});
|
|
28121
|
+
}
|
|
28122
|
+
}
|
|
28123
|
+
if (steps.includes("consolidate")) {
|
|
28124
|
+
try {
|
|
28125
|
+
const result = await consolidateMemories(client3, workspaceId, projectId, {
|
|
28126
|
+
dryRun,
|
|
28127
|
+
minClusterSize
|
|
28128
|
+
});
|
|
28129
|
+
report.steps.consolidate = {
|
|
28130
|
+
clustersFound: result.clustersFound,
|
|
28131
|
+
entitiesProcessed: result.entitiesProcessed,
|
|
28132
|
+
consolidated: result.consolidated,
|
|
28133
|
+
details: result.details
|
|
28134
|
+
};
|
|
28135
|
+
report.summary.issuesFound += result.clustersFound;
|
|
28136
|
+
if (!dryRun)
|
|
28137
|
+
report.summary.actionsTaken += result.consolidated;
|
|
28138
|
+
} catch (err) {
|
|
28139
|
+
report.errors.push({
|
|
28140
|
+
step: "consolidate",
|
|
28141
|
+
message: err.message
|
|
28142
|
+
});
|
|
28143
|
+
}
|
|
28144
|
+
}
|
|
28145
|
+
if (steps.includes("orphans")) {
|
|
28146
|
+
try {
|
|
28147
|
+
report.steps.orphans = await runOrphanStep(client3, entities, orphanAgeDays);
|
|
28148
|
+
if (!dryRun) {
|
|
28149
|
+
for (const item of report.steps.orphans.items) {
|
|
28150
|
+
try {
|
|
28151
|
+
await client3.deleteMemoryEntity(item.id);
|
|
28152
|
+
report.steps.orphans.removed++;
|
|
28153
|
+
} catch {}
|
|
28154
|
+
}
|
|
28155
|
+
report.summary.actionsTaken += report.steps.orphans.removed;
|
|
28156
|
+
}
|
|
28157
|
+
report.summary.issuesFound += report.steps.orphans.orphansFound;
|
|
28158
|
+
} catch (err) {
|
|
28159
|
+
report.errors.push({
|
|
28160
|
+
step: "orphans",
|
|
28161
|
+
message: err.message
|
|
28162
|
+
});
|
|
28163
|
+
}
|
|
28164
|
+
}
|
|
28165
|
+
if (steps.includes("duplicates")) {
|
|
28166
|
+
try {
|
|
28167
|
+
report.steps.duplicates = await runDuplicateStep(client3, entities, workspaceId, projectId);
|
|
28168
|
+
if (!dryRun) {
|
|
28169
|
+
for (const pair of report.steps.duplicates.pairs) {
|
|
28170
|
+
try {
|
|
28171
|
+
await client3.deleteMemoryEntity(pair.removeId);
|
|
28172
|
+
report.steps.duplicates.resolved++;
|
|
28173
|
+
} catch {}
|
|
28174
|
+
}
|
|
28175
|
+
report.summary.actionsTaken += report.steps.duplicates.resolved;
|
|
28176
|
+
}
|
|
28177
|
+
report.summary.issuesFound += report.steps.duplicates.duplicatePairsFound;
|
|
28178
|
+
} catch (err) {
|
|
28179
|
+
report.errors.push({
|
|
28180
|
+
step: "duplicates",
|
|
28181
|
+
message: err.message
|
|
28182
|
+
});
|
|
28183
|
+
}
|
|
28184
|
+
}
|
|
28185
|
+
if (steps.includes("backfill")) {
|
|
28186
|
+
try {
|
|
28187
|
+
if (dryRun) {
|
|
28188
|
+
report.steps.backfill = {
|
|
28189
|
+
processed: 0,
|
|
28190
|
+
remaining: -1,
|
|
28191
|
+
errors: []
|
|
28192
|
+
};
|
|
28193
|
+
} else {
|
|
28194
|
+
const result = await client3.backfillEmbeddings(workspaceId);
|
|
28195
|
+
report.steps.backfill = {
|
|
28196
|
+
processed: result.processed,
|
|
28197
|
+
remaining: result.remaining,
|
|
28198
|
+
errors: result.errors || []
|
|
28199
|
+
};
|
|
28200
|
+
report.summary.actionsTaken += result.processed;
|
|
28201
|
+
}
|
|
28202
|
+
} catch (err) {
|
|
28203
|
+
report.errors.push({
|
|
28204
|
+
step: "backfill",
|
|
28205
|
+
message: err.message
|
|
28206
|
+
});
|
|
28207
|
+
}
|
|
28208
|
+
}
|
|
28209
|
+
report.healthReport = generateHealthReport(report);
|
|
28210
|
+
return report;
|
|
28211
|
+
}
|
|
28212
|
+
function runPruneStep(entities, maxAgeDays) {
|
|
28213
|
+
const now = Date.now();
|
|
28214
|
+
const drafts = entities.filter((e) => e.memory_tier === "draft");
|
|
28215
|
+
const stale = [];
|
|
28216
|
+
for (const entity of drafts) {
|
|
28217
|
+
const ageDays = (now - new Date(entity.created_at).getTime()) / (1000 * 60 * 60 * 24);
|
|
28218
|
+
if (ageDays < maxAgeDays)
|
|
28219
|
+
continue;
|
|
28220
|
+
const lifecycle2 = evaluateLifecycle(entity);
|
|
28221
|
+
stale.push({
|
|
28222
|
+
id: entity.id,
|
|
28223
|
+
title: entity.title,
|
|
28224
|
+
ageDays: Math.round(ageDays),
|
|
28225
|
+
decayScore: Math.round(lifecycle2.decay.score * 100) / 100
|
|
28226
|
+
});
|
|
28227
|
+
}
|
|
28228
|
+
return { staleDraftsFound: stale.length, pruned: 0, items: stale };
|
|
28229
|
+
}
|
|
28230
|
+
async function runOrphanStep(client3, entities, orphanAgeDays) {
|
|
28231
|
+
const now = Date.now();
|
|
28232
|
+
const result = { orphansFound: 0, removed: 0, items: [] };
|
|
28233
|
+
const candidates = entities.filter((e) => {
|
|
28234
|
+
if (e.memory_tier === "reference")
|
|
28235
|
+
return false;
|
|
28236
|
+
if (e.access_count >= 2)
|
|
28237
|
+
return false;
|
|
28238
|
+
const ageDays = (now - new Date(e.created_at).getTime()) / (1000 * 60 * 60 * 24);
|
|
28239
|
+
return ageDays >= orphanAgeDays;
|
|
28240
|
+
});
|
|
28241
|
+
for (const entity of candidates) {
|
|
28242
|
+
try {
|
|
28243
|
+
const related = await client3.getRelatedEntities(entity.id);
|
|
28244
|
+
const totalRelations = (related.outgoing?.length || 0) + (related.incoming?.length || 0);
|
|
28245
|
+
if (totalRelations > 0)
|
|
28246
|
+
continue;
|
|
28247
|
+
const ageDays = (now - new Date(entity.created_at).getTime()) / (1000 * 60 * 60 * 24);
|
|
28248
|
+
result.items.push({
|
|
28249
|
+
id: entity.id,
|
|
28250
|
+
title: entity.title,
|
|
28251
|
+
type: entity.type,
|
|
28252
|
+
tier: entity.memory_tier,
|
|
28253
|
+
ageDays: Math.round(ageDays),
|
|
28254
|
+
accessCount: entity.access_count
|
|
28255
|
+
});
|
|
28256
|
+
result.orphansFound++;
|
|
28257
|
+
} catch {}
|
|
28258
|
+
}
|
|
28259
|
+
return result;
|
|
28260
|
+
}
|
|
28261
|
+
async function runDuplicateStep(client3, entities, workspaceId, projectId) {
|
|
28262
|
+
const result = {
|
|
28263
|
+
duplicatePairsFound: 0,
|
|
28264
|
+
resolved: 0,
|
|
28265
|
+
pairs: []
|
|
28266
|
+
};
|
|
28267
|
+
const seenPairs = new Set;
|
|
28268
|
+
const flaggedForRemoval = new Set;
|
|
28269
|
+
for (const entity of entities) {
|
|
28270
|
+
if (flaggedForRemoval.has(entity.id))
|
|
28271
|
+
continue;
|
|
28272
|
+
let similar;
|
|
28273
|
+
try {
|
|
28274
|
+
similar = await findSimilarEntities(client3, entity.title, entity.content, workspaceId, { projectId, limit: 5, minRrfScore: 0.05, excludeIds: [entity.id] });
|
|
28275
|
+
} catch {
|
|
28276
|
+
continue;
|
|
28277
|
+
}
|
|
28278
|
+
for (const match of similar) {
|
|
28279
|
+
if (flaggedForRemoval.has(match.id))
|
|
28280
|
+
continue;
|
|
28281
|
+
const pairKey = [entity.id, match.id].sort().join(":");
|
|
28282
|
+
if (seenPairs.has(pairKey))
|
|
28283
|
+
continue;
|
|
28284
|
+
seenPairs.add(pairKey);
|
|
28285
|
+
const sim = titleSimilarity(entity.title, match.title);
|
|
28286
|
+
if (sim < 0.85)
|
|
28287
|
+
continue;
|
|
28288
|
+
const entityScore = entityQualityScore(entity);
|
|
28289
|
+
const matchEntity = entities.find((e) => e.id === match.id);
|
|
28290
|
+
const matchScore = matchEntity ? entityQualityScore(matchEntity) : match.confidence;
|
|
28291
|
+
const [keep, remove] = entityScore >= matchScore ? [entity, { id: match.id, title: match.title }] : [{ id: match.id, title: match.title }, entity];
|
|
28292
|
+
flaggedForRemoval.add(remove.id);
|
|
28293
|
+
result.pairs.push({
|
|
28294
|
+
keepId: keep.id,
|
|
28295
|
+
keepTitle: keep.title,
|
|
28296
|
+
removeId: remove.id,
|
|
28297
|
+
removeTitle: remove.title,
|
|
28298
|
+
similarity: Math.round(sim * 100) / 100
|
|
28299
|
+
});
|
|
28300
|
+
result.duplicatePairsFound++;
|
|
28301
|
+
}
|
|
28302
|
+
}
|
|
28303
|
+
return result;
|
|
28304
|
+
}
|
|
28305
|
+
var TIER_WEIGHTS2 = {
|
|
28306
|
+
reference: 3,
|
|
28307
|
+
episode: 2,
|
|
28308
|
+
draft: 1
|
|
28309
|
+
};
|
|
28310
|
+
function entityQualityScore(entity) {
|
|
28311
|
+
return entity.confidence + (TIER_WEIGHTS2[entity.memory_tier] || 0) + Math.min(entity.access_count, 10) * 0.1;
|
|
28312
|
+
}
|
|
28313
|
+
function titleSimilarity(a, b) {
|
|
28314
|
+
const na = a.toLowerCase().trim();
|
|
28315
|
+
const nb = b.toLowerCase().trim();
|
|
28316
|
+
if (na === nb)
|
|
28317
|
+
return 1;
|
|
28318
|
+
const wordsA = new Set(na.split(/\W+/).filter(Boolean));
|
|
28319
|
+
const wordsB = new Set(nb.split(/\W+/).filter(Boolean));
|
|
28320
|
+
if (wordsA.size === 0 || wordsB.size === 0)
|
|
28321
|
+
return 0;
|
|
28322
|
+
let intersection3 = 0;
|
|
28323
|
+
for (const w of wordsA) {
|
|
28324
|
+
if (wordsB.has(w))
|
|
28325
|
+
intersection3++;
|
|
28326
|
+
}
|
|
28327
|
+
const union3 = wordsA.size + wordsB.size - intersection3;
|
|
28328
|
+
return union3 > 0 ? intersection3 / union3 : 0;
|
|
28329
|
+
}
|
|
28330
|
+
function generateHealthReport(report) {
|
|
28331
|
+
const mode = report.dryRun ? "Dry Run (preview)" : "Executed";
|
|
28332
|
+
const lines = [
|
|
28333
|
+
`# Memory Health Report
|
|
28334
|
+
`,
|
|
28335
|
+
`**Mode:** ${mode} | **Entities:** ${report.summary.totalEntities} | **Issues:** ${report.summary.issuesFound} | **Actions:** ${report.summary.actionsTaken}`,
|
|
28336
|
+
""
|
|
28337
|
+
];
|
|
28338
|
+
if (report.steps.prune) {
|
|
28339
|
+
const p = report.steps.prune;
|
|
28340
|
+
lines.push("## Stale Drafts");
|
|
28341
|
+
if (p.staleDraftsFound === 0) {
|
|
28342
|
+
lines.push(`No stale drafts found.
|
|
28343
|
+
`);
|
|
28344
|
+
} else {
|
|
28345
|
+
lines.push(`Found **${p.staleDraftsFound}** stale drafts${!report.dryRun ? ` (pruned ${p.pruned})` : ""}:`);
|
|
28346
|
+
lines.push("| Title | Age | Decay |");
|
|
28347
|
+
lines.push("|-------|-----|-------|");
|
|
28348
|
+
for (const item of p.items.slice(0, 20)) {
|
|
28349
|
+
lines.push(`| ${item.title} | ${item.ageDays}d | ${item.decayScore} |`);
|
|
28350
|
+
}
|
|
28351
|
+
lines.push("");
|
|
28352
|
+
}
|
|
28353
|
+
}
|
|
28354
|
+
if (report.steps.consolidate) {
|
|
28355
|
+
const c = report.steps.consolidate;
|
|
28356
|
+
lines.push("## Consolidation");
|
|
28357
|
+
if (c.clustersFound === 0) {
|
|
28358
|
+
lines.push(`Scanned ${c.entitiesProcessed} draft/episode entities — no clusters found.
|
|
28359
|
+
`);
|
|
28360
|
+
} else {
|
|
28361
|
+
lines.push(`Found **${c.clustersFound}** clusters across ${c.entitiesProcessed} entities:`);
|
|
28362
|
+
for (const d of c.details.slice(0, 10)) {
|
|
28363
|
+
lines.push(`- **${d.mergedTitle}** — ${d.clusterSize} entities`);
|
|
28364
|
+
}
|
|
28365
|
+
lines.push("");
|
|
28366
|
+
}
|
|
28367
|
+
}
|
|
28368
|
+
if (report.steps.orphans) {
|
|
28369
|
+
const o = report.steps.orphans;
|
|
28370
|
+
lines.push("## Orphaned Entities");
|
|
28371
|
+
if (o.orphansFound === 0) {
|
|
28372
|
+
lines.push(`No orphans found.
|
|
28373
|
+
`);
|
|
28374
|
+
} else {
|
|
28375
|
+
lines.push(`Found **${o.orphansFound}** orphans${!report.dryRun ? ` (removed ${o.removed})` : ""}:`);
|
|
28376
|
+
lines.push("| Title | Type | Tier | Age | Accesses |");
|
|
28377
|
+
lines.push("|-------|------|------|-----|----------|");
|
|
28378
|
+
for (const item of o.items.slice(0, 20)) {
|
|
28379
|
+
lines.push(`| ${item.title} | ${item.type} | ${item.tier} | ${item.ageDays}d | ${item.accessCount} |`);
|
|
28380
|
+
}
|
|
28381
|
+
lines.push("");
|
|
28382
|
+
}
|
|
28383
|
+
}
|
|
28384
|
+
if (report.steps.duplicates) {
|
|
28385
|
+
const d = report.steps.duplicates;
|
|
28386
|
+
lines.push("## Near-Duplicates");
|
|
28387
|
+
if (d.duplicatePairsFound === 0) {
|
|
28388
|
+
lines.push(`No duplicates found.
|
|
28389
|
+
`);
|
|
28390
|
+
} else {
|
|
28391
|
+
lines.push(`Found **${d.duplicatePairsFound}** duplicate pairs${!report.dryRun ? ` (resolved ${d.resolved})` : ""}:`);
|
|
28392
|
+
for (const pair of d.pairs.slice(0, 20)) {
|
|
28393
|
+
lines.push(`- "${pair.keepTitle}" ~ "${pair.removeTitle}" (${Math.round(pair.similarity * 100)}% similar, keep first)`);
|
|
28394
|
+
}
|
|
28395
|
+
lines.push("");
|
|
28396
|
+
}
|
|
28397
|
+
}
|
|
28398
|
+
if (report.steps.backfill) {
|
|
28399
|
+
const b = report.steps.backfill;
|
|
28400
|
+
lines.push("## Embedding Coverage");
|
|
28401
|
+
if (report.dryRun) {
|
|
28402
|
+
lines.push("Backfill will run when executed with `dryRun: false`.\n");
|
|
28403
|
+
} else if (b.remaining === 0) {
|
|
28404
|
+
lines.push(`All embeddings up to date (processed ${b.processed}).
|
|
28405
|
+
`);
|
|
28406
|
+
} else {
|
|
28407
|
+
lines.push(`Processed ${b.processed} entities. ${b.remaining} still need embeddings.
|
|
28408
|
+
`);
|
|
28409
|
+
}
|
|
28410
|
+
}
|
|
28411
|
+
if (report.errors.length > 0) {
|
|
28412
|
+
lines.push("## Errors");
|
|
28413
|
+
for (const e of report.errors) {
|
|
28414
|
+
lines.push(`- **${e.step}:** ${e.message}`);
|
|
28415
|
+
}
|
|
28416
|
+
lines.push("");
|
|
28417
|
+
}
|
|
28418
|
+
if (report.dryRun) {
|
|
28419
|
+
lines.push("---\n*Run with `dryRun: false` to execute cleanup.*");
|
|
28420
|
+
}
|
|
28421
|
+
return lines.join(`
|
|
28422
|
+
`);
|
|
28423
|
+
}
|
|
28424
|
+
|
|
28094
28425
|
// src/onboard.ts
|
|
28095
28426
|
async function onboardNewUser(params) {
|
|
28096
28427
|
const {
|
|
@@ -29525,6 +29856,47 @@ var TOOLS = {
|
|
|
29525
29856
|
},
|
|
29526
29857
|
required: []
|
|
29527
29858
|
}
|
|
29859
|
+
},
|
|
29860
|
+
harmony_cleanup_memories: {
|
|
29861
|
+
description: "Run a unified memory cleanup: prune stale drafts, consolidate similar memories, detect orphans and duplicates, and backfill embeddings. Returns a health report. Dry-run by default — run with dryRun=false to execute.",
|
|
29862
|
+
inputSchema: {
|
|
29863
|
+
type: "object",
|
|
29864
|
+
properties: {
|
|
29865
|
+
workspaceId: {
|
|
29866
|
+
type: "string",
|
|
29867
|
+
description: "Workspace ID (optional if context set)"
|
|
29868
|
+
},
|
|
29869
|
+
projectId: {
|
|
29870
|
+
type: "string",
|
|
29871
|
+
description: "Project ID (optional)"
|
|
29872
|
+
},
|
|
29873
|
+
dryRun: {
|
|
29874
|
+
type: "boolean",
|
|
29875
|
+
description: "Preview cleanup without executing changes (default: true)"
|
|
29876
|
+
},
|
|
29877
|
+
steps: {
|
|
29878
|
+
type: "array",
|
|
29879
|
+
items: {
|
|
29880
|
+
type: "string",
|
|
29881
|
+
enum: ["prune", "consolidate", "orphans", "duplicates", "backfill"]
|
|
29882
|
+
},
|
|
29883
|
+
description: "Which cleanup steps to run (default: all). Options: prune, consolidate, orphans, duplicates, backfill."
|
|
29884
|
+
},
|
|
29885
|
+
maxAgeDays: {
|
|
29886
|
+
type: "number",
|
|
29887
|
+
description: "Max age in days for stale draft pruning (default: 30)"
|
|
29888
|
+
},
|
|
29889
|
+
minClusterSize: {
|
|
29890
|
+
type: "number",
|
|
29891
|
+
description: "Min cluster size for consolidation (default: 3)"
|
|
29892
|
+
},
|
|
29893
|
+
orphanAgeDays: {
|
|
29894
|
+
type: "number",
|
|
29895
|
+
description: "Min age in days for orphan detection (default: 14)"
|
|
29896
|
+
}
|
|
29897
|
+
},
|
|
29898
|
+
required: []
|
|
29899
|
+
}
|
|
29528
29900
|
}
|
|
29529
29901
|
};
|
|
29530
29902
|
var RESOURCES = [
|
|
@@ -31035,6 +31407,27 @@ async function handleToolCall(name, args, deps) {
|
|
|
31035
31407
|
message: `Processed ${entitiesProcessed} entities, created ${relationsCreated} new relations.`
|
|
31036
31408
|
};
|
|
31037
31409
|
}
|
|
31410
|
+
case "harmony_cleanup_memories": {
|
|
31411
|
+
const workspaceId = args.workspaceId || deps.getActiveWorkspaceId();
|
|
31412
|
+
if (!workspaceId) {
|
|
31413
|
+
throw new Error("No workspace specified. Use harmony_set_workspace_context or provide workspaceId.");
|
|
31414
|
+
}
|
|
31415
|
+
const projectId = args.projectId || deps.getActiveProjectId() || undefined;
|
|
31416
|
+
const report = await runMemoryCleanup(client3, workspaceId, projectId, {
|
|
31417
|
+
dryRun: args.dryRun,
|
|
31418
|
+
steps: args.steps,
|
|
31419
|
+
maxAgeDays: args.maxAgeDays,
|
|
31420
|
+
minClusterSize: args.minClusterSize,
|
|
31421
|
+
orphanAgeDays: args.orphanAgeDays
|
|
31422
|
+
});
|
|
31423
|
+
return {
|
|
31424
|
+
success: report.success,
|
|
31425
|
+
dryRun: report.dryRun,
|
|
31426
|
+
summary: report.summary,
|
|
31427
|
+
errors: report.errors,
|
|
31428
|
+
healthReport: report.healthReport
|
|
31429
|
+
};
|
|
31430
|
+
}
|
|
31038
31431
|
default:
|
|
31039
31432
|
throw new Error(`Unknown tool: ${name}`);
|
|
31040
31433
|
}
|
|
@@ -31103,7 +31496,7 @@ class HarmonyMCPServer {
|
|
|
31103
31496
|
// src/skills.ts
|
|
31104
31497
|
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
31105
31498
|
import { dirname } from "node:path";
|
|
31106
|
-
var SKILLS_VERSION = "
|
|
31499
|
+
var SKILLS_VERSION = "4";
|
|
31107
31500
|
var VERSION_MARKER_PREFIX = "<!-- skills-version:";
|
|
31108
31501
|
var HARMONY_WORKFLOW_PROMPT = `# Harmony Card Workflow
|
|
31109
31502
|
|
|
@@ -31297,6 +31690,30 @@ Do not start an agent session.
|
|
|
31297
31690
|
|
|
31298
31691
|
---
|
|
31299
31692
|
|
|
31693
|
+
## Step E: Auto-Detect Card for Implementation Tasks
|
|
31694
|
+
|
|
31695
|
+
**IMPORTANT:** This step applies when you are about to implement a plan, feature, or fix
|
|
31696
|
+
that was NOT started via /hmy. Before writing any code, check if the work maps to an
|
|
31697
|
+
existing Harmony card.
|
|
31698
|
+
|
|
31699
|
+
### When to run this check
|
|
31700
|
+
- User says "implement this plan", "build this feature", "fix this bug" (without /hmy)
|
|
31701
|
+
- You are about to execute a plan file
|
|
31702
|
+
- Skip if: the user already started with /hmy, or no Harmony MCP tools are available
|
|
31703
|
+
|
|
31704
|
+
### Detection steps
|
|
31705
|
+
1. Call \`harmony_search_cards\` with keywords from the plan title or task description
|
|
31706
|
+
2. If a card matches (same feature/fix described), tell the user:
|
|
31707
|
+
"This maps to card #N — starting a session to track progress."
|
|
31708
|
+
3. Call \`harmony_start_agent_session\` with \`moveToColumn: "In Progress"\`, \`addLabels: ["agent"]\`
|
|
31709
|
+
4. Track progress with \`harmony_update_agent_progress\` at milestones
|
|
31710
|
+
5. When done, call \`harmony_end_agent_session\` with \`status: "completed"\`, \`moveToColumn: "Review"\`
|
|
31711
|
+
|
|
31712
|
+
### If no match
|
|
31713
|
+
Proceed normally without a session. No action needed.
|
|
31714
|
+
|
|
31715
|
+
---
|
|
31716
|
+
|
|
31300
31717
|
## Key Tools Reference
|
|
31301
31718
|
|
|
31302
31719
|
**Cards:** \`harmony_get_card\`, \`harmony_get_card_by_short_id\`, \`harmony_search_cards\`, \`harmony_create_card\`, \`harmony_update_card\`, \`harmony_move_card\`, \`harmony_delete_card\`, \`harmony_assign_card\`
|
|
@@ -33477,6 +33894,17 @@ When given a card reference (e.g., #42 or a card name), follow this workflow:
|
|
|
33477
33894
|
7. Update progress periodically with \`harmony_update_agent_progress\`
|
|
33478
33895
|
8. When done, call \`harmony_end_agent_session\` and move to "Review"
|
|
33479
33896
|
|
|
33897
|
+
## Auto-Detect Card for Implementation Tasks
|
|
33898
|
+
|
|
33899
|
+
Before implementing a plan or feature, check if it maps to an existing Harmony card:
|
|
33900
|
+
|
|
33901
|
+
1. Use \`harmony_search_cards\` with keywords from the task description
|
|
33902
|
+
2. If a match is found, call \`harmony_start_agent_session\` (agentIdentifier: "claude-code", agentName: "Claude Code", moveToColumn: "In Progress", addLabels: ["agent"])
|
|
33903
|
+
3. Update progress with \`harmony_update_agent_progress\` at milestones
|
|
33904
|
+
4. When done, call \`harmony_end_agent_session\` with status: "completed", moveToColumn: "Review"
|
|
33905
|
+
|
|
33906
|
+
Skip if: work was already started with a card reference, or no matching card exists.
|
|
33907
|
+
|
|
33480
33908
|
## Available Harmony Tools
|
|
33481
33909
|
|
|
33482
33910
|
- \`harmony_get_card\`, \`harmony_get_card_by_short_id\`, \`harmony_search_cards\` - Find cards
|