@vibekiln/cutline-mcp-cli 0.8.0 → 0.9.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.
@@ -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
  }
@@ -4442,6 +4455,17 @@ var UNIVERSAL_CONSTRAINTS = [
4442
4455
  file_patterns: ["**/auth/**", "**/api/session*", "**/api/login*", "**/middleware/**", "**/config/**"],
4443
4456
  framework: "baseline"
4444
4457
  },
4458
+ {
4459
+ id_suffix: "password_reset_token_expiry",
4460
+ category: "security",
4461
+ summary: "Password reset tokens/links MUST be single-use and time-limited. Expired or reused reset tokens must fail closed.",
4462
+ keywords: ["password-reset", "token", "expiry", "single-use", "account-takeover"],
4463
+ severity: "critical",
4464
+ action: "Enforce reset token TTL and one-time use semantics. Invalidate outstanding reset tokens after successful password change.",
4465
+ checklist_ref: "A10",
4466
+ file_patterns: ["**/auth/**", "**/api/reset*", "**/api/forgot*", "**/api/password*"],
4467
+ framework: "baseline"
4468
+ },
4445
4469
  {
4446
4470
  id_suffix: "backup_testing",
4447
4471
  category: "stability",
@@ -4687,6 +4711,83 @@ var RELIABILITY_CONSTRAINTS = [
4687
4711
  checklist_ref: "J10",
4688
4712
  file_patterns: ["**/utils/**", "**/lib/**", "**/services/**", "**/api/**"],
4689
4713
  framework: "baseline"
4714
+ },
4715
+ {
4716
+ id_suffix: "startup_env_schema_validation",
4717
+ category: "stability",
4718
+ summary: "Runtime environment variables MUST be validated against an explicit schema at startup, and app boot must fail fast on invalid critical config.",
4719
+ keywords: ["env", "startup", "schema", "validation", "fail-fast", "configuration"],
4720
+ severity: "critical",
4721
+ action: "Create startup env schema checks for server and public runtime variables. Crash startup when required production config is missing or malformed.",
4722
+ checklist_ref: "J11",
4723
+ file_patterns: ["**/config/**", "**/env/**", "**/server/**", "**/.env*"],
4724
+ framework: "baseline"
4725
+ },
4726
+ {
4727
+ id_suffix: "ui_error_boundaries",
4728
+ category: "stability",
4729
+ summary: "Critical user-facing UI surfaces MUST be wrapped in error boundaries to prevent full-app white screens during component crashes.",
4730
+ keywords: ["error-boundary", "react", "ui", "crash", "fallback", "reliability"],
4731
+ severity: "warning",
4732
+ action: "Add error boundaries around major routes/layouts and high-risk widgets. Provide fallback UI and telemetry when boundaries catch errors.",
4733
+ checklist_ref: "J12",
4734
+ file_patterns: ["**/components/**", "**/pages/**", "**/app/**", "**/*error*"],
4735
+ framework: "baseline"
4736
+ },
4737
+ {
4738
+ id_suffix: "health_readiness_endpoints",
4739
+ category: "stability",
4740
+ summary: "Services MUST expose dedicated liveness and readiness endpoints (e.g., /api/health and /api/readyz) for monitoring and deployment safety checks.",
4741
+ keywords: ["health", "readyz", "liveness", "readiness", "monitoring", "probe"],
4742
+ severity: "critical",
4743
+ action: "Implement lightweight health/readiness endpoints with no sensitive payload data. Integrate endpoints into uptime monitoring and deployment probes.",
4744
+ checklist_ref: "J13",
4745
+ file_patterns: ["**/api/health*", "**/api/ready*", "**/monitoring/**", "**/deploy/**"],
4746
+ framework: "baseline"
4747
+ },
4748
+ {
4749
+ id_suffix: "structured_production_logging",
4750
+ category: "stability",
4751
+ summary: "Production logging MUST be structured and include correlation/request IDs, with automatic redaction of tokens, API keys, and credentials.",
4752
+ keywords: ["logging", "structured", "correlation-id", "request-id", "redaction", "observability"],
4753
+ severity: "critical",
4754
+ action: "Emit JSON logs in production and propagate request/correlation IDs across handlers and background jobs. Apply secret-redaction middleware before log emission.",
4755
+ checklist_ref: "J14",
4756
+ file_patterns: ["**/logger/**", "**/api/**", "**/middleware/**", "**/monitoring/**"],
4757
+ framework: "baseline"
4758
+ },
4759
+ {
4760
+ id_suffix: "typed_ai_generated_code",
4761
+ category: "stability",
4762
+ summary: "AI-generated production code MUST use TypeScript (or equivalent static typing) and pass strict type-check gates before merge.",
4763
+ keywords: ["ai-generated", "typescript", "typing", "typecheck", "ci-gate", "quality"],
4764
+ severity: "warning",
4765
+ action: "Require strict type-check in CI for generated code changes and block merges on type errors. Prefer typed templates for agent-generated modules.",
4766
+ checklist_ref: "J15",
4767
+ file_patterns: ["**/*.ts", "**/*.tsx", "**/ai/**", "**/agents/**", "**/.github/**"],
4768
+ framework: "baseline"
4769
+ },
4770
+ {
4771
+ id_suffix: "async_email_dispatch",
4772
+ category: "performance",
4773
+ summary: "Transactional emails SHOULD be dispatched asynchronously (queue/background workers) so request handlers do not block on provider latency.",
4774
+ keywords: ["email", "async", "queue", "worker", "latency", "smtp", "request-path"],
4775
+ severity: "warning",
4776
+ action: "Move email delivery to async jobs/queues and return request responses before provider completion. Add retry/backoff for transient send failures.",
4777
+ checklist_ref: "J16",
4778
+ file_patterns: ["**/api/**", "**/jobs/**", "**/workers/**", "**/email/**", "**/notifications/**"],
4779
+ framework: "baseline"
4780
+ },
4781
+ {
4782
+ id_suffix: "cdn_media_delivery",
4783
+ category: "performance",
4784
+ summary: "User-uploaded media MUST be stored in object storage and delivered through CDN caching, not served directly from app servers.",
4785
+ keywords: ["cdn", "media", "uploads", "object-storage", "cache", "bandwidth"],
4786
+ severity: "warning",
4787
+ action: "Store uploads in object storage (S3/GCS/etc.) and serve via CDN URLs with cache headers. Keep app servers out of large media delivery paths.",
4788
+ checklist_ref: "J17",
4789
+ file_patterns: ["**/api/upload*", "**/storage/**", "**/media/**", "**/cdn/**", "**/config/**"],
4790
+ framework: "baseline"
4690
4791
  }
4691
4792
  ];
