@vibekiln/cutline-mcp-cli 0.4.3 → 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.
package/dist/commands/init.js
CHANGED
|
@@ -94,7 +94,7 @@ Skip: Pure styling, docs, formatting, non-security dep bumps.
|
|
|
94
94
|
## Magic Phrase
|
|
95
95
|
|
|
96
96
|
If the user invokes Cutline naturally (for example: **"use cutline"**, **"use cutline to..."**, **"using cutline..."**, **"with cutline..."**), route automatically based on tier and intent:
|
|
97
|
-
- Free/default intent: run \`code_audit(project_root)\`
|
|
97
|
+
- Free/default intent: run \`code_audit(project_root)\` (aka "security vibe check")
|
|
98
98
|
- Premium product-linked intent: run \`engineering_audit(product_id, project_root)\`
|
|
99
99
|
- Feature implementation intent: run \`rgr_plan(...)\` then \`constraints_auto(...)\`
|
|
100
100
|
`;
|
|
@@ -109,7 +109,7 @@ alwaysApply: true
|
|
|
109
109
|
|
|
110
110
|
# Cutline Constraints
|
|
111
111
|
|
|
112
|
-
Run \`code_audit(project_root)\` before major implementations to check constraint coverage.
|
|
112
|
+
Run \`code_audit(project_root)\` before major implementations to run a security vibe check on constraint coverage.
|
|
113
113
|
|
|
114
114
|
Severity levels:
|
|
115
115
|
- **CRITICAL**: Must address before proceeding
|
|
@@ -184,7 +184,7 @@ Skip for: pure styling, docs, formatting, non-security dep bumps.
|
|
|
184
184
|
## Magic Phrase
|
|
185
185
|
|
|
186
186
|
If the user invokes Cutline naturally (for example: **"use cutline"**, **"use cutline to..."**, **"using cutline..."**, **"with cutline..."**), route automatically based on tier and intent:
|
|
187
|
-
- Free/default intent: \`code_audit(project_root)\`
|
|
187
|
+
- Free/default intent: \`code_audit(project_root)\` (aka "security vibe check")
|
|
188
188
|
- Premium product-linked intent: \`engineering_audit(product_id, project_root)\`
|
|
189
189
|
- Feature implementation intent: \`rgr_plan(...)\` then \`constraints_auto(...)\`
|
|
190
190
|
`;
|
package/dist/commands/setup.js
CHANGED
|
@@ -392,8 +392,8 @@ export async function setupCommand(options) {
|
|
|
392
392
|
{ cmd: 'use cutline', desc: 'Magic phrase (also works with "use cutline to...", "using cutline...", "with cutline...") — Cutline infers intent and routes to the right flow' },
|
|
393
393
|
{ cmd: 'Run a deep dive on my product idea', desc: 'Pre-mortem analysis — risks, assumptions, experiments' },
|
|
394
394
|
{ cmd: 'Plan this feature with constraints from my product', desc: 'RGR plan — constraint-aware implementation roadmap' },
|
|
395
|
-
{ cmd: 'Run a
|
|
396
|
-
{ cmd: 'Run an engineering
|
|
395
|
+
{ cmd: 'Run a security vibe check on this codebase', desc: 'Free security vibe check (`code_audit`) — security, reliability, and scalability (generic, not product-linked)' },
|
|
396
|
+
{ cmd: 'Run an engineering vibe check for my product', desc: 'Premium deep vibe check (`engineering_audit`) — product-linked analysis + RGR remediation plan' },
|
|
397
397
|
{ cmd: 'Check constraints for src/api/upload.ts', desc: 'Get NFR boundaries for a specific file' },
|
|
398
398
|
{ cmd: 'Generate .cutline.md for my product', desc: 'Write the constraint routing engine' },
|
|
399
399
|
{ cmd: 'What does my persona think about X?', desc: 'AI persona feedback on features' },
|
|
@@ -407,7 +407,7 @@ export async function setupCommand(options) {
|
|
|
407
407
|
else {
|
|
408
408
|
const items = [
|
|
409
409
|
{ cmd: 'use cutline', desc: 'Magic phrase (also works with "use cutline to...", "using cutline...", "with cutline...") — Cutline routes to the highest-value free flow for your intent' },
|
|
410
|
-
{ cmd: 'Run a
|
|
410
|
+
{ cmd: 'Run a security vibe check on this codebase', desc: 'Free security vibe check (`code_audit`) — security, reliability, and scalability scan (3/month free)' },
|
|
411
411
|
{ cmd: 'Explore a product idea', desc: 'Free 6-act discovery flow to identify pain points and opportunities' },
|
|
412
412
|
{ cmd: 'Continue my exploration session', desc: 'Resume and refine an existing free exploration conversation' },
|
|
413
413
|
];
|
|
@@ -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
|
}
|
|
@@ -8106,7 +8253,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
8106
8253
|
},
|
|
8107
8254
|
{
|
|
8108
8255
|
name: "code_audit",
|
|
8109
|
-
description: "\u{1F513} FREE -
|
|
8256
|
+
description: "\u{1F513} FREE - Security vibe check (code audit). Evaluates your codebase against a stack-aware constraint graph covering security, reliability, and scalability. No deep dive or product_id required \u2014 just point at your codebase. Shows aggregate readiness scores and top critical findings; detailed analysis and remediation require Premium. Requires a Cutline account (free). 3 scans/month.",
|
|
8110
8257
|
inputSchema: {
|
|
8111
8258
|
type: "object",
|
|
8112
8259
|
properties: {
|
|
@@ -8119,7 +8266,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
8119
8266
|
},
|
|
8120
8267
|
{
|
|
8121
8268
|
name: "engineering_audit",
|
|
8122
|
-
description: "\u{1F512} PREMIUM - Engineering audit for your product. Security-focused deep audit that scans local code, cross-references the product constraint graph, extracts security gaps, builds an RGR remediation plan, and optionally queues a deep dive job.",
|
|
8269
|
+
description: "\u{1F512} PREMIUM - Engineering vibe check (engineering audit) for your product. Security-focused deep audit that scans local code, cross-references the product constraint graph, extracts security gaps, builds an RGR remediation plan, and optionally queues a deep dive job.",
|
|
8123
8270
|
inputSchema: {
|
|
8124
8271
|
type: "object",
|
|
8125
8272
|
properties: {
|
|
@@ -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