@gethmy/mcp 2.2.3 → 2.2.4

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 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
@@ -9201,7 +9202,45 @@ function truncateContent(content, maxTokens) {
9201
9202
  }
9202
9203
  return { text: result, truncated: true };
9203
9204
  }
9204
- function computeRelevanceScore(entity, taskContext, cardLabels) {
9205
+ function escapeRegex2(str) {
9206
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9207
+ }
9208
+ function expandQuery(taskContext) {
9209
+ const queries = [taskContext];
9210
+ const lowerQueries = [taskContext.toLowerCase()];
9211
+ const words = taskContext.toLowerCase().split(/\W+/).filter((w) => w.length > 2);
9212
+ const expandableWords = words.filter((w) => QUERY_SYNONYMS[w]);
9213
+ for (const word of expandableWords) {
9214
+ const synonyms = QUERY_SYNONYMS[word];
9215
+ if (!synonyms)
9216
+ continue;
9217
+ const variation = taskContext.replace(new RegExp(`\\b${escapeRegex2(word)}\\b`, "gi"), synonyms[0]);
9218
+ const lowerVariation = variation.toLowerCase();
9219
+ if (lowerVariation !== taskContext.toLowerCase() && !lowerQueries.includes(lowerVariation)) {
9220
+ queries.push(variation);
9221
+ lowerQueries.push(lowerVariation);
9222
+ }
9223
+ if (queries.length >= MAX_QUERY_VARIATIONS)
9224
+ break;
9225
+ }
9226
+ if (words.length >= 3) {
9227
+ const keyPhrases = words.filter((w) => ![
9228
+ "the",
9229
+ "and",
9230
+ "for",
9231
+ "with",
9232
+ "this",
9233
+ "that",
9234
+ "from",
9235
+ "into"
9236
+ ].includes(w)).slice(0, 4).join(" ");
9237
+ if (!lowerQueries.includes(keyPhrases)) {
9238
+ queries.push(keyPhrases);
9239
+ }
9240
+ }
9241
+ return queries.slice(0, MAX_QUERY_VARIATIONS);
9242
+ }
9243
+ function computeRelevanceScore(entity, taskContext, cardLabels, graphRelations) {
9205
9244
  const reasons = [];
9206
9245
  let score = 0;
9207
9246
  const hasRrfScore = entity.rrf_score !== undefined && entity.rrf_score > 0;
@@ -9260,7 +9299,23 @@ function computeRelevanceScore(entity, taskContext, cardLabels) {
9260
9299
  score += 0.1;
9261
9300
  reasons.push("procedure_boost");
9262
9301
  }
9263
- score = Math.min(score, 1);
9302
+ if (graphRelations && graphRelations.length > 0) {
9303
+ const entityRelations = graphRelations.filter((r) => r.source_id === entity.id || r.target_id === entity.id);
9304
+ if (entityRelations.length > 0) {
9305
+ let bestBonus = 0;
9306
+ let bestRelType = "";
9307
+ for (const rel of entityRelations) {
9308
+ const bonus = RELATION_BONUSES[rel.relation_type] ?? 0.1;
9309
+ if (bonus > bestBonus) {
9310
+ bestBonus = bonus;
9311
+ bestRelType = rel.relation_type;
9312
+ }
9313
+ }
9314
+ score += bestBonus;
9315
+ reasons.push(`graph_walk(${bestRelType})`);
9316
+ }
9317
+ }
9318
+ score = Math.max(0, Math.min(score, 1));
9264
9319
  const tierWeight = TIER_WEIGHTS[entity.memory_tier];
9265
9320
  score *= tierWeight;
9266
9321
  return { score, reasons };
@@ -9272,7 +9327,11 @@ async function assembleContext(options) {
9272
9327
  taskContext,
9273
9328
  cardLabels = [],
9274
9329
  tokenBudget = DEFAULT_TOKEN_BUDGET,
9275
- client: client2
9330
+ client: client2,
9331
+ graphWalkEnabled = true,
9332
+ queryExpansionEnabled = true,
9333
+ enableLlmReranking = false,
9334
+ rerankFn
9276
9335
  } = options;
9277
9336
  const assemblyId = generateAssemblyId();
9278
9337
  const manifest = {
@@ -9288,13 +9347,26 @@ async function assembleContext(options) {
9288
9347
  reference: { count: 0, tokens: 0 }
9289
9348
  }
9290
9349
  };
9291
- let candidates = [];
9292
- try {
9293
- const searchResult = await client2.searchMemoryEntities(workspaceId, taskContext, { project_id: projectId, limit: 30 });
9294
- if (searchResult.entities?.length > 0) {
9295
- candidates = searchResult.entities.map(mapToContextEntity);
9350
+ const candidates = [];
9351
+ const queries = queryExpansionEnabled ? expandQuery(taskContext) : [taskContext];
9352
+ const searchResults = await Promise.allSettled(queries.map((query) => client2.searchMemoryEntities(workspaceId, query, {
9353
+ project_id: projectId,
9354
+ limit: 30
9355
+ })));
9356
+ const candidateIds = new Set;
9357
+ for (const result of searchResults) {
9358
+ if (result.status !== "fulfilled")
9359
+ continue;
9360
+ if (result.value.entities?.length > 0) {
9361
+ for (const raw of result.value.entities) {
9362
+ const entity = mapToContextEntity(raw);
9363
+ if (!candidateIds.has(entity.id)) {
9364
+ candidateIds.add(entity.id);
9365
+ candidates.push(entity);
9366
+ }
9367
+ }
9296
9368
  }
9297
- } catch {}
9369
+ }
9298
9370
  if (candidates.length < 10 && projectId) {
9299
9371
  try {
9300
9372
  const listResult = await client2.listMemoryEntities({
@@ -9303,9 +9375,13 @@ async function assembleContext(options) {
9303
9375
  limit: 30
9304
9376
  });
9305
9377
  if (listResult.entities?.length > 0) {
9306
- const existingIds = new Set(candidates.map((c) => c.id));
9307
- const additional = listResult.entities.map(mapToContextEntity).filter((e) => !existingIds.has(e.id));
9308
- candidates.push(...additional);
9378
+ for (const raw of listResult.entities) {
9379
+ const entity = mapToContextEntity(raw);
9380
+ if (!candidateIds.has(entity.id)) {
9381
+ candidateIds.add(entity.id);
9382
+ candidates.push(entity);
9383
+ }
9384
+ }
9309
9385
  }
9310
9386
  } catch {}
9311
9387
  }
@@ -9317,9 +9393,33 @@ async function assembleContext(options) {
9317
9393
  limit: 20
9318
9394
  });
9319
9395
  if (wsResult.entities?.length > 0) {
9320
- const existingIds = new Set(candidates.map((c) => c.id));
9321
- const additional = wsResult.entities.map(mapToContextEntity).filter((e) => !existingIds.has(e.id));
9322
- candidates.push(...additional);
9396
+ for (const raw of wsResult.entities) {
9397
+ const entity = mapToContextEntity(raw);
9398
+ if (!candidateIds.has(entity.id)) {
9399
+ candidateIds.add(entity.id);
9400
+ candidates.push(entity);
9401
+ }
9402
+ }
9403
+ }
9404
+ } catch {}
9405
+ }
9406
+ let graphRelations = [];
9407
+ if (graphWalkEnabled && candidates.length > 0) {
9408
+ try {
9409
+ const seedCandidates = [...candidates].sort((a, b) => (b.rrf_score ?? 0) - (a.rrf_score ?? 0)).slice(0, GRAPH_WALK_SEED_COUNT);
9410
+ const seedIds = seedCandidates.map((c) => c.id);
9411
+ const walkResult = await discoverRelatedContext(client2, seedIds, GRAPH_WALK_MAX_DEPTH, GRAPH_WALK_MAX_ENTITIES, GRAPH_WALK_MIN_CONFIDENCE);
9412
+ graphRelations = walkResult.relations;
9413
+ const newEntityIds = walkResult.entities.filter((e) => !candidateIds.has(e.id)).map((e) => e.id);
9414
+ if (newEntityIds.length > 0) {
9415
+ const fetchResults = await Promise.allSettled(newEntityIds.map((id) => client2.getMemoryEntity(id)));
9416
+ for (const result of fetchResults) {
9417
+ if (result.status !== "fulfilled" || !result.value.entity)
9418
+ continue;
9419
+ const mapped = mapToContextEntity(result.value.entity);
9420
+ candidateIds.add(mapped.id);
9421
+ candidates.push(mapped);
9422
+ }
9323
9423
  }
9324
9424
  } catch {}
9325
9425
  }
@@ -9331,10 +9431,31 @@ async function assembleContext(options) {
9331
9431
  };
9332
9432
  }
