@vibedrift/cli 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -195,6 +195,7 @@ var init_version = __esm({
195
195
  });
196
196
 
197
197
  // src/codedna/semantic-fingerprint.ts
198
+ import { createHash as createHash2 } from "crypto";
198
199
  function normalizeBody(body, language) {
199
200
  let normalized = body;
200
201
  normalized = normalized.replace(/\/\/.*$/gm, "");
@@ -262,7 +263,11 @@ function computeSemanticFingerprints(functions) {
262
263
  normalizedHash: fnv1aHash(normalizeBody(fn.rawBody, fn.language))
263
264
  }));
264
265
  }
265
- function findDuplicateGroups(fingerprints) {
266
+ function findDuplicateGroups(fingerprints, functions) {
267
+ const fnLookup = /* @__PURE__ */ new Map();
268
+ for (const fn of functions) {
269
+ fnLookup.set(`${fn.name}:${fn.file}`, fn);
270
+ }
266
271
  const byHash = /* @__PURE__ */ new Map();
267
272
  for (const fp of fingerprints) {
268
273
  if (!byHash.has(fp.normalizedHash)) byHash.set(fp.normalizedHash, []);
@@ -272,13 +277,25 @@ function findDuplicateGroups(fingerprints) {
272
277
  let groupCounter = 0;
273
278
  for (const [hash, fps] of byHash) {
274
279
  if (fps.length < 2) continue;
275
- const uniqueFiles = new Set(fps.map((fp) => fp.functionRef.file));
276
- if (uniqueFiles.size < 2) continue;
277
- groups.push({
278
- groupId: `fingerprint-${groupCounter++}`,
279
- hash,
280
- functions: fps.map((fp) => fp.functionRef)
281
- });
280
+ const bySha = /* @__PURE__ */ new Map();
281
+ for (const fp of fps) {
282
+ const key = `${fp.functionRef.name}:${fp.functionRef.file}`;
283
+ const fn = fnLookup.get(key);
284
+ if (!fn) continue;
285
+ const sha = createHash2("sha256").update(normalizeBody(fn.rawBody, fn.language)).digest("hex");
286
+ if (!bySha.has(sha)) bySha.set(sha, []);
287
+ bySha.get(sha).push(fp);
288
+ }
289
+ for (const [, shaGroup] of bySha) {
290
+ if (shaGroup.length < 2) continue;
291
+ const uniqueFiles = new Set(shaGroup.map((fp) => fp.functionRef.file));
292
+ if (uniqueFiles.size < 2) continue;
293
+ groups.push({
294
+ groupId: `fingerprint-${groupCounter++}`,
295
+ hash,
296
+ functions: shaGroup.map((fp) => fp.functionRef)
297
+ });
298
+ }
282
299
  }
283
300
  return groups;
284
301
  }
@@ -923,7 +940,7 @@ function runCodeDnaAnalysis(ctx) {
923
940
  timings.extractionMs = Date.now() - t;
924
941
  t = Date.now();
925
942
  const fingerprints = computeSemanticFingerprints(functions);
926
- const duplicateGroups = findDuplicateGroups(fingerprints);
943
+ const duplicateGroups = findDuplicateGroups(fingerprints, functions);
927
944
  timings.fingerprintMs = Date.now() - t;
928
945
  t = Date.now();
929
946
  const sequences = extractOperationSequences(functions);
@@ -1206,9 +1223,9 @@ __export(project_name_exports, {
1206
1223
  });
1207
1224
  import { basename, join as join6 } from "path";
1208
1225
  import { readFile as readFile5 } from "fs/promises";
1209
- import { createHash as createHash2 } from "crypto";
1226
+ import { createHash as createHash3 } from "crypto";
1210
1227
  async function detectProjectIdentity(rootDir, override) {
1211
- const hash = createHash2("sha256").update(rootDir).digest("hex");
1228
+ const hash = createHash3("sha256").update(rootDir).digest("hex");
1212
1229
  if (override && override.trim()) {
1213
1230
  return { name: override.trim(), hash };
1214
1231
  }
@@ -2682,7 +2699,7 @@ var namingAnalyzer = {
2682
2699
  init_esm_shims();
2683
2700
  var ESM_PATTERN = /\b(?:import\s+|export\s+(?:default\s+)?(?:function|class|const|let|var|{))/;
2684
2701
  var CJS_PATTERN = /\b(?:require\s*\(|module\.exports|exports\.)/;
2685
- var CONFIG_FILE_PATTERN = /(?:\.config\.|\.setup\.|\.rc\.|jest\.|babel\.|webpack\.|rollup\.|vite\.)/;
2702
+ var CONFIG_FILE_PATTERN = /(?:\.config\.|\.setup\.|\.rc\.|jest\.|babel\.|webpack\.|rollup\.|vite\.|next\.config|tailwind\.config|postcss\.config|tsconfig|eslint\.config|prettier\.config|svelte\.config|nuxt\.config|astro\.config|vitest\.config|tsup\.config|esbuild\.config|turbo\.json|\.cjs$|\.mjs$)/;
2686
2703
  var importsAnalyzer = {
2687
2704
  id: "imports",
2688
2705
  name: "Import Patterns",
@@ -2743,8 +2760,6 @@ function densityPer1K(count, totalLines) {
2743
2760
 
2744
2761
  // src/analyzers/error-handling.ts
2745
2762
  var EMPTY_CATCH_PATTERN = /catch\s*\([^)]*\)\s*\{\s*\}/g;
2746
- var TRY_CATCH_PATTERN = /\btry\s*\{/g;
2747
- var ASYNC_PATTERN = /\basync\s+(?:function|\(|[a-zA-Z])/g;
2748
2763
  var errorHandlingAnalyzer = {
2749
2764
  id: "error-handling",
2750
2765
  name: "Error Handling",
@@ -2781,22 +2796,43 @@ var errorHandlingAnalyzer = {
2781
2796
  tags: ["error-handling", "empty-catch"]
2782
2797
  });
2783
2798
  }
2784
- const dirStats = /* @__PURE__ */ new Map();
2799
+ const ASYNC_FN_PATTERN = /async\s+(?:function\s+\w+|\(\w*\)|\w+)\s*\([^)]*\)\s*(?::\s*[^{]*)?\s*\{/g;
2800
+ const ERROR_HANDLING_PATTERNS = [
2801
+ /\btry\s*\{/,
2802
+ /\.catch\s*\(/,
2803
+ /\bcatch\s*\(/,
2804
+ /\b(?:Result|Either)\s*[<(]/
2805
+ ];
2806
+ const dirUnhandled = /* @__PURE__ */ new Map();
2785
2807
  for (const file of jsFiles) {
2786
2808
  const dir = file.relativePath.includes("/") ? file.relativePath.slice(0, file.relativePath.lastIndexOf("/")) : ".";
2787
- if (!dirStats.has(dir)) dirStats.set(dir, { tryCatch: 0, asyncFns: 0 });
2788
- const stats = dirStats.get(dir);
2789
- stats.tryCatch += (file.content.match(TRY_CATCH_PATTERN) ?? []).length;
2790
- stats.asyncFns += (file.content.match(ASYNC_PATTERN) ?? []).length;
2791
- }
2792
- for (const [dir, stats] of dirStats) {
2793
- const unhandled = stats.asyncFns - stats.tryCatch;
2794
- if (unhandled > 3) {
2809
+ const asyncRegex = new RegExp(ASYNC_FN_PATTERN.source, "g");
2810
+ let fnMatch;
2811
+ while ((fnMatch = asyncRegex.exec(file.content)) !== null) {
2812
+ const openBrace = file.content.indexOf("{", fnMatch.index + fnMatch[0].length - 1);
2813
+ if (openBrace === -1) continue;
2814
+ let depth = 1;
2815
+ let pos = openBrace + 1;
2816
+ while (pos < file.content.length && depth > 0) {
2817
+ if (file.content[pos] === "{") depth++;
2818
+ else if (file.content[pos] === "}") depth--;
2819
+ pos++;
2820
+ }
2821
+ const body = file.content.slice(openBrace + 1, pos - 1);
2822
+ if (!/\bawait\b/.test(body)) continue;
2823
+ const hasHandling = ERROR_HANDLING_PATTERNS.some((p) => p.test(body));
2824
+ if (!hasHandling) {
2825
+ dirUnhandled.set(dir, (dirUnhandled.get(dir) ?? 0) + 1);
2826
+ }
2827
+ }
2828
+ }
2829
+ for (const [dir, count] of dirUnhandled) {
2830
+ if (count > 3) {
2795
2831
  findings.push({
2796
2832
  analyzerId: "error-handling",
2797
2833
  severity: "info",
2798
2834
  confidence: 0.6,
2799
- message: `${unhandled} async functions without try/catch in ${dir}/`,
2835
+ message: `${count} async functions without error handling in ${dir}/`,
2800
2836
  locations: [{ file: dir }],
2801
2837
  tags: ["error-handling", "unhandled-async"]
2802
2838
  });
@@ -3418,6 +3454,16 @@ var duplicatesAnalyzer = {
3418
3454
  }
3419
3455
  }
3420
3456
  }
3457
+ if (allSequences.length > 200) {
3458
+ findings.push({
3459
+ analyzerId: "duplicates",
3460
+ severity: "info",
3461
+ confidence: 0.3,
3462
+ message: `Cross-function duplicate detection limited to hash-based matching (${allSequences.length} functions exceeds the 200-function threshold for full comparison). Use --include to narrow scope for deeper analysis.`,
3463
+ locations: [],
3464
+ tags: ["duplicates", "scalability"]
3465
+ });
3466
+ }
3421
3467
  if (allSequences.length <= 200) {
3422
3468
  for (let i = 0; i < allSequences.length; i++) {
3423
3469
  for (let j = i + 1; j < allSequences.length; j++) {
@@ -3768,7 +3814,9 @@ var SECURITY_PATTERNS = [
3768
3814
  message: "Math.random() is not cryptographically secure \u2014 use crypto.randomUUID()",
3769
3815
  languages: ["javascript", "typescript"],
3770
3816
  tags: ["security", "crypto"],
3771
- negativeFilter: /(?:test|mock|seed|shuffle|animation|color|position|offset|delay|jitter)/i
3817
+ negativeFilter: /(?:test|mock|seed|shuffle|animation|color|position|offset|delay|jitter)/i,
3818
+ // Only flag near security-relevant code — UI shuffles/animations are not a risk
3819
+ contextRequired: /(?:token|secret|password|key|nonce|salt|hash|crypto|auth|session|jwt|api.?key|credential)/i
3772
3820
  },
3773
3821
  // === Path Traversal ===
3774
3822
  {
@@ -3860,6 +3908,14 @@ var securityAnalyzer = {
3860
3908
  const line = file.content.slice(lineStart2, lineEnd2 === -1 ? void 0 : lineEnd2);
3861
3909
  if (pattern.negativeFilter.test(line)) continue;
3862
3910
  }
3911
+ if (pattern.contextRequired) {
3912
+ const lines = file.content.split("\n");
3913
+ const matchLine = file.content.slice(0, match.index).split("\n").length - 1;
3914
+ const start = Math.max(0, matchLine - 5);
3915
+ const end = Math.min(lines.length, matchLine + 6);
3916
+ const context = lines.slice(start, end).join("\n");
3917
+ if (!pattern.contextRequired.test(context)) continue;
3918
+ }
3863
3919
  const lineNum = getLineNumber(file.content, match.index);
3864
3920
  const lineStart = file.content.lastIndexOf("\n", match.index) + 1;
3865
3921
  const lineEnd = file.content.indexOf("\n", match.index);
@@ -3974,7 +4030,11 @@ function computeComplexityAST(node, language) {
3974
4030
  walk(node);
3975
4031
  return complexity;
3976
4032
  }
4033
+ function stripComments(code) {
4034
+ return code.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "").replace(/#.*$/gm, "");
4035
+ }
3977
4036
  function computeComplexityRegex(content) {
4037
+ const stripped = stripComments(content);
3978
4038
  let complexity = 1;
3979
4039
  const patterns = [
3980
4040
  /\bif\s*\(/g,
@@ -3997,7 +4057,7 @@ function computeComplexityRegex(content) {
3997
4057
  ];
3998
4058
  for (const p of patterns) {
3999
4059
  const regex = new RegExp(p.source, p.flags);
4000
- const matches = content.match(regex);
4060
+ const matches = stripped.match(regex);
4001
4061
  if (matches) complexity += matches.length;
4002
4062
  }
4003
4063
  return complexity;
@@ -5397,7 +5457,7 @@ function analyzeSecurityProperty(routes, propertyName, getter, excludePaths) {
5397
5457
  const withProperty = applicableRoutes.filter(getter);
5398
5458
  const withoutProperty = applicableRoutes.filter((r) => !getter(r));
5399
5459
  const ratio = withProperty.length / applicableRoutes.length;
5400
- if (ratio <= 0.6 || withoutProperty.length === 0) return null;
5460
+ if (ratio <= 0.75 || withoutProperty.length === 0) return null;
5401
5461
  return {
5402
5462
  detector: "security_posture",
5403
5463
  subCategory: propertyName,
@@ -6118,10 +6178,10 @@ function computeCategoryScore(findings, maxScore, totalLines, applicable, correl
6118
6178
  }
6119
6179
  rawWeight += base * confidence * fileWeight * corrWeight;
6120
6180
  }
6121
- const sizeFactor = totalLines > 100 ? Math.sqrt(totalLines / 1e3) : 1;
6181
+ const sizeFactor = totalLines > 500 ? Math.sqrt(totalLines / 1e3) : 1;
6122
6182
  const clampedSizeFactor = Math.max(0.5, Math.min(3, sizeFactor));
6123
6183
  const adjustedWeight = rawWeight / clampedSizeFactor;
6124
- const k = Math.LN2 / 10;
6184
+ const k = Math.LN2 / 15;
6125
6185
  const factor = Math.exp(-k * adjustedWeight);
6126
6186
  const score = Math.round(maxScore * factor * 10) / 10;
6127
6187
  return {
@@ -6172,7 +6232,7 @@ function computeScores(findings, totalLines, ctx, previousScores) {
6172
6232
  }
6173
6233
  const DRAG_CATEGORIES = ["architecturalConsistency", "redundancy"];
6174
6234
  const DRAG_THRESHOLD = 0.5;
6175
- const DRAG_MAX_PENALTY = 0.15;
6235
+ const DRAG_MAX_PENALTY = 0.1;
6176
6236
  for (const cat of DRAG_CATEGORIES) {
6177
6237
  const s = scores[cat];
6178
6238
  if (!s.applicable || s.maxScore === 0) continue;