@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 +90 -30
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
|
1226
|
+
import { createHash as createHash3 } from "crypto";
|
|
1210
1227
|
async function detectProjectIdentity(rootDir, override) {
|
|
1211
|
-
const hash =
|
|
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
|
|
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
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
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: `${
|
|
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 =
|
|
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.
|
|
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 >
|
|
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 /
|
|
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.
|
|
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;
|