9333
9433
  const scored = candidates.map((entity) => {
9334
- const { score, reasons } = computeRelevanceScore(entity, taskContext, cardLabels);
9434
+ const { score, reasons } = computeRelevanceScore(entity, taskContext, cardLabels, graphRelations.length > 0 ? graphRelations : undefined);
9335
9435
  return { entity, score, reasons };
9336
9436
  });
9337
9437
  scored.sort((a, b) => b.score - a.score);
9438
+ if (enableLlmReranking && rerankFn && scored.length >= RERANK_MIN_CANDIDATES) {
9439
+ const topN = scored.slice(0, RERANK_TOP_N);
9440
+ const scoreRange = topN[0].score - topN[topN.length - 1].score;
9441
+ if (scoreRange <= RERANK_CLUSTER_THRESHOLD) {
9442
+ try {
9443
+ const rerankCandidates = topN.map((s) => ({
9444
+ id: s.entity.id,
9445
+ title: s.entity.title,
9446
+ snippet: s.entity.content.slice(0, 200)
9447
+ }));
9448
+ const rerankedIds = await rerankFn(taskContext, rerankCandidates);
9449
+ const idOrder = new Map(rerankedIds.map((id, i) => [id, i]));
9450
+ topN.sort((a, b) => {
9451
+ const aIdx = idOrder.get(a.entity.id) ?? 999;
9452
+ const bIdx = idOrder.get(b.entity.id) ?? 999;
9453
+ return aIdx - bIdx;
9454
+ });
9455
+ scored.splice(0, topN.length, ...topN);
9456
+ } catch {}
9457
+ }
9458
+ }
9338
9459
  const procedureBudget = Math.floor(tokenBudget * PROCEDURE_BUDGET_FRACTION);
