@vibecheck-ai/mcp 24.6.11 → 25.0.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.
@@ -1,12 +1,12 @@
1
1
  import { createRequire } from 'module';
2
2
  import { fileURLToPath } from 'url';
3
- import * as path5__default from 'path';
4
- import path5__default__default, { dirname } from 'path';
3
+ import * as path6__default from 'path';
4
+ import path6__default__default, { dirname } from 'path';
5
5
  import { FrameworkPackEngine } from './chunk-MUP4JXOF.js';
6
6
  import { LogicGapEngine } from './chunk-DDTUTWRY.js';
7
- import { require_typescript, ErrorHandlingEngine } from './chunk-QFDZMUGO.js';
7
+ import { require_typescript, ErrorHandlingEngine } from './chunk-FRK2XZX5.js';
8
8
  import { BaseEngine } from './chunk-RR5ETBSV.js';
9
- import { PhantomDepEngine } from './chunk-G3FQJC2H.js';
9
+ import { PhantomDepEngine } from './chunk-FMRX5OVJ.js';
10
10
  import { APITruthEngine } from './chunk-JZSHXEYP.js';
11
11
  import { EnvVarEngine } from './chunk-QYXENOVK.js';
12
12
  import { GhostRouteEngine } from './chunk-LQSBUKYZ.js';
@@ -14,9 +14,9 @@ import { CredentialsEngine } from './chunk-5DADZJ3D.js';
14
14
  import { SecurityEngine } from './chunk-43XAAYST.js';
15
15
  import { VersionHallucinationEngine } from './chunk-F34MHA6A.js';
16
16
  import { __toESM } from './chunk-YWUMPN4Z.js';
17
+ import * as fs from 'fs/promises';
17
18
  import * as fs2 from 'fs';
18
19
  import fs2__default, { existsSync } from 'fs';
19
- import * as fs from 'fs/promises';
20
20
  import { setMaxListeners } from 'events';
21
21
  import { z } from 'zod';
22
22
 
@@ -27,7 +27,7 @@ dirname(__filename$1);
27
27
  // ../engines/dist/index.js
28
28
  var import_typescript = __toESM(require_typescript(), 1);
29
29
 
30
- // ../subscriptions/dist/chunk-2SHIVAZT.js
30
+ // ../subscriptions/dist/chunk-XDJH35I6.js
31
31
  var PLAN_IDS = ["pro", "team", "enterprise"];
