@vibgrate/cli 1.0.65 → 1.0.67

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,8 +1,8 @@
1
1
  import {
2
2
  baselineCommand,
3
3
  runBaseline
4
- } from "./chunk-VGZRUFB4.js";
5
- import "./chunk-XDZGC6KJ.js";
4
+ } from "./chunk-NASOHLRL.js";
5
+ import "./chunk-56UAFVE4.js";
6
6
  import "./chunk-TBE6NQ5Z.js";
7
7
  export {
8
8
  baselineCommand,
@@ -319,20 +319,6 @@ function formatText(artifact) {
319
319
  if (artifact.extended?.architecture) {
320
320
  lines.push(...formatArchitectureDiagram(artifact.extended.architecture));
321
321
  }
322
- if (artifact.relationshipDiagram?.mermaid) {
323
- lines.push(chalk.bold.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
324
- lines.push(chalk.bold.cyan("\u2551 Project Relationship Diagram \u2551"));
325
- lines.push(chalk.bold.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
326
- lines.push("");
327
- lines.push(chalk.bold(" Mermaid") + chalk.dim(" (copy into https://mermaid.live or a ```mermaid code block)"));
328
- lines.push("");
329
- lines.push(chalk.dim(" ```mermaid"));
330
- for (const mLine of artifact.relationshipDiagram.mermaid.split("\n")) {
331
- lines.push(chalk.dim(` ${mLine}`));
332
- }
333
- lines.push(chalk.dim(" ```"));
334
- lines.push("");
335
- }
336
322
  if (artifact.solutions && artifact.solutions.length > 0) {
337
323
  lines.push(chalk.bold.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
338
324
  lines.push(chalk.bold.cyan("\u2551 Solution Drift Summary \u2551"));
@@ -1169,6 +1155,262 @@ import * as crypto3 from "crypto";
1169
1155
  import * as path2 from "path";
1170
1156
  import { Command as Command2 } from "commander";
1171
1157
  import chalk3 from "chalk";
1158
+
1159
+ // src/utils/compact-artifact.ts
1160
+ import * as zlib from "zlib";
1161
+ import { promisify } from "util";
1162
+
1163
+ // src/utils/compact-evidence.ts
1164
+ var CATEGORY_PATTERNS = [
1165
+ { category: "pricing", pattern: /price|pricing|billing|subscri|trial|credit|plan|tier|upgrade|premium|pro|enterprise/i },
1166
+ { category: "auth", pattern: /sign[- ]?in|sign[- ]?up|log[- ]?in|log[- ]?out|auth|sso|oauth|password|register|invite|onboard/i },
1167
+ { category: "dashboard", pattern: /dashboard|overview|home|main|summary|stats/i },
1168
+ { category: "settings", pattern: /setting|config|preference|option|profile|account/i },
1169
+ { category: "users", pattern: /user|member|team|role|permission|access|admin|owner/i },
1170
+ { category: "integrations", pattern: /integrat|connect|webhook|api[- ]?key|sync|import|export/i },
1171
+ { category: "reports", pattern: /report|analy|metric|chart|graph|insight|track/i },
1172
+ { category: "workflows", pattern: /workflow|automat|schedule|trigger|action|job|task|pipeline/i },
1173
+ { category: "projects", pattern: /project|workspace|organization|folder|repo/i },
1174
+ { category: "navigation", pattern: /menu|nav|sidebar|header|footer|breadcrumb/i }
1175
+ ];
1176
+ function compactUiPurpose(result, maxSamplesPerCategory = 3) {
1177
+ const evidence = result.topEvidence;
1178
+ const dependencies = evidence.filter((e) => e.kind === "dependency").map((e) => e.value).slice(0, 10);
1179
+ const routes = dedupeRoutes(
1180
+ evidence.filter((e) => e.kind === "route").map((e) => e.value)
1181
+ ).slice(0, 15);
1182
+ const textEvidence = evidence.filter(
1183
+ (e) => e.kind !== "dependency" && e.kind !== "route" && e.kind !== "feature_flag"
1184
+ );
1185
+ const byCategory = /* @__PURE__ */ new Map();
1186
+ const categoryCounts = {};
1187
+ for (const item of textEvidence) {
1188
+ const category = categorize(item.value);
1189
+ if (!byCategory.has(category)) {
1190
+ byCategory.set(category, /* @__PURE__ */ new Set());
1191
+ }
1192
+ const normalized = normalizeValue(item.value);
1193
+ if (normalized.length >= 3) {
1194
+ byCategory.get(category).add(normalized);
1195
+ }
1196
+ }
1197
+ const samples = [];
1198
+ for (const [category, values] of byCategory) {
1199
+ const deduped = dedupeStrings([...values]);
1200
+ categoryCounts[category] = deduped.length;
1201
+ for (const value of deduped.slice(0, maxSamplesPerCategory)) {
1202
+ samples.push({ kind: "text", value, category });
1203
+ }
1204
+ }
1205
+ const featureFlags = evidence.filter((e) => e.kind === "feature_flag");
1206
+ if (featureFlags.length > 0) {
1207
+ categoryCounts["feature_flags"] = featureFlags.length;
1208
+ samples.push({ kind: "feature_flag", value: "feature flags detected", category: "feature_flags" });
1209
+ }
1210
+ return {
1211
+ samples,
1212
+ categoryCounts,
1213
+ originalCount: evidence.length,
1214
+ dependencies,
1215
+ routes,
1216
+ detectedFrameworks: result.detectedFrameworks
1217
+ };
1218
+ }
1219
+ function categorize(value) {
1220
+ for (const { category, pattern } of CATEGORY_PATTERNS) {
1221
+ if (pattern.test(value)) return category;
1222
+ }
1223
+ return "general";
1224
+ }
1225
+ function normalizeValue(value) {
1226
+ return value.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").replace(/\s+/g, " ").trim().slice(0, 60);
1227
+ }
1228
+ function dedupeStrings(values) {
1229
+ const sorted = values.sort((a, b) => b.length - a.length);
1230
+ const kept = [];
1231
+ for (const value of sorted) {
1232
+ const isDupe = kept.some((k) => {
1233
+ const stem = value.slice(0, 6);
1234
+ return k.startsWith(stem) || k.includes(value) || value.includes(k);
1235
+ });
1236
+ if (!isDupe) {
1237
+ kept.push(value);
1238
+ }
1239
+ }
1240
+ return kept;
1241
+ }
1242
+ function dedupeRoutes(routes) {
1243
+ const seen = /* @__PURE__ */ new Set();
1244
+ const result = [];
1245
+ for (const route of routes) {
1246
+ const normalized = route.replace(/:[a-z_]+/gi, ":param").replace(/\[\[*\.*\.*[a-z_]+\]*\]/gi, ":param").replace(/\/+$/, "").toLowerCase();
1247
+ if (!seen.has(normalized)) {
1248
+ seen.add(normalized);
1249
+ result.push(route);
1250
+ }
1251
+ }
1252
+ return result;
1253
+ }
1254
+
1255
+ // src/utils/compact-artifact.ts
1256
+ var gzip2 = promisify(zlib.gzip);
1257
+ var MAX_ITEMS = 50;
1258
+ function extractName(entry) {
1259
+ const match = entry.match(/^(.+?)\s*\(/);
1260
+ return match ? match[1].trim() : entry.trim();
1261
+ }
1262
+ function compactDataStores(result) {
1263
+ return {
1264
+ databaseTechnologies: result.databaseTechnologies.slice(0, 10),
1265
+ connectionStrings: [],
1266
+ // Don't include connection strings in upload
1267
+ connectionPoolSettings: result.connectionPoolSettings.slice(0, MAX_ITEMS),
1268
+ replicationSettings: result.replicationSettings.slice(0, 20),
1269
+ readReplicaSettings: result.readReplicaSettings.slice(0, 20),
1270
+ failoverSettings: result.failoverSettings.slice(0, 20),
1271
+ collationAndEncoding: result.collationAndEncoding.slice(0, 20),
1272
+ queryTimeoutDefaults: result.queryTimeoutDefaults.slice(0, 20),
1273
+ manualIndexes: result.manualIndexes.map(extractName).slice(0, MAX_ITEMS),
1274
+ tables: result.tables.map(extractName).slice(0, MAX_ITEMS),
1275
+ views: result.views.map(extractName).slice(0, MAX_ITEMS),
1276
+ storedProcedures: result.storedProcedures.map(extractName).slice(0, MAX_ITEMS),
1277
+ triggers: result.triggers.map(extractName).slice(0, MAX_ITEMS),
1278
+ rowLevelSecurityPolicies: result.rowLevelSecurityPolicies.slice(0, 20),
1279
+ otherServices: result.otherServices.slice(0, 20)
1280
+ };
1281
+ }
1282
+ function compactApiSurface(result) {
1283
+ const seenProviders = /* @__PURE__ */ new Set();
1284
+ const uniqueIntegrations = result.integrations.filter((i) => {
1285
+ const domain = i.provider.split(":")[0];
1286
+ if (seenProviders.has(domain)) return false;
1287
+ seenProviders.add(domain);
1288
+ return true;
1289
+ }).slice(0, MAX_ITEMS).map((i) => ({
1290
+ provider: i.provider,
1291
+ endpoint: "",
1292
+ // Don't include full endpoints
1293
+ version: i.version,
1294
+ parameters: [],
1295
+ // Don't include params
1296
+ configOptions: [],
1297
+ authHints: [],
1298
+ files: []
1299
+ // Don't include file paths
1300
+ }));
1301
+ return {
1302
+ integrations: uniqueIntegrations,
1303
+ openApiSpecifications: result.openApiSpecifications.slice(0, 10),
1304
+ webhookUrls: result.webhookUrls.slice(0, 20),
1305
+ callbackEndpoints: result.callbackEndpoints.slice(0, 20),
1306
+ apiVersionPins: result.apiVersionPins.slice(0, 20),
1307
+ tokenExpirationPolicies: result.tokenExpirationPolicies.slice(0, 20),
1308
+ rateLimitOverrides: result.rateLimitOverrides.slice(0, 20),
1309
+ customHeaders: result.customHeaders.slice(0, 20),
1310
+ corsPolicies: result.corsPolicies.slice(0, 20),
1311
+ oauthScopes: result.oauthScopes.slice(0, 20),
1312
+ apiTokens: []
1313
+ // Don't include token references
1314
+ };
1315
+ }
1316
+ function compactAssetBranding(result) {
1317
+ return {
1318
+ faviconFiles: result.faviconFiles.slice(0, 1),
1319
+ productLogos: []
1320
+ // Don't include logos
1321
+ };
1322
+ }
1323
+ function prepareArtifactForUpload(artifact) {
1324
+ const compacted = { ...artifact };
1325
+ if (compacted.extended) {
1326
+ const ext = { ...compacted.extended };
1327
+ if (ext.dataStores) {
1328
+ ext.dataStores = compactDataStores(ext.dataStores);
1329
+ }
1330
+ if (ext.apiSurface) {
1331
+ ext.apiSurface = compactApiSurface(ext.apiSurface);
1332
+ }
1333
+ if (ext.assetBranding) {
1334
+ ext.assetBranding = compactAssetBranding(ext.assetBranding);
1335
+ }
1336
+ if (ext.uiPurpose) {
1337
+ const compactedUi = compactUiPurpose(ext.uiPurpose);
1338
+ ext.uiPurpose = {
1339
+ enabled: ext.uiPurpose.enabled,
1340
+ detectedFrameworks: compactedUi.detectedFrameworks,
1341
+ evidenceCount: compactedUi.originalCount,
1342
+ capped: ext.uiPurpose.capped,
1343
+ topEvidence: [],
1344
+ // Clear full evidence
1345
+ unknownSignals: [],
1346
+ // Add compacted data under extended properties
1347
+ ...{ compacted: compactedUi }
1348
+ };
1349
+ }
1350
+ if (ext.runtimeConfiguration) {
1351
+ ext.runtimeConfiguration = {
1352
+ ...ext.runtimeConfiguration,
1353
+ environmentVariables: ext.runtimeConfiguration.environmentVariables.slice(0, 100),
1354
+ hiddenConfigFiles: ext.runtimeConfiguration.hiddenConfigFiles.slice(0, MAX_ITEMS),
1355
+ startupArguments: ext.runtimeConfiguration.startupArguments.slice(0, 100)
1356
+ };
1357
+ }
1358
+ if (ext.operationalResilience) {
1359
+ const ops = ext.operationalResilience;
1360
+ ext.operationalResilience = {
1361
+ implicitTimeouts: ops.implicitTimeouts.slice(0, 30),
1362
+ defaultPaginationSize: ops.defaultPaginationSize.slice(0, 30),
1363
+ implicitRetryLogic: ops.implicitRetryLogic.slice(0, 30),
1364
+ defaultLocale: ops.defaultLocale.slice(0, 20),
1365
+ defaultCurrency: ops.defaultCurrency.slice(0, 20),
1366
+ implicitTimezone: ops.implicitTimezone.slice(0, 20),
1367
+ defaultCharacterEncoding: ops.defaultCharacterEncoding.slice(0, 20),
1368
+ sessionStores: ops.sessionStores.slice(0, 20),
1369
+ distributedLocks: ops.distributedLocks.slice(0, 20),
1370
+ jobSchedulers: ops.jobSchedulers.slice(0, 30),
1371
+ idempotencyKeys: ops.idempotencyKeys.slice(0, 20),
1372
+ rateLimitingCounters: ops.rateLimitingCounters.slice(0, 20),
1373
+ circuitBreakerState: ops.circuitBreakerState.slice(0, 20),
1374
+ abTestToggles: ops.abTestToggles.slice(0, 20),
1375
+ regionalEnablementRules: ops.regionalEnablementRules.slice(0, 20),
1376
+ betaAccessGroups: ops.betaAccessGroups.slice(0, 20),
1377
+ licensingEnforcementLogic: ops.licensingEnforcementLogic.slice(0, 20),
1378
+ killSwitches: ops.killSwitches.slice(0, 20),
1379
+ connectorRetryLogic: ops.connectorRetryLogic.slice(0, 20),
1380
+ apiPollingIntervals: ops.apiPollingIntervals.slice(0, 20),
1381
+ fieldMappings: ops.fieldMappings.slice(0, 20),
1382
+ schemaRegistryRules: ops.schemaRegistryRules.slice(0, 20),
1383
+ deadLetterQueueBehavior: ops.deadLetterQueueBehavior.slice(0, 20),
1384
+ dataMaskingRules: ops.dataMaskingRules.slice(0, 20),
1385
+ transformationLogic: ops.transformationLogic.slice(0, 20),
1386
+ timezoneHandling: ops.timezoneHandling.slice(0, 20),
1387
+ encryptionSettings: ops.encryptionSettings.slice(0, 30),
1388
+ hardcodedSecretSignals: ops.hardcodedSecretSignals.slice(0, 20)
1389
+ };
1390
+ }
1391
+ if (ext.dependencyGraph) {
1392
+ ext.dependencyGraph = {
1393
+ ...ext.dependencyGraph,
1394
+ phantomDependencies: ext.dependencyGraph.phantomDependencies.slice(0, MAX_ITEMS),
1395
+ phantomDependencyDetails: ext.dependencyGraph.phantomDependencyDetails?.slice(0, MAX_ITEMS),
1396
+ duplicatedPackages: ext.dependencyGraph.duplicatedPackages.slice(0, MAX_ITEMS)
1397
+ };
1398
+ }
1399
+ compacted.extended = ext;
1400
+ }
1401
+ return compacted;
1402
+ }
1403
+ async function compressArtifact(artifact) {
1404
+ const json = JSON.stringify(artifact);
1405
+ return gzip2(json, { level: 9 });
1406
+ }
1407
+ async function prepareCompressedUpload(artifact) {
1408
+ const compacted = prepareArtifactForUpload(artifact);
1409
+ const compressed = await compressArtifact(compacted);
1410
+ return { body: compressed, contentEncoding: "gzip" };
1411
+ }
1412
+
1413
+ // src/commands/push.ts
1172
1414
  function parseDsn(dsn) {
1173
1415
  const cleaned = dsn.replace(/[\x00-\x1F\x7F\uFEFF\u200B-\u200D\u2060]/g, "").trim();
1174
1416
  const match = cleaned.match(/^vibgrate\+(https?):?\/\/([^:]+):([^@]+)@([^/]+)\/(.+)$/);
@@ -1203,7 +1445,8 @@ var pushCommand = new Command2("push").description("Push scan results to Vibgrat
1203
1445
  if (opts.strict) process.exit(1);
1204
1446
  return;
1205
1447
  }
1206
- const body = await readTextFile(filePath);
1448
+ const artifact = await readJsonFile(filePath);
1449
+ const { body, contentEncoding } = await prepareCompressedUpload(artifact);
1207
1450
  const timestamp = String(Date.now());
1208
1451
  let host = parsed.host;
1209
1452
  if (opts.region) {
@@ -1216,12 +1459,16 @@ var pushCommand = new Command2("push").description("Push scan results to Vibgrat
1216
1459
  }
1217
1460
  }
1218
1461
  const url = `${parsed.scheme}://${host}/v1/ingest/scan`;
1219
- console.log(chalk3.dim(`Uploading to ${host}...`));
1462
+ const originalSize = JSON.stringify(artifact).length;
1463
+ const compressedSize = body.length;
1464
+ const ratio = ((1 - compressedSize / originalSize) * 100).toFixed(0);
1465
+ console.log(chalk3.dim(`Uploading to ${host}... (${(compressedSize / 1024).toFixed(0)} KB, ${ratio}% smaller)`));
1220
1466
  try {
1221
1467
  const response = await fetch(url, {
1222
1468
  method: "POST",
1223
1469
  headers: {
1224
1470
  "Content-Type": "application/json",
1471
+ "Content-Encoding": contentEncoding,
1225
1472
  "X-Vibgrate-Timestamp": timestamp,
1226
1473
  "Authorization": `VibgrateDSN ${parsed.keyId}:${parsed.secret}`,
1227
1474
  "Connection": "close"
@@ -7467,6 +7714,17 @@ function extract(content, pattern, sourceFile) {
7467
7714
  }
7468
7715
  return out;
7469
7716
  }
7717
+ var LOCKFILE_NAMES = /* @__PURE__ */ new Set([
7718
+ "package-lock.json",
7719
+ "pnpm-lock.yaml",
7720
+ "yarn.lock",
7721
+ "composer.lock",
7722
+ "Gemfile.lock",
7723
+ "Cargo.lock",
7724
+ "poetry.lock",
7725
+ "Pipfile.lock",
7726
+ "packages.lock.json"
7727
+ ]);
7470
7728
  function detectDbBrand(raw) {
7471
7729
  const value = raw.toLowerCase();
7472
7730
  if (value.includes("postgres")) return { kind: "sql", brand: "PostgreSQL", version: null, evidence: raw };
@@ -7477,6 +7735,7 @@ function detectDbBrand(raw) {
7477
7735
  if (value.includes("mongodb")) return { kind: "nosql", brand: "MongoDB", version: null, evidence: raw };
7478
7736
  if (value.includes("redis")) return { kind: "nosql", brand: "Redis", version: null, evidence: raw };
7479
7737
  if (value.includes("cassandra")) return { kind: "nosql", brand: "Cassandra", version: null, evidence: raw };
7738
+ if (value.includes("cosmosdb") || value.includes("@azure/cosmos") || value.includes("cosmos")) return { kind: "nosql", brand: "CosmosDB", version: null, evidence: raw };
7480
7739
  if (value.includes("dynamodb")) return { kind: "nosql", brand: "DynamoDB", version: null, evidence: raw };
7481
7740
  if (value.includes("neo4j")) return { kind: "nosql", brand: "Neo4j", version: null, evidence: raw };
7482
7741
  return null;
@@ -7558,13 +7817,16 @@ async function scanDataStores(rootDir, fileCache) {
7558
7817
  for (const file of files) {
7559
7818
  const connStrings = extract(file.content, /\b(?:postgres(?:ql)?:\/\/[^\s'"`]+|mysql:\/\/[^\s'"`]+|mongodb(?:\+srv)?:\/\/[^\s'"`]+|redis:\/\/[^\s'"`]+)\b/gi, file.relPath);
7560
7819
  result.connectionStrings.push(...connStrings);
7561
- const dbEvidence = [
7562
- ...extract(file.content, /\b(?:postgres|postgresql|mysql|mariadb|mssql|sqlserver|oracle|sqlite|mongodb|redis|cassandra|dynamodb|neo4j)\b/gi, file.relPath),
7563
- ...connStrings
7564
- ];
7565
- for (const evidence of dbEvidence) {
7566
- const detected = detectDbBrand(evidence);
7567
- if (detected) dbTechnologies.push(detected);
7820
+ const isLockFile = LOCKFILE_NAMES.has(path23.basename(file.relPath));
7821
+ if (!isLockFile) {
7822
+ const dbEvidence = [
7823
+ ...extract(file.content, /\b(?:postgres|postgresql|mysql|mariadb|mssql|sqlserver|oracle|sqlite|mongodb|redis|cassandra|cosmosdb|cosmos|dynamodb|neo4j)\b/gi, file.relPath),
7824
+ ...connStrings
7825
+ ];
7826
+ for (const evidence of dbEvidence) {
7827
+ const detected = detectDbBrand(evidence);
7828
+ if (detected) dbTechnologies.push(detected);
7829
+ }
7568
7830
  }
7569
7831
  result.connectionPoolSettings.push(...extract(file.content, /\b(?:pool(?:Size|_size)?|maxPoolSize|minPoolSize|connectionLimit)\b[^\n]*/gi, file.relPath));
7570
7832
  result.replicationSettings.push(...extract(file.content, /\b(?:replication|cluster|replicaSet)\b[^\n]*/gi, file.relPath));
@@ -7581,7 +7843,10 @@ async function scanDataStores(rootDir, fileCache) {
7581
7843
  result.otherServices.push(...extract(file.content, /\b(?:redis:\/\/[^\s'"`]+|amqp:\/\/[^\s'"`]+|kafka:\/\/[^\s'"`]+|elasticsearch:\/\/[^\s'"`]+)\b/gi, file.relPath));
7582
7844
  }
7583
7845
  const dedupDb = /* @__PURE__ */ new Map();
7584
- for (const db of dbTechnologies) dedupDb.set(`${db.kind}:${db.brand}:${db.evidence}`, db);
7846
+ for (const db of dbTechnologies) {
7847
+ const key = `${db.kind}:${db.brand}`;
7848
+ if (!dedupDb.has(key)) dedupDb.set(key, db);
7849
+ }
7585
7850
  result.databaseTechnologies = [...dedupDb.values()].sort((a, b) => a.brand.localeCompare(b.brand));
7586
7851
  result.connectionStrings = uniq(result.connectionStrings);
7587
7852
  result.connectionPoolSettings = uniq(result.connectionPoolSettings);
@@ -7611,6 +7876,17 @@ function detectOpenApiSpecification(relPath, content) {
7611
7876
  const endpointCount = content.match(/(^|\n)\s*\/[^\n:]+:\s*(get|post|put|patch|delete|options|head)/gim)?.length ?? content.match(/"\/[^"]+"\s*:\s*\{/g)?.length ?? null;
7612
7877
  return { path: relPath, format, version, title, endpointCount };
7613
7878
  }
7879
+ function isInternalHost(hostname) {
7880
+ const h = hostname.toLowerCase();
7881
+ if (h === "localhost" || h === "127.0.0.1" || h === "::1" || h === "0.0.0.0") return true;
7882
+ if (/^192\.168\./.test(h) || /^10\./.test(h) || /^172\.(1[6-9]|2\d|3[01])\./.test(h)) return true;
7883
+ if (!h.includes(".")) return true;
7884
+ if (/^(example|test|localhost|local|internal)\b/.test(h)) return true;
7885
+ return false;
7886
+ }
7887
+ function normalizeUrl(raw) {
7888
+ return raw.replace(/[\u0000-\u001F\u007F-\u009F\u200B-\u200F\u202A-\u202E\u2066-\u2069\uFEFF]/g, "").trim();
7889
+ }
7614
7890
  async function scanApiSurface(rootDir, fileCache) {
7615
7891
  const files = await scanTextFiles(rootDir, fileCache);
7616
7892
  const integrations = [];
@@ -7630,18 +7906,29 @@ async function scanApiSurface(rootDir, fileCache) {
7630
7906
  for (const file of files) {
7631
7907
  const openApiSpec = detectOpenApiSpecification(file.relPath, file.content);
7632
7908
  if (openApiSpec) result.openApiSpecifications.push(openApiSpec);
7633
- const endpoints = extract(file.content, /\bhttps?:\/\/[^\s'"`]+/gi, file.relPath);
7634
- for (const endpoint of endpoints) {
7635
- const provider = endpoint.replace(/^https?:\/\//, "").split("/")[0] ?? "unknown";
7636
- const versionMatch = endpoint.match(/\/(v\d+(?:\.\d+)?)\b/i);
7637
- const params = endpoint.match(/[?&]([a-zA-Z0-9_.-]+)=/g)?.map((p) => p.replace(/[?&=]/g, "")) ?? [];
7909
+ const urlRegex = /\bhttps?:\/\/[^\s'"`]+/gi;
7910
+ let match;
7911
+ while ((match = urlRegex.exec(file.content)) !== null) {
7912
+ const rawUrl = match[0].replace(/[,;)\]}"'`]+$/, "");
7913
+ const cleanUrl = normalizeUrl(rawUrl);
7914
+ if (!cleanUrl.startsWith("http")) continue;
7915
+ let hostname;
7916
+ try {
7917
+ hostname = new URL(cleanUrl).hostname;
7918
+ } catch {
7919
+ continue;
7920
+ }
7921
+ if (isInternalHost(hostname)) continue;
7922
+ const versionMatch = cleanUrl.match(/\/(v\d+(?:\.\d+)?)\b/i);
7923
+ const params = cleanUrl.match(/[?&]([a-zA-Z0-9_.-]+)=/g)?.map((p) => p.replace(/[?&=]/g, "")) ?? [];
7638
7924
  integrations.push({
7639
- provider,
7640
- endpoint,
7925
+ provider: hostname,
7926
+ endpoint: cleanUrl,
7641
7927
  version: versionMatch ? versionMatch[1] : null,
7642
7928
  parameters: params,
7643
7929
  configOptions: extract(file.content, /\b(?:baseUrl|apiUrl|endpoint|timeout|retries|apiVersion)\b[^\n]*/gi, file.relPath),
7644
- authHints: extract(file.content, /\b(?:api[_-]?key|bearer\s+[a-z0-9_.-]+|client[_-]?secret|oauth)\b[^\n]*/gi, file.relPath)
7930
+ authHints: extract(file.content, /\b(?:api[_-]?key|bearer\s+[a-z0-9_.-]+|client[_-]?secret|oauth)\b[^\n]*/gi, file.relPath),
7931
+ files: [file.relPath]
7645
7932
  });
7646
7933
  }
7647
7934
  result.webhookUrls.push(...extract(file.content, /\bhttps?:\/\/[^\s'"`]*(?:webhook|hooks?)[^\s'"`]*/gi, file.relPath));
@@ -7651,18 +7938,27 @@ async function scanApiSurface(rootDir, fileCache) {
7651
7938
  result.rateLimitOverrides.push(...extract(file.content, /\b(?:rate[_-]?limit|max[_-]?requests|throttle)\b[^\n]*/gi, file.relPath));
7652
7939
  result.customHeaders.push(...extract(file.content, /\b(?:x-[a-z0-9-]+|authorization|user-agent)\b[^\n]*/gi, file.relPath));
7653
7940
  result.corsPolicies.push(...extract(file.content, /\b(?:cors|access-control-allow-origin|allowedOrigins)\b[^\n]*/gi, file.relPath));
7654
- result.oauthScopes.push(...extract(file.content, /\b(?:scope|scopes)\b[^\n]*(?:openid|profile|email|read|write)/gi, file.relPath));
7941
+ result.oauthScopes.push(...extract(file.content, /\bscopes?\s*[=:]\s*['"][^'"]*(?:openid|profile|email|offline_access|read(?::\w+)?|write(?::\w+)?)[^'"]*['"]/gi, file.relPath));
7655
7942
  result.apiTokens.push(...extract(file.content, /\b(?:api[_-]?token|access[_-]?token|bearer[_-]?token)\b[^\n]*/gi, file.relPath));
7656
7943
  }
7657
7944
  const integrationMap = /* @__PURE__ */ new Map();
7658
7945
  for (const integration of integrations) {
7659
- const key = `${integration.provider}:${integration.endpoint}`;
7660
- integrationMap.set(key, {
7661
- ...integration,
7662
- parameters: uniq(integration.parameters),
7663
- configOptions: uniq(integration.configOptions),
7664
- authHints: uniq(integration.authHints)
7665
- });
7946
+ const key = integration.endpoint;
7947
+ const existing = integrationMap.get(key);
7948
+ if (existing) {
7949
+ existing.files = uniq([...existing.files, ...integration.files]);
7950
+ existing.parameters = uniq([...existing.parameters, ...integration.parameters]);
7951
+ existing.configOptions = uniq([...existing.configOptions, ...integration.configOptions]);
7952
+ existing.authHints = uniq([...existing.authHints, ...integration.authHints]);
7953
+ } else {
7954
+ integrationMap.set(key, {
7955
+ ...integration,
7956
+ parameters: uniq(integration.parameters),
7957
+ configOptions: uniq(integration.configOptions),
7958
+ authHints: uniq(integration.authHints),
7959
+ files: [...integration.files]
7960
+ });
7961
+ }
7666
7962
  }
7667
7963
  result.integrations = [...integrationMap.values()].sort((a, b) => a.provider.localeCompare(b.provider));
7668
7964
  result.openApiSpecifications = [...new Map(result.openApiSpecifications.map((spec) => [spec.path, spec])).values()].sort((a, b) => a.path.localeCompare(b.path));
@@ -7677,8 +7973,27 @@ async function scanApiSurface(rootDir, fileCache) {
7677
7973
  result.apiTokens = uniq(result.apiTokens);
7678
7974
  return result;
7679
7975
  }
7976
+ var NON_CODE_EXTENSIONS = /* @__PURE__ */ new Set([
7977
+ ".md",
7978
+ ".mdx",
7979
+ ".markdown",
7980
+ ".txt",
7981
+ ".rst",
7982
+ ".adoc",
7983
+ ".asciidoc",
7984
+ ".html",
7985
+ ".htm",
7986
+ ".pdf",
7987
+ ".docx",
7988
+ ".doc",
7989
+ ".rtf"
7990
+ ]);
7680
7991
  async function scanOperationalResilience(rootDir, fileCache) {
7681
- const files = await scanTextFiles(rootDir, fileCache);
7992
+ const allFiles = await scanTextFiles(rootDir, fileCache);
7993
+ const files = allFiles.filter((f) => {
7994
+ const ext = path23.extname(f.relPath).toLowerCase();
7995
+ return !NON_CODE_EXTENSIONS.has(ext);
7996
+ });
7682
7997
  const result = {
7683
7998
  implicitTimeouts: [],
7684
7999
  defaultPaginationSize: [],
@@ -7795,98 +8110,6 @@ async function scanOssGovernance(rootDir, fileCache) {
7795
8110
  };
7796
8111
  }
7797
8112
 
7798
- // src/utils/compact-evidence.ts
7799
- var CATEGORY_PATTERNS = [
7800
- { category: "pricing", pattern: /price|pricing|billing|subscri|trial|credit|plan|tier|upgrade|premium|pro|enterprise/i },
7801
- { category: "auth", pattern: /sign[- ]?in|sign[- ]?up|log[- ]?in|log[- ]?out|auth|sso|oauth|password|register|invite|onboard/i },
7802
- { category: "dashboard", pattern: /dashboard|overview|home|main|summary|stats/i },
7803
- { category: "settings", pattern: /setting|config|preference|option|profile|account/i },
7804
- { category: "users", pattern: /user|member|team|role|permission|access|admin|owner/i },
7805
- { category: "integrations", pattern: /integrat|connect|webhook|api[- ]?key|sync|import|export/i },
7806
- { category: "reports", pattern: /report|analy|metric|chart|graph|insight|track/i },
7807
- { category: "workflows", pattern: /workflow|automat|schedule|trigger|action|job|task|pipeline/i },
7808
- { category: "projects", pattern: /project|workspace|organization|folder|repo/i },
7809
- { category: "navigation", pattern: /menu|nav|sidebar|header|footer|breadcrumb/i }
7810
- ];
7811
- function compactUiPurpose(result, maxSamplesPerCategory = 3) {
7812
- const evidence = result.topEvidence;
7813
- const dependencies = evidence.filter((e) => e.kind === "dependency").map((e) => e.value).slice(0, 10);
7814
- const routes = dedupeRoutes(
7815
- evidence.filter((e) => e.kind === "route").map((e) => e.value)
7816
- ).slice(0, 15);
7817
- const textEvidence = evidence.filter(
7818
- (e) => e.kind !== "dependency" && e.kind !== "route" && e.kind !== "feature_flag"
7819
- );
7820
- const byCategory = /* @__PURE__ */ new Map();
7821
- const categoryCounts = {};
7822
- for (const item of textEvidence) {
7823
- const category = categorize(item.value);
7824
- if (!byCategory.has(category)) {
7825
- byCategory.set(category, /* @__PURE__ */ new Set());
7826
- }
7827
- const normalized = normalizeValue(item.value);
7828
- if (normalized.length >= 3) {
7829
- byCategory.get(category).add(normalized);
7830
- }
7831
- }
7832
- const samples = [];
7833
- for (const [category, values] of byCategory) {
7834
- const deduped = dedupeStrings([...values]);
7835
- categoryCounts[category] = deduped.length;
7836
- for (const value of deduped.slice(0, maxSamplesPerCategory)) {
7837
- samples.push({ kind: "text", value, category });
7838
- }
7839
- }
7840
- const featureFlags = evidence.filter((e) => e.kind === "feature_flag");
7841
- if (featureFlags.length > 0) {
7842
- categoryCounts["feature_flags"] = featureFlags.length;
7843
- samples.push({ kind: "feature_flag", value: "feature flags detected", category: "feature_flags" });
7844
- }
7845
- return {
7846
- samples,
7847
- categoryCounts,
7848
- originalCount: evidence.length,
7849
- dependencies,
7850
- routes,
7851
- detectedFrameworks: result.detectedFrameworks
7852
- };
7853
- }
7854
- function categorize(value) {
7855
- for (const { category, pattern } of CATEGORY_PATTERNS) {
7856
- if (pattern.test(value)) return category;
7857
- }
7858
- return "general";
7859
- }
7860
- function normalizeValue(value) {
7861
- return value.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").replace(/\s+/g, " ").trim().slice(0, 60);
7862
- }
7863
- function dedupeStrings(values) {
7864
- const sorted = values.sort((a, b) => b.length - a.length);
7865
- const kept = [];
7866
- for (const value of sorted) {
7867
- const isDupe = kept.some((k) => {
7868
- const stem = value.slice(0, 6);
7869
- return k.startsWith(stem) || k.includes(value) || value.includes(k);
7870
- });
7871
- if (!isDupe) {
7872
- kept.push(value);
7873
- }
7874
- }
7875
- return kept;
7876
- }
7877
- function dedupeRoutes(routes) {
7878
- const seen = /* @__PURE__ */ new Set();
7879
- const result = [];
7880
- for (const route of routes) {
7881
- const normalized = route.replace(/:[a-z_]+/gi, ":param").replace(/\[\[*\.*\.*[a-z_]+\]*\]/gi, ":param").replace(/\/+$/, "").toLowerCase();
7882
- if (!seen.has(normalized)) {
7883
- seen.add(normalized);
7884
- result.push(route);
7885
- }
7886
- }
7887
- return result;
7888
- }
7889
-
7890
8113
  // src/utils/tool-installer.ts
7891
8114
  import { spawn as spawn5 } from "child_process";
7892
8115
  import chalk5 from "chalk";
@@ -8745,12 +8968,10 @@ async function runScan(rootDir, opts) {
8745
8968
  steps: progress.getStepTimings()
8746
8969
  });
8747
8970
  if (!opts.noLocalArtifacts && !maxPrivacyMode) {
8971
+ const projectScores = {};
8748
8972
  for (const project of allProjects) {
8749
8973
  if (project.drift && project.path) {
8750
- const projectDir = path24.resolve(rootDir, project.path);
8751
- const projectVibgrateDir = path24.join(projectDir, ".vibgrate");
8752
- await ensureDir(projectVibgrateDir);
8753
- await writeJsonFile(path24.join(projectVibgrateDir, "project_score.json"), {
8974
+ projectScores[project.path] = {
8754
8975
  projectId: project.projectId,
8755
8976
  name: project.name,
8756
8977
  type: project.type,
@@ -8763,9 +8984,14 @@ async function runScan(rootDir, opts) {
8763
8984
  vibgrateVersion: VERSION,
8764
8985
  solutionId: project.solutionId,
8765
8986
  solutionName: project.solutionName
8766
- });
8987
+ };
8767
8988
  }
8768
8989
  }
8990
+ if (Object.keys(projectScores).length > 0) {
8991
+ const vibgrateDir = path24.join(rootDir, ".vibgrate");
8992
+ await ensureDir(vibgrateDir);
8993
+ await writeJsonFile(path24.join(vibgrateDir, "project_scores.json"), projectScores);
8994
+ }
8769
8995
  }
8770
8996
  if (opts.format === "json") {
8771
8997
  const jsonStr = JSON.stringify(artifact, null, 2);
@@ -8836,7 +9062,7 @@ async function autoPush(artifact, rootDir, opts) {
8836
9062
  if (opts.strict) process.exit(1);
8837
9063
  return;
8838
9064
  }
8839
- const body = JSON.stringify(artifact);
9065
+ const { body, contentEncoding } = await prepareCompressedUpload(artifact);
8840
9066
  const timestamp = String(Date.now());
8841
9067
  let host = parsed.host;
8842
9068
  if (opts.region) {
@@ -8849,12 +9075,16 @@ async function autoPush(artifact, rootDir, opts) {
8849
9075
  }
8850
9076
  }
8851
9077
  const url = `${parsed.scheme}://${host}/v1/ingest/scan`;
8852
- console.log(chalk6.dim(`Uploading to ${host}...`));
9078
+ const originalSize = JSON.stringify(artifact).length;
9079
+ const compressedSize = body.length;
9080
+ const ratio = ((1 - compressedSize / originalSize) * 100).toFixed(0);
9081
+ console.log(chalk6.dim(`Uploading to ${host}... (${(compressedSize / 1024).toFixed(0)} KB, ${ratio}% smaller)`));
8853
9082
  try {
8854
9083
  const response = await fetch(url, {
8855
9084
  method: "POST",
8856
9085
  headers: {
8857
9086
  "Content-Type": "application/json",
9087
+ "Content-Encoding": contentEncoding,
8858
9088
  "X-Vibgrate-Timestamp": timestamp,
8859
9089
  "Authorization": `VibgrateDSN ${parsed.keyId}:${parsed.secret}`,
8860
9090
  "Connection": "close"
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  runScan
3
- } from "./chunk-XDZGC6KJ.js";
3
+ } from "./chunk-56UAFVE4.js";
4
4
  import {
5
5
  writeJsonFile
6
6
  } from "./chunk-TBE6NQ5Z.js";
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  baselineCommand
4
- } from "./chunk-VGZRUFB4.js";
4
+ } from "./chunk-NASOHLRL.js";
5
5
  import {
6
6
  VERSION,
7
7
  dsnCommand,
@@ -10,7 +10,7 @@ import {
10
10
  pushCommand,
11
11
  scanCommand,
12
12
  writeDefaultConfig
13
- } from "./chunk-XDZGC6KJ.js";
13
+ } from "./chunk-56UAFVE4.js";
14
14
  import {
15
15
  ensureDir,
16
16
  pathExists,
@@ -39,7 +39,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
39
39
  console.log(chalk.green("\u2714") + ` Created ${chalk.bold("vibgrate.config.ts")}`);
40
40
  }
41
41
  if (opts.baseline) {
42
- const { runBaseline } = await import("./baseline-WRLXEJGA.js");
42
+ const { runBaseline } = await import("./baseline-YGEO4GK7.js");
43
43
  await runBaseline(rootDir);
44
44
  }
45
45
  console.log("");
package/dist/index.d.ts CHANGED
@@ -541,6 +541,7 @@ interface ApiIntegration {
541
541
  parameters: string[];
542
542
  configOptions: string[];
543
543
  authHints: string[];
544
+ files: string[];
544
545
  }
545
546
  interface ApiSurfaceResult {
546
547
  integrations: ApiIntegration[];
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  formatText,
6
6
  generateFindings,
7
7
  runScan
8
- } from "./chunk-XDZGC6KJ.js";
8
+ } from "./chunk-56UAFVE4.js";
9
9
  import "./chunk-TBE6NQ5Z.js";
10
10
  export {
11
11
  computeDriftScore,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibgrate/cli",
3
- "version": "1.0.65",
3
+ "version": "1.0.67",
4
4
  "description": "CLI for measuring upgrade drift across Node, .NET, Python & Java projects",
5
5
  "type": "module",
6
6
  "bin": {