9339
9460
  const remainingBudget = tokenBudget - procedureBudget;
9340
9461
  const tierBudgets = {
@@ -9646,7 +9767,7 @@ async function recordContextFeedback(client2, cardId, sessionStatus, progressPer
9646
9767
  sessionAssemblyMap.delete(cardId);
9647
9768
  return { adjusted };
9648
9769
  }
9649
- var DEFAULT_TOKEN_BUDGET = 4000, MAX_TOKENS_PER_ENTITY = 500, MIN_RELEVANCE_THRESHOLD = 0.1, TIER_WEIGHTS, PROCEDURE_BUDGET_FRACTION = 0.15, TIER_BUDGET_ALLOCATION, MIN_REFERENCE_SLOTS = 3, manifestCache, MAX_CACHE_SIZE = 50, sessionAssemblyMap, MAX_SESSION_MAP_SIZE = 100;
9770
+ var DEFAULT_TOKEN_BUDGET = 4000, MAX_TOKENS_PER_ENTITY = 500, MIN_RELEVANCE_THRESHOLD = 0.1, TIER_WEIGHTS, PROCEDURE_BUDGET_FRACTION = 0.15, TIER_BUDGET_ALLOCATION, MIN_REFERENCE_SLOTS = 3, 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
9771
  var init_context_assembly = __esm(() => {
9651
9772
  init_dist();
9652
9773
  TIER_WEIGHTS = {
@@ -9659,6 +9780,33 @@ var init_context_assembly = __esm(() => {
9659
9780
  episode: 0.3,
9660
9781
  draft: 0.1
9661
9782
  };
9783
+ RELATION_BONUSES = {
9784
+ depends_on: 0.15,
9785
+ resolved_by: 0.2,
9786
+ relates_to: 0.1,
9787
+ implements: 0.15,
9788
+ blocks: 0.15,
9789
+ references: 0.1,
9790
+ extends: 0.1,
9791
+ caused_by: 0.15
9792
+ };
9793
+ QUERY_SYNONYMS = {
9794
+ auth: ["authentication", "authorization", "session"],
9795
+ authentication: ["auth", "session", "sign-in"],
9796
+ login: ["sign-in", "authentication", "session"],
9797
+ bug: ["error", "issue", "defect", "problem"],
9798
+ error: ["exception", "failure", "issue"],
9799
+ fix: ["resolve", "patch", "repair", "correct"],
9800
+ deploy: ["deployment", "release", "ship", "publish"],
9801
+ test: ["testing", "spec", "assertion", "verify"],
9802
+ config: ["configuration", "settings", "setup"],
9803
+ db: ["database", "storage", "persistence"],
9804
+ database: ["storage", "persistence", "data store"],
9805
+ api: ["endpoint", "route", "service"],
9806
+ ui: ["frontend", "component", "view"],
9807
+ perf: ["performance", "speed", "latency"],
9808
+ performance: ["speed", "latency", "optimization"]
9809
+ };
9662
9810
  manifestCache = new Map;
9663
9811
  sessionAssemblyMap = new Map;
9664
9812
  });
@@ -31103,7 +31251,7 @@ class HarmonyMCPServer {
31103
31251
  // src/skills.ts
31104
31252
  import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
31105
31253
  import { dirname } from "node:path";
31106
- var SKILLS_VERSION = "3";
31254
+ var SKILLS_VERSION = "4";
31107
31255
  var VERSION_MARKER_PREFIX = "<!-- skills-version:";
31108
31256
  var HARMONY_WORKFLOW_PROMPT = `# Harmony Card Workflow
31109
31257
 
@@ -31297,6 +31445,30 @@ Do not start an agent session.
31297
31445
 
31298
31446
  ---
31299
31447
 
31448
+ ## Step E: Auto-Detect Card for Implementation Tasks
31449
+
31450
+ **IMPORTANT:** This step applies when you are about to implement a plan, feature, or fix
31451
+ that was NOT started via /hmy. Before writing any code, check if the work maps to an
31452
+ existing Harmony card.
31453
+
31454
+ ### When to run this check
31455
+ - User says "implement this plan", "build this feature", "fix this bug" (without /hmy)
31456
+ - You are about to execute a plan file
31457
+ - Skip if: the user already started with /hmy, or no Harmony MCP tools are available
31458
+
31459
+ ### Detection steps
31460
+ 1. Call \`harmony_search_cards\` with keywords from the plan title or task description
31461
+ 2. If a card matches (same feature/fix described), tell the user:
31462
+ "This maps to card #N — starting a session to track progress."
31463
+ 3. Call \`harmony_start_agent_session\` with \`moveToColumn: "In Progress"\`, \`addLabels: ["agent"]\`
31464
+ 4. Track progress with \`harmony_update_agent_progress\` at milestones
31465
+ 5. When done, call \`harmony_end_agent_session\` with \`status: "completed"\`, \`moveToColumn: "Review"\`
31466
+
31467
+ ### If no match
31468
+ Proceed normally without a session. No action needed.
31469
+
31470
+ ---
31471
+
31300
31472
  ## Key Tools Reference
31301
31473
 
31302
31474
  **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 +33649,17 @@ When given a card reference (e.g., #42 or a card name), follow this workflow:
33477
33649
  7. Update progress periodically with \`harmony_update_agent_progress\`
33478
33650
  8. When done, call \`harmony_end_agent_session\` and move to "Review"
33479
33651
 
33652
+ ## Auto-Detect Card for Implementation Tasks
33653
+
33654
+ Before implementing a plan or feature, check if it maps to an existing Harmony card:
33655
+
33656
+ 1. Use \`harmony_search_cards\` with keywords from the task description
33657
+ 2. If a match is found, call \`harmony_start_agent_session\` (agentIdentifier: "claude-code", agentName: "Claude Code", moveToColumn: "In Progress", addLabels: ["agent"])
33658
+ 3. Update progress with \`harmony_update_agent_progress\` at milestones
33659
+ 4. When done, call \`harmony_end_agent_session\` with status: "completed", moveToColumn: "Review"
33660
+
33661
+ Skip if: work was already started with a card reference, or no matching card exists.
33662
+
33480
33663
  ## Available Harmony Tools
33481
33664
 
33482
33665
  - \`harmony_get_card\`, \`harmony_get_card_by_short_id\`, \`harmony_search_cards\` - Find cards
package/dist/index.js CHANGED
@@ -7070,6 +7070,7 @@ __export(exports_context_assembly, {
7070
7070
  mapToContextEntity: () => mapToContextEntity,
7071
7071
  getSessionAssemblyId: () => getSessionAssemblyId,
7072
7072
  getCachedManifest: () => getCachedManifest,
7073
+ expandQuery: () => expandQuery,
7073
7074
  computeRelevanceScore: () => computeRelevanceScore,
7074
7075
  cacheManifest: () => cacheManifest,
7075
7076
  assembleContext: () => assembleContext
@@ -7108,7 +7109,45 @@ function truncateContent(content, maxTokens) {
7108
7109
  }
7109
7110
  return { text: result, truncated: true };
7110
7111
  }
7111
- function computeRelevanceScore(entity, taskContext, cardLabels) {
7112
+ function escapeRegex2(str) {
7113
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7114
+ }
7115
+ function expandQuery(taskContext) {
7116
+ const queries = [taskContext];
7117
+ const lowerQueries = [taskContext.toLowerCase()];
7118
+ const words = taskContext.toLowerCase().split(/\W+/).filter((w) => w.length > 2);
7119
+ const expandableWords = words.filter((w) => QUERY_SYNONYMS[w]);
7120
+ for (const word of expandableWords) {
7121
+ const synonyms = QUERY_SYNONYMS[word];
7122
+ if (!synonyms)
7123
+ continue;
7124
+ const variation = taskContext.replace(new RegExp(`\\b${escapeRegex2(word)}\\b`, "gi"), synonyms[0]);
7125
+ const lowerVariation = variation.toLowerCase();
7126
+ if (lowerVariation !== taskContext.toLowerCase() && !lowerQueries.includes(lowerVariation)) {
7127
+ queries.push(variation);
7128
+ lowerQueries.push(lowerVariation);
7129
+ }
7130
+ if (queries.length >= MAX_QUERY_VARIATIONS)
7131
+ break;
7132
+ }
7133
+ if (words.length >= 3) {
7134
+ const keyPhrases = words.filter((w) => ![
7135
+ "the",
7136
+ "and",
7137
+ "for",
7138
+ "with",
7139
+ "this",
7140
+ "that",
7141
+ "from",
7142
+ "into"
7143
+ ].includes(w)).slice(0, 4).join(" ");
7144
+ if (!lowerQueries.includes(keyPhrases)) {
7145
+ queries.push(keyPhrases);
7146
+ }
7147
+ }
7148
+ return queries.slice(0, MAX_QUERY_VARIATIONS);
7149
+ }
7150
+ function computeRelevanceScore(entity, taskContext, cardLabels, graphRelations) {
7112
7151
  const reasons = [];
7113
7152
  let score = 0;
7114
7153
  const hasRrfScore = entity.rrf_score !== undefined && entity.rrf_score > 0;
@@ -7167,7 +7206,23 @@ function computeRelevanceScore(entity, taskContext, cardLabels) {
7167
7206
  score += 0.1;
7168
7207
  reasons.push("procedure_boost");
7169
7208
  }
7170
- score = Math.min(score, 1);
7209
+ if (graphRelations && graphRelations.length > 0) {
7210
+ const entityRelations = graphRelations.filter((r) => r.source_id === entity.id || r.target_id === entity.id);
7211
+ if (entityRelations.length > 0) {
7212
+ let bestBonus = 0;
7213
+ let bestRelType = "";
7214
+ for (const rel of entityRelations) {
7215
+ const bonus = RELATION_BONUSES[rel.relation_type] ?? 0.1;
7216
+ if (bonus > bestBonus) {
7217
+ bestBonus = bonus;
7218
+ bestRelType = rel.relation_type;
7219
+ }
7220
+ }
7221
+ score += bestBonus;
7222
+ reasons.push(`graph_walk(${bestRelType})`);
7223
+ }
7224
+ }
7225
+ score = Math.max(0, Math.min(score, 1));
7171
7226
  const tierWeight = TIER_WEIGHTS[entity.memory_tier];
7172
7227
  score *= tierWeight;
7173
7228
  return { score, reasons };
@@ -7179,7 +7234,11 @@ async function assembleContext(options) {
7179
7234
  taskContext,
7180
7235
  cardLabels = [],
7181
7236
  tokenBudget = DEFAULT_TOKEN_BUDGET,
7182
- client: client2
7237
+ client: client2,
7238
+ graphWalkEnabled = true,
7239
+ queryExpansionEnabled = true,
7240
+ enableLlmReranking = false,
7241
+ rerankFn
7183
7242
  } = options;
7184
7243
  const assemblyId = generateAssemblyId();
7185
7244
  const manifest = {
@@ -7195,13 +7254,26 @@ async function assembleContext(options) {
7195
7254
  reference: { count: 0, tokens: 0 }
7196
7255
  }
7197
7256
  };
7198
- let candidates = [];
7199
- try {
7200
- const searchResult = await client2.searchMemoryEntities(workspaceId, taskContext, { project_id: projectId, limit: 30 });
7201
- if (searchResult.entities?.length > 0) {
7202
- candidates = searchResult.entities.map(mapToContextEntity);
7257
+ const candidates = [];
7258
+ const queries = queryExpansionEnabled ? expandQuery(taskContext) : [taskContext];
7259
+ const searchResults = await Promise.allSettled(queries.map((query) => client2.searchMemoryEntities(workspaceId, query, {
7260
+ project_id: projectId,
7261
+ limit: 30
7262
+ })));
7263
+ const candidateIds = new Set;
7264
+ for (const result of searchResults) {
7265
+ if (result.status !== "fulfilled")
7266
+ continue;
7267
+ if (result.value.entities?.length > 0) {
7268
+ for (const raw of result.value.entities) {
7269
+ const entity = mapToContextEntity(raw);
7270
+ if (!candidateIds.has(entity.id)) {
7271
+ candidateIds.add(entity.id);
7272
+ candidates.push(entity);
7273
+ }
7274
+ }
7203
7275
  }
7204
- } catch {}
7276
+ }
7205
7277
  if (candidates.length < 10 && projectId) {
7206
7278
  try {
7207
7279
  const listResult = await client2.listMemoryEntities({
@@ -7210,9 +7282,13 @@ async function assembleContext(options) {
7210
7282
  limit: 30
7211
7283
  });
7212
7284
  if (listResult.entities?.length > 0) {
7213
- const existingIds = new Set(candidates.map((c) => c.id));
7214
- const additional = listResult.entities.map(mapToContextEntity).filter((e) => !existingIds.has(e.id));
7215
- candidates.push(...additional);
7285
+ for (const raw of listResult.entities) {
7286
+ const entity = mapToContextEntity(raw);
7287
+ if (!candidateIds.has(entity.id)) {
7288
+ candidateIds.add(entity.id);
7289
+ candidates.push(entity);
7290
+ }
7291
+ }
7216
7292
  }
7217
7293
  } catch {}
7218
7294
  }
@@ -7224,9 +7300,33 @@ async function assembleContext(options) {
7224
7300
  limit: 20
7225
7301
  });
7226
7302
  if (wsResult.entities?.length > 0) {
7227
- const existingIds = new Set(candidates.map((c) => c.id));
7228
- const additional = wsResult.entities.map(mapToContextEntity).filter((e) => !existingIds.has(e.id));
7229
- candidates.push(...additional);
7303
+ for (const raw of wsResult.entities) {
7304
+ const entity = mapToContextEntity(raw);
7305
+ if (!candidateIds.has(entity.id)) {
7306
+ candidateIds.add(entity.id);
7307
+ candidates.push(entity);
7308
+ }
7309
+ }
7310
+ }
7311
+ } catch {}
7312
+ }
7313
+ let graphRelations = [];
7314
+ if (graphWalkEnabled && candidates.length > 0) {
7315
+ try {
7316
+ const seedCandidates = [...candidates].sort((a, b) => (b.rrf_score ?? 0) - (a.rrf_score ?? 0)).slice(0, GRAPH_WALK_SEED_COUNT);
7317
+ const seedIds = seedCandidates.map((c) => c.id);
7318
+ const walkResult = await discoverRelatedContext(client2, seedIds, GRAPH_WALK_MAX_DEPTH, GRAPH_WALK_MAX_ENTITIES, GRAPH_WALK_MIN_CONFIDENCE);
7319
+ graphRelations = walkResult.relations;
7320
+ const newEntityIds = walkResult.entities.filter((e) => !candidateIds.has(e.id)).map((e) => e.id);
7321
+ if (newEntityIds.length > 0) {
7322
+ const fetchResults = await Promise.allSettled(newEntityIds.map((id) => client2.getMemoryEntity(id)));
7323
+ for (const result of fetchResults) {
7324
+ if (result.status !== "fulfilled" || !result.value.entity)
7325
+ continue;
7326
+ const mapped = mapToContextEntity(result.value.entity);
7327
+ candidateIds.add(mapped.id);
7328
+ candidates.push(mapped);
7329
+ }
7230
7330
  }
7231
7331
  } catch {}
7232
7332
  }
@@ -7238,10 +7338,31 @@ async function assembleContext(options) {
7238
7338
  };
7239
7339
  }
7240
7340
  const scored = candidates.map((entity) => {
7241
- const { score, reasons } = computeRelevanceScore(entity, taskContext, cardLabels);
7341
+ const { score, reasons } = computeRelevanceScore(entity, taskContext, cardLabels, graphRelations.length > 0 ? graphRelations : undefined);
7242
7342
  return { entity, score, reasons };
7243
7343
  });
7244
7344
  scored.sort((a, b) => b.score - a.score);
7345
+ if (enableLlmReranking && rerankFn && scored.length >= RERANK_MIN_CANDIDATES) {
7346
+ const topN = scored.slice(0, RERANK_TOP_N);
7347
+ const scoreRange = topN[0].score - topN[topN.length - 1].score;
7348
+ if (scoreRange <= RERANK_CLUSTER_THRESHOLD) {
7349
+ try {
7350
+ const rerankCandidates = topN.map((s) => ({
7351
+ id: s.entity.id,
7352
+ title: s.entity.title,
7353
+ snippet: s.entity.content.slice(0, 200)
7354
+ }));
7355
+ const rerankedIds = await rerankFn(taskContext, rerankCandidates);
7356
+ const idOrder = new Map(rerankedIds.map((id, i) => [id, i]));
7357
+ topN.sort((a, b) => {
7358
+ const aIdx = idOrder.get(a.entity.id) ?? 999;
7359
+ const bIdx = idOrder.get(b.entity.id) ?? 999;
7360
+ return aIdx - bIdx;
7361
+ });
7362
+ scored.splice(0, topN.length, ...topN);
7363
+ } catch {}
7364
+ }
7365
+ }
7245
7366
  const procedureBudget = Math.floor(tokenBudget * PROCEDURE_BUDGET_FRACTION);
7246
7367
  const remainingBudget = tokenBudget - procedureBudget;
7247
7368
  const tierBudgets = {
@@ -7553,7 +7674,7 @@ async function recordContextFeedback(client2, cardId, sessionStatus, progressPer
7553
7674
  sessionAssemblyMap.delete(cardId);
7554
7675
  return { adjusted };
7555
7676
  }
7556
- var DEFAULT_TOKEN_BUDGET = 4000, MAX_TOKENS_PER_ENTITY = 500, MIN_RELEVANCE_THRESHOLD = 0.1, TIER_WEIGHTS, PROCEDURE_BUDGET_FRACTION = 0.15, TIER_BUDGET_ALLOCATION, MIN_REFERENCE_SLOTS = 3, manifestCache, MAX_CACHE_SIZE = 50, sessionAssemblyMap, MAX_SESSION_MAP_SIZE = 100;
7677
+ var DEFAULT_TOKEN_BUDGET = 4000, MAX_TOKENS_PER_ENTITY = 500, MIN_RELEVANCE_THRESHOLD = 0.1, TIER_WEIGHTS, PROCEDURE_BUDGET_FRACTION = 0.15, TIER_BUDGET_ALLOCATION, MIN_REFERENCE_SLOTS = 3, 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;
7557
7678
  var init_context_assembly = __esm(() => {
7558
7679
  init_dist();
7559
7680
  TIER_WEIGHTS = {
@@ -7566,6 +7687,33 @@ var init_context_assembly = __esm(() => {
7566
7687
  episode: 0.3,
7567
7688
  draft: 0.1
7568
7689
  };
7690
+ RELATION_BONUSES = {
7691
+ depends_on: 0.15,
7692
+ resolved_by: 0.2,
7693
+ relates_to: 0.1,
7694
+ implements: 0.15,
7695
+ blocks: 0.15,
7696
+ references: 0.1,
7697
+ extends: 0.1,
7698
+ caused_by: 0.15
7699
+ };
7700
+ QUERY_SYNONYMS = {
7701
+ auth: ["authentication", "authorization", "session"],
7702
+ authentication: ["auth", "session", "sign-in"],
7703
+ login: ["sign-in", "authentication", "session"],
7704
+ bug: ["error", "issue", "defect", "problem"],
7705
+ error: ["exception", "failure", "issue"],
7706
+ fix: ["resolve", "patch", "repair", "correct"],
7707
+ deploy: ["deployment", "release", "ship", "publish"],
7708
+ test: ["testing", "spec", "assertion", "verify"],
7709
+ config: ["configuration", "settings", "setup"],
7710
+ db: ["database", "storage", "persistence"],
7711
+ database: ["storage", "persistence", "data store"],
7712
+ api: ["endpoint", "route", "service"],
7713
+ ui: ["frontend", "component", "view"],
7714
+ perf: ["performance", "speed", "latency"],
7715
+ performance: ["speed", "latency", "optimization"]
7716
+ };
7569
7717
  manifestCache = new Map;
7570
7718
  sessionAssemblyMap = new Map;
7571
7719
  });