4692
4793
  var IAC_CONSTRAINTS = [
@@ -7489,6 +7590,59 @@ function inferEntityNameFromTask(task) {
7489
7590
  function normalizeName(value) {
7490
7591
  return value.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
7491
7592
  }
7593
+ function canonicalizeBindings(bindings, entities) {
7594
+ const entityTypes = new Map(entities.map((e) => [e.id, e.type]));
7595
+ const normalized = [];
7596
+ for (const raw of bindings) {
7597
+ const legacyRaw = raw;
7598
+ const entityId = typeof raw.entity_id === "string" ? raw.entity_id : typeof legacyRaw.entityId === "string" ? legacyRaw.entityId : void 0;
7599
+ if (!entityId || !entityTypes.has(entityId))
7600
+ continue;
7601
+ if (!Array.isArray(raw.file_patterns) || raw.file_patterns.length === 0)
7602
+ continue;
7603
+ const filePatterns = raw.file_patterns.filter((v) => typeof v === "string");
7604
+ if (filePatterns.length === 0)
7605
+ continue;
7606
+ normalized.push({
7607
+ id: typeof raw.id === "string" ? raw.id : `bind:${entityId}`,
7608
+ entity_id: entityId,
7609
+ entity_type: typeof raw.entity_type === "string" ? raw.entity_type : entityTypes.get(entityId),
7610
+ file_patterns: filePatterns,
7611
+ confidence: typeof raw.confidence === "number" ? raw.confidence : 0.5,
7612
+ manual: Boolean(raw.manual)
7613
+ });
7614
+ }
7615
+ return normalized;
7616
+ }
7617
+ function rankEntitiesForFilePath(filePath, entities) {
7618
+ const normalizedPath = filePath.toLowerCase().replace(/\\/g, "/");
7619
+ const fileStem = normalizedPath.split("/").pop()?.replace(/\.[^.]+$/, "") ?? "";
7620
+ const pathTokens = new Set(normalizedPath.replace(/\.[^.]+$/, "").split(/[\/._-]/).filter((t) => t.length >= 3));
7621
+ const scored = entities.map((entity) => {
7622
+ const labels = [entity.name, ...entity.aliases].filter(Boolean);
7623
+ if (labels.length === 0)
7624
+ return { entity, score: 0 };
7625
+ let score = 0;
7626
+ for (const label of labels) {
7627
+ const labelNorm = normalizeName(label);
7628
+ const labelTokens = labelNorm.split("_").filter((t) => t.length >= 3);
7629
+ if (labelTokens.length === 0)
7630
+ continue;
7631
+ const matchedTokens = labelTokens.filter((t) => pathTokens.has(t)).length;
7632
+ if (matchedTokens > 0) {
7633
+ score = Math.max(score, matchedTokens / labelTokens.length);
7634
+ }
7635
+ if (normalizedPath.includes(labelNorm.replace(/_/g, "/"))) {
7636
+ score = Math.max(score, 0.9);
7637
+ }
7638
+ if (fileStem === labelNorm || fileStem.includes(labelNorm) || labelNorm.includes(fileStem)) {
7639
+ score = Math.max(score, 0.8);
7640
+ }
7641
+ }
7642
+ return { entity, score };
7643
+ }).filter((item) => item.score >= 0.45).sort((a, b) => b.score - a.score);
7644
+ return scored.map((item) => item.entity);
7645
+ }
7492
7646
  async function seedScopeEntityFromAuto(params) {
7493
7647
  const { product_id, name: name2, entity_type, description, parent_id, tags, similarity_threshold } = params;
7494
7648
  const slug = normalizeName(name2).slice(0, 40);
@@ -9557,7 +9711,6 @@ ${recommendation}`;
9557
9711
  used_category_prefilter: usePreFilter,
9558
9712
  phase: autoPhase,
9559
9713
  rgr_plan: autoRgrPlan || void 0,
9560
- requested_outcome: requestedOutcome,
9561
9714
  scope_expansion: scopeExpansion,
9562
9715
  scope_seeded: scopeSeedResult || void 0,
9563
9716
  requested_outcome: requestedOutcome
@@ -10414,20 +10567,22 @@ ${JSON.stringify(metrics, null, 2)}` }
10414
10567
  }]
