@vibekiln/cutline-mcp-cli 0.4.4 → 0.5.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.
@@ -7408,6 +7408,146 @@ var ACT_NAMES = {
7408
7408
  5: "Value Ranking",
7409
7409
  6: "Graduation"
7410
7410
  };
7411
+ function assessScopeExpansionIntent(params) {
7412
+ const task = (params.task_description || "").trim();
7413
+ const filePaths = params.file_paths || [];
7414
+ const reasons = [];
7415
+ let confidence = 0;
7416
+ const explicitScopePatterns = [
7417
+ /\b(scope increase|expand(?:ing)? scope|broaden scope|new scope)\b/i,
7418
+ /\b(seed|ingest|add)\b.{0,24}\b(graph|constraint|entity|entities)\b/i,
7419
+ /\b(new feature|new module|new domain|new subsystem)\b/i
7420
+ ];
7421
+ const hasExplicitScopeIntent = explicitScopePatterns.some((rx) => rx.test(task));
7422
+ if (hasExplicitScopeIntent) {
7423
+ confidence += 0.55;
7424
+ reasons.push("Task description includes explicit scope-expansion language.");
7425
+ }
7426
+ const hintedName = params.hinted_entity_name?.trim();
7427
+ const inferredName = hintedName || inferEntityNameFromTask(task);
7428
+ if (inferredName) {
7429
+ confidence += 0.12;
7430
+ reasons.push("Potential new entity name detected from task context.");
7431
+ }
7432
+ if (filePaths.length > 0) {
7433
+ const dirHints = new Set(filePaths.map((p) => p.split("/").filter(Boolean)).filter((parts) => parts.length > 1).map((parts) => parts[parts.length - 2].toLowerCase()).filter((v) => v.length >= 3));
7434
+ if (dirHints.size >= 2) {
7435
+ confidence += 0.08;
7436
+ reasons.push("Multiple code areas touched, indicating potential boundary expansion.");
7437
+ }
7438
+ }
7439
+ if (inferredName && params.known_entity_names?.length) {
7440
+ const norm = normalizeName(inferredName);
7441
+ const known = params.known_entity_names.some((n) => normalizeName(n) === norm);
7442
+ if (!known) {
7443
+ confidence += 0.25;
7444
+ reasons.push("Entity candidate does not match existing graph entities.");
7445
+ } else {
7446
+ confidence -= 0.1;
7447
+ reasons.push("Entity candidate already exists in graph.");
7448
+ }
7449
+ }
7450
+ confidence = Math.max(0, Math.min(1, confidence));
7451
+ const triggered = confidence >= 0.6 || hasExplicitScopeIntent;
7452
+ const recommended_tool_calls = triggered ? [
7453
+ "constraints_learn(product_id, name, entity_type, description, ...)",
7454
+ "graph_bind_codebase(product_id, file_paths)",
7455
+ "graph_bind_confirm(product_id, entity_id, file_patterns)",
7456
+ "graph_metrics(product_id)"
7457
+ ] : [];
7458
+ return {
7459
+ triggered,
7460
+ confidence: Math.round(confidence * 100) / 100,
7461
+ reasons,
7462
+ candidate_name: inferredName,
7463
+ recommended_tool_calls
7464
+ };
7465
+ }
7466
+ function inferEntityNameFromTask(task) {
7467
+ if (!task)
7468
+ return void 0;
7469
+ const match = task.match(/\b(?:add|build|create|introduce|expand(?:ing)?\s+scope(?:\s+for|\s+to)?)\s+(?:a|an|the)?\s*([a-zA-Z0-9][a-zA-Z0-9 _-]{2,60})/i);
7470
+ if (!match?.[1])
7471
+ return void 0;
7472
+ const candidate = match[1].trim().replace(/\s{2,}/g, " ");
7473
+ if (candidate.length < 3)
7474
+ return void 0;
7475
+ return candidate;
7476
+ }
7477
+ function normalizeName(value) {
7478
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
7479
+ }
7480
+ async function seedScopeEntityFromAuto(params) {
7481
+ const { product_id, name: name2, entity_type, description, parent_id, tags, similarity_threshold } = params;
7482
+ const slug = normalizeName(name2).slice(0, 40);
7483
+ const entityId = `${entity_type}:${slug}`;
7484
+ const now = /* @__PURE__ */ new Date();
7485
+ const entity = {
7486
+ id: entityId,
7487
+ type: entity_type,
7488
+ name: name2,
7489
+ aliases: tags ?? [],
7490
+ description,
7491
+ source_id: "ide",
7492
+ source_type: "ide",
7493
+ ingested_at: now
7494
+ };
7495
+ await addEntity(product_id, entity);
7496
+ if (parent_id) {
7497
+ const edgeType = entity_type === "component" ? "REQUIRES" : "DEPENDS_ON";
7498
+ await addEdges(product_id, [{
7499
+ id: `edge:auto_scope:${parent_id}:${entityId}`,
7500
+ source_id: parent_id,
7501
+ source_type: "feature",
7502
+ target_id: entityId,
7503
+ target_type: entity_type,
7504
+ edge_type: edgeType
7505
+ }]);
7506
+ }
7507
+ let embeddingGenerated = false;
7508
+ try {
7509
+ const embedding = await generateEntityEmbedding(entity);
7510
+ if (embedding.some((v) => v !== 0)) {
7511
+ await updateEntityEmbedding(product_id, entityId, embedding);
7512
+ entity.embedding = embedding;
7513
+ embeddingGenerated = true;
7514
+ }
7515
+ } catch {
7516
+ }
7517
+ const similar = embeddingGenerated ? await findSimilarEntities(product_id, entity, {
7518
+ threshold: similarity_threshold ?? 0.65
7519
+ }) : [];
7520
+ let propagatedCount = 0;
7521
+ for (const match of similar) {
7522
+ const propagated = await propagateConstraints(product_id, match.entity.id, entityId, match.similarity);
7523
+ propagatedCount += propagated.length;
7524
+ }
7525
+ try {
7526
+ const [entities, edges, constraints, bindings] = await Promise.all([
7527
+ getAllEntities(product_id),
7528
+ getAllEdges(product_id),
7529
+ getAllNodes(product_id),
7530
+ getAllBindings(product_id)
7531
+ ]);
7532
+ const metrics = computeMetricsFromGraph(entities, edges, constraints, bindings);
7533
+ await updateGraphMetadata(product_id, {
7534
+ last_build: now,
7535
+ entity_count: entities.length,
7536
+ edge_count: edges.length,
7537
+ constraint_count: constraints.length,
7538
+ binding_count: bindings.length,
7539
+ sources: ["ide", "deep_dive_pipeline"],
7540
+ metrics
7541
+ });
7542
+ await recordScoreSnapshot(product_id, metrics, "constraint_learn", name2);
7543
+ } catch {
7544
+ }
7545
+ return {
7546
+ entity_id: entityId,
7547
+ propagated_count: propagatedCount,
7548
+ similar_count: similar.length
7549
+ };
7550
+ }
7411
7551
  var server = new Server({
7412
7552
  name: "cutline",
7413
7553
  version: "0.2.0"
@@ -7862,7 +8002,14 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
7862
8002
  },
7863
8003
  max_constraints: { type: "number", description: "Max constraints to return (default: 5)" },
7864
8004
  use_semantic: { type: "boolean", description: "Use semantic search if embeddings available (default: true)" },
7865
- phase: { type: "string", enum: ["test_spec", "functional", "security", "performance", "economics", "full", "auto"], description: "RGR phase filter. 'auto' uses complexity heuristic. Default: 'full'" }
8005
+ phase: { type: "string", enum: ["test_spec", "functional", "security", "performance", "economics", "full", "auto"], description: "RGR phase filter. 'auto' uses complexity heuristic. Default: 'full'" },
8006
+ auto_scope_expand: { type: "boolean", description: "If true, auto-seed a new graph entity when scope expansion intent is confidently detected and entity fields are provided." },
8007
+ scope_entity_name: { type: "string", description: "Optional explicit entity name to seed during scope expansion (for example 'Vibe Check Extension')." },
8008
+ scope_entity_type: { type: "string", enum: ["feature", "component", "data_type"], description: "Entity type for auto scope expansion." },
8009
+ scope_entity_description: { type: "string", description: "1-2 sentence description of the new entity to seed." },
8010
+ scope_entity_tags: { type: "array", items: { type: "string" }, description: "Optional tags/aliases for the new entity." },
8011
+ scope_parent_id: { type: "string", description: "Optional parent graph entity ID for linking this scope expansion." },
8012
+ scope_similarity_threshold: { type: "number", description: "Optional similarity threshold for propagated constraints (default: 0.65)." }
7866
8013
  },