32
32
  var PLAN_RANK = {
33
33
  pro: 0,
@@ -1442,7 +1442,7 @@ Object.fromEntries(
1442
1442
 
1443
1443
  // ../shared-types/dist/entitlements.js
1444
1444
  var ENTITLEMENTS2 = {
1445
- // === PRO ($19.00/mo) Tier Entitlements ===
1445
+ // === PRO Tier Entitlements ===
1446
1446
  REALITY_MODE: "reality_mode",
1447
1447
  AUTOFIX: "autofix",
1448
1448
  AUTOFIX_APPLY: "autofix_apply",
@@ -1454,7 +1454,7 @@ var ENTITLEMENTS2 = {
1454
1454
  CLOUD_SYNC: "cloud_sync",
1455
1455
  PRIORITY_SUPPORT: "priority_support",
1456
1456
  SHAREABLE_REPORTS: "shareable_reports",
1457
- // === TEAM ($9.99/mo) Tier Entitlements ===
1457
+ // === TEAM Tier Entitlements ===
1458
1458
  CONTEXT_ENGINE: "context_engine",
1459
1459
  FIREWALL_AGENT: "firewall_agent",
1460
1460
  FIREWALL_ENFORCE: "firewall_enforce",
@@ -1561,6 +1561,46 @@ var TEAM_ENTITLEMENTS = new Set(PRO_ENTITLEMENTS);
1561
1561
  ENTITLEMENTS2.ENTERPRISE_SIGNED_BUNDLES,
1562
1562
  ENTITLEMENTS2.ENTERPRISE_MULTI_REPO
1563
1563
  ]);
1564
+ function quotasToPlanLimits(q) {
1565
+ return {
1566
+ findingDetailLimit: q.findingDetailLimit,
1567
+ canAutoFix: q.canAutoFix,
1568
+ canHealPR: q.canHealPR,
1569
+ canModelFingerprint: q.canModelFingerprint,
1570
+ canRealityMode: q.canRealityMode,
1571
+ canISLGenerate: q.canISLGenerate,
1572
+ canCIBlock: q.canCIBlock,
1573
+ scanHistoryDays: q.scanHistoryDays,
1574
+ maxProjects: q.maxProjects,
1575
+ apiRequestsPerDay: q.apiRequestsPerDay,
1576
+ canExportCSV: q.canExportCSV,
1577
+ canSlackIntegration: q.canSlackIntegration,
1578
+ scansPerDay: q.scansPerDay
1579
+ };
1580
+ }
1581
+ ({
1582
+ free: quotasToPlanLimits(getQuotas(null)),
1583
+ pro: quotasToPlanLimits(PLAN_QUOTAS.pro),
1584
+ team: quotasToPlanLimits(PLAN_QUOTAS.team),
1585
+ enterprise: quotasToPlanLimits(PLAN_QUOTAS.enterprise)
1586
+ });
1587
+ ({
1588
+ pro: {
1589
+ name: PLANS.pro.displayName,
1590
+ tagline: PLANS.pro.tagline,
1591
+ price: PLANS.pro.monthlyPriceUsd,
1592
+ priceLabel: PLANS.pro.priceLabel},
1593
+ team: {
1594
+ name: PLANS.team.displayName,
1595
+ tagline: PLANS.team.tagline,
1596
+ price: PLANS.team.monthlyPriceUsd,
1597
+ priceLabel: PLANS.team.priceLabel},
1598
+ enterprise: {
1599
+ name: PLANS.enterprise.displayName,
1600
+ tagline: PLANS.enterprise.tagline,
1601
+ price: PLANS.enterprise.monthlyPriceUsd,
1602
+ priceLabel: PLANS.enterprise.priceLabel}
1603
+ });
1564
1604
 
1565
1605
  // ../shared-types/dist/risk-dimensions.js
1566
1606
  var RISK_DIMENSIONS = [
@@ -1994,6 +2034,26 @@ function gateCanonicalScanReportFindings(report, planInput) {
1994
2034
  };
1995
2035
  }
1996
2036
 
2037
+ // ../shared-types/dist/billing.js
2038
+ function quotasToTierLimits(q) {
2039
+ return {
2040
+ scansPerMonth: q.scansPerMonth === Infinity ? -1 : q.scansPerMonth,
2041
+ projects: q.maxProjects === Infinity ? -1 : q.maxProjects,
2042
+ seats: q.maxSeats === Infinity ? -1 : q.maxSeats,
2043
+ apiCallsPerMinute: Math.ceil(q.apiRequestsPerDay / 1440),
2044
+ retentionDays: q.scanHistoryDays
2045
+ };
2046
+ }
2047
+ ({
2048
+ free: quotasToTierLimits(getQuotas(null)),
2049
+ pro: quotasToTierLimits(PLAN_QUOTAS.pro),
2050
+ team: quotasToTierLimits(PLAN_QUOTAS.team),
2051
+ enterprise: quotasToTierLimits(PLAN_QUOTAS.enterprise)
2052
+ });
2053
+ ({
2054
+ ...PLAN_PRICE_LABELS
2055
+ });
2056
+
1997
2057
  // ../shared-types/dist/api-client.js
1998
2058
  var PROOF_RUN_SOURCES = ["cli", "mcp", "extension", "github", "dashboard"];
1999
2059
  new Set(PROOF_RUN_SOURCES);
@@ -2197,9 +2257,11 @@ var TypeContractEngine = class extends BaseEngine {
2197
2257
  async scan(delta, signal) {
2198
2258
  const findings = [];
2199
2259
  const source = delta.fullText;
2200
- const uri = delta.documentUri.replace(/^file:\/\//, "");
2260
+ const uri = delta.documentUri.replace(/^file:\/\//, "").replace(/\\/g, "/");
2201
2261
  const lines = delta.lines ?? source.split("\n");
2202
- if (/\.(test|spec)\.(ts|tsx)$/i.test(uri) || /__tests__|__mocks__|fixtures?/i.test(uri)) return findings;
2262
+ if (/\.(test|spec)\.(ts|tsx)$/i.test(uri) || /\.bench\.(ts|tsx)$/i.test(uri) || /__tests__|__mocks__|fixtures?/i.test(uri) || /(?:^|[\\/])(?:bench|benchmarks?)(?:[\\/]|$)/i.test(uri)) {
2263
+ return findings;
2264
+ }
2203
2265
  this.checkAbort(signal);
2204
2266
  for (let i = 0; i < lines.length; i++) {
2205
2267
  const line = lines[i];
@@ -3152,136 +3214,1202 @@ var PerformanceAntipatternEngine = class extends BaseEngine {
3152
3214
  return findings;
3153
3215
  }
3154
3216
  };
3155
- var BreakerState = /* @__PURE__ */ ((BreakerState2) => {
3156
- BreakerState2[BreakerState2["Closed"] = 0] = "Closed";
3157
- BreakerState2[BreakerState2["Open"] = 1] = "Open";
3158
- BreakerState2[BreakerState2["HalfOpen"] = 2] = "HalfOpen";
3159
- return BreakerState2;
3160
- })(BreakerState || {});
3161
- var CircuitBreaker = class {
3162
- constructor(_threshold = 5, _cooldownMs = 3e4) {
3163
- this._threshold = _threshold;
3164
- this._cooldownMs = _cooldownMs;
3217
+ var TRUTHPACK_DIR = ".vibecheck/truthpack";
3218
+ async function loadTruthpack(workspaceRoot) {
3219
+ const dir = path6__default.join(workspaceRoot, TRUTHPACK_DIR);
3220
+ try {
3221
+ await fs.access(dir);
3222
+ } catch {
3223
+ return null;
3165
3224
  }
3166
- _state = 0;
3167
- _failures = 0;
3168
- _halfOpenTimer = null;
3169
- get isOpen() {
3170
- return this._state === 1;
3225
+ try {
3226
+ const [routesData, envData, integrationsData] = await Promise.all([
3227
+ fs.readFile(path6__default.join(dir, "routes.json"), "utf-8").catch(() => '{"routes":[]}'),
3228
+ fs.readFile(path6__default.join(dir, "env.json"), "utf-8").catch(() => '{"variables":[]}'),
3229
+ fs.readFile(path6__default.join(dir, "integrations.json"), "utf-8").catch(() => '{"integrations":[]}')
3230
+ ]);
3231
+ const routesJson = JSON.parse(routesData);
3232
+ const envJson = JSON.parse(envData);
3233
+ const integrationsJson = JSON.parse(integrationsData);
3234
+ return {
3235
+ routes: routesJson.routes ?? [],
3236
+ env: envJson.variables ?? [],
3237
+ integrations: integrationsJson.integrations ?? []
3238
+ };
3239
+ } catch {
3240
+ return null;
3171
3241
  }
3172
- get state() {
3173
- return BreakerState[this._state];
3242
+ }
3243
+ function extractKnownHostsFromIntegrations(integrations) {
3244
+ const out = /* @__PURE__ */ new Set([
3245
+ "localhost",
3246
+ "127.0.0.1",
3247
+ "0.0.0.0",
3248
+ "::1"
3249
+ ]);
3250
+ const sdkApex = {
3251
+ stripe: "stripe.com",
3252
+ "@octokit/rest": "github.com",
3253
+ "posthog-node": "posthog.com",
3254
+ "posthog-js": "posthog.com",
3255
+ "@sentry/node": "sentry.io",
3256
+ resend: "resend.com",
3257
+ openai: "openai.com",
3258
+ "@anthropic-ai/sdk": "anthropic.com",
3259
+ ioredis: "redis.io",
3260
+ "drizzle-orm": "drizzle.team"
3261
+ };
3262
+ for (const integ of integrations) {
3263
+ if (integ.docsUrl) {
3264
+ const apex = apexFromUrl(integ.docsUrl);
3265
+ if (apex) out.add(apex);
3266
+ }
3267
+ if (integ.sdkPackage && sdkApex[integ.sdkPackage]) {
3268
+ out.add(sdkApex[integ.sdkPackage]);
3269
+ }
3174
3270
  }
3175
- recordSuccess() {
3176
- this._failures = 0;
3177
- this._state = 0;
3271
+ return out;
3272
+ }
3273
+ function apexFromUrl(url) {
3274
+ try {
3275
+ const u = new URL(url);
3276
+ return apexFromHost(u.hostname);
3277
+ } catch {
3278
+ return null;
3178
3279
  }
3179
- recordFailure() {
3180
- this._failures++;
3181
- if (this._failures >= this._threshold) {
3182
- this._state = 1;
3183
- this._scheduleHalfOpen();
3184
- }
3280
+ }
3281
+ function apexFromHost(hostname) {
3282
+ const host = hostname.toLowerCase();
3283
+ if (/^[\d.]+$/.test(host) || host.includes(":") || host === "localhost") {
3284
+ return host;
3185
3285
  }
3186
- tryAllow() {
3187
- if (this._state === 0) return true;
3188
- if (this._state === 2) {
3189
- this._state = 0;
3190
- return true;
3286
+ const parts = host.split(".");
3287
+ if (parts.length <= 2) return host;
3288
+ return parts.slice(-2).join(".");
3289
+ }
3290
+ function hostMatchesKnownSet(host, knownHosts) {
3291
+ const lower = host.toLowerCase();
3292
+ if (knownHosts.has(lower)) return true;
3293
+ const apex = apexFromHost(lower);
3294
+ if (knownHosts.has(apex)) return true;
3295
+ for (const known of knownHosts) {
3296
+ if (lower.endsWith(`.${known}`)) return true;
3297
+ }
3298
+ return false;
3299
+ }
3300
+ var TruthpackEnvIndex = class {
3301
+ _index;
3302
+ constructor(envVars, allowlistEnvVars) {
3303
+ this._index = new Set(envVars.map((v) => v.name));
3304
+ if (Array.isArray(allowlistEnvVars)) {
3305
+ for (const v of allowlistEnvVars) {
3306
+ if (typeof v === "string" && /^[A-Z_][A-Z0-9_]*$/.test(v)) this._index.add(v);
3307
+ }
3191
3308
  }
3192
- return false;
3193
3309
  }
3194
- _scheduleHalfOpen() {
3195
- if (this._halfOpenTimer) clearTimeout(this._halfOpenTimer);
3196
- this._halfOpenTimer = setTimeout(() => {
3197
- this._state = 2;
3198
- this._halfOpenTimer = null;
3199
- }, this._cooldownMs);
3310
+ get index() {
3311
+ return this._index;
3200
3312
  }
3201
- dispose() {
3202
- if (this._halfOpenTimer) clearTimeout(this._halfOpenTimer);
3313
+ has(name) {
3314
+ return this._index.has(name);
3203
3315
  }
3204
3316
  };
3205
- var EngineRegistry = class {
3206
- _slots = /* @__PURE__ */ new Map();
3207
- _breakers = /* @__PURE__ */ new Map();
3208
- _registrationOrder = /* @__PURE__ */ new Map();
3209
- _nextOrder = 0;
3210
- _activeCache = null;
3211
- /**
3212
- * Register an engine. If an engine with the same ID already exists, it is replaced.
3213
- */
3214
- register(engine, options = {}) {
3215
- const slot = {
3216
- engine,
3217
- timeoutMs: options.timeoutMs ?? 200,
3218
- priority: options.priority ?? 100,
3219
- enabled: options.enabled ?? true,
3220
- extensions: options.extensions ?? void 0
3221
- };
3222
- this._slots.set(engine.id, slot);
3223
- this._breakers.set(engine.id, new CircuitBreaker());
3224
- if (!this._registrationOrder.has(engine.id)) {
3225
- this._registrationOrder.set(engine.id, this._nextOrder++);
3226
- }
3227
- this._activeCache = null;
3228
- }
3229
- /**
3230
- * Deregister an engine by ID. Disposes the engine and its circuit breaker.
3231
- */
3232
- deregister(id) {
3233
- const slot = this._slots.get(id);
3234
- if (!slot) return false;
3235
- slot.engine.dispose?.();
3236
- this._breakers.get(id)?.dispose();
3237
- this._slots.delete(id);
3238
- this._breakers.delete(id);
3239
- this._activeCache = null;
3240
- return true;
3317
+ function compileRoutePattern(routePath) {
3318
+ let isDynamic = false;
3319
+ let isCatchAll = false;
3320
+ let pattern = routePath.replace(/\[\[\.\.\.(\w+)\]\]/g, () => {
3321
+ isDynamic = true;
3322
+ isCatchAll = true;
3323
+ return "(?:\\/.*)?";
3324
+ }).replace(/\[\.\.\.(\w+)\]/g, () => {
3325
+ isDynamic = true;
3326
+ isCatchAll = true;
3327
+ return "\\/.*";
3328
+ }).replace(/\[(\w+)\]/g, () => {
3329
+ isDynamic = true;
3330
+ return "\\/[^/]+";
3331
+ }).replace(/:\w+/g, () => {
3332
+ isDynamic = true;
3333
+ return "\\/[^/]+";
3334
+ });
3335
+ pattern = pattern.replace(/\//g, "\\/").replace(/\\\\\//g, "\\/");
3336
+ return {
3337
+ regex: new RegExp(`^${pattern}$`),
3338
+ isDynamic,
3339
+ isCatchAll
3340
+ };
3341
+ }
3342
+ function truthpackToRouteIndex(routes, workspaceRoot) {
3343
+ const entries = [];
3344
+ const seen = /* @__PURE__ */ new Set();
3345
+ for (const r of routes) {
3346
+ const pathStr = r.path ?? "";
3347
+ if (!pathStr.startsWith("/")) continue;
3348
+ const pattern = pathStr.startsWith("/api") ? pathStr : pathStr;
3349
+ const key = `${pattern}:${r.method ?? "GET"}`;
3350
+ if (seen.has(key)) continue;
3351
+ seen.add(key);
3352
+ const compiled = compileRoutePattern(pattern);
3353
+ entries.push({
3354
+ pattern,
3355
+ regex: compiled.regex,
3356
+ isDynamic: compiled.isDynamic,
3357
+ isCatchAll: compiled.isCatchAll,
3358
+ filePath: r.file ? path6__default.resolve(workspaceRoot, r.file) : ""
3359
+ });
3241
3360
  }
3242
- /**
3243
- * Get a specific engine slot by ID.
3244
- */
3245
- get(id) {
3246
- return this._slots.get(id);
3361
+ return {
3362
+ routes: entries,
3363
+ framework: "truthpack",
3364
+ builtAt: Date.now()
3365
+ };
3366
+ }
3367
+ function isAIRulesFile(uri) {
3368
+ const path24 = uri.replace(/^file:\/\//, "").toLowerCase();
3369
+ const base = path24.split("/").pop() ?? "";
3370
+ if (base === ".cursorrules") return true;
3371
+ if (/\.cursor\/rules\/.+\.(md|mdc)$/.test(path24)) return true;
3372
+ if (base === "claude.md" || base === ".claude.md") return true;
3373
+ if (/\.claude\/(?:claude\.md|agents\/|commands\/|skills\/|memory\/)/i.test(path24)) return true;
3374
+ if (base === "agents.md") return true;
3375
+ if (base === "gemini.md") return true;
3376
+ if (/\.gemini\/(?!mcp\.json).+\.(md|toml)$/i.test(path24)) return true;
3377
+ if (base === ".windsurfrules" || base === ".codeiumrc") return true;
3378
+ if (/\.github\/copilot-instructions\.md$/.test(path24)) return true;
3379
+ if (base === ".aider.conf.yml" || base === "conventions.md") return true;
3380
+ if (/\.continue\/config\.(json|yaml|yml)$/.test(path24)) return true;
3381
+ if (base === ".clinerules" || base === ".roorules") return true;
3382
+ return false;
3383
+ }
3384
+ function isMCPConfigFile(uri) {
3385
+ const path24 = uri.replace(/^file:\/\//, "").toLowerCase();
3386
+ const base = path24.split("/").pop() ?? "";
3387
+ if (base === "mcp.json" || base === ".mcp.json") return true;
3388
+ if (base === "claude_desktop_config.json") return true;
3389
+ if (base === "mcp_servers.json") return true;
3390
+ if (/\/\.cursor\/mcp\.json$/.test(path24)) return true;
3391
+ if (/\/\.vscode\/mcp\.json$/.test(path24)) return true;
3392
+ if (/\/\.gemini\/(?:settings|mcp)\.json$/.test(path24)) return true;
3393
+ if (/\/\.mcp\/config\.json$/.test(path24)) return true;
3394
+ return false;
3395
+ }
3396
+ var INVISIBLE_UNICODE_RE = /[\u200B\u200C\u200D\u2060\uFEFF\u00AD\u200E\u200F]/u;
3397
+ var BIDI_OVERRIDE_RE = /[\u202A-\u202E\u2066-\u2069]/u;
3398
+ var TAG_CHARS_RE = /[\u{E0000}-\u{E007F}]/u;
3399
+ var VARIATION_SELECTORS_RE = /[\uFE00-\uFE0F\u{E0100}-\u{E01EF}]/u;
3400
+ var HOMOGLYPH_LATIN_RE = /[A-Za-z]/;
3401
+ var HOMOGLYPH_CYRILLIC_LOOKALIKES_RE = /[\u0430\u0435\u043E\u0440\u0441\u0445\u0443\u0456\u0458\u04CF\u0410\u0412\u0415\u041A\u041C\u041D\u041E\u0420\u0421\u0422\u0425]/;
3402
+ var HOMOGLYPH_GREEK_LOOKALIKES_RE = /[\u03BF\u03B1\u03BD\u03C5\u03C7\u03C9\u0391\u0392\u0395\u0397\u0399\u039A\u039C\u039D\u039F\u03A1\u03A4\u03A5\u03A7]/;
3403
+ var INSTRUCTION_OVERRIDE_RE = /\b(?:ignore|disregard|forget|override|bypass|skip)\s+(?:all\s+)?(?:the\s+)?(?:previous|prior|above|earlier|original|preceding)\s+(?:instructions?|rules?|prompts?|context|guidelines?|directives?|constraints?)\b/i;
3404
+ var ROLE_REASSIGNMENT_RE = /\b(?:you\s+are\s+now|act\s+as|pretend\s+to\s+be|assume\s+the\s+role(?:\s+of)?|from\s+now\s+on\s+you|you'?re\s+(?:no\s+longer|not)\s+claude|you\s+have\s+no\s+restrictions)\b/i;
3405
+ var AI_COMMENT_HIJACK_RE = /(?:\/\/|\/\*|#|<!--|;)\s*(?:AI|assistant|copilot|cursor|claude|gpt|codex|gemini|windsurf|aider|cline)\s*[:\s]?\s*(?:ignore|override|instead|actually|disregard|don'?t|skip|bypass)/i;
3406
+ var BASE64_PAYLOAD_RE = /(?:base64|atob|decode|b64decode|fromBase64)\s*\(|(?<![A-Za-z0-9+/])([A-Za-z0-9+/]{60,}={0,2})(?![A-Za-z0-9+/])/;
3407
+ var HIDDEN_CONTENT_BLOCK_RE = /<!--[\s\S]{40,}?-->|<(?:div|span|p|section)[^>]*(?:display\s*:\s*none|visibility\s*:\s*hidden|opacity\s*:\s*0|height\s*:\s*0|font-size\s*:\s*0|color\s*:\s*(?:white|#fff|transparent))[^>]*>/i;
3408
+ var SYSTEM_PROMPT_EXTRACTION_RE = /\b(?:print|reveal|repeat|show|output|expose|leak|reproduce|tell\s+me)\s+(?:your\s+)?(?:system\s+prompt|initial\s+instructions?|original\s+prompt|hidden\s+rules?|internal\s+(?:prompt|context)|developer\s+mode\s+settings)\b/i;
3409
+ var JAILBREAK_TOKEN_RE = /\b(?:DAN(?:\s+mode)?|developer\s+mode\s+enabled|do\s+anything\s+now|STAN\s+mode|jailbreak\s+(?:mode|prompt)|unfiltered\s+mode|sudo\s+mode)\b/i;
3410
+ var EXFIL_INTENT_RE = /\b(?:send|post|upload|transmit|forward|exfiltrate|leak|deliver|push)\b[\s\S]{0,40}?\b(?:code|context|secrets?|env(?:ironment)?|credentials?|tokens?|api[_\s-]?keys?|source|files?|contents?|data|history|conversation)\b/i;
3411
+ var EXTERNAL_URL_RE = /\bhttps?:\/\/(?!(?:localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1?\]|example\.com|github\.com\/[^/]+\/[^/]+(?:#|$|\s)))[\w.-]+(?:\.[a-z]{2,}|:\d+)/i;
3412
+ var WEBHOOK_VERB_RE = /\b(?:webhook|callback|fetch|request|curl|wget|http\.(?:get|post)|axios|XMLHttpRequest|navigator\.sendBeacon)\b/i;
3413
+ var TOOL_POISONING_RE = /\b(?:ALWAYS|MUST|REQUIRED|CRITICAL|IMPORTANT)\s+(?:use|call|invoke|run|execute|prefer)\s+this\s+tool|\b(?:before|prior\s+to)\s+(?:any|every|all)\s+(?:other|response|reply)|ignore\s+(?:previous|other)\s+tools?|do\s+not\s+(?:tell|inform)\s+the\s+user/i;
3414
+ var NPX_AUTO_YES_RE = /\b(?:npx|bunx|pnpx|pnpm\s+dlx|deno\s+run\s+--allow[^\s]*)\s+(?:-y|--yes|-Y)\b/i;
3415
+ var HTTP_NOT_HTTPS_RE = /["'`]http:\/\/(?!localhost|127\.0\.0\.1|\[::1?\]|0\.0\.0\.0)/i;
3416
+ var UNVERIFIED_GIT_URL_RE = /^git\+(?:https?|ssh):\/\/(?!.*#[a-f0-9]{7,40})/i;
3417
+ var DENO_BROAD_PERMS_RE = /--allow-all|--allow-net(?!=)|--allow-read(?!=)|--allow-write(?!=)|--allow-run(?!=)|--allow-env(?!=)|-A\b/;
3418
+ var PLAINTEXT_SECRET_VALUE_RE = /(?:sk-(?:live|test|proj|ant|or)[_-]|ghp_|gho_|ghu_|ghs_|github_pat_|xox[abprs]-|AKIA[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{35})/;
3419
+ function parseMCPDocument(content) {
3420
+ try {
3421
+ return JSON.parse(content);
3422
+ } catch {
3423
+ return null;
3247
3424
  }
3248
- /**
3249
- * Get the circuit breaker for an engine.
3250
- */
3251
- getBreaker(id) {
3252
- return this._breakers.get(id);
3425
+ }
3426
+ function listMCPServers(doc) {
3427
+ const out = [];
3428
+ const collect = (root) => {
3429
+ const block = doc[root];
3430
+ if (!block || typeof block !== "object") return;
3431
+ for (const [name, entry] of Object.entries(block)) {
3432
+ if (entry && typeof entry === "object") {
3433
+ out.push({ name, entry, pointer: `${root}.${name}` });
3434
+ }
3435
+ }
3436
+ };
3437
+ collect("mcpServers");
3438
+ collect("servers");
3439
+ return out;
3440
+ }
3441
+ function findLineNumber(source, needle) {
3442
+ const idx = source.indexOf(needle);
3443
+ if (idx === -1) return 1;
3444
+ let line = 1;
3445
+ for (let i = 0; i < idx; i++) {
3446
+ if (source.charCodeAt(i) === 10) line++;
3253
3447
  }
3254
- /**
3255
- * Get all enabled engine slots, sorted by priority (ascending), then registration order.
3256
- */
3257
- getActive() {
3258
- if (this._activeCache) return this._activeCache;
3259
- this._activeCache = [...this._slots.values()].filter((s) => s.enabled !== false).sort((a, b) => {
3260
- if (a.priority !== b.priority) return a.priority - b.priority;
3261
- return (this._registrationOrder.get(a.engine.id) ?? 0) - (this._registrationOrder.get(b.engine.id) ?? 0);
3262
- });
3263
- return this._activeCache;
3448
+ return line;
3449
+ }
3450
+ function arrayContainsWildcard(value) {
3451
+ if (!Array.isArray(value)) return false;
3452
+ return value.some((v) => typeof v === "string" && (v === "*" || v.trim() === "*"));
3453
+ }
3454
+ function objectHasWildcardKey(value) {
3455
+ if (!value || typeof value !== "object") return false;
3456
+ for (const [k, v] of Object.entries(value)) {
3457
+ if (k === "*") return true;
3458
+ if (typeof v === "string" && (v === "*" || v === "all")) return true;
3459
+ if (Array.isArray(v) && arrayContainsWildcard(v)) return true;
3264
3460
  }
3461
+ return false;
3462
+ }
3463
+ function isAuthDisabled(entry) {
3464
+ const checkVal = (v) => {
3465
+ if (v === false) return true;
3466
+ if (typeof v === "string") {
3467
+ const s = v.toLowerCase();
3468
+ return s === "none" || s === "disabled" || s === "off" || s === "false";
3469
+ }
3470
+ if (v && typeof v === "object") {
3471
+ const obj = v;
3472
+ if (obj.required === false) return true;
3473
+ if (obj.enabled === false) return true;
3474
+ if (typeof obj.type === "string" && obj.type.toLowerCase() === "none") return true;
3475
+ }
3476
+ return false;
3477
+ };
3478
+ return checkVal(entry.auth) || checkVal(entry.authentication);
3479
+ }
3480
+ var SEVERITY_LADDER = ["info", "low", "medium", "high", "critical"];
3481
+ var AIRulesAttackEngine = class extends BaseEngine {
3482
+ id = "ai-rules-attack";
3483
+ name = "AI Rules File & MCP Attack Engine";
3484
+ version = "1.0.0";
3265
3485
  /**
3266
- * Get all registered engine slots (including disabled).
3486
+ * `null` means "any extension". Rules files (`.cursorrules`, `.clinerules`,
3487
+ * `.windsurfrules`) have no extension, so we cannot use a Set here. The
3488
+ * scan() function does its own path-based gating in <1µs.
3267
3489
  */
3268
- getAll() {
3269
- return [...this._slots.values()];
3490
+ supportedExtensions = null;
3491
+ _knownHosts;
3492
+ _hasTruthpack;
3493
+ constructor(opts = {}) {
3494
+ super();
3495
+ this._knownHosts = opts.knownHosts ?? /* @__PURE__ */ new Set();
3496
+ this._hasTruthpack = (opts.knownHosts?.size ?? 0) > 0;
3270
3497
  }
3271
- /**
3272
- * Enable or disable an engine by ID.
3273
- */
3274
- setEnabled(id, enabled) {
3275
- const slot = this._slots.get(id);
3276
- if (!slot) return false;
3277
- slot.enabled = enabled;
3278
- this._activeCache = null;
3279
- return true;
3498
+ async scan(delta, signal) {
3499
+ const uri = delta.documentUri;
3500
+ const isRules = isAIRulesFile(uri);
3501
+ const isMCP = isMCPConfigFile(uri);
3502
+ if (!isRules && !isMCP) return [];
3503
+ this.checkAbort(signal);
3504
+ const source = delta.fullText;
3505
+ const cleanUri = uri.replace(/^file:\/\//, "");
3506
+ const pending = [];
3507
+ if (isRules) {
3508
+ this._checkRulesFile(source, pending);
3509
+ }
3510
+ if (isMCP) {
3511
+ this._checkMCPConfig(source, pending);
3512
+ this._checkRulesFile(source, pending);
3513
+ }
3514
+ if (pending.length === 0) return [];
3515
+ this._applyCoOccurrenceBoost(pending);
3516
+ return pending.map(
3517
+ (p) => this.createFinding({
3518
+ id: this.deterministicId(cleanUri, p.line, p.column, p.ruleId, p.evidence.slice(0, 32)),
3519
+ ruleId: p.ruleId,
3520
+ category: "ai_rules_attack",
3521
+ message: p.message,
3522
+ severity: p.severity,
3523
+ confidence: p.confidence,
3524
+ file: cleanUri,
3525
+ line: p.line,
3526
+ column: p.column,
3527
+ evidence: p.evidence,
3528
+ suggestion: p.suggestion,
3529
+ autoFixable: p.autoFixable
3530
+ })
3531
+ );
3280
3532
  }
3281
- /**
3282
- * Activate all registered engines. Call once at startup.
3283
- */
3284
- async activateAll(onError) {
3533
+ // ─── Rules-file checks ──────────────────────────────────────────────────
3534
+ _checkRulesFile(source, out) {
3535
+ const lines = source.split("\n");
3536
+ for (let i = 0; i < lines.length; i++) {
3537
+ const raw = lines[i];
3538
+ const line = i + 1;
3539
+ if (INVISIBLE_UNICODE_RE.test(raw)) {
3540
+ out.push({
3541
+ ruleId: "AIRA001",
3542
+ category: "unicode",
3543
+ severity: "critical",
3544
+ confidence: 0.95,
3545
+ message: "Invisible Unicode character in rules file \u2014 may conceal hidden instructions [CWE-1007]",
3546
+ evidence: this._safeEvidence(raw),
3547
+ suggestion: "Strip zero-width and bidi-related characters from this file. Use a hex-aware editor to verify.",
3548
+ line,
3549
+ column: raw.search(INVISIBLE_UNICODE_RE),
3550
+ autoFixable: true
3551
+ });
3552
+ }
3553
+ if (BIDI_OVERRIDE_RE.test(raw)) {
3554
+ out.push({
3555
+ ruleId: "AIRA002",
3556
+ category: "unicode",
3557
+ severity: "critical",
3558
+ confidence: 0.98,
3559
+ message: "Bidirectional text override in rules file (Trojan Source variant) [CVE-2021-42574]",
3560
+ evidence: this._safeEvidence(raw),
3561
+ suggestion: "Remove all bidi control characters (LRO/RLO/LRE/RLE/PDF/LRI/RLI/FSI/PDI). These flip the visual order of text and are almost never legitimate in agent rules.",
3562
+ line,
3563
+ column: raw.search(BIDI_OVERRIDE_RE),
3564
+ autoFixable: true
3565
+ });
3566
+ }
3567
+ if (TAG_CHARS_RE.test(raw)) {
3568
+ out.push({
3569
+ ruleId: "AIRA003",
3570
+ category: "unicode",
3571
+ severity: "critical",
3572
+ confidence: 0.99,
3573
+ message: "Unicode tag characters detected \u2014 invisible ASCII payload encoding (Goodside-class attack)",
3574
+ evidence: this._safeEvidence(raw),
3575
+ suggestion: "Tag characters (U+E0000\u2013U+E007F) encode hidden ASCII text invisibly. Strip them entirely.",
3576
+ line,
3577
+ column: 0,
3578
+ autoFixable: true
3579
+ });
3580
+ }
3581
+ if (VARIATION_SELECTORS_RE.test(raw) && raw.length > 10) {
3582
+ out.push({
3583
+ ruleId: "AIRA004",
3584
+ category: "unicode",
3585
+ severity: "high",
3586
+ confidence: 0.7,
3587
+ message: "Variation selectors in rules file \u2014 may mask invisible payload",
3588
+ evidence: this._safeEvidence(raw),
3589
+ suggestion: "Variation selectors are rarely needed in agent instructions. Verify each one is legitimate (e.g., emoji styling) and strip the rest.",
3590
+ line,
3591
+ column: 0,
3592
+ autoFixable: false
3593
+ });
3594
+ }
3595
+ if (this._hasHomoglyphMix(raw)) {
3596
+ out.push({
3597
+ ruleId: "AIRA005",
3598
+ category: "unicode",
3599
+ severity: "high",
3600
+ confidence: 0.85,
3601
+ message: "Homoglyph mixing \u2014 Cyrillic or Greek letters used inside Latin words to spoof tokens",
3602
+ evidence: this._safeEvidence(raw),
3603
+ suggestion: "Words mixing Latin with Cyrillic/Greek lookalikes can spoof package names, auth headers, or commands. Replace non-Latin lookalikes with their ASCII equivalents.",
3604
+ line,
3605
+ column: 0,
3606
+ autoFixable: false
3607
+ });
3608
+ }
3609
+ if (INSTRUCTION_OVERRIDE_RE.test(raw)) {
3610
+ out.push({
3611
+ ruleId: "AIRA006",
3612
+ category: "prompt_injection",
3613
+ severity: "critical",
3614
+ confidence: 0.9,
3615
+ message: "Instruction-override pattern in rules file [CVE-2025-54135, OWASP LLM01]",
3616
+ evidence: raw.trim().slice(0, 80),
3617
+ suggestion: "Remove this instruction. Rules files should describe the project \u2014 they should never tell the AI to ignore prior context.",
3618
+ line,
3619
+ column: raw.search(INSTRUCTION_OVERRIDE_RE),
3620
+ autoFixable: false
3621
+ });
3622
+ }
3623
+ if (ROLE_REASSIGNMENT_RE.test(raw)) {
3624
+ out.push({
3625
+ ruleId: "AIRA007",
3626
+ category: "prompt_injection",
3627
+ severity: "critical",
3628
+ confidence: 0.85,
3629
+ message: "Role-reassignment pattern in rules file [OWASP LLM01]",
3630
+ evidence: raw.trim().slice(0, 80),
3631
+ suggestion: "Rules files should not redefine the assistant's identity. Remove or rewrite this directive.",
3632
+ line,
3633
+ column: raw.search(ROLE_REASSIGNMENT_RE),
3634
+ autoFixable: false
3635
+ });
3636
+ }
3637
+ if (AI_COMMENT_HIJACK_RE.test(raw)) {
3638
+ out.push({
3639
+ ruleId: "AIRA008",
3640
+ category: "prompt_injection",
3641
+ severity: "high",
3642
+ confidence: 0.8,
3643
+ message: "AI-targeted comment with override directive \u2014 hijack attempt",
3644
+ evidence: raw.trim().slice(0, 80),
3645
+ suggestion: 'Comments addressed to AI assistants that contain "ignore"/"override"/"actually" are an attack vector. Remove or rewrite.',
3646
+ line,
3647
+ column: raw.search(AI_COMMENT_HIJACK_RE),
3648
+ autoFixable: false
3649
+ });
3650
+ }
3651
+ if (HIDDEN_CONTENT_BLOCK_RE.test(raw)) {
3652
+ out.push({
3653
+ ruleId: "AIRA010",
3654
+ category: "prompt_injection",
3655
+ severity: "high",
3656
+ confidence: 0.85,
3657
+ message: "Hidden content block \u2014 may conceal instructions from human reviewer",
3658
+ evidence: raw.trim().slice(0, 80),
3659
+ suggestion: "Visually hidden HTML / large MD comment blocks are a known prompt-injection vector. Remove or expose the content.",
3660
+ line,
3661
+ column: raw.search(HIDDEN_CONTENT_BLOCK_RE),
3662
+ autoFixable: false
3663
+ });
3664
+ }
3665
+ if (SYSTEM_PROMPT_EXTRACTION_RE.test(raw)) {
3666
+ out.push({
3667
+ ruleId: "AIRA011",
3668
+ category: "prompt_injection",
3669
+ severity: "high",
3670
+ confidence: 0.85,
3671
+ message: "System-prompt extraction attempt in rules file [OWASP LLM07: System Prompt Leakage]",
3672
+ evidence: raw.trim().slice(0, 80),
3673
+ suggestion: "Rules files should never instruct the assistant to reveal its system prompt. Remove this directive.",
3674
+ line,
3675
+ column: raw.search(SYSTEM_PROMPT_EXTRACTION_RE),
3676
+ autoFixable: false
3677
+ });
3678
+ }
3679
+ if (JAILBREAK_TOKEN_RE.test(raw)) {
3680
+ out.push({
3681
+ ruleId: "AIRA012",
3682
+ category: "prompt_injection",
3683
+ severity: "critical",
3684
+ confidence: 0.95,
3685
+ message: "Jailbreak / persona-override token in rules file",
3686
+ evidence: raw.trim().slice(0, 80),
3687
+ suggestion: 'Tokens like "DAN", "developer mode", "do anything now" are well-known jailbreak markers. Remove this content.',
3688
+ line,
3689
+ column: raw.search(JAILBREAK_TOKEN_RE),
3690
+ autoFixable: false
3691
+ });
3692
+ }
3693
+ if (EXFIL_INTENT_RE.test(raw)) {
3694
+ out.push({
3695
+ ruleId: "AIRA013",
3696
+ category: "exfiltration",
3697
+ severity: "critical",
3698
+ confidence: 0.85,
3699
+ message: "Data-exfiltration directive in rules file [CVE-2025-55284, OWASP LLM02]",
3700
+ evidence: raw.trim().slice(0, 80),
3701
+ suggestion: "Rules files should never instruct the assistant to send code/secrets/context to external endpoints. Remove this directive.",
3702
+ line,
3703
+ column: raw.search(EXFIL_INTENT_RE),
3704
+ autoFixable: false
3705
+ });
3706
+ }
3707
+ if (EXTERNAL_URL_RE.test(raw) && WEBHOOK_VERB_RE.test(raw)) {
3708
+ const urlMatch = raw.match(/https?:\/\/[\w.-]+(?::\d+)?/);
3709
+ const host = urlMatch ? this._extractHost(urlMatch[0]) : null;
3710
+ const isKnown = host && hostMatchesKnownSet(host, this._knownHosts);
3711
+ if (!isKnown) {
3712
+ const elevated = this._hasTruthpack;
3713
+ out.push({
3714
+ ruleId: "AIRA014",
3715
+ category: "exfiltration",
3716
+ severity: elevated ? "critical" : "high",
3717
+ confidence: elevated ? 0.99 : 0.8,
3718
+ message: elevated ? `External webhook / fetch URL to UNKNOWN host '${host ?? "?"}' (not in truthpack/integrations.json)` : "External webhook / fetch URL in rules file",
3719
+ evidence: raw.trim().slice(0, 80),
3720
+ suggestion: elevated ? `Host '${host ?? "?"}' is not a known integration for this project. If this is legitimate, add it to integrations.json and rerun \`vibecheck scan\`. Otherwise remove it.` : "Rules files should describe the project, not invoke external endpoints. Verify this URL is necessary; if so, document why.",
3721
+ line,
3722
+ column: raw.search(EXTERNAL_URL_RE),
3723
+ autoFixable: false
3724
+ });
3725
+ }
3726
+ }
3727
+ }
3728
+ if (BASE64_PAYLOAD_RE.test(source)) {
3729
+ const m = BASE64_PAYLOAD_RE.exec(source);
3730
+ const evidence = m ? m[0].slice(0, 60) : "[base64 blob]";
3731
+ out.push({
3732
+ ruleId: "AIRA009",
3733
+ category: "prompt_injection",
3734
+ severity: "high",
3735
+ confidence: 0.7,
3736
+ message: "Base64-encoded payload or decoder call in rules file",
3737
+ evidence,
3738
+ suggestion: "Encoded payloads in rules files are an obfuscation vector. Decode the content and inline it in plaintext, or remove it.",
3739
+ line: findLineNumber(source, m?.[0] ?? ""),
3740
+ column: 0,
3741
+ autoFixable: false
3742
+ });
3743
+ }
3744
+ }
3745
+ // ─── MCP-config checks ──────────────────────────────────────────────────
3746
+ _checkMCPConfig(source, out) {
3747
+ const doc = parseMCPDocument(source);
3748
+ if (!doc) return;
3749
+ const servers = listMCPServers(doc);
3750
+ for (const { name, entry } of servers) {
3751
+ if (arrayContainsWildcard(entry.tools)) {
3752
+ out.push({
3753
+ ruleId: "AIRA016",
3754
+ category: "mcp_misconfig",
3755
+ severity: "critical",
3756
+ confidence: 0.99,
3757
+ message: `MCP server '${name}' exposes wildcard tool permissions ("*") [CWE-269; CVE-2025-54135]`,
3758
+ evidence: `tools: ["*"]`,
3759
+ suggestion: 'Replace "*" with an explicit tool allowlist, e.g. tools: ["read_file", "search"].',
3760
+ line: findLineNumber(source, `"${name}"`),
3761
+ column: 0,
3762
+ autoFixable: false
3763
+ });
3764
+ }
3765
+ const remoteUrl = typeof entry.url === "string" ? entry.url : void 0;
3766
+ if (remoteUrl && /^https?:\/\//i.test(remoteUrl)) {
3767
+ const host = this._extractHost(remoteUrl);
3768
+ const isKnown = host && hostMatchesKnownSet(host, this._knownHosts);
3769
+ const elevated = this._hasTruthpack && !isKnown;
3770
+ const downgraded = isKnown;
3771
+ out.push({
3772
+ ruleId: "AIRA017",
3773
+ category: "mcp_misconfig",
3774
+ severity: elevated ? "critical" : downgraded ? "low" : "high",
3775
+ confidence: elevated ? 0.99 : downgraded ? 0.5 : 0.9,
3776
+ message: elevated ? `MCP server '${name}' connects to UNKNOWN host '${host ?? "?"}' (not in truthpack/integrations.json) [CWE-829]` : downgraded ? `MCP server '${name}' connects to known integration '${host ?? "?"}' (informational)` : `MCP server '${name}' connects to a remote endpoint [CWE-829]`,
3777
+ evidence: remoteUrl.slice(0, 80),
3778
+ suggestion: elevated ? `Host '${host ?? "?"}' is not in your project's integrations. Confirm this is intentional; if legitimate, add it to truthpack/integrations.json.` : "Verify the remote MCP server is from a trusted operator. Prefer local stdio servers when possible.",
3779
+ line: findLineNumber(source, remoteUrl),
3780
+ column: 0,
3781
+ autoFixable: false
3782
+ });
3783
+ if (HTTP_NOT_HTTPS_RE.test(`"${remoteUrl}"`)) {
3784
+ out.push({
3785
+ ruleId: "AIRA024",
3786
+ category: "mcp_misconfig",
3787
+ severity: "critical",
3788
+ confidence: 0.99,
3789
+ message: `MCP server '${name}' uses unencrypted HTTP \u2014 MITM exposure`,
3790
+ evidence: remoteUrl.slice(0, 80),
3791
+ suggestion: "Use https:// for remote MCP servers. Reject any operator that requires plaintext HTTP.",
3792
+ line: findLineNumber(source, remoteUrl),
3793
+ column: 0,
3794
+ autoFixable: false
3795
+ });
3796
+ }
3797
+ }
3798
+ if (Array.isArray(entry.args)) {
3799
+ for (const arg of entry.args) {
3800
+ if (typeof arg === "string" && DENO_BROAD_PERMS_RE.test(arg)) {
3801
+ out.push({
3802
+ ruleId: "AIRA018",
3803
+ category: "mcp_misconfig",
3804
+ severity: "high",
3805
+ confidence: 0.95,
3806
+ message: `MCP server '${name}' uses overly permissive Deno args (${arg}) [CWE-732]`,
3807
+ evidence: arg,
3808
+ suggestion: "Replace --allow-all / -A with scoped permissions like --allow-read=/specific/path or --allow-net=api.example.com.",
3809
+ line: findLineNumber(source, arg),
3810
+ column: 0,
3811
+ autoFixable: false
3812
+ });
3813
+ }
3814
+ }
3815
+ }
3816
+ if (entry.env && typeof entry.env === "object") {
3817
+ for (const [k, v] of Object.entries(entry.env)) {
3818
+ if (typeof v !== "string") continue;
3819
+ if (/^\$\{?[A-Z_][A-Z0-9_]*\}?$/.test(v)) continue;
3820
+ if (PLAINTEXT_SECRET_VALUE_RE.test(v) || k.match(/key|secret|token|password/i) && v.length > 12 && !/^\$/.test(v)) {
3821
+ out.push({
3822
+ ruleId: "AIRA019",
3823
+ category: "mcp_misconfig",
3824
+ severity: "critical",
3825
+ confidence: 0.95,
3826
+ message: `MCP server '${name}' exposes a plaintext secret in env (${k}) [CWE-798]`,
3827
+ evidence: `${k}=[REDACTED]`,
3828
+ suggestion: `Reference the secret indirectly: env: { ${k}: "\${${k}}" } and set ${k} in your shell environment or OS keychain.`,
3829
+ line: findLineNumber(source, `"${k}"`),
3830
+ column: 0,
3831
+ autoFixable: false
3832
+ });
3833
+ }
3834
+ }
3835
+ }
3836
+ if (isAuthDisabled(entry)) {
3837
+ out.push({
3838
+ ruleId: "AIRA020",
3839
+ category: "mcp_misconfig",
3840
+ severity: "high",
3841
+ confidence: 0.9,
3842
+ message: `MCP server '${name}' has authentication explicitly disabled [CWE-306]`,
3843
+ evidence: `auth: disabled`,
3844
+ suggestion: "Enable MCP server authentication. If the server cannot authenticate, do not expose it.",
3845
+ line: findLineNumber(source, `"${name}"`),
3846
+ column: 0,
3847
+ autoFixable: false
3848
+ });
3849
+ }
3850
+ if (objectHasWildcardKey(entry.permissions)) {
3851
+ out.push({
3852
+ ruleId: "AIRA021",
3853
+ category: "mcp_misconfig",
3854
+ severity: "critical",
3855
+ confidence: 0.95,
3856
+ message: `MCP server '${name}' has wildcard permissions [CWE-250]`,
3857
+ evidence: `permissions: { "*": ... }`,
3858
+ suggestion: "Replace wildcard with a scoped permission map.",
3859
+ line: findLineNumber(source, `"${name}"`),
3860
+ column: 0,
3861
+ autoFixable: false
3862
+ });
3863
+ }
3864
+ if (objectHasWildcardKey(entry.network) || typeof entry.network === "string" && entry.network === "*") {
3865
+ out.push({
3866
+ ruleId: "AIRA022",
3867
+ category: "mcp_misconfig",
3868
+ severity: "high",
3869
+ confidence: 0.9,
3870
+ message: `MCP server '${name}' allows unrestricted network access [CWE-284]`,
3871
+ evidence: `network: *`,
3872
+ suggestion: 'Restrict network: { allow: ["specific-host.example.com"] }.',
3873
+ line: findLineNumber(source, `"${name}"`),
3874
+ column: 0,
3875
+ autoFixable: false
3876
+ });
3877
+ }
3878
+ if (typeof entry.command === "string" && NPX_AUTO_YES_RE.test(entry.command)) {
3879
+ out.push({
3880
+ ruleId: "AIRA023",
3881
+ category: "mcp_misconfig",
3882
+ severity: "high",
3883
+ confidence: 0.95,
3884
+ message: `MCP server '${name}' auto-confirms package install (npx -y / equivalent)`,
3885
+ evidence: entry.command.slice(0, 80),
3886
+ suggestion: "Pin the package + version (e.g. npx --package=@org/pkg@1.2.3) and remove -y. Consider installing as a dev dep instead.",
3887
+ line: findLineNumber(source, entry.command),
3888
+ column: 0,
3889
+ autoFixable: false
3890
+ });
3891
+ }
3892
+ if (Array.isArray(entry.args) && entry.args.some((a) => typeof a === "string" && /^-y$|^--yes$/.test(a))) {
3893
+ const cmd = typeof entry.command === "string" ? entry.command : "";
3894
+ if (!NPX_AUTO_YES_RE.test(cmd)) {
3895
+ out.push({
3896
+ ruleId: "AIRA023",
3897
+ category: "mcp_misconfig",
3898
+ severity: "high",
3899
+ confidence: 0.9,
3900
+ message: `MCP server '${name}' passes -y / --yes \u2014 auto-confirms package install`,
3901
+ evidence: `args: ["-y", ...]`,
3902
+ suggestion: "Remove -y. Pin the package version explicitly.",
3903
+ line: findLineNumber(source, `"${name}"`),
3904
+ column: 0,
3905
+ autoFixable: false
3906
+ });
3907
+ }
3908
+ }
3909
+ if (typeof entry.command === "string" && UNVERIFIED_GIT_URL_RE.test(entry.command)) {
3910
+ out.push({
3911
+ ruleId: "AIRA025",
3912
+ category: "mcp_misconfig",
3913
+ severity: "high",
3914
+ confidence: 0.9,
3915
+ message: `MCP server '${name}' installs from a Git URL with no commit pin`,
3916
+ evidence: entry.command.slice(0, 80),
3917
+ suggestion: "Pin to a specific commit: git+https://...#<full-sha>. A branch ref alone can change under you.",
3918
+ line: findLineNumber(source, entry.command),
3919
+ column: 0,
3920
+ autoFixable: false
3921
+ });
3922
+ }
3923
+ const desc = entry.description ?? entry.description_for_model;
3924
+ if (typeof desc === "string" && TOOL_POISONING_RE.test(desc)) {
3925
+ out.push({
3926
+ ruleId: "AIRA015",
3927
+ category: "tool_poisoning",
3928
+ severity: "critical",
3929
+ confidence: 0.9,
3930
+ message: `MCP server '${name}' description contains tool-poisoning directives`,
3931
+ evidence: desc.slice(0, 80),
3932
+ suggestion: "Tool descriptions should describe what the tool does, not instruct the LLM to always call it or hide info from the user. Rewrite as a neutral description.",
3933
+ line: findLineNumber(source, `"${name}"`),
3934
+ column: 0,
3935
+ autoFixable: false
3936
+ });
3937
+ }
3938
+ }
3939
+ if (Array.isArray(doc.tools)) {
3940
+ for (const tool of doc.tools) {
3941
+ if (tool && typeof tool.description === "string" && TOOL_POISONING_RE.test(tool.description)) {
3942
+ out.push({
3943
+ ruleId: "AIRA015",
3944
+ category: "tool_poisoning",
3945
+ severity: "critical",
3946
+ confidence: 0.9,
3947
+ message: `Tool '${tool.name ?? "?"}' description contains tool-poisoning directives`,
3948
+ evidence: tool.description.slice(0, 80),
3949
+ suggestion: 'Rewrite tool descriptions as neutral capability statements. Remove "ALWAYS"/"MUST"/"hide from user" directives.',
3950
+ line: findLineNumber(source, tool.description),
3951
+ column: 0,
3952
+ autoFixable: false
3953
+ });
3954
+ }
3955
+ }
3956
+ }
3957
+ }
3958
+ // ─── Helpers ────────────────────────────────────────────────────────────
3959
+ _hasHomoglyphMix(text) {
3960
+ if (!HOMOGLYPH_LATIN_RE.test(text)) return false;
3961
+ if (!HOMOGLYPH_CYRILLIC_LOOKALIKES_RE.test(text) && !HOMOGLYPH_GREEK_LOOKALIKES_RE.test(text)) {
3962
+ return false;
3963
+ }
3964
+ for (const word of text.split(/\s+/)) {
3965
+ if (word.length >= 3 && HOMOGLYPH_LATIN_RE.test(word) && (HOMOGLYPH_CYRILLIC_LOOKALIKES_RE.test(word) || HOMOGLYPH_GREEK_LOOKALIKES_RE.test(word))) {
3966
+ return true;
3967
+ }
3968
+ }
3969
+ return false;
3970
+ }
3971
+ /**
3972
+ * Extract the lowercased hostname from a URL substring. Returns null if the
3973
+ * URL is malformed. Tolerant of trailing punctuation/quotes.
3974
+ */
3975
+ _extractHost(url) {
3976
+ const cleaned = url.replace(/["'`,;).\]]+$/, "");
3977
+ try {
3978
+ return new URL(cleaned).hostname.toLowerCase();
3979
+ } catch {
3980
+ const m = cleaned.match(/^https?:\/\/([\w.-]+)/i);
3981
+ return m ? m[1].toLowerCase() : null;
3982
+ }
3983
+ }
3984
+ /**
3985
+ * Render evidence safely — strip the offending char so log/UI consumers
3986
+ * don't accidentally re-trigger downstream Unicode bugs. Cap at 80 chars.
3987
+ */
3988
+ _safeEvidence(line) {
3989
+ return line.replace(INVISIBLE_UNICODE_RE, "\xB7").replace(BIDI_OVERRIDE_RE, "\xB7").replace(TAG_CHARS_RE, "\xB7").trim().slice(0, 80);
3990
+ }
3991
+ /**
3992
+ * Co-occurrence boost — when 3+ distinct attack categories fire in the
3993
+ * same file, every finding's severity advances one rung and confidence
3994
+ * floors at 0.99. Empirical signal: an attack file usually triggers
3995
+ * multiple categories at once; benign noise rarely does.
3996
+ */
3997
+ _applyCoOccurrenceBoost(findings) {
3998
+ const categories = new Set(findings.map((f) => f.category));
3999
+ if (categories.size < 3) return;
4000
+ for (const f of findings) {
4001
+ const idx = SEVERITY_LADDER.indexOf(f.severity);
4002
+ if (idx >= 0 && idx < SEVERITY_LADDER.length - 1) {
4003
+ f.severity = SEVERITY_LADDER[idx + 1];
4004
+ }
4005
+ f.confidence = Math.max(f.confidence, 0.99);
4006
+ f.message = `${f.message} [BOOSTED: ${categories.size}-category co-occurrence]`;
4007
+ }
4008
+ }
4009
+ };
4010
+ var TEST_FILE_RE = /(?:\.(?:test|spec)\.(?:t|j)sx?$)|(?:^|\/)(?:__tests__|tests?|e2e|spec|cypress|playwright)\//i;
4011
+ var STORY_FILE_RE = /\.stories\.(?:ts|tsx|js|jsx|mdx)$/i;
4012
+ var EXPECT_CALL_RE = /\bexpect\s*\(/g;
4013
+ var IT_CALL_RE = /\b(?:it|test)\s*\(/g;
4014
+ var DESCRIBE_CALL_RE = /\bdescribe\s*\(/g;
4015
+ var SKIPPED_TEST_RE = /\b(?:it|test|describe)\.(?:skip|todo)\s*\(|\b(?:xit|xtest|xdescribe)\s*\(/g;
4016
+ var MOCK_CALL_RE = /\b(?:vi|jest)\.(?:mock|spyOn|fn)\s*\(|\b(?:mock|spyOn|stub|sinon\.(?:stub|spy|mock))\s*\(/g;
4017
+ var TAUTOLOGICAL_ASSERTIONS = [
4018
+ /expect\s*\(\s*true\s*\)\s*\.toBe\s*\(\s*true\s*\)/i,
4019
+ /expect\s*\(\s*false\s*\)\s*\.toBe\s*\(\s*false\s*\)/i,
4020
+ /expect\s*\(\s*1\s*\)\s*\.toBe\s*\(\s*1\s*\)/i,
4021
+ /expect\s*\(\s*['"]?\s*['"]?\s*\)\s*\.toBe\s*\(\s*['"]?\s*['"]?\s*\)/i,
4022
+ /expect\s*\(\s*null\s*\)\s*\.toBe\s*\(\s*null\s*\)/i,
4023
+ /expect\s*\(\s*undefined\s*\)\s*\.toBe\s*\(\s*undefined\s*\)/i,
4024
+ // expect(x).toBe(x) — backreference requires the variable to be captured.
4025
+ /expect\s*\(\s*([A-Za-z_$][\w$]*)\s*\)\s*\.toBe\s*\(\s*\1\s*\)/i,
4026
+ /expect\s*\(\s*([A-Za-z_$][\w$]*)\s*\)\s*\.toEqual\s*\(\s*\1\s*\)/i,
4027
+ /expect\s*\(\s*typeof\s+[\w$.]+\s*\)\s*\.toBe\s*\(\s*['"](?:string|number|boolean|object|function|undefined)['"]\s*\)/i
4028
+ ];
4029
+ function extractTestBlocks(source) {
4030
+ const blocks = [];
4031
+ const re = /\b(?:it|test)\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*(?:async\s*)?\([^)]*\)\s*=>\s*\{/g;
4032
+ let m;
4033
+ while ((m = re.exec(source)) !== null) {
4034
+ const name = m[1] ?? "";
4035
+ const startIdx = m.index + m[0].length;
4036
+ const startLine = lineOf(source, m.index);
4037
+ let depth = 1;
4038
+ let i = startIdx;
4039
+ let inStr = null;
4040
+ let inTpl = false;
4041
+ while (i < source.length && depth > 0) {
4042
+ const c2 = source[i];
4043
+ if (inStr) {
4044
+ if (c2 === "\\") {
4045
+ i += 2;
4046
+ continue;
4047
+ }
4048
+ if (c2 === inStr) inStr = null;
4049
+ } else if (inTpl) {
4050
+ if (c2 === "\\") {
4051
+ i += 2;
4052
+ continue;
4053
+ }
4054
+ if (c2 === "`") inTpl = false;
4055
+ } else {
4056
+ if (c2 === '"' || c2 === "'") inStr = c2;
4057
+ else if (c2 === "`") inTpl = true;
4058
+ else if (c2 === "{") depth++;
4059
+ else if (c2 === "}") depth--;
4060
+ }
4061
+ i++;
4062
+ }
4063
+ const endLine = lineOf(source, i);
4064
+ blocks.push({
4065
+ startLine,
4066
+ endLine,
4067
+ name,
4068
+ body: source.slice(startIdx, i)
4069
+ });
4070
+ }
4071
+ return blocks;
4072
+ }
4073
+ function lineOf(source, index) {
4074
+ let line = 1;
4075
+ for (let i = 0; i < index && i < source.length; i++) {
4076
+ if (source.charCodeAt(i) === 10) line++;
4077
+ }
4078
+ return line;
4079
+ }
4080
+ function countMatches(source, re) {
4081
+ const r = new RegExp(re.source, re.flags);
4082
+ let count = 0;
4083
+ while (r.exec(source) !== null) count++;
4084
+ return count;
4085
+ }
4086
+ var TestQualityEngine = class extends BaseEngine {
4087
+ id = "test-quality";
4088
+ name = "Test Quality Engine";
4089
+ version = "1.0.0";
4090
+ supportedExtensions = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mts", ".cts"]);
4091
+ async scan(delta, signal) {
4092
+ const uri = delta.documentUri;
4093
+ const cleanUri = uri.replace(/^file:\/\//, "");
4094
+ if (!TEST_FILE_RE.test(cleanUri)) return [];
4095
+ if (STORY_FILE_RE.test(cleanUri)) return [];
4096
+ if (/\/tests?\/(?:setup|helpers?|utils?|fixtures?)[^/]*$/i.test(cleanUri)) return [];
4097
+ this.checkAbort(signal);
4098
+ const source = delta.fullText;
4099
+ const lines = source.split("\n");
4100
+ const pending = [];
4101
+ const expectCount = countMatches(source, EXPECT_CALL_RE);
4102
+ const itCount = countMatches(source, IT_CALL_RE);
4103
+ const describeCount = countMatches(source, DESCRIBE_CALL_RE);
4104
+ const mockCount = countMatches(source, MOCK_CALL_RE);
4105
+ if ((itCount > 0 || describeCount > 0) && expectCount === 0) {
4106
+ const usesAssert = /\bassert\s*\(|\bassert\.(?:ok|equal|deepEqual|strictEqual)/i.test(source);
4107
+ const usesChai = /\.\s*should\b|\bexpect\s*\([^)]+\)\s*\.\s*to\b/i.test(source);
4108
+ if (!usesAssert && !usesChai) {
4109
+ pending.push({
4110
+ ruleId: "TQ001",
4111
+ severity: "high",
4112
+ confidence: 0.9,
4113
+ message: `Test file has ${itCount} test block(s) but zero assertions`,
4114
+ evidence: `${itCount} it/test blocks, 0 expect()`,
4115
+ suggestion: "Add `expect(...)` (vitest/jest) or `assert(...)` (node:test) calls to verify behavior. A test without assertions cannot fail except by throwing.",
4116
+ line: 1,
4117
+ column: 0
4118
+ });
4119
+ }
4120
+ }
4121
+ {
4122
+ const r = new RegExp(SKIPPED_TEST_RE.source, SKIPPED_TEST_RE.flags);
4123
+ let m;
4124
+ while ((m = r.exec(source)) !== null) {
4125
+ pending.push({
4126
+ ruleId: "TQ005",
4127
+ severity: "medium",
4128
+ confidence: 0.95,
4129
+ message: `Skipped test left in source: ${m[0]}`,
4130
+ evidence: m[0],
4131
+ suggestion: "Remove `.skip` / `xit` / `xdescribe` once the test is fixed, or delete the test if it is no longer relevant. Skipped tests are silent dead weight in CI.",
4132
+ line: lineOf(source, m.index),
4133
+ column: 0
4134
+ });
4135
+ }
4136
+ }
4137
+ for (let i = 0; i < lines.length; i++) {
4138
+ const raw = lines[i];
4139
+ for (const re of TAUTOLOGICAL_ASSERTIONS) {
4140
+ if (re.test(raw)) {
4141
+ pending.push({
4142
+ ruleId: "TQ002",
4143
+ severity: "high",
4144
+ confidence: 0.95,
4145
+ message: "Tautological assertion \u2014 passes regardless of implementation",
4146
+ evidence: raw.trim().slice(0, 80),
4147
+ suggestion: "Replace with an assertion that compares actual behavior against an expected outcome (e.g. expect(result).toBe(expectedValue)).",
4148
+ line: i + 1,
4149
+ column: raw.search(re)
4150
+ });
4151
+ break;
4152
+ }
4153
+ }
4154
+ }
4155
+ for (let i = 0; i < lines.length; i++) {
4156
+ const raw = lines[i];
4157
+ if (!/\bconsole\.(?:log|info|debug)\s*\(/.test(raw)) continue;
4158
+ if (i === 0 && /^\s*(?:\/\/|\/\*)/.test(raw)) continue;
4159
+ const window = lines.slice(Math.max(0, i - 2), Math.min(lines.length, i + 3)).join("\n");
4160
+ if (/\bexpect\s*\(/.test(window)) continue;
4161
+ pending.push({
4162
+ ruleId: "TQ007",
4163
+ severity: "medium",
4164
+ confidence: 0.7,
4165
+ message: "console.log in test without nearby expect() \u2014 likely debugging leftover or assertion stand-in",
4166
+ evidence: raw.trim().slice(0, 80),
4167
+ suggestion: "Remove the console.log if it was for debugging, or replace with an `expect()` call that verifies the condition.",
4168
+ line: i + 1,
4169
+ column: 0
4170
+ });
4171
+ }
4172
+ if (itCount > 0) {
4173
+ const blocks = extractTestBlocks(source);
4174
+ for (const block of blocks) {
4175
+ const blockExpects = countMatches(block.body, EXPECT_CALL_RE);
4176
+ const hasNotToThrow = /\.\s*not\s*\.\s*toThrow\s*\(/.test(block.body);
4177
+ const hasStrongAssertion = /\.\s*(?:toBe|toEqual|toMatch|toContain|toHaveLength|toHaveProperty|toHaveBeenCalled|toStrictEqual|toBeNull|toBeUndefined|toBeDefined|toBeTruthy|toBeFalsy|toBeGreaterThan|toBeLessThan|toBeCloseTo|toBeInstanceOf|toThrow)\b/.test(
4178
+ block.body
4179
+ );
4180
+ const hasNonNegatedStrongAssertion = hasStrongAssertion && /(?<!\.\s*not\s*)\.\s*(?:toBe|toEqual|toMatch|toContain|toHaveLength|toHaveProperty|toHaveBeenCalled|toStrictEqual|toBeNull|toBeUndefined|toBeDefined|toBeTruthy|toBeFalsy|toBeGreaterThan|toBeLessThan|toBeCloseTo|toBeInstanceOf|toThrow)\b/.test(
4181
+ block.body
4182
+ );
4183
+ const onlyNotToThrow = blockExpects > 0 && hasNotToThrow && !hasNonNegatedStrongAssertion;
4184
+ if (onlyNotToThrow && /^\s*should\b/i.test(block.name) && !/throw|error|reject|fail/i.test(block.name)) {
4185
+ pending.push({
4186
+ ruleId: "TQ006",
4187
+ severity: "medium",
4188
+ confidence: 0.8,
4189
+ message: `Test "${block.name}" only checks that nothing throws \u2014 name implies a stronger assertion`,
4190
+ evidence: `it("${block.name}", ...) { expect(...).not.toThrow() }`,
4191
+ suggestion: "Add an explicit assertion against the function output. `not.toThrow()` only catches exceptions; it does not verify return values, side effects, or state.",
4192
+ line: block.startLine,
4193
+ column: 0
4194
+ });
4195
+ }
4196
+ const weakPresenceRe = /\.\s*(?:toBeDefined|toBeTruthy|not\s*\.\s*toBeUndefined|not\s*\.\s*toBeNull)\s*\(/;
4197
+ const hasWeakPresence = weakPresenceRe.test(block.body);
4198
+ const hasReallyStrong = /\.\s*(?:toBe|toEqual|toMatch|toContain|toHaveLength|toHaveProperty|toHaveBeenCalled|toStrictEqual|toBeGreaterThan|toBeLessThan|toBeCloseTo|toBeInstanceOf|toThrow)\b/.test(
4199
+ block.body
4200
+ );
4201
+ const onlyWeakPresence = blockExpects > 0 && hasWeakPresence && !hasReallyStrong && !hasNotToThrow;
4202
+ if (onlyWeakPresence && /^\s*should\b/i.test(block.name) && !/defined|exist|present|truthy|render|return|undefined|null/i.test(block.name)) {
4203
+ pending.push({
4204
+ ruleId: "TQ009",
4205
+ severity: "medium",
4206
+ confidence: 0.8,
4207
+ message: `Test "${block.name}" only checks presence \u2014 name implies a stronger assertion`,
4208
+ evidence: `it("${block.name}", ...) { expect(...).toBeDefined() }`,
4209
+ suggestion: "`.toBeDefined()` / `.toBeTruthy()` confirm the value exists but not that it is correct. Add an explicit assertion against the expected output.",
4210
+ line: block.startLine,
4211
+ column: 0
4212
+ });
4213
+ }
4214
+ const tryCatchSwallow = /try\s*\{[\s\S]*?expect\s*\([\s\S]*?\}\s*catch\s*\(\s*\w*\s*\)\s*\{[^}]*\}/.test(
4215
+ block.body
4216
+ ) && !/throw|expect\.fail|done\s*\(/.test(block.body.split("catch")[1] ?? "");
4217
+ if (tryCatchSwallow) {
4218
+ pending.push({
4219
+ ruleId: "TQ004",
4220
+ severity: "critical",
4221
+ confidence: 0.9,
4222
+ message: `Test "${block.name}" swallows assertion failures inside try/catch`,
4223
+ evidence: "try { expect(...) } catch { /* swallowed */ }",
4224
+ suggestion: "Remove the try/catch around assertions, or have the catch block re-throw / call `expect.fail`. Assertion errors must propagate to the runner.",
4225
+ line: block.startLine,
4226
+ column: 0
4227
+ });
4228
+ }
4229
+ }
4230
+ if (mockCount >= 4 && expectCount >= 1 && mockCount >= expectCount * 2) {
4231
+ pending.push({
4232
+ ruleId: "TQ003",
4233
+ severity: "medium",
4234
+ confidence: 0.7,
4235
+ message: `Mock-heavy test file: ${mockCount} mock calls vs ${expectCount} assertion(s)`,
4236
+ evidence: `mocks=${mockCount}, expects=${expectCount}`,
4237
+ suggestion: 'When mocks dominate assertions, the test usually verifies "the function called the mock", not real behavior. Reduce mocking surface or add behavioral assertions.',
4238
+ line: 1,
4239
+ column: 0
4240
+ });
4241
+ }
4242
+ const importRe = /^\s*import\s+(?:[\w*\s{},]+\s+from\s+)?['"]([^'"]+)['"]/gm;
4243
+ const projectImport = (() => {
4244
+ let m;
4245
+ while ((m = importRe.exec(source)) !== null) {
4246
+ const spec = m[1] ?? "";
4247
+ if (spec.startsWith(".") || spec.startsWith("@/") || spec.startsWith("~/")) return true;
4248
+ }
4249
+ return false;
4250
+ })();
4251
+ if (!projectImport && itCount >= 2 && expectCount >= 2) {
4252
+ pending.push({
4253
+ ruleId: "TQ008",
4254
+ severity: "medium",
4255
+ confidence: 0.65,
4256
+ message: "Test file imports nothing from the project \u2014 what is being tested?",
4257
+ evidence: "no relative or aliased imports",
4258
+ suggestion: "A test that does not import any project module either tests nothing or duplicates the production code inline. Either import the unit under test or delete this file.",
4259
+ line: 1,
4260
+ column: 0
4261
+ });
4262
+ }
4263
+ }
4264
+ if (pending.length === 0) return [];
4265
+ return pending.map(
4266
+ (p) => this.createFinding({
4267
+ id: this.deterministicId(cleanUri, p.line, p.column, p.ruleId, p.evidence.slice(0, 32)),
4268
+ ruleId: p.ruleId,
4269
+ category: "test_quality",
4270
+ message: p.message,
4271
+ severity: p.severity,
4272
+ confidence: p.confidence,
4273
+ file: cleanUri,
4274
+ line: p.line,
4275
+ column: p.column,
4276
+ evidence: p.evidence,
4277
+ suggestion: p.suggestion,
4278
+ autoFixable: false
4279
+ })
4280
+ );
4281
+ }
4282
+ };
4283
+ var BreakerState = /* @__PURE__ */ ((BreakerState2) => {
4284
+ BreakerState2[BreakerState2["Closed"] = 0] = "Closed";
4285
+ BreakerState2[BreakerState2["Open"] = 1] = "Open";
4286
+ BreakerState2[BreakerState2["HalfOpen"] = 2] = "HalfOpen";
4287
+ return BreakerState2;
4288
+ })(BreakerState || {});
4289
+ var CircuitBreaker = class {
4290
+ constructor(_threshold = 5, _cooldownMs = 3e4) {
4291
+ this._threshold = _threshold;
4292
+ this._cooldownMs = _cooldownMs;
4293
+ }
4294
+ _state = 0;
4295
+ _failures = 0;
4296
+ _halfOpenTimer = null;
4297
+ get isOpen() {
4298
+ return this._state === 1;
4299
+ }
4300
+ get state() {
4301
+ return BreakerState[this._state];
4302
+ }
4303
+ recordSuccess() {
4304
+ this._failures = 0;
4305
+ this._state = 0;
4306
+ }
4307
+ recordFailure() {
4308
+ this._failures++;
4309
+ if (this._failures >= this._threshold) {
4310
+ this._state = 1;
4311
+ this._scheduleHalfOpen();
4312
+ }
4313
+ }
4314
+ tryAllow() {
4315
+ if (this._state === 0) return true;
4316
+ if (this._state === 2) {
4317
+ this._state = 0;
4318
+ return true;
4319
+ }
4320
+ return false;
4321
+ }
4322
+ _scheduleHalfOpen() {
4323
+ if (this._halfOpenTimer) clearTimeout(this._halfOpenTimer);
4324
+ this._halfOpenTimer = setTimeout(() => {
4325
+ this._state = 2;
4326
+ this._halfOpenTimer = null;
4327
+ }, this._cooldownMs);
4328
+ }
4329
+ dispose() {
4330
+ if (this._halfOpenTimer) clearTimeout(this._halfOpenTimer);
4331
+ }
4332
+ };
4333
+ var EngineRegistry = class {
4334
+ _slots = /* @__PURE__ */ new Map();
4335
+ _breakers = /* @__PURE__ */ new Map();
4336
+ _registrationOrder = /* @__PURE__ */ new Map();
4337
+ _nextOrder = 0;
4338
+ _activeCache = null;
4339
+ /**
4340
+ * Register an engine. If an engine with the same ID already exists, it is replaced.
4341
+ */
4342
+ register(engine, options = {}) {
4343
+ const slot = {
4344
+ engine,
4345
+ timeoutMs: options.timeoutMs ?? 200,
4346
+ priority: options.priority ?? 100,
4347
+ enabled: options.enabled ?? true,
4348
+ extensions: options.extensions ?? void 0
4349
+ };
4350
+ this._slots.set(engine.id, slot);
4351
+ this._breakers.set(engine.id, new CircuitBreaker());
4352
+ if (!this._registrationOrder.has(engine.id)) {
4353
+ this._registrationOrder.set(engine.id, this._nextOrder++);
4354
+ }
4355
+ this._activeCache = null;
4356
+ }
4357
+ /**
4358
+ * Deregister an engine by ID. Disposes the engine and its circuit breaker.
4359
+ */
4360
+ deregister(id) {
4361
+ const slot = this._slots.get(id);
4362
+ if (!slot) return false;
4363
+ slot.engine.dispose?.();
4364
+ this._breakers.get(id)?.dispose();
4365
+ this._slots.delete(id);
4366
+ this._breakers.delete(id);
4367
+ this._activeCache = null;
4368
+ return true;
4369
+ }
4370
+ /**
4371
+ * Get a specific engine slot by ID.
4372
+ */
4373
+ get(id) {
4374
+ return this._slots.get(id);
4375
+ }
4376
+ /**
4377
+ * Get the circuit breaker for an engine.
4378
+ */
4379
+ getBreaker(id) {
4380
+ return this._breakers.get(id);
4381
+ }
4382
+ /**
4383
+ * Get all enabled engine slots, sorted by priority (ascending), then registration order.
4384
+ */
4385
+ getActive() {
4386
+ if (this._activeCache) return this._activeCache;
4387
+ this._activeCache = [...this._slots.values()].filter((s) => s.enabled !== false).sort((a, b) => {
4388
+ if (a.priority !== b.priority) return a.priority - b.priority;
4389
+ return (this._registrationOrder.get(a.engine.id) ?? 0) - (this._registrationOrder.get(b.engine.id) ?? 0);
4390
+ });
4391
+ return this._activeCache;
4392
+ }
4393
+ /**
4394
+ * Get all registered engine slots (including disabled).
4395
+ */
4396
+ getAll() {
4397
+ return [...this._slots.values()];
4398
+ }
4399
+ /**
4400
+ * Enable or disable an engine by ID.
4401
+ */
4402
+ setEnabled(id, enabled) {
4403
+ const slot = this._slots.get(id);
4404
+ if (!slot) return false;
4405
+ slot.enabled = enabled;
4406
+ this._activeCache = null;
4407
+ return true;
4408
+ }
4409
+ /**
4410
+ * Activate all registered engines. Call once at startup.
4411
+ */
4412
+ async activateAll(onError) {
3285
4413
  const tasks = [...this._slots.entries()].map(async ([id, slot]) => {
3286
4414
  try {
3287
4415
  await slot.engine.activate?.();
@@ -3312,7 +4440,7 @@ var EngineRegistry = class {
3312
4440
  if (supportedExtensions) {
3313
4441
  const uri = delta.documentUri;
3314
4442
  const pathPart = uri.replace(/^file:\/\//, "");
3315
- const ext = path5__default.extname(pathPart).toLowerCase() || ".ts";
4443
+ const ext = path6__default.extname(pathPart).toLowerCase() || ".ts";
3316
4444
  if (!supportedExtensions.has(ext)) {
3317
4445
  return {
3318
4446
  findings: [],
@@ -3408,6 +4536,8 @@ function createDefaultRegistry() {
3408
4536
  registry.register(new TypeContractEngine(), { priority: 50, timeoutMs: 120 });
3409
4537
  registry.register(new SecurityPatternEngine(), { priority: 55, timeoutMs: 100 });
3410
4538
  registry.register(new PerformanceAntipatternEngine(), { priority: 60, timeoutMs: 120 });
4539
+ registry.register(new AIRulesAttackEngine(), { priority: 45, timeoutMs: 60 });
4540
+ registry.register(new TestQualityEngine(), { priority: 47, timeoutMs: 30 });
3411
4541
  return registry;
3412
4542
  }
3413
4543
  function isTestFile2(uri) {
@@ -4103,6 +5233,14 @@ function literalKey(expr) {
4103
5233
  if (import_typescript.default.isBigIntLiteral(e)) return `bigint:${e.text}`;
4104
5234
  return null;
4105
5235
  }
5236
+ var IMPL001_SKIP_LONG_SAME_STRING = 200;
5237
+ function isLongSameLiteralReturn(expr) {
5238
+ const e = unwrap(expr);
5239
+ if (import_typescript.default.isStringLiteral(e) || import_typescript.default.isNoSubstitutionTemplateLiteral(e)) {
5240
+ return e.text.length >= IMPL001_SKIP_LONG_SAME_STRING;
5241
+ }
5242
+ return false;
5243
+ }
4106
5244
  function getFunctionName(fn) {
4107
5245
  if (import_typescript.default.isFunctionDeclaration(fn) && fn.name) return fn.name.text;
4108
5246
  if (import_typescript.default.isFunctionExpression(fn) && fn.name) return fn.name.text;
@@ -4129,6 +5267,7 @@ function jsdocSuggestsStableLiteral(node) {
4129
5267
  );
4130
5268
  }
4131
5269
  var IMPL001_NAME_ALLOW = /^(noop|identity|constant|tojson|valueof|jsonstringify)$/i;
5270
+ var IMPL008_EMPTY_BODY_ALLOW_NAMES = /* @__PURE__ */ new Set(["deactivate"]);
4132
5271
  function isBooleanPredicateName(name) {
4133
5272
  if (!name) return false;
4134
5273
  return /^(is|has|can|must|should|will|did)[A-Z]/.test(name) || /^(is|has|can|must|should)_[a-z]/i.test(name);
@@ -4350,6 +5489,7 @@ var IncompleteImplEngine = class extends BaseEngine {
4350
5489
  if (jsdocSuggestsStableLiteral(fn)) return;
4351
5490
  const rets = collectReturns(fn);
4352
5491
  if (rets.length === 0) return;
5492
+ if (rets.some((r) => isLongSameLiteralReturn(r))) return;
4353
5493
  const keys = rets.map(literalKey);
4354
5494
  if (keys.some((k) => k === null)) return;
4355
5495
  const first = keys[0];
@@ -4391,13 +5531,15 @@ var IncompleteImplEngine = class extends BaseEngine {
4391
5531
  ruleIMPL008(fn, filePath, uri, findings, lines) {
4392
5532
  if (isAbstractOrOverloadStub(fn)) return;
4393
5533
  if (!functionHasEmptyBody(fn)) return;
5534
+ const fnName = getFunctionName(fn);
5535
+ if (fnName && IMPL008_EMPTY_BODY_ALLOW_NAMES.has(fnName)) return;
4394
5536
  const pos = fn.getStart(fn.getSourceFile());
4395
5537
  const { line, character } = fn.getSourceFile().getLineAndCharacterOfPosition(pos);
4396
5538
  const lineNum = line + 1;
4397
5539
  if (this.lineHasEmptyBodySuppression(lines, lineNum)) return;
4398
5540
  const name = getFunctionName(fn);
4399
- const n = uri.replace(/\\/g, "/");
4400
- const sharedLibPath = /(?:^|\/)packages\/[^/]+\/src\//i.test(n) || /(?:^|\/)lib(?:s)?\/src\//i.test(n);
5541
+ const normUri = uri.replace(/\\/g, "/");
5542
+ const sharedLibPath = /(?:^|\/)packages\/[^/]+\/src\//i.test(normUri) || /(?:^|\/)lib(?:s)?\/src\//i.test(normUri);
4401
5543
  const confidence = sharedLibPath ? 0.72 : 0.78;
4402
5544
  findings.push(
4403
5545
  this.createFinding({
@@ -5137,12 +6279,31 @@ function hasDetachedAnnotation(sf, node) {
5137
6279
  }
5138
6280
  return false;
5139
6281
  }
6282
+ var _sfLinesCache = /* @__PURE__ */ new WeakMap();
6283
+ function getSourceLines(sf) {
6284
+ let lines = _sfLinesCache.get(sf);
6285
+ if (!lines) {
6286
+ lines = sf.getFullText().split("\n");
6287
+ _sfLinesCache.set(sf, lines);
6288
+ }
6289
+ return lines;
6290
+ }
6291
+ var IGNORE_REGEXES = /* @__PURE__ */ new Map();
6292
+ function getIgnoreRegex(rule) {
6293
+ let re = IGNORE_REGEXES.get(rule);
6294
+ if (!re) {
6295
+ re = new RegExp(`@vibecheck-ignore(?:-${rule})?\\b`, "i");
6296
+ IGNORE_REGEXES.set(rule, re);
6297
+ }
6298
+ return re;
6299
+ }
5140
6300
  function ignoreCommentNear(sf, pos, rule) {
5141
6301
  const lc = sf.getLineAndCharacterOfPosition(pos);
5142
- const lines = sf.getFullText().split("\n");
6302
+ const lines = getSourceLines(sf);
6303
+ const re = getIgnoreRegex(rule);
5143
6304
  for (let L = Math.max(0, lc.line - 3); L <= lc.line; L++) {
5144
6305
  const row = lines[L] ?? "";
5145
- if (new RegExp(`@vibecheck-ignore(?:-${rule})?\\b`, "i").test(row)) return true;
6306
+ if (re.test(row)) return true;
5146
6307
  }
5147
6308
  return false;
5148
6309
  }
@@ -5474,13 +6635,893 @@ var OutcomeVerificationEngine = class extends BaseEngine {
5474
6635
  file: ctx.uri,
5475
6636
  line,
5476
6637
  column,
5477
- evidence: ret.getText().slice(0, 100),
5478
- suggestion: "Await the request, validate the outcome, then return based on the actual response (or return the promise).",
6638
+ evidence: ret.getText().slice(0, 100),
6639
+ suggestion: "Await the request, validate the outcome, then return based on the actual response (or return the promise).",
6640
+ autoFixable: false
6641
+ })
6642
+ );
6643
+ }
6644
+ };
6645
+ function isNpmManifest(uri) {
6646
+ const path24 = uri.replace(/^file:\/\//, "").toLowerCase();
6647
+ const base = path24.split("/").pop() ?? "";
6648
+ if (base !== "package.json") return false;
6649
+ return !/\/(node_modules|dist|build|\.next|\.turbo|out)\//.test(path24);
6650
+ }
6651
+ function isPythonManifest(uri) {
6652
+ const path24 = uri.replace(/^file:\/\//, "").toLowerCase();
6653
+ const base = path24.split("/").pop() ?? "";
6654
+ if (base === "requirements.txt" || base === "pipfile" || base === "pipfile.lock") return true;
6655
+ if (base === "pyproject.toml") return true;
6656
+ if (/^requirements[-.][\w.-]+\.txt$/.test(base)) return true;
6657
+ return false;
6658
+ }
6659
+ var CANONICAL_AI_PACKAGES_NPM = /* @__PURE__ */ new Set([
6660
+ // Anthropic
6661
+ "@anthropic-ai/sdk",
6662
+ "@anthropic-ai/claude-code",
6663
+ "@anthropic-ai/bedrock-sdk",
6664
+ "@anthropic-ai/vertex-sdk",
6665
+ "@anthropic-ai/tokenizer",
6666
+ // OpenAI
6667
+ "openai",
6668
+ // Google
6669
+ "@google/genai",
6670
+ "@google/generative-ai",
6671
+ "@google-ai/generativelanguage",
6672
+ "@google-cloud/vertexai",
6673
+ "@google-cloud/aiplatform",
6674
+ // LangChain
6675
+ "langchain",
6676
+ "@langchain/core",
6677
+ "@langchain/anthropic",
6678
+ "@langchain/openai",
6679
+ "@langchain/google-genai",
6680
+ "@langchain/community",
6681
+ // MCP
6682
+ "@modelcontextprotocol/sdk",
6683
+ "@modelcontextprotocol/server-everything",
6684
+ "@modelcontextprotocol/server-filesystem",
6685
+ "@modelcontextprotocol/server-github",
6686
+ "@modelcontextprotocol/server-gitlab",
6687
+ "@modelcontextprotocol/server-memory",
6688
+ "@modelcontextprotocol/server-postgres",
6689
+ "@modelcontextprotocol/server-puppeteer",
6690
+ "@modelcontextprotocol/server-sequentialthinking",
6691
+ "@modelcontextprotocol/server-slack",
6692
+ "@modelcontextprotocol/server-time",
6693
+ "@modelcontextprotocol/inspector",
6694
+ // Vercel AI
6695
+ "ai",
6696
+ "@ai-sdk/anthropic",
6697
+ "@ai-sdk/openai",
6698
+ "@ai-sdk/google",
6699
+ "@ai-sdk/react",
6700
+ // Cohere
6701
+ "cohere-ai",
6702
+ // Mistral
6703
+ "@mistralai/mistralai"
6704
+ ]);
6705
+ var CANONICAL_AI_PACKAGES_PYPI = /* @__PURE__ */ new Set([
6706
+ "anthropic",
6707
+ "openai",
6708
+ "google-generativeai",
6709
+ "google-cloud-aiplatform",
6710
+ "langchain",
6711
+ "langchain-core",
6712
+ "langchain-anthropic",
6713
+ "langchain-openai",
6714
+ "langchain-google-genai",
6715
+ "langchain-community",
6716
+ "mcp",
6717
+ "fastmcp",
6718
+ "mcp-server-fetch",
6719
+ "mcp-server-git",
6720
+ "mcp-server-time",
6721
+ "mistralai",
6722
+ "cohere",
6723
+ "tiktoken"
6724
+ ]);
6725
+ var BRAND_KEYWORDS = [
6726
+ "claude",
6727
+ "anthropic",
6728
+ "openai",
6729
+ "chatgpt",
6730
+ "gpt-4",
6731
+ "gpt4",
6732
+ "gemini",
6733
+ "modelcontextprotocol"
6734
+ ];
6735
+ var AI_HALLUCINATED_PACKAGES = /* @__PURE__ */ new Set([
6736
+ // npm — Anthropic / Claude
6737
+ "@anthropic/sdk",
6738
+ "@anthropic/claude",
6739
+ "@anthropic/claude-sdk",
6740
+ "@anthropic/api",
6741
+ "claude-sdk",
6742
+ "claude-api",
6743
+ "claude-ai",
6744
+ "anthropic-sdk",
6745
+ "anthropic-api",
6746
+ "anthropic-client",
6747
+ "anthropic-typescript",
6748
+ "claude-typescript",
6749
+ "claude-node",
6750
+ // npm — OpenAI
6751
+ "@openai/sdk",
6752
+ "@openai/api",
6753
+ "@openai/node",
6754
+ "openai-sdk",
6755
+ "openai-api",
6756
+ "openai-client",
6757
+ "openai-node-sdk",
6758
+ "gpt-sdk",
6759
+ "chatgpt-sdk",
6760
+ // npm — MCP
6761
+ "@mcp/sdk",
6762
+ "@mcp/server",
6763
+ "@mcp/client",
6764
+ "mcp-sdk",
6765
+ "mcp-protocol",
6766
+ "mcp-server-sdk",
6767
+ "modelcontext-sdk",
6768
+ // npm — Gemini / Google
6769
+ "@google/gemini",
6770
+ "gemini-sdk",
6771
+ "gemini-api",
6772
+ "google-gemini-sdk",
6773
+ // PyPI
6774
+ "claude-python",
6775
+ "anthropic-python",
6776
+ "anthropic-claude",
6777
+ "claude-ai-sdk",
6778
+ "openai-python-sdk",
6779
+ "gpt-python",
6780
+ "mcp-sdk-python",
6781
+ "modelcontextprotocol",
6782
+ "gemini-python"
6783
+ ]);
6784
+ var WILDCARD_VERSION_RE = /^(?:\*|latest|x|>=?\s*0(?:\.0)?(?:\.0)?)$/i;
6785
+ var UNPINNED_GIT_URL_RE = /^git(?:\+(?:https?|ssh))?:\/\/[^#\s]+(?:\.git)?(?:#(?!(?:[a-f0-9]{7,40}\b|semver:|v?\d+\.\d+\.\d+)).*)?$/i;
6786
+ var HTTP_TARBALL_RE = /^http:\/\//i;
6787
+ var PYPI_LINE_RE = /^\s*([A-Za-z][A-Za-z0-9._-]*)\s*(?:\[[^\]]+\])?\s*(?:([<>=!~]=?|===)\s*([\w.+!*-]+))?\s*(?:;.*)?$/;
6788
+ function detectBrandImpersonationNpm(pkg) {
6789
+ const lower = pkg.toLowerCase();
6790
+ if (CANONICAL_AI_PACKAGES_NPM.has(lower)) return null;
6791
+ for (const brand of BRAND_KEYWORDS) {
6792
+ if (lower.includes(brand)) {
6793
+ if (lower.startsWith("@anthropic-ai/")) return null;
6794
+ if (lower.startsWith("@openai/")) return null;
6795
+ if (lower.startsWith("@modelcontextprotocol/")) return null;
6796
+ if (lower.startsWith("@langchain/")) return null;
6797
+ if (lower.startsWith("@ai-sdk/")) return null;
6798
+ if (lower.startsWith("@google-ai/") || lower.startsWith("@google-cloud/")) return null;
6799
+ return brand;
6800
+ }
6801
+ }
6802
+ return null;
6803
+ }
6804
+ function detectBrandImpersonationPypi(pkg) {
6805
+ const lower = pkg.toLowerCase().replace(/_/g, "-");
6806
+ if (CANONICAL_AI_PACKAGES_PYPI.has(lower)) return null;
6807
+ if (lower.startsWith("langchain-")) return null;
6808
+ for (const brand of BRAND_KEYWORDS) {
6809
+ if (lower.includes(brand)) return brand;
6810
+ }
6811
+ return null;
6812
+ }
6813
+ function detectMCPSlopsquatNpm(pkg) {
6814
+ const lower = pkg.toLowerCase();
6815
+ if (CANONICAL_AI_PACKAGES_NPM.has(lower)) return false;
6816
+ if (lower.startsWith("@modelcontextprotocol/")) return false;
6817
+ return /^mcp-server-|^@[\w-]+\/mcp-server-|^mcp-/.test(lower) || /-mcp-server$|-mcp$/.test(lower);
6818
+ }
6819
+ function detectMCPSlopsquatPypi(pkg) {
6820
+ const lower = pkg.toLowerCase().replace(/_/g, "-");
6821
+ if (CANONICAL_AI_PACKAGES_PYPI.has(lower)) return false;
6822
+ return /^mcp-server-|^mcp-/.test(lower);
6823
+ }
6824
+ var DEP_KEYS = [
6825
+ "dependencies",
6826
+ "devDependencies",
6827
+ "peerDependencies",
6828
+ "optionalDependencies"
6829
+ ];
6830
+ var SlopsquatEngine = class extends BaseEngine {
6831
+ id = "slopsquat";
6832
+ name = "AI Slopsquat & Dependency Hygiene Engine";
6833
+ version = "1.0.0";
6834
+ supportedExtensions = null;
6835
+ async scan(delta, signal) {
6836
+ const uri = delta.documentUri;
6837
+ const isNpm = isNpmManifest(uri);
6838
+ const isPy = isPythonManifest(uri);
6839
+ if (!isNpm && !isPy) return [];
6840
+ this.checkAbort(signal);
6841
+ const source = delta.fullText;
6842
+ const cleanUri = uri.replace(/^file:\/\//, "");
6843
+ const pending = [];
6844
+ if (isNpm) {
6845
+ this._checkNpmManifest(source, pending);
6846
+ } else {
6847
+ this._checkPythonManifest(source, pending);
6848
+ }
6849
+ if (pending.length === 0) return [];
6850
+ return pending.map(
6851
+ (p) => this.createFinding({
6852
+ id: this.deterministicId(cleanUri, p.line, p.column, p.ruleId, p.evidence.slice(0, 32)),
6853
+ ruleId: p.ruleId,
6854
+ category: "slopsquat",
6855
+ message: p.message,
6856
+ severity: p.severity,
6857
+ confidence: p.confidence,
6858
+ file: cleanUri,
6859
+ line: p.line,
6860
+ column: p.column,
6861
+ evidence: p.evidence,
6862
+ suggestion: p.suggestion,
6863
+ autoFixable: false
6864
+ })
6865
+ );
6866
+ }
6867
+ // ─── npm ────────────────────────────────────────────────────────────────
6868
+ _checkNpmManifest(source, out) {
6869
+ let doc;
6870
+ try {
6871
+ doc = JSON.parse(source);
6872
+ } catch {
6873
+ return;
6874
+ }
6875
+ for (const depKey of DEP_KEYS) {
6876
+ const deps = doc[depKey];
6877
+ if (!deps || typeof deps !== "object") continue;
6878
+ for (const [pkg, rawVersion] of Object.entries(deps)) {
6879
+ if (typeof rawVersion !== "string") continue;
6880
+ const version = rawVersion.trim();
6881
+ const line = this._findLine(source, `"${pkg}"`);
6882
+ if (WILDCARD_VERSION_RE.test(version)) {
6883
+ out.push({
6884
+ ruleId: "SLOP001",
6885
+ severity: "high",
6886
+ confidence: 0.95,
6887
+ message: `Wildcard version for '${pkg}' \u2014 accepts any future release including malicious ones`,
6888
+ evidence: `"${pkg}": "${version}"`,
6889
+ suggestion: `Pin to a specific version: "${pkg}": "^X.Y.Z" (or exact "X.Y.Z").`,
6890
+ line,
6891
+ column: 0
6892
+ });
6893
+ }
6894
+ if (UNPINNED_GIT_URL_RE.test(version)) {
6895
+ out.push({
6896
+ ruleId: "SLOP006",
6897
+ severity: "high",
6898
+ confidence: 0.9,
6899
+ message: `Git dependency '${pkg}' is not pinned to a commit`,
6900
+ evidence: `"${pkg}": "${version}"`,
6901
+ suggestion: `Pin to a full commit SHA: "${pkg}": "git+https://...#<full-sha>". A branch reference can change under you.`,
6902
+ line,
6903
+ column: 0
6904
+ });
6905
+ }
6906
+ if (HTTP_TARBALL_RE.test(version)) {
6907
+ out.push({
6908
+ ruleId: "SLOP007",
6909
+ severity: "critical",
6910
+ confidence: 0.99,
6911
+ message: `Dependency '${pkg}' installs over plaintext HTTP \u2014 MITM exposure`,
6912
+ evidence: `"${pkg}": "${version}"`,
6913
+ suggestion: "Use https:// for tarball URLs.",
6914
+ line,
6915
+ column: 0
6916
+ });
6917
+ }
6918
+ if (AI_HALLUCINATED_PACKAGES.has(pkg.toLowerCase())) {
6919
+ out.push({
6920
+ ruleId: "SLOP004",
6921
+ severity: "critical",
6922
+ confidence: 0.99,
6923
+ message: `'${pkg}' is a known AI-hallucinated package name \u2014 does not exist in the official ecosystem`,
6924
+ evidence: `"${pkg}"`,
6925
+ suggestion: this._fixHintForHallucinated(pkg),
6926
+ line,
6927
+ column: 0
6928
+ });
6929
+ continue;
6930
+ }
6931
+ const brand = detectBrandImpersonationNpm(pkg);
6932
+ if (brand) {
6933
+ out.push({
6934
+ ruleId: "SLOP002",
6935
+ severity: "critical",
6936
+ confidence: 0.9,
6937
+ message: `'${pkg}' uses the '${brand}' brand but is not from the official organization`,
6938
+ evidence: `"${pkg}"`,
6939
+ suggestion: this._fixHintForBrand(brand),
6940
+ line,
6941
+ column: 0
6942
+ });
6943
+ }
6944
+ if (detectMCPSlopsquatNpm(pkg)) {
6945
+ out.push({
6946
+ ruleId: "SLOP003",
6947
+ severity: "high",
6948
+ confidence: 0.85,
6949
+ message: `'${pkg}' claims to be an MCP server but is not in the @modelcontextprotocol/ scope`,
6950
+ evidence: `"${pkg}"`,
6951
+ suggestion: "Verify the package author. Official MCP servers live under @modelcontextprotocol/. Vetted third-party MCP servers should be added to an explicit allowlist.",
6952
+ line,
6953
+ column: 0
6954
+ });
6955
+ }
6956
+ }
6957
+ }
6958
+ }
6959
+ // ─── PyPI ───────────────────────────────────────────────────────────────
6960
+ _checkPythonManifest(source, out) {
6961
+ const lines = source.split("\n");
6962
+ for (let i = 0; i < lines.length; i++) {
6963
+ const raw = lines[i];
6964
+ const line = i + 1;
6965
+ const trimmed = raw.replace(/(^|[^\\])#.*$/, "$1").trim();
6966
+ if (!trimmed || trimmed.startsWith("-")) continue;
6967
+ const m = trimmed.match(PYPI_LINE_RE);
6968
+ if (!m) continue;
6969
+ const [, pkgRaw, op, version] = m;
6970
+ if (!pkgRaw) continue;
6971
+ const pkg = pkgRaw.toLowerCase();
6972
+ if (!op || version && (version === "*" || version.startsWith("0"))) {
6973
+ if (!op || version === "*") {
6974
+ out.push({
6975
+ ruleId: "SLOP001",
6976
+ severity: "medium",
6977
+ confidence: 0.85,
6978
+ message: `Unpinned PyPI dependency '${pkg}' \u2014 accepts any version`,
6979
+ evidence: trimmed.slice(0, 80),
6980
+ suggestion: `Pin to a specific version: ${pkg}==X.Y.Z (or use ~= for compatible release).`,
6981
+ line,
6982
+ column: 0
6983
+ });
6984
+ }
6985
+ }
6986
+ if (AI_HALLUCINATED_PACKAGES.has(pkg)) {
6987
+ out.push({
6988
+ ruleId: "SLOP004",
6989
+ severity: "critical",
6990
+ confidence: 0.99,
6991
+ message: `'${pkg}' is a known AI-hallucinated PyPI package name \u2014 does not exist in the official ecosystem`,
6992
+ evidence: trimmed.slice(0, 80),
6993
+ suggestion: this._fixHintForHallucinated(pkg),
6994
+ line,
6995
+ column: 0
6996
+ });
6997
+ continue;
6998
+ }
6999
+ const brand = detectBrandImpersonationPypi(pkg);
7000
+ if (brand) {
7001
+ out.push({
7002
+ ruleId: "SLOP005",
7003
+ severity: "critical",
7004
+ confidence: 0.9,
7005
+ message: `'${pkg}' uses the '${brand}' brand on PyPI but is not the canonical package`,
7006
+ evidence: trimmed.slice(0, 80),
7007
+ suggestion: this._fixHintForBrand(brand),
7008
+ line,
7009
+ column: 0
7010
+ });
7011
+ }
7012
+ if (detectMCPSlopsquatPypi(pkg)) {
7013
+ out.push({
7014
+ ruleId: "SLOP003",
7015
+ severity: "high",
7016
+ confidence: 0.8,
7017
+ message: `'${pkg}' claims to be an MCP server on PyPI \u2014 verify the author`,
7018
+ evidence: trimmed.slice(0, 80),
7019
+ suggestion: "Official MCP packages on PyPI start with `mcp-server-` (e.g. mcp-server-fetch, mcp-server-git). Confirm the package author and source repository before installing.",
7020
+ line,
7021
+ column: 0
7022
+ });
7023
+ }
7024
+ }
7025
+ }
7026
+ // ─── Helpers ────────────────────────────────────────────────────────────
7027
+ _findLine(source, needle) {
7028
+ const idx = source.indexOf(needle);
7029
+ if (idx === -1) return 1;
7030
+ let line = 1;
7031
+ for (let i = 0; i < idx; i++) {
7032
+ if (source.charCodeAt(i) === 10) line++;
7033
+ }
7034
+ return line;
7035
+ }
7036
+ _fixHintForHallucinated(pkg) {
7037
+ const lower = pkg.toLowerCase();
7038
+ if (lower.includes("claude") || lower.includes("anthropic")) {
7039
+ return "Use the official package: @anthropic-ai/sdk (npm) or anthropic (PyPI).";
7040
+ }
7041
+ if (lower.includes("openai") || lower.includes("gpt") || lower.includes("chatgpt")) {
7042
+ return "Use the official package: openai (npm and PyPI).";
7043
+ }
7044
+ if (lower.includes("mcp") || lower.includes("modelcontext")) {
7045
+ return "Use the official MCP SDK: @modelcontextprotocol/sdk (npm) or mcp (PyPI).";
7046
+ }
7047
+ if (lower.includes("gemini") || lower.includes("google")) {
7048
+ return "Use the official package: @google/genai (npm) or google-generativeai (PyPI).";
7049
+ }
7050
+ return "Search npm/PyPI for the actual canonical package name. AI assistants frequently invent plausible-looking package names that do not exist.";
7051
+ }
7052
+ _fixHintForBrand(brand) {
7053
+ switch (brand) {
7054
+ case "claude":
7055
+ case "anthropic":
7056
+ return "Use the official @anthropic-ai/sdk (npm) or anthropic (PyPI). Anything else with the Anthropic/Claude brand in its name is likely impersonation.";
7057
+ case "openai":
7058
+ case "chatgpt":
7059
+ case "gpt-4":
7060
+ case "gpt4":
7061
+ return "Use the official openai (npm and PyPI). The OpenAI org does not publish other branded packages.";
7062
+ case "gemini":
7063
+ return "Use the official @google/genai (npm) or google-generativeai (PyPI). Google does not publish gemini-* packages from third-party scopes.";
7064
+ case "modelcontextprotocol":
7065
+ return "Official MCP packages are scoped under @modelcontextprotocol/ (npm) or named mcp / mcp-server-* (PyPI). Third-party MCP packages should be vetted against an explicit allowlist.";
7066
+ default:
7067
+ return `Verify '${brand}' is the official organization on the registry. AI brand impersonation packages are a common supply-chain attack.`;
7068
+ }
7069
+ }
7070
+ };
7071
+ var INITIAL_VERSION = "1.0.0";
7072
+ var baseLifecycle = {
7073
+ scan: "src/scan.ts"
7074
+ // engines export their scan via class.scan(); pointer is a stub for the manifest schema
7075
+ };
7076
+ var slopsquatManifest = {
7077
+ id: "slopsquat",
7078
+ version: INITIAL_VERSION,
7079
+ description: "Detects typo-squatted package names \u2014 imports that look real but are a known typo of a popular package.",
7080
+ capabilities: ["scan_files", "hallucination"],
7081
+ lifecycle: baseLifecycle,
7082
+ telemetry: {
7083
+ events: ["scan_started", "scan_completed", "slopsquat.match"]
7084
+ }
7085
+ };
7086
+ var fakeFeaturesManifest = {
7087
+ id: "fake_features",
7088
+ version: INITIAL_VERSION,
7089
+ description: "Detects code that simulates a feature without implementing it \u2014 placeholder UI bound to no backend, fake success returns, mock toggles in production.",
7090
+ capabilities: ["scan_files", "hallucination", "quality"],
7091
+ lifecycle: baseLifecycle,
7092
+ telemetry: {
7093
+ events: ["scan_started", "scan_completed", "fake_features.match"]
7094
+ }
7095
+ };
7096
+ var incompleteImplManifest = {
7097
+ id: "incomplete_impl",
7098
+ version: INITIAL_VERSION,
7099
+ description: "Detects partial implementations \u2014 exported functions never called, TODO throws, stub bodies, half-wired routes.",
7100
+ capabilities: ["scan_files", "hallucination", "quality"],
7101
+ lifecycle: baseLifecycle,
7102
+ telemetry: {
7103
+ events: ["scan_started", "scan_completed", "incomplete_impl.match"]
7104
+ }
7105
+ };
7106
+ var aiRulesAttackManifest = {
7107
+ id: "ai-rules-attack",
7108
+ version: INITIAL_VERSION,
7109
+ description: "Detects prompt-injection style patterns embedded in code or rules files that try to manipulate AI assistants downstream.",
7110
+ capabilities: ["scan_files", "hallucination", "security"],
7111
+ lifecycle: baseLifecycle,
7112
+ telemetry: {
7113
+ events: ["scan_started", "scan_completed", "ai_rules_attack.match"]
7114
+ }
7115
+ };
7116
+ var securityPatternManifest = {
7117
+ id: "security-pattern",
7118
+ version: INITIAL_VERSION,
7119
+ description: "Detects classic security antipatterns \u2014 eval on user input, weak crypto, unsafe URL construction, missing CSRF, etc.",
7120
+ capabilities: ["scan_files", "security"],
7121
+ lifecycle: baseLifecycle,
7122
+ telemetry: {
7123
+ events: ["scan_started", "scan_completed", "security_pattern.match"]
7124
+ }
7125
+ };
7126
+ var typeContractManifest = {
7127
+ id: "type-contract",
7128
+ version: INITIAL_VERSION,
7129
+ description: "Detects type-contract violations across module boundaries \u2014 return shape mismatches, missing required props, drift between consumer and producer.",
7130
+ capabilities: ["scan_files", "type_safety", "quality"],
7131
+ lifecycle: baseLifecycle,
7132
+ telemetry: {
7133
+ events: ["scan_started", "scan_completed", "type_contract.match"]
7134
+ }
7135
+ };
7136
+ var perfAntipatternManifest = {
7137
+ id: "perf-antipattern",
7138
+ version: INITIAL_VERSION,
7139
+ description: "Detects performance antipatterns \u2014 N+1 queries, sync I/O on hot paths, unbounded loops, accidental quadratic.",
7140
+ capabilities: ["scan_files", "quality"],
7141
+ lifecycle: baseLifecycle,
7142
+ telemetry: {
7143
+ events: ["scan_started", "scan_completed", "perf_antipattern.match"]
7144
+ }
7145
+ };
7146
+ var errorHandlingManifest = {
7147
+ id: "error_handling",
7148
+ version: INITIAL_VERSION,
7149
+ description: "Detects missing or swallowed error handling \u2014 empty catch, unawaited rejections, success-on-error, lost stack traces.",
7150
+ capabilities: ["scan_files", "quality"],
7151
+ lifecycle: baseLifecycle,
7152
+ telemetry: {
7153
+ events: ["scan_started", "scan_completed", "error_handling.match"]
7154
+ }
7155
+ };
7156
+ var logicGapManifest = {
7157
+ id: "logic_gap",
7158
+ version: INITIAL_VERSION,
7159
+ description: "Detects logical gaps \u2014 branches that never execute, conditions that always evaluate the same way, dead state, contradictory guards.",
7160
+ capabilities: ["scan_files", "quality"],
7161
+ lifecycle: baseLifecycle,
7162
+ telemetry: {
7163
+ events: ["scan_started", "scan_completed", "logic_gap.match"]
7164
+ }
7165
+ };
7166
+ var testQualityManifest = {
7167
+ id: "test-quality",
7168
+ version: INITIAL_VERSION,
7169
+ description: "Detects tests that pass without exercising real behavior \u2014 bare mocks, no assertions, expect-anything, snapshot rot.",
7170
+ capabilities: ["scan_files", "quality"],
7171
+ lifecycle: baseLifecycle,
7172
+ telemetry: {
7173
+ events: ["scan_started", "scan_completed", "test_quality.match"]
7174
+ }
7175
+ };
7176
+ var outcomeVerificationManifest = {
7177
+ id: "outcome_verification",
7178
+ version: INITIAL_VERSION,
7179
+ description: "Verifies that observable outcomes match declared intent \u2014 function signatures vs. callers, return shapes vs. consumers.",
7180
+ capabilities: ["scan_files", "quality"],
7181
+ lifecycle: baseLifecycle,
7182
+ telemetry: {
7183
+ events: ["scan_started", "scan_completed", "outcome_verification.match"]
7184
+ }
7185
+ };
7186
+ var CORE_ENGINE_MANIFESTS = [
7187
+ // Hallucination
7188
+ slopsquatManifest,
7189
+ fakeFeaturesManifest,
7190
+ incompleteImplManifest,
7191
+ aiRulesAttackManifest,
7192
+ // Security
7193
+ securityPatternManifest,
7194
+ // Type
7195
+ typeContractManifest,
7196
+ // Quality
7197
+ perfAntipatternManifest,
7198
+ errorHandlingManifest,
7199
+ logicGapManifest,
7200
+ testQualityManifest,
7201
+ outcomeVerificationManifest
7202
+ ];
7203
+ var phantomDepManifest = {
7204
+ id: "phantom_dep",
7205
+ version: "1.0.0",
7206
+ description: "Detects hallucinated package imports \u2014 modules that do not exist in package.json or node_modules. The most undeniable AI-generated-code failure class.",
7207
+ capabilities: ["scan_files", "hallucination", "incremental"],
7208
+ lifecycle: baseLifecycle,
7209
+ telemetry: {
7210
+ events: ["scan_started", "scan_completed", "phantom_dep.match"]
7211
+ }
7212
+ };
7213
+ var ghostRouteManifest = {
7214
+ id: "ghost_route",
7215
+ version: "1.0.0",
7216
+ description: 'Detects API calls and links pointing at routes that do not exist in the workspace. The "this UI is wired to nothing" hallucination class.',
7217
+ capabilities: ["scan_files", "hallucination"],
7218
+ lifecycle: baseLifecycle,
7219
+ telemetry: {
7220
+ events: ["scan_started", "scan_completed", "ghost_route.match"]
7221
+ }
7222
+ };
7223
+ var WORKSPACE_ENGINE_MANIFESTS = [
7224
+ phantomDepManifest,
7225
+ ghostRouteManifest
7226
+ ];
7227
+ function registerCoreEngines(registry) {
7228
+ registry.register(aiRulesAttackManifest, new AIRulesAttackEngine());
7229
+ registry.register(errorHandlingManifest, new ErrorHandlingEngine());
7230
+ registry.register(fakeFeaturesManifest, new FakeFeaturesEngine());
7231
+ registry.register(incompleteImplManifest, new IncompleteImplEngine());
7232
+ registry.register(logicGapManifest, new LogicGapEngine());
7233
+ registry.register(outcomeVerificationManifest, new OutcomeVerificationEngine());
7234
+ registry.register(perfAntipatternManifest, new PerformanceAntipatternEngine());
7235
+ registry.register(securityPatternManifest, new SecurityPatternEngine());
7236
+ registry.register(slopsquatManifest, new SlopsquatEngine());
7237
+ registry.register(testQualityManifest, new TestQualityEngine());
7238
+ registry.register(typeContractManifest, new TypeContractEngine());
7239
+ }
7240
+ var CORE_ENGINE_IDS = [
7241
+ aiRulesAttackManifest.id,
7242
+ errorHandlingManifest.id,
7243
+ fakeFeaturesManifest.id,
7244
+ incompleteImplManifest.id,
7245
+ logicGapManifest.id,
7246
+ outcomeVerificationManifest.id,
7247
+ perfAntipatternManifest.id,
7248
+ securityPatternManifest.id,
7249
+ slopsquatManifest.id,
7250
+ testQualityManifest.id,
7251
+ typeContractManifest.id
7252
+ ];
7253
+ function registerWorkspaceEngines(registry, options) {
7254
+ registry.register(
7255
+ phantomDepManifest,
7256
+ new PhantomDepEngine(options.workspaceRoot)
7257
+ );
7258
+ registry.register(
7259
+ ghostRouteManifest,
7260
+ new GhostRouteEngine(
7261
+ options.workspaceRoot,
7262
+ options.ghostRouteConfidenceThreshold ?? 0.75,
7263
+ options.ghostRouteSafePrefixes ?? []
7264
+ )
7265
+ );
7266
+ }
7267
+ var WORKSPACE_ENGINE_IDS = [
7268
+ phantomDepManifest.id,
7269
+ ghostRouteManifest.id
7270
+ ];
7271
+ function registerAllEngines(registry, options) {
7272
+ registerCoreEngines(registry);
7273
+ registerWorkspaceEngines(registry, options);
7274
+ }
7275
+ async function tryLoadNativeApi() {
7276
+ try {
7277
+ return await import('@vibecheck/native');
7278
+ } catch {
7279
+ return null;
7280
+ }
7281
+ }
7282
+ var VibecheckNativeEngine = class extends BaseEngine {
7283
+ id = "vibecheck-native";
7284
+ name = "Vibecheck Native Engine";
7285
+ version = "2.0.0";
7286
+ supportedExtensions = /* @__PURE__ */ new Set([
7287
+ ".ts",
7288
+ ".tsx",
7289
+ ".js",
7290
+ ".jsx",
7291
+ ".cts",
7292
+ ".mts",
7293
+ ".cjs",
7294
+ ".mjs"
7295
+ ]);
7296
+ opts;
7297
+ constructor(opts = {}) {
7298
+ super();
7299
+ this.opts = {
7300
+ deadCode: true,
7301
+ duplication: true,
7302
+ health: true,
7303
+ ...opts
7304
+ };
7305
+ }
7306
+ async scan(delta, signal) {
7307
+ this.checkAbort(signal);
7308
+ const native = await tryLoadNativeApi();
7309
+ if (!native) {
7310
+ return [];
7311
+ }
7312
+ const root = this.opts.root ?? rootFromDelta(delta) ?? process.cwd();
7313
+ const configPath = this.opts.configPath;
7314
+ const findings = [];
7315
+ if (this.opts.deadCode) {
7316
+ this.checkAbort(signal);
7317
+ const dcOpts = { root, configPath };
7318
+ const result = await native.detectDeadCode(dcOpts);
7319
+ findings.push(...this.mapIssues(result, root, "native-dead-code", "medium"));
7320
+ }
7321
+ if (this.opts.duplication) {
7322
+ this.checkAbort(signal);
7323
+ const dupOpts = { root, configPath };
7324
+ const result = await native.detectDuplication(dupOpts);
7325
+ findings.push(...this.mapIssues(result, root, "native-duplication", "low"));
7326
+ }
7327
+ if (this.opts.health) {
7328
+ this.checkAbort(signal);
7329
+ const compOpts = { root, configPath };
7330
+ const result = await native.computeHealth(compOpts);
7331
+ findings.push(...this.mapIssues(result, root, "native-health", "medium"));
7332
+ }
7333
+ return findings;
7334
+ }
7335
+ mapIssues(raw, root, defaultRuleId, defaultSeverity) {
7336
+ return readIssues(raw).map((issue) => {
7337
+ const file = fileOf(issue, root);
7338
+ const line = lineOf2(issue);
7339
+ const column = colOf(issue);
7340
+ const message = messageOf(issue);
7341
+ return this.createFinding({
7342
+ id: this.deterministicId(file, line, column, defaultRuleId, message),
7343
+ ruleId: kindOf(issue) ?? defaultRuleId,
7344
+ message,
7345
+ file,
7346
+ line,
7347
+ column,
7348
+ evidence: evidenceOf(issue, message),
7349
+ severity: severityOf(issue) ?? defaultSeverity,
7350
+ confidence: 0.9,
5479
7351
  autoFixable: false
5480
- })
5481
- );
7352
+ });
7353
+ });
5482
7354
  }
5483
7355
  };
7356
+ function readIssues(raw) {
7357
+ if (!raw || typeof raw !== "object") return [];
7358
+ const obj = raw;
7359
+ for (const key of ["issues", "findings", "results", "items"]) {
7360
+ const v = obj[key];
7361
+ if (Array.isArray(v)) return v;
7362
+ }
7363
+ return [];
7364
+ }
7365
+ function rootFromDelta(delta) {
7366
+ const uri = delta.documentUri;
7367
+ if (typeof uri === "string" && uri.startsWith("file://")) {
7368
+ return void 0;
7369
+ }
7370
+ return void 0;
7371
+ }
7372
+ function fileOf(issue, root) {
7373
+ const f = issue.file ?? issue.path ?? issue.location_file;
7374
+ return f ?? root;
7375
+ }
7376
+ function lineOf2(issue) {
7377
+ const l = issue.line ?? issue.start_line ?? issue.row;
7378
+ return typeof l === "number" ? l : 0;
7379
+ }
7380
+ function colOf(issue) {
7381
+ const c2 = issue.column ?? issue.start_column ?? issue.col;
7382
+ return typeof c2 === "number" ? c2 : 0;
7383
+ }
7384
+ function messageOf(issue) {
7385
+ const m = issue.message ?? issue.title ?? issue.summary ?? issue.kind;
7386
+ return typeof m === "string" ? m : "Vibecheck native finding";
7387
+ }
7388
+ function kindOf(issue) {
7389
+ const k = issue.kind ?? issue.rule ?? issue.type;
7390
+ return typeof k === "string" ? k : void 0;
7391
+ }
7392
+ function severityOf(issue) {
7393
+ const s = issue.severity;
7394
+ if (s === "critical" || s === "high" || s === "medium" || s === "low" || s === "info") {
7395
+ return s;
7396
+ }
7397
+ return void 0;
7398
+ }
7399
+ function evidenceOf(issue, fallback) {
7400
+ const e = issue.evidence ?? issue.snippet ?? issue.code;
7401
+ return typeof e === "string" ? e : fallback;
7402
+ }
7403
+ var SEVERITY_ORDER = ["info", "low", "medium", "high", "critical"];
7404
+ function buildClusters(findings, windowLines) {
7405
+ if (findings.length === 0) return [];
7406
+ const tagged = findings.map((finding, index) => ({ finding, index }));
7407
+ tagged.sort((a, b) => {
7408
+ if (a.finding.file !== b.finding.file) {
7409
+ return a.finding.file < b.finding.file ? -1 : 1;
7410
+ }
7411
+ return a.finding.line - b.finding.line;
7412
+ });
7413
+ const clusters = [];
7414
+ let current = [];
7415
+ let currentFile = "";
7416
+ let lastLine = -Infinity;
7417
+ for (const entry of tagged) {
7418
+ const startsNewCluster = entry.finding.file !== currentFile || entry.finding.line - lastLine > windowLines;
7419
+ if (startsNewCluster) {
7420
+ if (current.length > 0) clusters.push(current);
7421
+ current = [entry];
7422
+ currentFile = entry.finding.file;
7423
+ } else {
7424
+ current.push(entry);
7425
+ }
7426
+ lastLine = entry.finding.line;
7427
+ }
7428
+ if (current.length > 0) clusters.push(current);
7429
+ return clusters;
7430
+ }
7431
+ function fuseFindings(findings, options = {}) {
7432
+ const {
7433
+ windowLines = 5,
7434
+ minEnginesForBoost = 2,
7435
+ minEnginesForEscalation = 3,
7436
+ confidenceCeiling = 0.99
7437
+ } = options;
7438
+ if (findings.length === 0) return [];
7439
+ if (findings.length === 1) {
7440
+ const single = findings[0];
7441
+ return [
7442
+ {
7443
+ ...single,
7444
+ relatedFindings: single.relatedFindings ?? []
7445
+ }
7446
+ ];
7447
+ }
7448
+ const input = [...findings];
7449
+ const clusters = buildClusters(input, windowLines);
7450
+ const out = new Array(input.length);
7451
+ for (const cluster of clusters) {
7452
+ const distinctEngines = new Set(cluster.map((e) => e.finding.engine));
7453
+ const clusterSize = distinctEngines.size;
7454
+ const ids = cluster.map((e) => e.finding.id).filter((id) => Boolean(id));
7455
+ let maxSeverityIdx = -1;
7456
+ for (const e of cluster) {
7457
+ const sevIdx = SEVERITY_ORDER.indexOf(e.finding.severity);
7458
+ if (sevIdx > maxSeverityIdx) maxSeverityIdx = sevIdx;
7459
+ }
7460
+ const shouldBoost = clusterSize >= minEnginesForBoost;
7461
+ const shouldEscalate = clusterSize >= minEnginesForEscalation && maxSeverityIdx >= SEVERITY_ORDER.indexOf("high");
7462
+ for (const entry of cluster) {
7463
+ const f = entry.finding;
7464
+ const relatedFindings = ids.filter((id) => id !== f.id);
7465
+ let nextSeverity = f.severity;
7466
+ let nextConfidence = f.confidence;
7467
+ let reason;
7468
+ if (shouldBoost) {
7469
+ const extraEngines = clusterSize - 1;
7470
+ let conf = f.confidence;
7471
+ for (let i = 0; i < extraEngines; i++) {
7472
+ conf = conf + (confidenceCeiling - conf) * 0.5;
7473
+ }
7474
+ nextConfidence = Math.min(confidenceCeiling, conf);
7475
+ reason = `${clusterSize}-engine concurrence`;
7476
+ }
7477
+ if (shouldEscalate) {
7478
+ const idx = SEVERITY_ORDER.indexOf(f.severity);
7479
+ if (idx >= 0 && idx < SEVERITY_ORDER.length - 1) {
7480
+ nextSeverity = SEVERITY_ORDER[idx + 1];
7481
+ }
7482
+ reason = `${clusterSize}-engine cluster (escalated)`;
7483
+ }
7484
+ const fused = {
7485
+ ...f,
7486
+ severity: nextSeverity,
7487
+ confidence: nextConfidence,
7488
+ relatedFindings,
7489
+ ...shouldBoost || shouldEscalate ? {
7490
+ fusion: {
7491
+ ...f.fusion ?? {},
7492
+ originalSeverity: f.fusion?.originalSeverity ?? f.severity,
7493
+ originalConfidence: f.fusion?.originalConfidence ?? f.confidence,
7494
+ clusterSize,
7495
+ reason
7496
+ }
7497
+ } : {}
7498
+ };
7499
+ out[entry.index] = fused;
7500
+ }
7501
+ }
7502
+ return out;
7503
+ }
7504
+ function computeFusionStats(findings, windowLines = 5) {
7505
+ const clusters = buildClusters([...findings], windowLines);
7506
+ let boosted = 0;
7507
+ let escalated = 0;
7508
+ let largest = 0;
7509
+ for (const f of findings) {
7510
+ if (f.fusion?.reason?.includes("escalated")) escalated++;
7511
+ else if (f.fusion?.reason) boosted++;
7512
+ }
7513
+ for (const cluster of clusters) {
7514
+ const distinctEngines = new Set(cluster.map((e) => e.finding.engine)).size;
7515
+ if (distinctEngines > largest) largest = distinctEngines;
7516
+ }
7517
+ return {
7518
+ totalFindings: findings.length,
7519
+ totalClusters: clusters.length,
7520
+ boostedFindings: boosted,
7521
+ escalatedFindings: escalated,
7522
+ largestClusterSize: largest
7523
+ };
7524
+ }
5484
7525
  function isTestLikeScanPath(filePath) {
5485
7526
  const n = filePath.replace(/\\/g, "/");
5486
7527
  return /\.(test|spec)\.(ts|tsx|js|jsx)$/i.test(n) || /(?:^|\/)(?:__tests__|__mocks__|tests?|fixtures?|e2e|spec|cypress|playwright|__snapshots__|stubs?|mocks?)\//i.test(
@@ -5511,11 +7552,73 @@ function fnv1aId(input) {
5511
7552
  }
5512
7553
  return hash.toString(16).padStart(8, "0");
5513
7554
  }
5514
- function collectCallAndNewNames(sf) {
7555
+ function normalizeFileKey(abs) {
7556
+ return path6__default__default.normalize(abs);
7557
+ }
7558
+ function resolveRelativeModule(fromFileAbs, specifier, knownFiles) {
7559
+ if (!specifier.startsWith(".")) return void 0;
7560
+ const baseDir = path6__default__default.dirname(fromFileAbs);
7561
+ const joined = normalizeFileKey(path6__default__default.join(baseDir, specifier));
7562
+ if (knownFiles.has(joined)) return joined;
7563
+ if (path6__default__default.extname(joined)) {
7564
+ return void 0;
7565
+ }
7566
+ for (const ext of [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"]) {
7567
+ const c2 = normalizeFileKey(joined + ext);
7568
+ if (knownFiles.has(c2)) return c2;
7569
+ }
7570
+ for (const idx of ["index.ts", "index.tsx", "index.js", "index.jsx"]) {
7571
+ const c2 = normalizeFileKey(path6__default__default.join(joined, idx));
7572
+ if (knownFiles.has(c2)) return c2;
7573
+ }
7574
+ return void 0;
7575
+ }
7576
+ function moduleSpecifierText(m) {
7577
+ if (m && import_typescript.default.isStringLiteral(m)) return m.text;
7578
+ if (m && import_typescript.default.isNoSubstitutionTemplateLiteral(m)) return m.text;
7579
+ return void 0;
7580
+ }
7581
+ function isDynamicImportCall(n) {
7582
+ return n.expression.kind === import_typescript.default.SyntaxKind.ImportKeyword;
7583
+ }
7584
+ function addJsxTagNameToCalls(tag, into) {
7585
+ if (import_typescript.default.isIdentifier(tag)) {
7586
+ into.add(tag.text);
7587
+ } else if (import_typescript.default.isPropertyAccessExpression(tag)) {
7588
+ if (import_typescript.default.isIdentifier(tag.name)) into.add(tag.name.text);
7589
+ if (import_typescript.default.isIdentifier(tag.expression)) into.add(tag.expression.text);
7590
+ } else if (tag.kind === import_typescript.default.SyntaxKind.ThisKeyword) ;
7591
+ else if (import_typescript.default.isJsxNamespacedName(tag)) {
7592
+ into.add(tag.name.text);
7593
+ }
7594
+ }
7595
+ function addCreateElementComponentArg(n, into) {
7596
+ const e = n.expression;
7597
+ let isComponentFactory = false;
7598
+ if (import_typescript.default.isIdentifier(e)) {
7599
+ isComponentFactory = /^(createElement|h|jsx|jsxs|jsxDEV)$/.test(e.text);
7600
+ } else if (import_typescript.default.isPropertyAccessExpression(e) && import_typescript.default.isIdentifier(e.name)) {
7601
+ isComponentFactory = /^(createElement|jsx|jsxs|jsxDEV)$/.test(e.name.text);
7602
+ }
7603
+ if (!isComponentFactory) return;
7604
+ const first = n.arguments[0];
7605
+ if (first && import_typescript.default.isIdentifier(first)) {
7606
+ into.add(first.text);
7607
+ }
7608
+ }
7609
+ function collectCallAndNewNames(sf, fromFileAbs, knownFiles, dynamicImportTargets) {
5515
7610
  const calls = /* @__PURE__ */ new Set();
5516
7611
  const ctors = /* @__PURE__ */ new Set();
5517
7612
  const visit = (n) => {
5518
7613
  if (import_typescript.default.isCallExpression(n)) {
7614
+ if (isDynamicImportCall(n)) {
7615
+ const a0 = n.arguments[0];
7616
+ if (a0 && import_typescript.default.isStringLiteral(a0)) {
7617
+ const t = resolveRelativeModule(fromFileAbs, a0.text, knownFiles);
7618
+ if (t) dynamicImportTargets.add(t);
7619
+ }
7620
+ }
7621
+ addCreateElementComponentArg(n, calls);
5519
7622
  const e = n.expression;
5520
7623
  if (import_typescript.default.isIdentifier(e)) {
5521
7624
  calls.add(e.text);
@@ -5524,6 +7627,10 @@ function collectCallAndNewNames(sf) {
5524
7627
  }
5525
7628
  } else if (import_typescript.default.isNewExpression(n) && import_typescript.default.isIdentifier(n.expression)) {
5526
7629
  ctors.add(n.expression.text);
7630
+ } else if (import_typescript.default.isJsxSelfClosingElement(n) || import_typescript.default.isJsxOpeningElement(n)) {
7631
+ addJsxTagNameToCalls(n.tagName, calls);
7632
+ } else if (import_typescript.default.isObjectLiteralExpression(n)) {
7633
+ collectObjectRouteStringNames(n, calls);
5527
7634
  }
5528
7635
  import_typescript.default.forEachChild(n, visit);
5529
7636
  };
@@ -5591,6 +7698,83 @@ function collectExportedValueFunctions(sf, absolutePath) {
5591
7698
  }
5592
7699
  return out;
5593
7700
  }
7701
+ function addReExportAliases(fromFileAbs, st, knownFiles, into) {
7702
+ const spec = st.moduleSpecifier && moduleSpecifierText(st.moduleSpecifier);
7703
+ if (!spec) return;
7704
+ const target = resolveRelativeModule(fromFileAbs, spec, knownFiles);
7705
+ if (!target) return;
7706
+ if (!st.exportClause || !import_typescript.default.isNamedExports(st.exportClause)) return;
7707
+ for (const el of st.exportClause.elements) {
7708
+ const local = (el.propertyName ?? el.name).text;
7709
+ const exportAs = el.name.text;
7710
+ let perFile = into.get(target);
7711
+ if (!perFile) {
7712
+ perFile = /* @__PURE__ */ new Map();
7713
+ into.set(target, perFile);
7714
+ }
7715
+ let set = perFile.get(local);
7716
+ if (!set) {
7717
+ set = /* @__PURE__ */ new Set();
7718
+ perFile.set(local, set);
7719
+ }
7720
+ set.add(local);
7721
+ set.add(exportAs);
7722
+ }
7723
+ }
7724
+ function getNamesFromExportRecordList(records) {
7725
+ return records.map((r) => r.exportName);
7726
+ }
7727
+ var ROUTE_STRING_PROP = /^(component|element|page|screen|loader|pathcomponent)$/i;
7728
+ function collectObjectRouteStringNames(n, into) {
7729
+ if (!import_typescript.default.isObjectLiteralExpression(n)) {
7730
+ return;
7731
+ }
7732
+ for (const p of n.properties) {
7733
+ if (!import_typescript.default.isPropertyAssignment(p)) continue;
7734
+ const key = p.name;
7735
+ let keyText;
7736
+ if (import_typescript.default.isIdentifier(key)) {
7737
+ keyText = key.text;
7738
+ } else if (import_typescript.default.isStringLiteral(key) || import_typescript.default.isNoSubstitutionTemplateLiteral(key)) {
7739
+ keyText = key.text;
7740
+ }
7741
+ if (!keyText || !ROUTE_STRING_PROP.test(keyText)) continue;
7742
+ const v = p.initializer;
7743
+ if (import_typescript.default.isStringLiteral(v) || import_typescript.default.isNoSubstitutionTemplateLiteral(v)) {
7744
+ const s = v.text;
7745
+ if (/^[A-Z][A-Za-z0-9_]*$/.test(s) && s.length >= 2) {
7746
+ into.add(s);
7747
+ }
7748
+ }
7749
+ }
7750
+ }
7751
+ function saturateReexportSeeds(mergedCalls, mergedCtors, reExportAliases) {
7752
+ const S = /* @__PURE__ */ new Set([...mergedCalls, ...mergedCtors]);
7753
+ const MAX_ITER = 100;
7754
+ let iter = 0;
7755
+ let changed = true;
7756
+ while (changed && iter < MAX_ITER) {
7757
+ iter++;
7758
+ changed = false;
7759
+ for (const perLocal of reExportAliases.values()) {
7760
+ for (const names of perLocal.values()) {
7761
+ if (![...names].some((n) => S.has(n))) continue;
7762
+ for (const m of names) {
7763
+ if (!S.has(m)) {
7764
+ S.add(m);
7765
+ changed = true;
7766
+ }
7767
+ }
7768
+ }
7769
+ }
7770
+ }
7771
+ if (iter >= MAX_ITER && changed) {
7772
+ console.debug(
7773
+ `[IMPL007] saturateReexportSeeds hit MAX_ITER=${MAX_ITER}; results may be incomplete`
7774
+ );
7775
+ }
7776
+ return S;
7777
+ }
5594
7778
  var IMPL007_SKIP_NAMES = /* @__PURE__ */ new Set([
5595
7779
  "handler",
5596
7780
  "main",
@@ -5613,43 +7797,86 @@ function findUncalledExportedFunctions(files, options) {
5613
7797
  const signal = options?.signal;
5614
7798
  const findings = [];
5615
7799
  if (files.length === 0) return findings;
5616
- const mergedCalls = /* @__PURE__ */ new Set();
5617
- const mergedCtors = /* @__PURE__ */ new Set();
7800
+ const knownFiles = /* @__PURE__ */ new Set();
7801
+ for (const f of files) {
7802
+ knownFiles.add(normalizeFileKey(f.absolutePath));
7803
+ }
7804
+ const sourceByAbs = /* @__PURE__ */ new Map();
5618
7805
  for (const f of files) {
5619
- if (signal?.aborted) return [];
5620
7806
  if (f.relativePath.endsWith(".d.ts")) continue;
5621
- let sf;
7807
+ const key = normalizeFileKey(f.absolutePath);
5622
7808
  try {
5623
- sf = import_typescript.default.createSourceFile(
7809
+ const sf = import_typescript.default.createSourceFile(
5624
7810
  f.absolutePath,
5625
7811
  f.source,
5626
7812
  import_typescript.default.ScriptTarget.Latest,
5627
7813
  true,
5628
7814
  scriptKind(f.absolutePath)
5629
7815
  );
7816
+ sourceByAbs.set(key, sf);
5630
7817
  } catch {
5631
- continue;
5632
7818
  }
5633
- const { calls, ctors } = collectCallAndNewNames(sf);
7819
+ }
7820
+ const reExportAliases = /* @__PURE__ */ new Map();
7821
+ for (const f of files) {
7822
+ if (signal?.aborted) return [];
7823
+ if (f.relativePath.endsWith(".d.ts")) continue;
7824
+ const k = normalizeFileKey(f.absolutePath);
7825
+ const sf = sourceByAbs.get(k);
7826
+ if (!sf) continue;
7827
+ for (const st of sf.statements) {
7828
+ if (import_typescript.default.isExportDeclaration(st) && st.moduleSpecifier) {
7829
+ if (st.exportClause && import_typescript.default.isNamedExports(st.exportClause)) {
7830
+ addReExportAliases(f.absolutePath, st, knownFiles, reExportAliases);
7831
+ }
7832
+ }
7833
+ }
7834
+ }
7835
+ const mergedCalls = /* @__PURE__ */ new Set();
7836
+ const mergedCtors = /* @__PURE__ */ new Set();
7837
+ const dynamicImportTargets = /* @__PURE__ */ new Set();
7838
+ const exportStarTargets = /* @__PURE__ */ new Set();
7839
+ for (const f of files) {
7840
+ if (signal?.aborted) return [];
7841
+ if (f.relativePath.endsWith(".d.ts")) continue;
7842
+ const k = normalizeFileKey(f.absolutePath);
7843
+ const sf = sourceByAbs.get(k);
7844
+ if (!sf) continue;
7845
+ for (const st of sf.statements) {
7846
+ if (import_typescript.default.isExportDeclaration(st) && st.moduleSpecifier && st.exportClause === void 0) {
7847
+ const spec = moduleSpecifierText(st.moduleSpecifier);
7848
+ if (spec) {
7849
+ const t = resolveRelativeModule(f.absolutePath, spec, knownFiles);
7850
+ if (t) exportStarTargets.add(t);
7851
+ }
7852
+ }
7853
+ }
7854
+ const { calls, ctors } = collectCallAndNewNames(
7855
+ sf,
7856
+ f.absolutePath,
7857
+ knownFiles,
7858
+ dynamicImportTargets
7859
+ );
5634
7860
  for (const c2 of calls) mergedCalls.add(c2);
5635
7861
  for (const c2 of ctors) mergedCtors.add(c2);
5636
7862
  }
7863
+ for (const targetAbs of /* @__PURE__ */ new Set([...dynamicImportTargets, ...exportStarTargets])) {
7864
+ if (signal?.aborted) return [];
7865
+ const tKey = normalizeFileKey(targetAbs);
7866
+ const tSf = sourceByAbs.get(tKey);
7867
+ if (!tSf) continue;
7868
+ for (const n of getNamesFromExportRecordList(collectExportedValueFunctions(tSf, tKey))) {
7869
+ mergedCalls.add(n);
7870
+ }
7871
+ }
7872
+ const usageNames = saturateReexportSeeds(mergedCalls, mergedCtors, reExportAliases);
5637
7873
  for (const f of files) {
5638
7874
  if (signal?.aborted) return [];
5639
7875
  if (f.isTest || f.relativePath.endsWith(".d.ts")) continue;
5640
7876
  if (isIndexBarrel(f.relativePath)) continue;
5641
- let sf;
5642
- try {
5643
- sf = import_typescript.default.createSourceFile(
5644
- f.absolutePath,
5645
- f.source,
5646
- import_typescript.default.ScriptTarget.Latest,
5647
- true,
5648
- scriptKind(f.absolutePath)
5649
- );
5650
- } catch {
5651
- continue;
5652
- }
7877
+ const k = normalizeFileKey(f.absolutePath);
7878
+ const sf = sourceByAbs.get(k);
7879
+ if (!sf) continue;
5653
7880
  const exports$1 = collectExportedValueFunctions(sf, f.absolutePath);
5654
7881
  const seen = /* @__PURE__ */ new Set();
5655
7882
  for (const ex of exports$1) {
@@ -5659,9 +7886,9 @@ function findUncalledExportedFunctions(files, options) {
5659
7886
  if (name.startsWith("_")) continue;
5660
7887
  if (IMPL007_SKIP_NAMES.has(name)) continue;
5661
7888
  if (name.length <= 1) continue;
5662
- const usedAsCall = mergedCalls.has(name);
5663
- const usedAsCtor = mergedCtors.has(name);
5664
- if (usedAsCall || usedAsCtor) continue;
7889
+ if (usageNames.has(name)) {
7890
+ continue;
7891
+ }
5665
7892
  const id = fnv1aId(`${ex.absolutePath}::${ex.line}::${ex.column}::IMPL007::${name}`);
5666
7893
  findings.push({
5667
7894
  id,
@@ -5682,96 +7909,6 @@ function findUncalledExportedFunctions(files, options) {
5682
7909
  }
5683
7910
  return findings;
5684
7911
  }
5685
- var TRUTHPACK_DIR = ".vibecheck/truthpack";
5686
- async function loadTruthpack(workspaceRoot) {
5687
- const dir = path5__default.join(workspaceRoot, TRUTHPACK_DIR);
5688
- try {
5689
- await fs.access(dir);
5690
- } catch {
5691
- return null;
5692
- }
5693
- try {
5694
- const [routesData, envData] = await Promise.all([
5695
- fs.readFile(path5__default.join(dir, "routes.json"), "utf-8").catch(() => '{"routes":[]}'),
5696
- fs.readFile(path5__default.join(dir, "env.json"), "utf-8").catch(() => '{"variables":[]}')
5697
- ]);
5698
- const routesJson = JSON.parse(routesData);
5699
- const envJson = JSON.parse(envData);
5700
- return {
5701
- routes: routesJson.routes ?? [],
5702
- env: envJson.variables ?? []
5703
- };
5704
- } catch {
5705
- return null;
5706
- }
5707
- }
5708
- var TruthpackEnvIndex = class {
5709
- _index;
5710
- constructor(envVars, allowlistEnvVars) {
5711
- this._index = new Set(envVars.map((v) => v.name));
5712
- if (Array.isArray(allowlistEnvVars)) {
5713
- for (const v of allowlistEnvVars) {
5714
- if (typeof v === "string" && /^[A-Z_][A-Z0-9_]*$/.test(v)) this._index.add(v);
5715
- }
5716
- }
5717
- }
5718
- get index() {
5719
- return this._index;
5720
- }
5721
- has(name) {
5722
- return this._index.has(name);
5723
- }
5724
- };
5725
- function compileRoutePattern(routePath) {
5726
- let isDynamic = false;
5727
- let isCatchAll = false;
5728
- let pattern = routePath.replace(/\[\[\.\.\.(\w+)\]\]/g, () => {
5729
- isDynamic = true;
5730
- isCatchAll = true;
5731
- return "(?:\\/.*)?";
5732
- }).replace(/\[\.\.\.(\w+)\]/g, () => {
5733
- isDynamic = true;
5734
- isCatchAll = true;
5735
- return "\\/.*";
5736
- }).replace(/\[(\w+)\]/g, () => {
5737
- isDynamic = true;
5738
- return "\\/[^/]+";
5739
- }).replace(/:\w+/g, () => {
5740
- isDynamic = true;
5741
- return "\\/[^/]+";
5742
- });
5743
- pattern = pattern.replace(/\//g, "\\/").replace(/\\\\\//g, "\\/");
5744
- return {
5745
- regex: new RegExp(`^${pattern}$`),
5746
- isDynamic,
5747
- isCatchAll
5748
- };
5749
- }
5750
- function truthpackToRouteIndex(routes, workspaceRoot) {
5751
- const entries = [];
5752
- const seen = /* @__PURE__ */ new Set();
5753
- for (const r of routes) {
5754
- const pathStr = r.path ?? "";
5755
- if (!pathStr.startsWith("/")) continue;
5756
- const pattern = pathStr.startsWith("/api") ? pathStr : pathStr;
5757
- const key = `${pattern}:${r.method ?? "GET"}`;
5758
- if (seen.has(key)) continue;
5759
- seen.add(key);
5760
- const compiled = compileRoutePattern(pattern);
5761
- entries.push({
5762
- pattern,
5763
- regex: compiled.regex,
5764
- isDynamic: compiled.isDynamic,
5765
- isCatchAll: compiled.isCatchAll,
5766
- filePath: r.file ? path5__default.resolve(workspaceRoot, r.file) : ""
5767
- });
5768
- }
5769
- return {
5770
- routes: entries,
5771
- framework: "truthpack",
5772
- builtAt: Date.now()
5773
- };
5774
- }
5775
7912
  function deterministicId2(workspaceRoot, routePath, status) {
5776
7913
  const input = `rtprobe:${workspaceRoot}::${routePath}::${status}::VRD007`;
5777
7914
  let hash = 2166136261;
@@ -5789,20 +7926,20 @@ var DEFAULT_CONFIG = {
5789
7926
  };
5790
7927
  var probedWorkspaces = /* @__PURE__ */ new Set();
5791
7928
  function getWorkspaceRoot(filePath) {
5792
- let dir = path5__default.dirname(filePath);
5793
- const root = path5__default.parse(dir).root;
7929
+ let dir = path6__default.dirname(filePath);
7930
+ const root = path6__default.parse(dir).root;
5794
7931
  while (dir !== root) {
5795
7932
  try {
5796
- const pkg = path5__default.join(dir, "package.json");
7933
+ const pkg = path6__default.join(dir, "package.json");
5797
7934
  if (existsSync(pkg)) return dir;
5798
7935
  } catch (err) {
5799
7936
  if (process.env.VIBECHECK_DEBUG) {
5800
7937
  console.warn("[runtime_probe] getWorkspaceRoot check failed:", err instanceof Error ? err.message : err);
5801
7938
  }
5802
7939
  }
5803
- dir = path5__default.dirname(dir);
7940
+ dir = path6__default.dirname(dir);
5804
7941
  }
5805
- return path5__default.dirname(filePath);
7942
+ return path6__default.dirname(filePath);
5806
7943
  }
5807
7944
  function matchGlob(routePath, pattern) {
5808
7945
  const regex = pattern.replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/{{GLOBSTAR}}/g, ".*").replace(/\?/g, ".");
@@ -5946,7 +8083,7 @@ var EnvLoader = class {
5946
8083
  let hasAnyEnvFile = false;
5947
8084
  for (const f of envFiles) {
5948
8085
  try {
5949
- const content = fs2.readFileSync(path5__default.join(workspaceRoot, f), "utf-8");
8086
+ const content = fs2.readFileSync(path6__default.join(workspaceRoot, f), "utf-8");
5950
8087
  hasAnyEnvFile = true;
5951
8088
  if (!EXAMPLE_ONLY_FILES.has(f)) {
5952
8089
  hasRealEnvFile = true;
@@ -5957,7 +8094,7 @@ var EnvLoader = class {
5957
8094
  }
5958
8095
  } catch (err) {
5959
8096
  if (err?.code === "EACCES") {
5960
- process.stderr.write(`[vibecheck] Permission denied: ${path5__default.join(workspaceRoot, f)}
8097
+ process.stderr.write(`[vibecheck] Permission denied: ${path6__default.join(workspaceRoot, f)}
5961
8098
  `);
5962
8099
  }
5963
8100
  }
@@ -5968,17 +8105,17 @@ var EnvLoader = class {
5968
8105
  if (hasAnyEnvFile && !hasRealEnvFile) {
5969
8106
  this.exampleOnly = true;
5970
8107
  }
5971
- const workflowsDir = path5__default.join(workspaceRoot, ".github", "workflows");
8108
+ const workflowsDir = path6__default.join(workspaceRoot, ".github", "workflows");
5972
8109
  if (fs2.existsSync(workflowsDir)) {
5973
8110
  for (const f of fs2.readdirSync(workflowsDir)) {
5974
8111
  if (!f.endsWith(".yml") && !f.endsWith(".yaml")) continue;
5975
8112
  try {
5976
- const content = fs2.readFileSync(path5__default.join(workflowsDir, f), "utf-8");
8113
+ const content = fs2.readFileSync(path6__default.join(workflowsDir, f), "utf-8");
5977
8114
  const matches = content.match(/([A-Z_][A-Z0-9_]*):/g) ?? [];
5978
8115
  matches.forEach((v) => this._index.add(v.replace(":", "")));
5979
8116
  } catch (err) {
5980
8117
  if (err?.code === "EACCES") {
5981
- process.stderr.write(`[vibecheck] Permission denied: ${path5__default.join(workflowsDir, f)}
8118
+ process.stderr.write(`[vibecheck] Permission denied: ${path6__default.join(workflowsDir, f)}
5982
8119
  `);
5983
8120
  }
5984
8121
  }
@@ -6534,10 +8671,11 @@ var DEFAULT_ENGINE_THRESHOLDS = {
6534
8671
  ghost_route: 0.65,
6535
8672
  phantom_dep: 0.55,
6536
8673
  api_truth: 0.65,
6537
- logic_gap: 0.55,
6538
- error_handling: 0.55,
6539
- outcome_verification: 0.55,
6540
- incomplete_impl: 0.5,
8674
+ /** Slightly higher default confidence to reduce heuristic false positives. */
8675
+ logic_gap: 0.57,
8676
+ error_handling: 0.57,
8677
+ outcome_verification: 0.57,
8678
+ incomplete_impl: 0.52,
6541
8679
  framework_packs: 0.55
6542
8680
  };
6543
8681
  var INLINE_SUPPRESS_RE = /\/\/\s*vibecheck-ignore-next-line(?:\s+([A-Z0-9]+(?:\s+[A-Z0-9]+)*))?/i;
@@ -6582,17 +8720,28 @@ function deduplicateFindings(findings) {
6582
8720
  }
6583
8721
  }
6584
8722
  const byLocation = /* @__PURE__ */ new Map();
8723
+ const wordSetCache = /* @__PURE__ */ new WeakMap();
8724
+ const getWords = (f) => {
8725
+ let s = wordSetCache.get(f);
8726
+ if (!s) {
8727
+ const slice = f.message.slice(0, 40).toLowerCase();
8728
+ s = new Set(slice.split(/\W+/).filter((w) => w.length > 3));
8729
+ wordSetCache.set(f, s);
8730
+ }
8731
+ return s;
8732
+ };
6585
8733
  for (const f of map.values()) {
6586
8734
  const locKey = `${f.file}::${f.line}`;
6587
8735
  const existing = byLocation.get(locKey);
6588
8736
  if (!existing) {
6589
8737
  byLocation.set(locKey, f);
6590
8738
  } else {
6591
- const msgOverlap = f.message.slice(0, 40).toLowerCase();
6592
- const existMsgOverlap = existing.message.slice(0, 40).toLowerCase();
6593
- const fWords = new Set(msgOverlap.split(/\W+/).filter((w) => w.length > 3));
6594
- const eWords = new Set(existMsgOverlap.split(/\W+/).filter((w) => w.length > 3));
6595
- const overlap = [...fWords].filter((w) => eWords.has(w)).length;
8739
+ const fWords = getWords(f);
8740
+ const eWords = getWords(existing);
8741
+ let overlap = 0;
8742
+ for (const w of fWords) {
8743
+ if (eWords.has(w)) overlap++;
8744
+ }
6596
8745
  if (overlap >= 2) {
6597
8746
  if (f.confidence > existing.confidence) {
6598
8747
  byLocation.set(locKey, f);
@@ -6605,7 +8754,7 @@ function deduplicateFindings(findings) {
6605
8754
  return [...byLocation.values()];
6606
8755
  }
6607
8756
  function makeDelta(filePath, text) {
6608
- const ext = path5__default.extname(filePath).toLowerCase();
8757
+ const ext = path6__default.extname(filePath).toLowerCase();
6609
8758
  const lines = text.split("\n");
6610
8759
  return {
6611
8760
  documentUri: filePath,
@@ -6724,9 +8873,9 @@ function normalizeGhostRoutePrefixes(routes) {
6724
8873
  }
6725
8874
  function toApiFinding(f, projectRoot) {
6726
8875
  const abs = f.file.replace(/^file:\/\//, "");
6727
- const rel = path5__default.relative(projectRoot, abs).replace(/\\/g, "/");
8876
+ const rel = path6__default.relative(projectRoot, abs).replace(/\\/g, "/");
6728
8877
  const fileOut = rel && !rel.startsWith("..") ? rel : abs;
6729
- return {
8878
+ const out = {
6730
8879
  id: f.id,
6731
8880
  ruleId: f.ruleId ?? f.engine,
6732
8881
  engine: String(f.engine),
@@ -6738,8 +8887,21 @@ function toApiFinding(f, projectRoot) {
6738
8887
  code: f.evidence ?? "",
6739
8888
  message: f.message,
6740
8889
  fix: f.suggestion ?? "",
6741
- autoFixable: f.autoFixable
8890
+ autoFixable: f.autoFixable,
8891
+ confidence: f.confidence
6742
8892
  };
8893
+ if (f.fusion) {
8894
+ out.fusion = {
8895
+ ...f.fusion.reason ? { reason: f.fusion.reason } : {},
8896
+ ...f.fusion.clusterSize != null ? { clusterSize: f.fusion.clusterSize } : {},
8897
+ ...f.fusion.originalSeverity ? { originalSeverity: f.fusion.originalSeverity } : {},
8898
+ ...f.fusion.originalConfidence != null ? { originalConfidence: f.fusion.originalConfidence } : {}
8899
+ };
8900
+ }
8901
+ if (f.relatedFindings && f.relatedFindings.length > 0) {
8902
+ out.relatedFindings = f.relatedFindings;
8903
+ }
8904
+ return out;
6743
8905
  }
6744
8906
  async function scan(opts) {
6745
8907
  const {
@@ -6763,7 +8925,7 @@ async function scan(opts) {
6763
8925
  const internalSignal = ac.signal;
6764
8926
  setMaxListeners(64, internalSignal);
6765
8927
  const absoluteFiles = rawFiles.map(
6766
- (f) => path5__default.isAbsolute(f) ? path5__default.normalize(f) : path5__default.normalize(path5__default.join(projectRoot, f))
8928
+ (f) => path6__default.isAbsolute(f) ? path6__default.normalize(f) : path6__default.normalize(path6__default.join(projectRoot, f))
6767
8929
  );
6768
8930
  if (internalSignal.aborted || absoluteFiles.length === 0) {
6769
8931
  return {
@@ -6804,7 +8966,7 @@ async function scan(opts) {
6804
8966
  registry.register(
6805
8967
  new PhantomDepEngine(projectRoot, {
6806
8968
  confidenceThreshold: getThreshold("phantom_dep"),
6807
- registryCachePath: path5__default.join(projectRoot, ".vibecheck", "registry-cache.json")
8969
+ registryCachePath: path6__default.join(projectRoot, ".vibecheck", "registry-cache.json")
6808
8970
  }),
6809
8971
  { timeoutMs: engineTimeouts["phantom-dep"] ?? 100, priority: 2 }
6810
8972
  );
@@ -6848,6 +9010,19 @@ async function scan(opts) {
6848
9010
  timeoutMs: engineTimeouts["incomplete-impl"] ?? 40,
6849
9011
  priority: 11
6850
9012
  });
9013
+ registry.register(new SlopsquatEngine(), {
9014
+ timeoutMs: engineTimeouts.slopsquat ?? 30,
9015
+ priority: 2.7
9016
+ });
9017
+ const knownHosts = truthpack ? extractKnownHostsFromIntegrations(truthpack.integrations) : /* @__PURE__ */ new Set();
9018
+ registry.register(new AIRulesAttackEngine({ knownHosts }), {
9019
+ timeoutMs: engineTimeouts["ai-rules-attack"] ?? 60,
9020
+ priority: 4.5
9021
+ });
9022
+ registry.register(new TestQualityEngine(), {
9023
+ timeoutMs: engineTimeouts["test-quality"] ?? 30,
9024
+ priority: 11.5
9025
+ });
6851
9026
  applyEngineAllowlist(registry, expandEngineAllowlist(enginesOpt));
6852
9027
  await registry.activateAll((id, err) => {
6853
9028
  console.warn(`[VibeCheck] Engine "${id}" activation failed:`, err);
@@ -6865,11 +9040,11 @@ async function scan(opts) {
6865
9040
  const rows = [];
6866
9041
  for (const file of absoluteFiles) {
6867
9042
  if (internalSignal.aborted) break;
6868
- const ext = path5__default.extname(file).toLowerCase();
9043
+ const ext = path6__default.extname(file).toLowerCase();
6869
9044
  if (!CHECKED_EXTS.has(ext)) {
6870
9045
  rows.push({
6871
9046
  file,
6872
- relativePath: path5__default.relative(projectRoot, file).replace(/\\/g, "/"),
9047
+ relativePath: path6__default.relative(projectRoot, file).replace(/\\/g, "/"),
6873
9048
  findings: [],
6874
9049
  skipped: true,
6875
9050
  skipReason: "Unsupported extension"
@@ -6882,7 +9057,7 @@ async function scan(opts) {
6882
9057
  } catch {
6883
9058
  rows.push({
6884
9059
  file,
6885
- relativePath: path5__default.relative(projectRoot, file).replace(/\\/g, "/"),
9060
+ relativePath: path6__default.relative(projectRoot, file).replace(/\\/g, "/"),
6886
9061
  findings: [],
6887
9062
  skipped: true,
6888
9063
  skipReason: "Stat error"
@@ -6892,7 +9067,7 @@ async function scan(opts) {
6892
9067
  if (stat2.size > MAX_FILE_SIZE) {
6893
9068
  rows.push({
6894
9069
  file,
6895
- relativePath: path5__default.relative(projectRoot, file).replace(/\\/g, "/"),
9070
+ relativePath: path6__default.relative(projectRoot, file).replace(/\\/g, "/"),
6896
9071
  findings: [],
6897
9072
  skipped: true,
6898
9073
  skipReason: "File too large"
@@ -6905,7 +9080,7 @@ async function scan(opts) {
6905
9080
  } catch {
6906
9081
  rows.push({
6907
9082
  file,
6908
- relativePath: path5__default.relative(projectRoot, file).replace(/\\/g, "/"),
9083
+ relativePath: path6__default.relative(projectRoot, file).replace(/\\/g, "/"),
6909
9084
  findings: [],
6910
9085
  skipped: true,
6911
9086
  skipReason: "Read error"
@@ -6915,7 +9090,7 @@ async function scan(opts) {
6915
9090
  if (text.trim().length === 0) {
6916
9091
  rows.push({
6917
9092
  file,
6918
- relativePath: path5__default.relative(projectRoot, file).replace(/\\/g, "/"),
9093
+ relativePath: path6__default.relative(projectRoot, file).replace(/\\/g, "/"),
6919
9094
  findings: [],
6920
9095
  skipped: true,
6921
9096
  skipReason: "Empty file"
@@ -6925,7 +9100,7 @@ async function scan(opts) {
6925
9100
  if (incompleteWorkspaceEnabled && (ext === ".ts" || ext === ".tsx") && !file.endsWith(".d.ts")) {
6926
9101
  workspaceTsFiles.push({
6927
9102
  absolutePath: file,
6928
- relativePath: path5__default.relative(projectRoot, file).replace(/\\/g, "/"),
9103
+ relativePath: path6__default.relative(projectRoot, file).replace(/\\/g, "/"),
6929
9104
  source: text,
6930
9105
  isTest: isTestLikeScanPath(file)
6931
9106
  });
@@ -6958,7 +9133,7 @@ async function scan(opts) {
6958
9133
  }
6959
9134
  rows.push({
6960
9135
  file,
6961
- relativePath: path5__default.relative(projectRoot, file).replace(/\\/g, "/"),
9136
+ relativePath: path6__default.relative(projectRoot, file).replace(/\\/g, "/"),
6962
9137
  findings: deduped,
6963
9138
  skipped: false
6964
9139
  });
@@ -6970,20 +9145,24 @@ async function scan(opts) {
6970
9145
  if (extra007.length > 0) {
6971
9146
  const byFile = /* @__PURE__ */ new Map();
6972
9147
  for (const x of extra007) {
6973
- const k = path5__default.normalize(x.file);
9148
+ const k = path6__default.normalize(x.file);
6974
9149
  const list = byFile.get(k) ?? [];
6975
9150
  list.push(x);
6976
9151
  byFile.set(k, list);
6977
9152
  }
6978
9153
  for (const row of rows) {
6979
9154
  if (row.skipped) continue;
6980
- const add = byFile.get(path5__default.normalize(row.file));
9155
+ const add = byFile.get(path6__default.normalize(row.file));
6981
9156
  if (add?.length) {
6982
9157
  row.findings = deduplicateFindings([...row.findings, ...add]);
6983
9158
  }
6984
9159
  }
6985
9160
  }
6986
9161
  }
9162
+ for (const row of rows) {
9163
+ if (row.skipped || row.findings.length === 0) continue;
9164
+ row.findings = fuseFindings(row.findings);
9165
+ }
6987
9166
  await registry.prepareDispose();
6988
9167
  registry.dispose();
6989
9168
  let filesScanned = 0;
@@ -7077,7 +9256,7 @@ function severityRank(s) {
7077
9256
  return 4;
7078
9257
  }
7079
9258
  function relPath(workspaceRoot, file) {
7080
- return path5__default.relative(workspaceRoot, file).replace(/\\/g, "/");
9259
+ return path6__default.relative(workspaceRoot, file).replace(/\\/g, "/");
7081
9260
  }
7082
9261
  function clusterWeight(f) {
7083
9262
  if (f.severity === "critical") return 4;
@@ -7156,6 +9335,7 @@ function buildCiSummaryJson(findings, score, workspaceRoot, opts = {}) {
7156
9335
  const fixableCount = findings.filter((f) => f.autoFixable).length;
7157
9336
  const fixableWithSuggestion = findings.filter((f) => f.autoFixable && f.suggestion).length;
7158
9337
  const shipBlockers = buildShipBlockers(findings, score, opts);
9338
+ const fusionStats = computeFusionStats(findings);
7159
9339
  const md = formatCiSummaryMarkdownFromParts({
7160
9340
  score,
7161
9341
  findingsCount: findings.length,
@@ -7165,7 +9345,8 @@ function buildCiSummaryJson(findings, score, workspaceRoot, opts = {}) {
7165
9345
  fixableWithSuggestion,
7166
9346
  shipBlockers,
7167
9347
  threshold: opts.threshold,
7168
- failOn: opts.failOn
9348
+ failOn: opts.failOn,
9349
+ fusionStats
7169
9350
  });
7170
9351
  return {
7171
9352
  summaryMarkdown: md,
@@ -7173,7 +9354,8 @@ function buildCiSummaryJson(findings, score, workspaceRoot, opts = {}) {
7173
9354
  topFindings,
7174
9355
  fixableCount,
7175
9356
  fixableWithSuggestion,
7176
- shipBlockers
9357
+ shipBlockers,
9358
+ fusionStats
7177
9359
  };
7178
9360
  }
7179
9361
  function formatCiSummaryMarkdownFromParts(p) {
@@ -7228,6 +9410,25 @@ function formatCiSummaryMarkdownFromParts(p) {
7228
9410
  );
7229
9411
  }
7230
9412
  lines.push("");
9413
+ if (p.fusionStats && p.fusionStats.totalFindings > 0 && (p.fusionStats.boostedFindings > 0 || p.fusionStats.escalatedFindings > 0)) {
9414
+ const s = p.fusionStats;
9415
+ lines.push("### Cross-engine fusion");
9416
+ lines.push("");
9417
+ const parts = [];
9418
+ if (s.escalatedFindings > 0) {
9419
+ parts.push(`**${s.escalatedFindings}** escalated`);
9420
+ }
9421
+ if (s.boostedFindings > 0) {
9422
+ parts.push(`**${s.boostedFindings}** confidence-boosted`);
9423
+ }
9424
+ parts.push(`largest cluster size: **${s.largestClusterSize}**`);
9425
+ lines.push(parts.join(" \xB7 ") + ".");
9426
+ lines.push("");
9427
+ lines.push(
9428
+ `_Across ${s.totalFindings} finding(s) in ${s.totalClusters} cluster(s), multiple engines independently flagged ${s.boostedFindings + s.escalatedFindings} location(s). Cross-engine concurrence is a strong independent signal._`
9429
+ );
9430
+ lines.push("");
9431
+ }
7231
9432
  const gateParts = [];
7232
9433
  if (typeof p.threshold === "number") {
7233
9434
  gateParts.push(`threshold ${p.threshold}`);
@@ -7271,6 +9472,18 @@ function formatCiSummaryPlainLines(findings, score, _workspaceRoot, opts = {}) {
7271
9472
  if (fixable > 0) {
7272
9473
  out.push(` Auto-fixable: ${fixable}${withSug ? ` (${withSug} with suggestions)` : ""}`);
7273
9474
  }
9475
+ const fusionStats = computeFusionStats(findings);
9476
+ if (fusionStats.boostedFindings > 0 || fusionStats.escalatedFindings > 0) {
9477
+ const parts = [];
9478
+ if (fusionStats.escalatedFindings > 0) {
9479
+ parts.push(`${fusionStats.escalatedFindings} escalated`);
9480
+ }
9481
+ if (fusionStats.boostedFindings > 0) {
9482
+ parts.push(`${fusionStats.boostedFindings} boosted`);
9483
+ }
9484
+ parts.push(`largest cluster ${fusionStats.largestClusterSize}`);
9485
+ out.push(` Cross-engine fusion: ${parts.join(", ")}`);
9486
+ }
7274
9487
  return out;
7275
9488
  }
7276
9489
  var SARIF_SCHEMA_URL = "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json";
@@ -7291,6 +9504,15 @@ function toSarif(input, workspaceRoot, options = {}) {
7291
9504
  } = options;
7292
9505
  const rulesMap = /* @__PURE__ */ new Map();
7293
9506
  const sarifResults = [];
9507
+ const findingIndex = /* @__PURE__ */ new Map();
9508
+ for (const fileResult of input.files) {
9509
+ const relPathForFile = fileResult.relativePath ?? path6__default.relative(workspaceRoot, fileResult.file).replace(/\\/g, "/");
9510
+ for (const finding of fileResult.findings) {
9511
+ if (finding.id) {
9512
+ findingIndex.set(finding.id, { finding, relPath: relPathForFile });
9513
+ }
9514
+ }
9515
+ }
7294
9516
  for (const fileResult of input.files) {
7295
9517
  for (const finding of fileResult.findings) {
7296
9518
  const ruleId = finding.ruleId ?? `${finding.engine}-${finding.category}`;
@@ -7300,7 +9522,7 @@ function toSarif(input, workspaceRoot, options = {}) {
7300
9522
  shortDescription: { text: finding.message }
7301
9523
  });
7302
9524
  }
7303
- const relPath2 = fileResult.relativePath ?? path5__default.relative(workspaceRoot, finding.file).replace(/\\/g, "/");
9525
+ const relPath2 = fileResult.relativePath ?? path6__default.relative(workspaceRoot, finding.file).replace(/\\/g, "/");
7304
9526
  const region = {
7305
9527
  startLine: finding.line
7306
9528
  };
@@ -7313,9 +9535,12 @@ function toSarif(input, workspaceRoot, options = {}) {
7313
9535
  if (finding.endColumn != null && finding.endColumn >= 0) {
7314
9536
  region.endColumn = finding.endColumn + 1;
7315
9537
  }
7316
- const messageText = finding.suggestion ? `${finding.message}
9538
+ const fusionNote = finding.fusion?.reason ? `
9539
+
9540
+ [Fusion: ${finding.fusion.reason}` + (finding.fusion.originalSeverity ? ` \u2014 originally ${finding.fusion.originalSeverity}` : "") + "]" : "";
9541
+ const messageText = (finding.suggestion ? `${finding.message}
7317
9542
 
7318
- Suggestion: ${finding.suggestion}` : finding.message;
9543
+ Suggestion: ${finding.suggestion}` : finding.message) + fusionNote;
7319
9544
  const sarifResult = {
7320
9545
  ruleId,
7321
9546
  level: SEVERITY_TO_SARIF_LEVEL[finding.severity] ?? "warning",
@@ -7332,6 +9557,50 @@ Suggestion: ${finding.suggestion}` : finding.message;
7332
9557
  if (finding.suggestion) {
7333
9558
  sarifResult.fixes = [{ description: { text: finding.suggestion } }];
7334
9559
  }
9560
+ const relatedIds = finding.relatedFindings ?? [];
9561
+ if (relatedIds.length > 0) {
9562
+ const relatedLocations = [];
9563
+ for (const relId of relatedIds) {
9564
+ const target = findingIndex.get(relId);
9565
+ if (!target) continue;
9566
+ relatedLocations.push({
9567
+ physicalLocation: {
9568
+ artifactLocation: { uri: target.relPath },
9569
+ region: {
9570
+ startLine: target.finding.line,
9571
+ ...target.finding.column != null && target.finding.column >= 0 ? { startColumn: target.finding.column + 1 } : {}
9572
+ }
9573
+ },
9574
+ message: {
9575
+ text: `Related: ${target.finding.ruleId ?? target.finding.engine} \u2014 ${target.finding.message.slice(0, 100)}`
9576
+ }
9577
+ });
9578
+ }
9579
+ if (relatedLocations.length > 0) {
9580
+ sarifResult.relatedLocations = relatedLocations;
9581
+ }
9582
+ }
9583
+ const properties = {};
9584
+ if (typeof finding.confidence === "number") {
9585
+ properties.confidence = finding.confidence;
9586
+ }
9587
+ if (finding.id) {
9588
+ properties.fingerprint = finding.id;
9589
+ }
9590
+ if (finding.fusion) {
9591
+ properties.fusion = {
9592
+ ...finding.fusion.reason ? { reason: finding.fusion.reason } : {},
9593
+ ...finding.fusion.clusterSize != null ? { clusterSize: finding.fusion.clusterSize } : {},
9594
+ ...finding.fusion.originalSeverity ? { originalSeverity: finding.fusion.originalSeverity } : {},
9595
+ ...finding.fusion.originalConfidence != null ? { originalConfidence: finding.fusion.originalConfidence } : {}
9596
+ };
9597
+ }
9598
+ if (relatedIds.length > 0) {
9599
+ properties.relatedFindings = relatedIds;
9600
+ }
9601
+ if (Object.keys(properties).length > 0) {
9602
+ sarifResult.properties = properties;
9603
+ }
7335
9604
  sarifResults.push(sarifResult);
7336
9605
  }
7337
9606
  }
@@ -7378,7 +9647,7 @@ function formatJson(result, options) {
7378
9647
  return {
7379
9648
  ...f,
7380
9649
  filePath: fr.file,
7381
- relativePath: fr.relativePath ?? path5__default.relative(options.workspaceRoot, fr.file).replace(/\\/g, "/"),
9650
+ relativePath: fr.relativePath ?? path6__default.relative(options.workspaceRoot, fr.file).replace(/\\/g, "/"),
7382
9651
  riskDimension,
7383
9652
  suggestion: improveRemediationText(f.suggestion, riskDimension)
7384
9653
  };
@@ -7485,7 +9754,7 @@ async function findFile(dir, pattern, maxDepth = 5, currentDepth = 0) {
7485
9754
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build") {
7486
9755
  continue;
7487
9756
  }
7488
- const fullPath = path5__default__default.join(dir, entry.name);
9757
+ const fullPath = path6__default__default.join(dir, entry.name);
7489
9758
  if (entry.isDirectory()) {
7490
9759
  const found = await findFile(fullPath, pattern, maxDepth, currentDepth + 1);
7491
9760
  if (found) return found;
@@ -7507,7 +9776,7 @@ async function readFileSafe(filePath) {
7507
9776
  }
7508
9777
  async function seoEngine(projectPath) {
7509
9778
  const issues = [];
7510
- const hasRobots = await pathExists(path5__default__default.join(projectPath, "public", "robots.txt"));
9779
+ const hasRobots = await pathExists(path6__default__default.join(projectPath, "public", "robots.txt"));
7511
9780
  if (!hasRobots) {
7512
9781
  issues.push({
7513
9782
  id: "missing-robots",
@@ -7519,7 +9788,7 @@ async function seoEngine(projectPath) {
7519
9788
  autoFixable: true
7520
9789
  });
7521
9790
  }
7522
- const hasSitemap = await pathExists(path5__default__default.join(projectPath, "public", "sitemap.xml")) || await findFile(projectPath, /sitemap/i);
9791
+ const hasSitemap = await pathExists(path6__default__default.join(projectPath, "public", "sitemap.xml")) || await findFile(projectPath, /sitemap/i);
7523
9792
  if (!hasSitemap) {
7524
9793
  issues.push({
7525
9794
  id: "missing-sitemap",
@@ -7543,7 +9812,7 @@ async function seoEngine(projectPath) {
7543
9812
  autoFixable: true
7544
9813
  });
7545
9814
  }
7546
- const hasOgImage = await pathExists(path5__default__default.join(projectPath, "public", "og-image.png")) || await pathExists(path5__default__default.join(projectPath, "public", "og.png")) || await findFile(path5__default__default.join(projectPath, "public"), /og[-_]?image/i);
9815
+ const hasOgImage = await pathExists(path6__default__default.join(projectPath, "public", "og-image.png")) || await pathExists(path6__default__default.join(projectPath, "public", "og.png")) || await findFile(path6__default__default.join(projectPath, "public"), /og[-_]?image/i);
7547
9816
  if (!hasOgImage) {
7548
9817
  issues.push({
7549
9818
  id: "missing-og-image",
@@ -7559,7 +9828,7 @@ async function seoEngine(projectPath) {
7559
9828
  }
7560
9829
  async function securityEngine(projectPath) {
7561
9830
  const issues = [];
7562
- const hasEnvExample = await pathExists(path5__default__default.join(projectPath, ".env.example")) || await pathExists(path5__default__default.join(projectPath, ".env.sample"));
9831
+ const hasEnvExample = await pathExists(path6__default__default.join(projectPath, ".env.example")) || await pathExists(path6__default__default.join(projectPath, ".env.sample"));
7563
9832
  if (!hasEnvExample) {
7564
9833
  issues.push({
7565
9834
  id: "missing-env-example",
@@ -7571,7 +9840,7 @@ async function securityEngine(projectPath) {
7571
9840
  autoFixable: true
7572
9841
  });
7573
9842
  }
7574
- const gitignore = await readFileSafe(path5__default__default.join(projectPath, ".gitignore"));
9843
+ const gitignore = await readFileSafe(path6__default__default.join(projectPath, ".gitignore"));
7575
9844
  if (gitignore && !gitignore.includes(".env")) {
7576
9845
  issues.push({
7577
9846
  id: "env-not-gitignored",
@@ -7583,7 +9852,7 @@ async function securityEngine(projectPath) {
7583
9852
  autoFixable: true
7584
9853
  });
7585
9854
  }
7586
- const packageJson = await readFileSafe(path5__default__default.join(projectPath, "package.json"));
9855
+ const packageJson = await readFileSafe(path6__default__default.join(projectPath, "package.json"));
7587
9856
  const hasHelmet = packageJson && /helmet|next-secure-headers/i.test(packageJson);
7588
9857
  if (!hasHelmet) {
7589
9858
  issues.push({
@@ -7597,7 +9866,7 @@ async function securityEngine(projectPath) {
7597
9866
  });
7598
9867
  }
7599
9868
  const hasCors = packageJson && /cors|@fastify\/cors/i.test(packageJson);
7600
- const nextConfig = await readFileSafe(path5__default__default.join(projectPath, "next.config.js")) || await readFileSafe(path5__default__default.join(projectPath, "next.config.mjs")) || await readFileSafe(path5__default__default.join(projectPath, "next.config.ts"));
9869
+ const nextConfig = await readFileSafe(path6__default__default.join(projectPath, "next.config.js")) || await readFileSafe(path6__default__default.join(projectPath, "next.config.mjs")) || await readFileSafe(path6__default__default.join(projectPath, "next.config.ts"));
7601
9870
  const hasNextCors = nextConfig && /headers|Access-Control/i.test(nextConfig ?? "");
7602
9871
  if (!hasCors && !hasNextCors) {
7603
9872
  issues.push({
@@ -7614,8 +9883,8 @@ async function securityEngine(projectPath) {
7614
9883
  }
7615
9884
  async function resilienceEngine(projectPath) {
7616
9885
  const issues = [];
7617
- const packageJson = await readFileSafe(path5__default__default.join(projectPath, "package.json"));
7618
- const srcPath = path5__default__default.join(projectPath, "src");
9886
+ const packageJson = await readFileSafe(path6__default__default.join(projectPath, "package.json"));
9887
+ const srcPath = path6__default__default.join(projectPath, "src");
7619
9888
  const hasSrc = await pathExists(srcPath);
7620
9889
  const searchPath = hasSrc ? srcPath : projectPath;
7621
9890
  if (!(packageJson && /opossum|cockatiel|resilience4j|circuit.*breaker/i.test(packageJson))) {
@@ -7686,8 +9955,8 @@ async function resilienceEngine(projectPath) {
7686
9955
  }
7687
9956
  async function performanceEngine(projectPath) {
7688
9957
  const issues = [];
7689
- const packageJson = await readFileSafe(path5__default__default.join(projectPath, "package.json"));
7690
- const nextConfig = await readFileSafe(path5__default__default.join(projectPath, "next.config.js")) || await readFileSafe(path5__default__default.join(projectPath, "next.config.mjs"));
9958
+ const packageJson = await readFileSafe(path6__default__default.join(projectPath, "package.json"));
9959
+ const nextConfig = await readFileSafe(path6__default__default.join(projectPath, "next.config.js")) || await readFileSafe(path6__default__default.join(projectPath, "next.config.mjs"));
7691
9960
  const hasImageOptimization = packageJson && /sharp|next\/image|@next\/image/i.test(packageJson) || nextConfig && /images:/i.test(nextConfig ?? "");
7692
9961
  if (!hasImageOptimization) {
7693
9962
  issues.push({
@@ -7728,8 +9997,8 @@ async function performanceEngine(projectPath) {
7728
9997
  }
7729
9998
  async function observabilityEngine(projectPath) {
7730
9999
  const issues = [];
7731
- const packageJson = await readFileSafe(path5__default__default.join(projectPath, "package.json"));
7732
- const srcPath = path5__default__default.join(projectPath, "src");
10000
+ const packageJson = await readFileSafe(path6__default__default.join(projectPath, "package.json"));
10001
+ const srcPath = path6__default__default.join(projectPath, "src");
7733
10002
  const hasSrc = await pathExists(srcPath);
7734
10003
  const searchPath = hasSrc ? srcPath : projectPath;
7735
10004
  if (!(packageJson && /@opentelemetry|otel/i.test(packageJson))) {
@@ -7785,8 +10054,8 @@ async function observabilityEngine(projectPath) {
7785
10054
  }
7786
10055
  async function infrastructureEngine(projectPath) {
7787
10056
  const issues = [];
7788
- const packageJson = await readFileSafe(path5__default__default.join(projectPath, "package.json"));
7789
- const hasDocker = await pathExists(path5__default__default.join(projectPath, "Dockerfile")) || await pathExists(path5__default__default.join(projectPath, "docker-compose.yml"));
10057
+ const packageJson = await readFileSafe(path6__default__default.join(projectPath, "package.json"));
10058
+ const hasDocker = await pathExists(path6__default__default.join(projectPath, "Dockerfile")) || await pathExists(path6__default__default.join(projectPath, "docker-compose.yml"));
7790
10059
  if (!hasDocker) {
7791
10060
  issues.push({
7792
10061
  id: "missing-docker",
@@ -7798,7 +10067,7 @@ async function infrastructureEngine(projectPath) {
7798
10067
  autoFixable: true
7799
10068
  });
7800
10069
  }
7801
- const hasCi = await pathExists(path5__default__default.join(projectPath, ".github", "workflows")) || await pathExists(path5__default__default.join(projectPath, ".gitlab-ci.yml")) || await pathExists(path5__default__default.join(projectPath, ".circleci"));
10070
+ const hasCi = await pathExists(path6__default__default.join(projectPath, ".github", "workflows")) || await pathExists(path6__default__default.join(projectPath, ".gitlab-ci.yml")) || await pathExists(path6__default__default.join(projectPath, ".circleci"));
7802
10071
  if (!hasCi) {
7803
10072
  issues.push({
7804
10073
  id: "missing-ci",
@@ -7810,7 +10079,7 @@ async function infrastructureEngine(projectPath) {
7810
10079
  autoFixable: true
7811
10080
  });
7812
10081
  }
7813
- const hasDeployConfig = await pathExists(path5__default__default.join(projectPath, "vercel.json")) || await pathExists(path5__default__default.join(projectPath, "netlify.toml")) || await pathExists(path5__default__default.join(projectPath, "railway.json"));
10082
+ const hasDeployConfig = await pathExists(path6__default__default.join(projectPath, "vercel.json")) || await pathExists(path6__default__default.join(projectPath, "netlify.toml")) || await pathExists(path6__default__default.join(projectPath, "railway.json"));
7814
10083
  if (!hasDeployConfig && !hasDocker) {
7815
10084
  issues.push({
7816
10085
  id: "missing-deployment-config",
@@ -7838,7 +10107,7 @@ async function infrastructureEngine(projectPath) {
7838
10107
  }
7839
10108
  async function documentationEngine(projectPath) {
7840
10109
  const issues = [];
7841
- const hasReadme = await pathExists(path5__default__default.join(projectPath, "README.md"));
10110
+ const hasReadme = await pathExists(path6__default__default.join(projectPath, "README.md"));
7842
10111
  if (!hasReadme) {
7843
10112
  issues.push({
7844
10113
  id: "missing-readme",
@@ -7850,7 +10119,7 @@ async function documentationEngine(projectPath) {
7850
10119
  autoFixable: true
7851
10120
  });
7852
10121
  } else {
7853
- const readme = await readFileSafe(path5__default__default.join(projectPath, "README.md"));
10122
+ const readme = await readFileSafe(path6__default__default.join(projectPath, "README.md"));
7854
10123
  if (readme && readme.length < 500) {
7855
10124
  issues.push({
7856
10125
  id: "insufficient-readme",
@@ -7863,7 +10132,7 @@ async function documentationEngine(projectPath) {
7863
10132
  });
7864
10133
  }
7865
10134
  }
7866
- if (!await pathExists(path5__default__default.join(projectPath, "CHANGELOG.md"))) {
10135
+ if (!await pathExists(path6__default__default.join(projectPath, "CHANGELOG.md"))) {
7867
10136
  issues.push({
7868
10137
  id: "missing-changelog",
7869
10138
  category: "Documentation",
@@ -7874,7 +10143,7 @@ async function documentationEngine(projectPath) {
7874
10143
  autoFixable: true
7875
10144
  });
7876
10145
  }
7877
- if (!await pathExists(path5__default__default.join(projectPath, "CONTRIBUTING.md"))) {
10146
+ if (!await pathExists(path6__default__default.join(projectPath, "CONTRIBUTING.md"))) {
7878
10147
  issues.push({
7879
10148
  id: "missing-contributing",
7880
10149
  category: "Documentation",
@@ -7885,7 +10154,7 @@ async function documentationEngine(projectPath) {
7885
10154
  autoFixable: true
7886
10155
  });
7887
10156
  }
7888
- const hasLicense = await pathExists(path5__default__default.join(projectPath, "LICENSE")) || await pathExists(path5__default__default.join(projectPath, "LICENSE.md"));
10157
+ const hasLicense = await pathExists(path6__default__default.join(projectPath, "LICENSE")) || await pathExists(path6__default__default.join(projectPath, "LICENSE.md"));
7889
10158
  if (!hasLicense) {
7890
10159
  issues.push({
7891
10160
  id: "missing-license",
@@ -7901,7 +10170,7 @@ async function documentationEngine(projectPath) {
7901
10170
  }
7902
10171
  async function configurationEngine(projectPath) {
7903
10172
  const issues = [];
7904
- const tsConfig = await readFileSafe(path5__default__default.join(projectPath, "tsconfig.json"));
10173
+ const tsConfig = await readFileSafe(path6__default__default.join(projectPath, "tsconfig.json"));
7905
10174
  if (tsConfig) {
7906
10175
  try {
7907
10176
  const parsed = JSON.parse(tsConfig);
@@ -7919,7 +10188,7 @@ async function configurationEngine(projectPath) {
7919
10188
  } catch {
7920
10189
  }
7921
10190
  }
7922
- const hasEslint = await pathExists(path5__default__default.join(projectPath, ".eslintrc.json")) || await pathExists(path5__default__default.join(projectPath, ".eslintrc.js")) || await pathExists(path5__default__default.join(projectPath, "eslint.config.js"));
10191
+ const hasEslint = await pathExists(path6__default__default.join(projectPath, ".eslintrc.json")) || await pathExists(path6__default__default.join(projectPath, ".eslintrc.js")) || await pathExists(path6__default__default.join(projectPath, "eslint.config.js"));
7923
10192
  if (!hasEslint) {
7924
10193
  issues.push({
7925
10194
  id: "missing-eslint",
@@ -7931,7 +10200,7 @@ async function configurationEngine(projectPath) {
7931
10200
  autoFixable: true
7932
10201
  });
7933
10202
  }
7934
- const hasPrettier = await pathExists(path5__default__default.join(projectPath, ".prettierrc")) || await pathExists(path5__default__default.join(projectPath, ".prettierrc.json")) || await pathExists(path5__default__default.join(projectPath, "prettier.config.js"));
10203
+ const hasPrettier = await pathExists(path6__default__default.join(projectPath, ".prettierrc")) || await pathExists(path6__default__default.join(projectPath, ".prettierrc.json")) || await pathExists(path6__default__default.join(projectPath, "prettier.config.js"));
7935
10204
  if (!hasPrettier) {
7936
10205
  issues.push({
7937
10206
  id: "missing-prettier",
@@ -7943,7 +10212,7 @@ async function configurationEngine(projectPath) {
7943
10212
  autoFixable: true
7944
10213
  });
7945
10214
  }
7946
- const hasEditorConfig = await pathExists(path5__default__default.join(projectPath, ".editorconfig"));
10215
+ const hasEditorConfig = await pathExists(path6__default__default.join(projectPath, ".editorconfig"));
7947
10216
  if (!hasEditorConfig) {
7948
10217
  issues.push({
7949
10218
  id: "missing-editorconfig",
@@ -7959,13 +10228,13 @@ async function configurationEngine(projectPath) {
7959
10228
  }
7960
10229
  async function backendEngine(projectPath) {
7961
10230
  const issues = [];
7962
- const apiPath = path5__default__default.join(projectPath, "src", "server");
7963
- const apiAltPath = path5__default__default.join(projectPath, "api");
7964
- const pagesApiPath = path5__default__default.join(projectPath, "pages", "api");
7965
- const appApiPath = path5__default__default.join(projectPath, "app", "api");
10231
+ const apiPath = path6__default__default.join(projectPath, "src", "server");
10232
+ const apiAltPath = path6__default__default.join(projectPath, "api");
10233
+ const pagesApiPath = path6__default__default.join(projectPath, "pages", "api");
10234
+ const appApiPath = path6__default__default.join(projectPath, "app", "api");
7966
10235
  const hasApi = await pathExists(apiPath) || await pathExists(apiAltPath) || await pathExists(pagesApiPath) || await pathExists(appApiPath);
7967
10236
  if (!hasApi) return issues;
7968
- const packageJson = await readFileSafe(path5__default__default.join(projectPath, "package.json"));
10237
+ const packageJson = await readFileSafe(path6__default__default.join(projectPath, "package.json"));
7969
10238
  const hasHealth = await findFile(projectPath, /health|healthcheck|status/i);
7970
10239
  if (!hasHealth) {
7971
10240
  issues.push({
@@ -8020,7 +10289,7 @@ async function backendEngine(projectPath) {
8020
10289
  }
8021
10290
  async function accessibilityEngine(projectPath) {
8022
10291
  const issues = [];
8023
- const packageJson = await readFileSafe(path5__default__default.join(projectPath, "package.json"));
10292
+ const packageJson = await readFileSafe(path6__default__default.join(projectPath, "package.json"));
8024
10293
  const hasA11yTesting = packageJson && /axe-core|@axe-core|jest-axe|cypress-axe|pa11y/i.test(packageJson);
8025
10294
  if (!hasA11yTesting) {
8026
10295
  issues.push({
@@ -8045,7 +10314,7 @@ async function accessibilityEngine(projectPath) {
8045
10314
  autoFixable: true
8046
10315
  });
8047
10316
  }
8048
- const globalCss = await readFileSafe(path5__default__default.join(projectPath, "src", "styles", "globals.css")) || await readFileSafe(path5__default__default.join(projectPath, "app", "globals.css")) || await readFileSafe(path5__default__default.join(projectPath, "styles", "globals.css"));
10317
+ const globalCss = await readFileSafe(path6__default__default.join(projectPath, "src", "styles", "globals.css")) || await readFileSafe(path6__default__default.join(projectPath, "app", "globals.css")) || await readFileSafe(path6__default__default.join(projectPath, "styles", "globals.css"));
8049
10318
  if (globalCss && /outline:\s*none|outline:\s*0/i.test(globalCss) && !/focus-visible/i.test(globalCss)) {
8050
10319
  issues.push({
8051
10320
  id: "focus-removed",
@@ -8083,9 +10352,9 @@ function hasLibrary(packageJsonContent, libraries) {
8083
10352
  return null;
8084
10353
  }
8085
10354
  async function detectProjectType(projectPath, packageJsonContent) {
8086
- const hasApp = await pathExists(path5__default__default.join(projectPath, "app"));
8087
- const hasPages = await pathExists(path5__default__default.join(projectPath, "pages"));
8088
- const hasSrc = await pathExists(path5__default__default.join(projectPath, "src"));
10355
+ const hasApp = await pathExists(path6__default__default.join(projectPath, "app"));
10356
+ const hasPages = await pathExists(path6__default__default.join(projectPath, "pages"));
10357
+ const hasSrc = await pathExists(path6__default__default.join(projectPath, "src"));
8089
10358
  const isNextJs = !!packageJsonContent && /["']next["']/.test(packageJsonContent);
8090
10359
  const isRemix = !!packageJsonContent && /["']@remix-run\//.test(packageJsonContent);
8091
10360
  const isVite = !!packageJsonContent && /["']vite["']/.test(packageJsonContent);
@@ -8176,7 +10445,7 @@ var ENGINES = [
8176
10445
  ];
8177
10446
  async function runPolish(projectPath) {
8178
10447
  const allIssues = [];
8179
- const packageJson = await readFileSafe(path5__default__default.join(projectPath, "package.json"));
10448
+ const packageJson = await readFileSafe(path6__default__default.join(projectPath, "package.json"));
8180
10449
  const projectType = await detectProjectType(projectPath, packageJson);
8181
10450
  const engineResults = await Promise.allSettled(
8182
10451
  ENGINES.map((engine) => engine(projectPath))
@@ -8591,13 +10860,13 @@ function estimateBlastRadius(steps, file) {
8591
10860
  }
8592
10861
  }
8593
10862
  }
8594
- const path23 = file.toLowerCase();
8595
- if (path23.includes("checkout") || path23.includes("payment")) flows.add("payment flow");
8596
- if (path23.includes("auth") || path23.includes("login") || path23.includes("signup"))
10863
+ const path24 = file.toLowerCase();
10864
+ if (path24.includes("checkout") || path24.includes("payment")) flows.add("payment flow");
10865
+ if (path24.includes("auth") || path24.includes("login") || path24.includes("signup"))
8597
10866
  flows.add("authentication");
8598
- if (path23.includes("api/") || path23.includes("route")) flows.add("API endpoint");
8599
- if (path23.includes("middleware")) flows.add("request pipeline");
8600
- if (path23.includes("webhook")) flows.add("webhook processing");
10867
+ if (path24.includes("api/") || path24.includes("route")) flows.add("API endpoint");
10868
+ if (path24.includes("middleware")) flows.add("request pipeline");
10869
+ if (path24.includes("webhook")) flows.add("webhook processing");
8601
10870
  if (flows.size === 0) return void 0;
8602
10871
  const flowList = [...flows].slice(0, 3).join(", ");
8603
10872
  const errorPct = Math.round(errors.length / Math.max(steps.length, 1) * 100);
@@ -8657,7 +10926,7 @@ async function loadEngines(workspaceRoot) {
8657
10926
  })();
8658
10927
  const ghostRouteIndex = truthpack ? truthpackToRouteIndex(truthpack.routes, workspaceRoot) : void 0;
8659
10928
  const ghostRoutePrefixes = [];
8660
- const { PhantomDepEngine: PhantomDepEngine2 } = await import('./PhantomDepEngine-HQEXAS25-TRVEXBMF.js');
10929
+ const { PhantomDepEngine: PhantomDepEngine2 } = await import('./PhantomDepEngine-5O7Z7MDE-4A37GGL4.js');
8661
10930
  const { APITruthEngine: APITruthEngine2 } = await import('./APITruthEngine-IZRR3NT5-LPFUOMLD.js');
8662
10931
  const { EnvVarEngine: EnvVarEngine2 } = await import('./EnvVarEngine-ZFNW2XKP-6HRTZULP.js');
8663
10932
  const { GhostRouteEngine: GhostRouteEngine2 } = await import('./GhostRouteEngine-UMYBCOCL-MSZOPVZY.js');
@@ -8666,7 +10935,7 @@ async function loadEngines(workspaceRoot) {
8666
10935
  const { VersionHallucinationEngine: VersionHallucinationEngine2 } = await import('./VersionHallucinationEngine-673DJ26J-BD4SK6JX.js');
8667
10936
  const { FrameworkPackEngine: FrameworkPackEngine2 } = await import('./FrameworkPackEngine-RRBJW4MC-KH7WRXXS.js');
8668
10937
  const { LogicGapEngine: LogicGapEngine2 } = await import('./LogicGapEngine-OK5UKZQ5-YGXZDERB.js');
8669
- const { ErrorHandlingEngine: ErrorHandlingEngine2 } = await import('./ErrorHandlingEngine-VAHVDVFG-3GDGRN3U.js');
10938
+ const { ErrorHandlingEngine: ErrorHandlingEngine2 } = await import('./ErrorHandlingEngine-FG65SFRB-4NEANCMF.js');
8670
10939
  const confidenceThreshold = 0.75;
8671
10940
  return [
8672
10941
  new EnvVarEngine2(envIndex, {
@@ -8682,7 +10951,7 @@ async function loadEngines(workspaceRoot) {
8682
10951
  ),
8683
10952
  new PhantomDepEngine2(workspaceRoot, {
8684
10953
  confidenceThreshold: 0.55,
8685
- registryCachePath: path5__default.join(workspaceRoot, ".vibecheck", "registry-cache.json")
10954
+ registryCachePath: path6__default.join(workspaceRoot, ".vibecheck", "registry-cache.json")
8686
10955
  }),
8687
10956
  new APITruthEngine2(0.65, void 0, workspaceRoot),
8688
10957
  new CredentialsEngine2(),
@@ -8694,7 +10963,7 @@ async function loadEngines(workspaceRoot) {
8694
10963
  ];
8695
10964
  }
8696
10965
  function buildDelta(filePath, source) {
8697
- const ext = path5__default.extname(filePath).toLowerCase();
10966
+ const ext = path6__default.extname(filePath).toLowerCase();
8698
10967
  const lines = source.split("\n");
8699
10968
  return {
8700
10969
  documentUri: filePath,
@@ -8797,10 +11066,10 @@ function findingsForFile(findings, relativePath) {
8797
11066
  async function runGhostTrace(options) {
8798
11067
  const { workspaceRoot, filePath, signal, engineTimeout = 1e4 } = options;
8799
11068
  const start = Date.now();
8800
- const absolutePath = path5__default.isAbsolute(filePath) ? filePath : path5__default.resolve(workspaceRoot, filePath);
11069
+ const absolutePath = path6__default.isAbsolute(filePath) ? filePath : path6__default.resolve(workspaceRoot, filePath);
8801
11070
  const source = fs2.readFileSync(absolutePath, "utf-8");
8802
11071
  const lines = source.split("\n");
8803
- const relativePath = path5__default.relative(workspaceRoot, absolutePath).replace(/\\/g, "/");
11072
+ const relativePath = path6__default.relative(workspaceRoot, absolutePath).replace(/\\/g, "/");
8804
11073
  const callSites = parseCallSites(source);
8805
11074
  const delta = buildDelta(absolutePath, source);
8806
11075
  const engines = await loadEngines(workspaceRoot);
@@ -8858,4 +11127,4 @@ async function runGhostTrace(options) {
8858
11127
  };
8859
11128
  }
8860
11129
 
8861
- export { CircuitBreaker, DAILY_SCAN_LIMIT_UPGRADE_URL, ENGINE_FOCUS_PRESETS, EngineRegistry, EnvLoader, FEATURE_NAMES, FakeFeaturesEngine, IncompleteImplEngine, OutcomeVerificationEngine, PerformanceAntipatternEngine, RULE_AUTOFIX_SAFETY_BY_ID, RULE_MATURITY_BY_ID, RULE_TRUST_IMPACT_BY_ID, RuntimeProbeEngine, SCAN_ENGINE_FOCUS_PRESET_NAMES, SecurityPatternEngine, TruthpackEnvIndex, TypeContractEngine, VIBECHECK_DEFAULT_ENGINE_IDS, accessibilityEngine, autofixSafetyForRule, backendEngine, buildCiSummaryJson, buildCliUpgradeBlock, buildGatedScanResponse, buildRiskClusters, buildShipBlockers, canAccessFeature, categoryIcons, computeTrustScore, configurationEngine, createDefaultRegistry, dashboardFindingUrl, diffScores, documentationEngine, engineTogglesAllowOnly, estimateBlastRadius, fetchCanonicalAccess, findUncalledExportedFunctions, formatCiSummaryMarkdown, formatCiSummaryPlainLines, formatDailyScanLimitMessage, formatFindingSeverityBreakdown, formatFindings, formatSummary, formatTraceAsJson, formatTraceForTerminal, formatTrustScoreMarkdown, formatTrustScoreMcp, gateCanonicalScanReportFindings, generateVerdict, getCheckoutUrl, getMinPlanForApiSurface, getMinimumPlanForFeature, getQuotas, getTrustScoreStatus, icons, infrastructureEngine, isTestLikeScanPath, isVibecheckFinding, loadTruthpack, maturityTierForRule, normalizeCanonicalScanReport, normalizePlanId, observabilityEngine, parseCallSites, performanceEngine, planHasApiSurface, resilienceEngine, runGhostTrace, runPolish, scan, securityEngine, seoEngine, toCanonicalFinding, toCanonicalFindingFromGuardrail, toCanonicalFindingFromLegacyFinding, toCanonicalFindingFromMissionFinding, toCanonicalFindingFromPolish, toCanonicalFindingFromReport, toCanonicalFindingFromScanApi, toCanonicalFindingFromScanFinding, toCanonicalFindings, toSarif, trustImpactForRule, trustPenaltyScaleForFinding, truthpackToRouteIndex };
11130
+ export { AIRulesAttackEngine, AI_HALLUCINATED_PACKAGES, CANONICAL_AI_PACKAGES_NPM, CORE_ENGINE_IDS, CORE_ENGINE_MANIFESTS, CircuitBreaker, DAILY_SCAN_LIMIT_UPGRADE_URL, ENGINE_FOCUS_PRESETS, EngineRegistry, EnvLoader, FEATURE_NAMES, FakeFeaturesEngine, IncompleteImplEngine, OutcomeVerificationEngine, PerformanceAntipatternEngine, RULE_AUTOFIX_SAFETY_BY_ID, RULE_MATURITY_BY_ID, RULE_TRUST_IMPACT_BY_ID, RuntimeProbeEngine, SCAN_ENGINE_FOCUS_PRESET_NAMES, SecurityPatternEngine, SlopsquatEngine, TestQualityEngine, TruthpackEnvIndex, TypeContractEngine, VIBECHECK_DEFAULT_ENGINE_IDS, VibecheckNativeEngine, WORKSPACE_ENGINE_IDS, WORKSPACE_ENGINE_MANIFESTS, accessibilityEngine, aiRulesAttackManifest, apexFromHost, apexFromUrl, autofixSafetyForRule, backendEngine, buildCiSummaryJson, buildCliUpgradeBlock, buildGatedScanResponse, buildRiskClusters, buildShipBlockers, canAccessFeature, categoryIcons, computeFusionStats, computeTrustScore, configurationEngine, createDefaultRegistry, dashboardFindingUrl, detectBrandImpersonationNpm, diffScores, documentationEngine, engineTogglesAllowOnly, errorHandlingManifest, estimateBlastRadius, extractKnownHostsFromIntegrations, fakeFeaturesManifest, fetchCanonicalAccess, findUncalledExportedFunctions, formatCiSummaryMarkdown, formatCiSummaryPlainLines, formatDailyScanLimitMessage, formatFindingSeverityBreakdown, formatFindings, formatSummary, formatTraceAsJson, formatTraceForTerminal, formatTrustScoreMarkdown, formatTrustScoreMcp, fuseFindings, gateCanonicalScanReportFindings, generateVerdict, getCheckoutUrl, getMinPlanForApiSurface, getMinimumPlanForFeature, getQuotas, getTrustScoreStatus, ghostRouteManifest, hostMatchesKnownSet, icons, incompleteImplManifest, infrastructureEngine, isTestLikeScanPath, isVibecheckFinding, loadTruthpack, logicGapManifest, maturityTierForRule, normalizeCanonicalScanReport, normalizePlanId, observabilityEngine, outcomeVerificationManifest, parseCallSites, perfAntipatternManifest, performanceEngine, phantomDepManifest, planHasApiSurface, registerAllEngines, registerCoreEngines, registerWorkspaceEngines, resilienceEngine, runGhostTrace, runPolish, scan, securityEngine, securityPatternManifest, seoEngine, slopsquatManifest, testQualityManifest, toCanonicalFinding, toCanonicalFindingFromGuardrail, toCanonicalFindingFromLegacyFinding, toCanonicalFindingFromMissionFinding, toCanonicalFindingFromPolish, toCanonicalFindingFromReport, toCanonicalFindingFromScanApi, toCanonicalFindingFromScanFinding, toCanonicalFindings, toSarif, trustImpactForRule, trustPenaltyScaleForFinding, truthpackToRouteIndex, typeContractManifest };