10415
10568
  };
10416
10569
  }
10417
- const rgrTraverser = new GraphTraverser(rgrEntities, rgrEdges, rgrConstraints, rgrBindings);
10570
+ const normalizedBindings = canonicalizeBindings(rgrBindings, rgrEntities);
10571
+ const rgrTraverser = new GraphTraverser(rgrEntities, rgrEdges, rgrConstraints, normalizedBindings);
10418
10572
  const rgrMatched = rgrTraverser.findEntitiesByFilePath(file_path);
10419
10573
  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
- }
10574
+ const reverseMatchedIds = findBoundEntities(file_path, normalizedBindings);
10575
+ for (const id of reverseMatchedIds) {
10576
+ const found = rgrEntities.find((entity) => entity.id === id);
10577
+ if (found) {
10578
+ rgrMatched.push(found);
10428
10579
  }
10429
10580
  }
10430
10581
  }
10582
+ if (rgrMatched.length === 0) {
10583
+ const rankedFallback = rankEntitiesForFilePath(file_path, rgrEntities).slice(0, 2);
10584
+ rgrMatched.push(...rankedFallback);
10585
+ }
10431
10586
  if (rgrMatched.length === 0) {
10432
10587
  const governance2 = buildGovernanceEnvelope({
10433
10588
  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.9.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",