7867
8014
  required: ["product_id"]
7868
8015
  }
@@ -9035,7 +9182,7 @@ Meta: ${JSON.stringify(output.meta)}` }
9035
9182
  return void 0;
9036
9183
  };
9037
9184
  var detectFramework = detectFramework2;
9038
- const { product_id, file_paths, code_snippet, task_description, mode = "auto", max_constraints = 5, use_semantic = true, phase: autoPhase = "full" } = args;
9185
+ const { product_id, file_paths, code_snippet, task_description, mode = "auto", max_constraints = 5, use_semantic = true, phase: autoPhase = "full", auto_scope_expand = false, scope_entity_name, scope_entity_type, scope_entity_description, scope_entity_tags, scope_parent_id, scope_similarity_threshold } = args;
9039
9186
  if (!product_id) {
9040
9187
  throw new McpError(ErrorCode.InvalidParams, "product_id is required");
9041
9188
  }
@@ -9061,6 +9208,34 @@ Meta: ${JSON.stringify(output.meta)}` }
9061
9208
  };
9062
9209
  }
9063
9210
  const analysis = analyzeFileContext(fileContext);
9211
+ let knownEntityNames = [];
9212
+ if (task_description || scope_entity_name) {
9213
+ try {
9214
+ const entities = await getAllEntities(product_id);
9215
+ knownEntityNames = entities.map((e) => e.name);
9216
+ } catch {
9217
+ }
9218
+ }
9219
+ const scopeExpansion = assessScopeExpansionIntent({
9220
+ task_description,
9221
+ file_paths,
9222
+ known_entity_names: knownEntityNames,
9223
+ hinted_entity_name: scope_entity_name
9224
+ });
9225
+ let scopeSeedResult;
9226
+ const hasSeedPayload = !!scope_entity_name && !!scope_entity_type && !!scope_entity_description;
9227
+ const shouldAutoSeed = auto_scope_expand && scopeExpansion.triggered && hasSeedPayload;
9228
+ if (shouldAutoSeed) {
9229
+ scopeSeedResult = await seedScopeEntityFromAuto({
9230
+ product_id,
9231
+ name: scope_entity_name,
9232
+ entity_type: scope_entity_type,
9233
+ description: scope_entity_description,
9234
+ parent_id: scope_parent_id,
9235
+ tags: scope_entity_tags,
9236
+ similarity_threshold: scope_similarity_threshold
9237
+ });
9238
+ }
9064
9239
  const actualMode = mode === "auto" ? analysis.suggested_mode : mode;
