@vibekiln/cutline-mcp-cli 0.8.0 → 0.8.1

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.
@@ -2291,6 +2291,19 @@ function healBindings(entities, bindings, filePaths) {
2291
2291
  total_entities: entities.length
2292
2292
  };
2293
2293
  }
2294
+ function findBoundEntities(filePath, bindings) {
2295
+ const normalized = filePath.replace(/\\/g, "/");
2296
+ const matched = /* @__PURE__ */ new Set();
2297
+ for (const binding of bindings) {
2298
+ for (const pattern of binding.file_patterns) {
2299
+ if (globMatch(normalized, pattern)) {
2300
+ matched.add(binding.entity_id);
2301
+ break;
2302
+ }
2303
+ }
2304
+ }
2305
+ return [...matched];
2306
+ }
2294
2307
  function tokenize(name2) {
2295
2308
  return name2.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length > 2);
2296
2309
  }
@@ -7489,6 +7502,59 @@ function inferEntityNameFromTask(task) {
7489
7502
  function normalizeName(value) {
7490
7503
  return value.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
7491
7504
  }
7505
+ function canonicalizeBindings(bindings, entities) {
7506
+ const entityTypes = new Map(entities.map((e) => [e.id, e.type]));
7507
+ const normalized = [];
7508
+ for (const raw of bindings) {
7509
+ const legacyRaw = raw;
7510
+ const entityId = typeof raw.entity_id === "string" ? raw.entity_id : typeof legacyRaw.entityId === "string" ? legacyRaw.entityId : void 0;
7511
+ if (!entityId || !entityTypes.has(entityId))
7512
+ continue;
7513
+ if (!Array.isArray(raw.file_patterns) || raw.file_patterns.length === 0)
7514
+ continue;
7515
+ const filePatterns = raw.file_patterns.filter((v) => typeof v === "string");
7516
+ if (filePatterns.length === 0)
7517
+ continue;
7518
+ normalized.push({
7519
+ id: typeof raw.id === "string" ? raw.id : `bind:${entityId}`,
7520
+ entity_id: entityId,
7521
+ entity_type: typeof raw.entity_type === "string" ? raw.entity_type : entityTypes.get(entityId),
7522
+ file_patterns: filePatterns,
7523
+ confidence: typeof raw.confidence === "number" ? raw.confidence : 0.5,
7524
+ manual: Boolean(raw.manual)
7525
+ });
7526
+ }
7527
+ return normalized;
7528
+ }
7529
+ function rankEntitiesForFilePath(filePath, entities) {
7530
+ const normalizedPath = filePath.toLowerCase().replace(/\\/g, "/");
7531
+ const fileStem = normalizedPath.split("/").pop()?.replace(/\.[^.]+$/, "") ?? "";
7532
+ const pathTokens = new Set(normalizedPath.replace(/\.[^.]+$/, "").split(/[\/._-]/).filter((t) => t.length >= 3));
7533
+ const scored = entities.map((entity) => {
7534
+ const labels = [entity.name, ...entity.aliases].filter(Boolean);
7535
+ if (labels.length === 0)
7536
+ return { entity, score: 0 };
7537
+ let score = 0;
7538
+ for (const label of labels) {
7539
+ const labelNorm = normalizeName(label);
7540
+ const labelTokens = labelNorm.split("_").filter((t) => t.length >= 3);
7541
+ if (labelTokens.length === 0)
7542
+ continue;
7543
+ const matchedTokens = labelTokens.filter((t) => pathTokens.has(t)).length;
7544
+ if (matchedTokens > 0) {
7545
+ score = Math.max(score, matchedTokens / labelTokens.length);
7546
+ }
7547
+ if (normalizedPath.includes(labelNorm.replace(/_/g, "/"))) {
7548
+ score = Math.max(score, 0.9);
7549
+ }
7550
+ if (fileStem === labelNorm || fileStem.includes(labelNorm) || labelNorm.includes(fileStem)) {
7551
+ score = Math.max(score, 0.8);
7552
+ }
7553
+ }
7554
+ return { entity, score };
7555
+ }).filter((item) => item.score >= 0.45).sort((a, b) => b.score - a.score);
7556
+ return scored.map((item) => item.entity);
7557
+ }
7492
7558
  async function seedScopeEntityFromAuto(params) {
7493
7559
  const { product_id, name: name2, entity_type, description, parent_id, tags, similarity_threshold } = params;
7494
7560
  const slug = normalizeName(name2).slice(0, 40);
@@ -9557,7 +9623,6 @@ ${recommendation}`;
9557
9623
  used_category_prefilter: usePreFilter,
9558
9624
  phase: autoPhase,
9559
9625
  rgr_plan: autoRgrPlan || void 0,
9560
- requested_outcome: requestedOutcome,
9561
9626
  scope_expansion: scopeExpansion,
9562
9627
  scope_seeded: scopeSeedResult || void 0,
9563
9628
  requested_outcome: requestedOutcome
@@ -10414,20 +10479,22 @@ ${JSON.stringify(metrics, null, 2)}` }
10414
10479
  }]
10415
10480
  };
10416
10481
  }
10417
- const rgrTraverser = new GraphTraverser(rgrEntities, rgrEdges, rgrConstraints, rgrBindings);
10482
+ const normalizedBindings = canonicalizeBindings(rgrBindings, rgrEntities);
10483
+ const rgrTraverser = new GraphTraverser(rgrEntities, rgrEdges, rgrConstraints, normalizedBindings);
10418
10484
  const rgrMatched = rgrTraverser.findEntitiesByFilePath(file_path);
10419
10485
  if (rgrMatched.length === 0) {
10420
- const segs = file_path.split("/").pop()?.replace(/\.[^.]+$/, "")?.split(/[-_]/) ?? [];
10421
- for (const seg of segs) {
10422
- if (seg.length > 2) {
10423
- const found = rgrTraverser.findEntityByName(seg);
10424
- if (found) {
10425
- rgrMatched.push(found);
10426
- break;
10427
- }
10486
+ const reverseMatchedIds = findBoundEntities(file_path, normalizedBindings);
10487
+ for (const id of reverseMatchedIds) {
10488
+ const found = rgrEntities.find((entity) => entity.id === id);
10489
+ if (found) {
10490
+ rgrMatched.push(found);
10428
10491
  }
10429
10492
  }
10430
10493
  }
10494
+ if (rgrMatched.length === 0) {
10495
+ const rankedFallback = rankEntitiesForFilePath(file_path, rgrEntities).slice(0, 2);
10496
+ rgrMatched.push(...rankedFallback);
10497
+ }
10431
10498
  if (rgrMatched.length === 0) {
10432
10499
  const governance2 = buildGovernanceEnvelope({
10433
10500
  decision: "revise",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibekiln/cutline-mcp-cli",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "CLI and MCP servers for Cutline — authenticate, then run constraint-aware MCP servers in Cursor or any MCP client.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",