9065
9240
  if (actualMode === "silent") {
9066
9241
  return {
@@ -9074,7 +9249,9 @@ Meta: ${JSON.stringify(output.meta)}` }
9074
9249
  mode: "silent",
9075
9250
  detected_domains: analysis.detected_domains,
9076
9251
  signal_strength: Math.round(analysis.confidence * 100) / 100,
9077
- keywords_detected: analysis.signal.keywords?.slice(0, 5)
9252
+ keywords_detected: analysis.signal.keywords?.slice(0, 5),
9253
+ scope_expansion: scopeExpansion,
9254
+ scope_seeded: scopeSeedResult || void 0
9078
9255
  }
9079
9256
  })
9080
9257
  }]
@@ -9229,6 +9406,12 @@ Meta: ${JSON.stringify(output.meta)}` }
9229
9406
  \u2192 You may need to prioritize one over the other.`;
9230
9407
  }).join("\n\n");
9231
9408
  }
9409
+ if (scopeExpansion.triggered) {
9410
+ const recommendation = shouldAutoSeed ? `\u2705 Scope expansion detected and entity seeded: ${scopeSeedResult?.entity_id ?? "(created)"}` : `\u{1F9ED} Scope expansion detected. Recommended next tool: constraints_learn(...)`;
9411
+ humanReadable += `
9412
+
9413
+ ${recommendation}`;
9414
+ }
9232
9415
  }
9233
9416
  return {
9234
9417
  content: [{
@@ -9249,7 +9432,9 @@ Meta: ${JSON.stringify(output.meta)}` }
9249
9432
  used_semantic: use_semantic && finalResults.some((r) => r.match_type === "semantic" || r.match_type === "hybrid"),
9250
9433
  used_category_prefilter: usePreFilter,
9251
9434
  phase: autoPhase,
9252
- rgr_plan: autoRgrPlan || void 0
9435
+ rgr_plan: autoRgrPlan || void 0,
9436
+ scope_expansion: scopeExpansion,
9437
+ scope_seeded: scopeSeedResult || void 0
9253
9438
  }
9254
9439
  })
9255
9440
  }]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibekiln/cutline-mcp-cli",
3
- "version": "0.4.4",
3
+ "version": "0.5.0",
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",