@vibecheckai/cli 3.2.3 → 3.2.4
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/bin/.generated +25 -25
- package/bin/dev/run-v2-torture.js +30 -30
- package/bin/runners/ENHANCEMENT_GUIDE.md +121 -121
- package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -295
- package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +474 -0
- package/bin/runners/lib/agent-firewall/claims/extractor.js +117 -28
- package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +23 -14
- package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +72 -1
- package/bin/runners/lib/agent-firewall/interceptor/base.js +2 -2
- package/bin/runners/lib/agent-firewall/policy/default-policy.json +6 -0
- package/bin/runners/lib/agent-firewall/policy/engine.js +34 -3
- package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +29 -4
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +12 -0
- package/bin/runners/lib/agent-firewall/truthpack/loader.js +21 -0
- package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +118 -0
- package/bin/runners/lib/analyzers.js +606 -325
- package/bin/runners/lib/auth-truth.js +193 -193
- package/bin/runners/lib/backup.js +62 -62
- package/bin/runners/lib/billing.js +107 -107
- package/bin/runners/lib/claims.js +118 -118
- package/bin/runners/lib/cli-ui.js +540 -540
- package/bin/runners/lib/contracts/auth-contract.js +202 -202
- package/bin/runners/lib/contracts/env-contract.js +181 -181
- package/bin/runners/lib/contracts/external-contract.js +206 -206
- package/bin/runners/lib/contracts/guard.js +168 -168
- package/bin/runners/lib/contracts/index.js +89 -89
- package/bin/runners/lib/contracts/plan-validator.js +311 -311
- package/bin/runners/lib/contracts/route-contract.js +199 -199
- package/bin/runners/lib/contracts.js +804 -804
- package/bin/runners/lib/detect.js +89 -89
- package/bin/runners/lib/doctor/autofix.js +254 -254
- package/bin/runners/lib/doctor/index.js +37 -37
- package/bin/runners/lib/doctor/modules/dependencies.js +325 -325
- package/bin/runners/lib/doctor/modules/index.js +46 -46
- package/bin/runners/lib/doctor/modules/network.js +250 -250
- package/bin/runners/lib/doctor/modules/project.js +312 -312
- package/bin/runners/lib/doctor/modules/runtime.js +224 -224
- package/bin/runners/lib/doctor/modules/security.js +348 -348
- package/bin/runners/lib/doctor/modules/system.js +213 -213
- package/bin/runners/lib/doctor/modules/vibecheck.js +394 -394
- package/bin/runners/lib/doctor/reporter.js +262 -262
- package/bin/runners/lib/doctor/service.js +262 -262
- package/bin/runners/lib/doctor/types.js +113 -113
- package/bin/runners/lib/doctor/ui.js +263 -263
- package/bin/runners/lib/doctor-v2.js +608 -608
- package/bin/runners/lib/drift.js +425 -425
- package/bin/runners/lib/enforcement.js +72 -72
- package/bin/runners/lib/engines/accessibility-engine.js +190 -0
- package/bin/runners/lib/engines/api-consistency-engine.js +162 -0
- package/bin/runners/lib/engines/ast-cache.js +99 -0
- package/bin/runners/lib/engines/code-quality-engine.js +255 -0
- package/bin/runners/lib/engines/console-logs-engine.js +115 -0
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +268 -0
- package/bin/runners/lib/engines/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/deprecated-api-engine.js +226 -0
- package/bin/runners/lib/engines/empty-catch-engine.js +150 -0
- package/bin/runners/lib/engines/file-filter.js +131 -0
- package/bin/runners/lib/engines/hardcoded-secrets-engine.js +251 -0
- package/bin/runners/lib/engines/mock-data-engine.js +272 -0
- package/bin/runners/lib/engines/parallel-processor.js +71 -0
- package/bin/runners/lib/engines/performance-issues-engine.js +265 -0
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +243 -0
- package/bin/runners/lib/engines/todo-fixme-engine.js +115 -0
- package/bin/runners/lib/engines/type-aware-engine.js +152 -0
- package/bin/runners/lib/engines/unsafe-regex-engine.js +225 -0
- package/bin/runners/lib/engines/vibecheck-engines/README.md +53 -0
- package/bin/runners/lib/engines/vibecheck-engines/index.js +15 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +139 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
- package/bin/runners/lib/engines/vibecheck-engines/package.json +13 -0
- package/bin/runners/lib/enterprise-detect.js +603 -603
- package/bin/runners/lib/enterprise-init.js +942 -942
- package/bin/runners/lib/env-resolver.js +417 -417
- package/bin/runners/lib/env-template.js +66 -66
- package/bin/runners/lib/env.js +189 -189
- package/bin/runners/lib/extractors/client-calls.js +990 -990
- package/bin/runners/lib/extractors/fastify-route-dump.js +573 -573
- package/bin/runners/lib/extractors/fastify-routes.js +426 -426
- package/bin/runners/lib/extractors/index.js +363 -363
- package/bin/runners/lib/extractors/next-routes.js +524 -524
- package/bin/runners/lib/extractors/proof-graph.js +431 -431
- package/bin/runners/lib/extractors/route-matcher.js +451 -451
- package/bin/runners/lib/extractors/truthpack-v2.js +377 -377
- package/bin/runners/lib/extractors/ui-bindings.js +547 -547
- package/bin/runners/lib/findings-schema.js +281 -281
- package/bin/runners/lib/firewall-prompt.js +50 -50
- package/bin/runners/lib/global-flags.js +213 -213
- package/bin/runners/lib/graph/graph-builder.js +265 -265
- package/bin/runners/lib/graph/html-renderer.js +413 -413
- package/bin/runners/lib/graph/index.js +32 -32
- package/bin/runners/lib/graph/runtime-collector.js +215 -215
- package/bin/runners/lib/graph/static-extractor.js +518 -518
- package/bin/runners/lib/html-report.js +650 -650
- package/bin/runners/lib/interactive-menu.js +1496 -1496
- package/bin/runners/lib/llm.js +75 -75
- package/bin/runners/lib/meter.js +61 -61
- package/bin/runners/lib/missions/evidence.js +126 -126
- package/bin/runners/lib/patch.js +40 -40
- package/bin/runners/lib/permissions/auth-model.js +213 -213
- package/bin/runners/lib/permissions/idor-prover.js +205 -205
- package/bin/runners/lib/permissions/index.js +45 -45
- package/bin/runners/lib/permissions/matrix-builder.js +198 -198
- package/bin/runners/lib/pkgjson.js +28 -28
- package/bin/runners/lib/policy.js +295 -295
- package/bin/runners/lib/preflight.js +142 -142
- package/bin/runners/lib/reality/correlation-detectors.js +359 -359
- package/bin/runners/lib/reality/index.js +318 -318
- package/bin/runners/lib/reality/request-hashing.js +416 -416
- package/bin/runners/lib/reality/request-mapper.js +453 -453
- package/bin/runners/lib/reality/safety-rails.js +463 -463
- package/bin/runners/lib/reality/semantic-snapshot.js +408 -408
- package/bin/runners/lib/reality/toast-detector.js +393 -393
- package/bin/runners/lib/reality-findings.js +84 -84
- package/bin/runners/lib/receipts.js +179 -179
- package/bin/runners/lib/redact.js +29 -29
- package/bin/runners/lib/replay/capsule-manager.js +154 -154
- package/bin/runners/lib/replay/index.js +263 -263
- package/bin/runners/lib/replay/player.js +348 -348
- package/bin/runners/lib/replay/recorder.js +331 -331
- package/bin/runners/lib/report-output.js +187 -187
- package/bin/runners/lib/report.js +135 -135
- package/bin/runners/lib/route-detection.js +1140 -1140
- package/bin/runners/lib/sandbox/index.js +59 -59
- package/bin/runners/lib/sandbox/proof-chain.js +399 -399
- package/bin/runners/lib/sandbox/sandbox-runner.js +205 -205
- package/bin/runners/lib/sandbox/worktree.js +174 -174
- package/bin/runners/lib/scan-output.js +525 -190
- package/bin/runners/lib/schema-validator.js +350 -350
- package/bin/runners/lib/schemas/contracts.schema.json +160 -160
- package/bin/runners/lib/schemas/finding.schema.json +100 -100
- package/bin/runners/lib/schemas/mission-pack.schema.json +206 -206
- package/bin/runners/lib/schemas/proof-graph.schema.json +176 -176
- package/bin/runners/lib/schemas/reality-report.schema.json +162 -162
- package/bin/runners/lib/schemas/share-pack.schema.json +180 -180
- package/bin/runners/lib/schemas/ship-report.schema.json +117 -117
- package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -303
- package/bin/runners/lib/schemas/validator.js +438 -438
- package/bin/runners/lib/score-history.js +282 -282
- package/bin/runners/lib/share-pack.js +239 -239
- package/bin/runners/lib/snippets.js +67 -67
- package/bin/runners/lib/status-output.js +253 -253
- package/bin/runners/lib/terminal-ui.js +350 -350
- package/bin/runners/lib/upsell.js +510 -510
- package/bin/runners/lib/usage.js +153 -153
- package/bin/runners/lib/validate-patch.js +156 -156
- package/bin/runners/lib/verdict-engine.js +628 -628
- package/bin/runners/reality/engine.js +917 -917
- package/bin/runners/reality/flows.js +122 -122
- package/bin/runners/reality/report.js +378 -378
- package/bin/runners/reality/session.js +193 -193
- package/bin/runners/runGuard.js +168 -168
- package/bin/runners/runProof.zip +0 -0
- package/bin/runners/runProve.js +8 -0
- package/bin/runners/runReality.js +14 -0
- package/bin/runners/runScan.js +17 -1
- package/bin/runners/runTruth.js +15 -3
- package/mcp-server/tier-auth.js +4 -4
- package/mcp-server/tools/index.js +72 -72
- package/package.json +1 -1
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Quality Detection Engine
|
|
3
|
+
* Detects code smells, complexity issues, maintainability problems
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { getAST } = require("./ast-cache");
|
|
7
|
+
const traverse = require("@babel/traverse").default;
|
|
8
|
+
const t = require("@babel/types");
|
|
9
|
+
const { shouldExcludeFile } = require("./file-filter");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Calculate cyclomatic complexity of a function
|
|
13
|
+
*/
|
|
14
|
+
function calculateComplexity(node) {
|
|
15
|
+
let complexity = 1; // Base complexity
|
|
16
|
+
|
|
17
|
+
traverse(node, {
|
|
18
|
+
IfStatement: () => complexity++,
|
|
19
|
+
SwitchCase: () => complexity++,
|
|
20
|
+
ForStatement: () => complexity++,
|
|
21
|
+
ForInStatement: () => complexity++,
|
|
22
|
+
ForOfStatement: () => complexity++,
|
|
23
|
+
WhileStatement: () => complexity++,
|
|
24
|
+
DoWhileStatement: () => complexity++,
|
|
25
|
+
CatchClause: () => complexity++,
|
|
26
|
+
ConditionalExpression: () => complexity++,
|
|
27
|
+
LogicalExpression: (path) => {
|
|
28
|
+
if (path.node.operator === "&&" || path.node.operator === "||") {
|
|
29
|
+
complexity++;
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return complexity;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Analyze a file for code quality issues
|
|
39
|
+
*/
|
|
40
|
+
function analyzeCodeQuality(code, filePath) {
|
|
41
|
+
const findings = [];
|
|
42
|
+
|
|
43
|
+
// Skip excluded files
|
|
44
|
+
if (shouldExcludeFile(filePath)) return findings;
|
|
45
|
+
|
|
46
|
+
const ast = getAST(code, filePath);
|
|
47
|
+
if (!ast) return findings;
|
|
48
|
+
|
|
49
|
+
const lines = code.split("\n");
|
|
50
|
+
|
|
51
|
+
// Function complexity
|
|
52
|
+
traverse(ast, {
|
|
53
|
+
FunctionDeclaration(path) {
|
|
54
|
+
const node = path.node;
|
|
55
|
+
const complexity = calculateComplexity(node);
|
|
56
|
+
|
|
57
|
+
if (complexity > 15) {
|
|
58
|
+
const line = node.loc.start.line;
|
|
59
|
+
findings.push({
|
|
60
|
+
type: "high_complexity",
|
|
61
|
+
severity: "WARN",
|
|
62
|
+
category: "CodeQuality",
|
|
63
|
+
file: filePath,
|
|
64
|
+
line,
|
|
65
|
+
column: node.loc.start.column,
|
|
66
|
+
title: `High cyclomatic complexity: ${complexity}`,
|
|
67
|
+
message: `Function "${node.id?.name || "anonymous"}" has complexity ${complexity} - consider refactoring`,
|
|
68
|
+
codeSnippet: lines[line - 1]?.trim(),
|
|
69
|
+
confidence: "high",
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
ArrowFunctionExpression(path) {
|
|
74
|
+
const node = path.node;
|
|
75
|
+
const complexity = calculateComplexity(node);
|
|
76
|
+
|
|
77
|
+
if (complexity > 15) {
|
|
78
|
+
const line = node.loc.start.line;
|
|
79
|
+
findings.push({
|
|
80
|
+
type: "high_complexity",
|
|
81
|
+
severity: "WARN",
|
|
82
|
+
category: "CodeQuality",
|
|
83
|
+
file: filePath,
|
|
84
|
+
line,
|
|
85
|
+
column: node.loc.start.column,
|
|
86
|
+
title: `High cyclomatic complexity: ${complexity}`,
|
|
87
|
+
message: `Arrow function has complexity ${complexity} - consider refactoring`,
|
|
88
|
+
codeSnippet: lines[line - 1]?.trim(),
|
|
89
|
+
confidence: "high",
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Long functions
|
|
96
|
+
traverse(ast, {
|
|
97
|
+
FunctionDeclaration(path) {
|
|
98
|
+
const node = path.node;
|
|
99
|
+
const startLine = node.loc.start.line;
|
|
100
|
+
const endLine = node.loc.end.line;
|
|
101
|
+
const length = endLine - startLine;
|
|
102
|
+
|
|
103
|
+
if (length > 100) {
|
|
104
|
+
findings.push({
|
|
105
|
+
type: "long_function",
|
|
106
|
+
severity: "WARN",
|
|
107
|
+
category: "CodeQuality",
|
|
108
|
+
file: filePath,
|
|
109
|
+
line: startLine,
|
|
110
|
+
column: node.loc.start.column,
|
|
111
|
+
title: `Long function: ${length} lines`,
|
|
112
|
+
message: `Function "${node.id?.name || "anonymous"}" is ${length} lines - consider breaking into smaller functions`,
|
|
113
|
+
codeSnippet: lines[startLine - 1]?.trim(),
|
|
114
|
+
confidence: "med",
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Deeply nested code
|
|
121
|
+
let nestingDepth = 0;
|
|
122
|
+
let maxNesting = 0;
|
|
123
|
+
let maxNestingLine = 0;
|
|
124
|
+
|
|
125
|
+
traverse(ast, {
|
|
126
|
+
enter(path) {
|
|
127
|
+
if (t.isIfStatement(path.node) ||
|
|
128
|
+
t.isForStatement(path.node) ||
|
|
129
|
+
t.isWhileStatement(path.node) ||
|
|
130
|
+
t.isSwitchStatement(path.node) ||
|
|
131
|
+
t.isTryStatement(path.node)) {
|
|
132
|
+
nestingDepth++;
|
|
133
|
+
if (nestingDepth > maxNesting) {
|
|
134
|
+
maxNesting = nestingDepth;
|
|
135
|
+
maxNestingLine = path.node.loc.start.line;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
exit(path) {
|
|
140
|
+
if (t.isIfStatement(path.node) ||
|
|
141
|
+
t.isForStatement(path.node) ||
|
|
142
|
+
t.isWhileStatement(path.node) ||
|
|
143
|
+
t.isSwitchStatement(path.node) ||
|
|
144
|
+
t.isTryStatement(path.node)) {
|
|
145
|
+
nestingDepth--;
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
if (maxNesting > 5) {
|
|
151
|
+
findings.push({
|
|
152
|
+
type: "deep_nesting",
|
|
153
|
+
severity: "WARN",
|
|
154
|
+
category: "CodeQuality",
|
|
155
|
+
file: filePath,
|
|
156
|
+
line: maxNestingLine,
|
|
157
|
+
column: 0,
|
|
158
|
+
title: `Deep nesting: ${maxNesting} levels`,
|
|
159
|
+
message: `Code has ${maxNesting} levels of nesting - consider refactoring with early returns or extraction`,
|
|
160
|
+
codeSnippet: lines[maxNestingLine - 1]?.trim(),
|
|
161
|
+
confidence: "med",
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Duplicate code detection (simple string-based)
|
|
166
|
+
const functionBodies = new Map();
|
|
167
|
+
traverse(ast, {
|
|
168
|
+
FunctionDeclaration(path) {
|
|
169
|
+
const node = path.node;
|
|
170
|
+
if (node.body) {
|
|
171
|
+
const bodyCode = code.substring(node.body.start, node.body.end);
|
|
172
|
+
const normalized = bodyCode.replace(/\s+/g, " ").trim();
|
|
173
|
+
|
|
174
|
+
if (normalized.length > 50) { // Only check substantial functions
|
|
175
|
+
if (functionBodies.has(normalized)) {
|
|
176
|
+
const original = functionBodies.get(normalized);
|
|
177
|
+
findings.push({
|
|
178
|
+
type: "duplicate_code",
|
|
179
|
+
severity: "WARN",
|
|
180
|
+
category: "CodeQuality",
|
|
181
|
+
file: filePath,
|
|
182
|
+
line: node.loc.start.line,
|
|
183
|
+
column: node.loc.start.column,
|
|
184
|
+
title: "Potential duplicate code",
|
|
185
|
+
message: `Function "${node.id?.name || "anonymous"}" appears similar to function at line ${original}`,
|
|
186
|
+
codeSnippet: lines[node.loc.start.line - 1]?.trim(),
|
|
187
|
+
confidence: "low",
|
|
188
|
+
});
|
|
189
|
+
} else {
|
|
190
|
+
functionBodies.set(normalized, node.loc.start.line);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Magic numbers
|
|
198
|
+
traverse(ast, {
|
|
199
|
+
NumericLiteral(path) {
|
|
200
|
+
const node = path.node;
|
|
201
|
+
const value = node.value;
|
|
202
|
+
|
|
203
|
+
// Skip common constants (0, 1, -1, 100, 1000)
|
|
204
|
+
if ([0, 1, -1, 100, 1000].includes(value)) return;
|
|
205
|
+
|
|
206
|
+
// Check if it's not in a common context (array index, etc.)
|
|
207
|
+
const parent = path.parent;
|
|
208
|
+
if (!t.isArrayExpression(parent) &&
|
|
209
|
+
!t.isMemberExpression(parent) &&
|
|
210
|
+
!t.isBinaryExpression(parent)) {
|
|
211
|
+
const line = node.loc.start.line;
|
|
212
|
+
findings.push({
|
|
213
|
+
type: "magic_number",
|
|
214
|
+
severity: "WARN",
|
|
215
|
+
category: "CodeQuality",
|
|
216
|
+
file: filePath,
|
|
217
|
+
line,
|
|
218
|
+
column: node.loc.start.column,
|
|
219
|
+
title: `Magic number: ${value}`,
|
|
220
|
+
message: `Consider extracting ${value} as a named constant`,
|
|
221
|
+
codeSnippet: lines[line - 1]?.trim(),
|
|
222
|
+
confidence: "low",
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Long parameter lists
|
|
229
|
+
traverse(ast, {
|
|
230
|
+
FunctionDeclaration(path) {
|
|
231
|
+
const node = path.node;
|
|
232
|
+
if (node.params && node.params.length > 5) {
|
|
233
|
+
const line = node.loc.start.line;
|
|
234
|
+
findings.push({
|
|
235
|
+
type: "long_parameter_list",
|
|
236
|
+
severity: "WARN",
|
|
237
|
+
category: "CodeQuality",
|
|
238
|
+
file: filePath,
|
|
239
|
+
line,
|
|
240
|
+
column: node.loc.start.column,
|
|
241
|
+
title: `Long parameter list: ${node.params.length} parameters`,
|
|
242
|
+
message: `Function "${node.id?.name || "anonymous"}" has ${node.params.length} parameters - consider using an options object`,
|
|
243
|
+
codeSnippet: lines[line - 1]?.trim(),
|
|
244
|
+
confidence: "med",
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
return findings;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
module.exports = {
|
|
254
|
+
analyzeCodeQuality,
|
|
255
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Console Log Detection Engine
|
|
3
|
+
* Uses AST analysis to detect console.* calls in production code
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { getAST, parseCode } = require("./ast-cache");
|
|
7
|
+
const traverse = require("@babel/traverse").default;
|
|
8
|
+
const t = require("@babel/types");
|
|
9
|
+
const { shouldExcludeFile, isTestContext: isTestContextCheck } = require("./file-filter");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Check if a call expression is a console.* call
|
|
13
|
+
*/
|
|
14
|
+
function isConsoleCall(node) {
|
|
15
|
+
if (!t.isCallExpression(node)) return false;
|
|
16
|
+
|
|
17
|
+
const callee = node.callee;
|
|
18
|
+
|
|
19
|
+
// console.log(), console.warn(), etc.
|
|
20
|
+
if (t.isMemberExpression(callee)) {
|
|
21
|
+
const object = callee.object;
|
|
22
|
+
const property = callee.property;
|
|
23
|
+
|
|
24
|
+
if (t.isIdentifier(object) && object.name === "console") {
|
|
25
|
+
if (t.isIdentifier(property)) {
|
|
26
|
+
const methods = ["log", "warn", "debug", "info", "trace", "error", "dir", "table"];
|
|
27
|
+
return methods.includes(property.name);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Analyze a file for console.* calls
|
|
37
|
+
*/
|
|
38
|
+
function analyzeConsoleLogs(code, filePath) {
|
|
39
|
+
const findings = [];
|
|
40
|
+
|
|
41
|
+
// Skip excluded files
|
|
42
|
+
if (shouldExcludeFile(filePath)) return findings;
|
|
43
|
+
|
|
44
|
+
// Skip test contexts
|
|
45
|
+
if (isTestContextCheck(code, filePath)) return findings;
|
|
46
|
+
|
|
47
|
+
const ast = getAST(code, filePath);
|
|
48
|
+
if (!ast) return findings;
|
|
49
|
+
|
|
50
|
+
const lines = code.split("\n");
|
|
51
|
+
const MAX_FINDINGS = 15;
|
|
52
|
+
let consoleCount = 0;
|
|
53
|
+
|
|
54
|
+
traverse(ast, {
|
|
55
|
+
CallExpression(path) {
|
|
56
|
+
if (isConsoleCall(path.node)) {
|
|
57
|
+
// Check if it's commented out
|
|
58
|
+
const leadingComments = path.node.leadingComments || [];
|
|
59
|
+
const isCommented = leadingComments.some(c =>
|
|
60
|
+
c.type === "CommentLine" || c.type === "CommentBlock"
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
if (isCommented) return;
|
|
64
|
+
|
|
65
|
+
consoleCount++;
|
|
66
|
+
|
|
67
|
+
if (findings.length < MAX_FINDINGS) {
|
|
68
|
+
const line = path.node.loc.start.line;
|
|
69
|
+
const snippet = lines[line - 1]?.trim() || "";
|
|
70
|
+
|
|
71
|
+
// Get method name
|
|
72
|
+
let method = "log";
|
|
73
|
+
if (t.isMemberExpression(path.node.callee) &&
|
|
74
|
+
t.isIdentifier(path.node.callee.property)) {
|
|
75
|
+
method = path.node.callee.property.name;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
findings.push({
|
|
79
|
+
type: `console_${method}`,
|
|
80
|
+
severity: "WARN",
|
|
81
|
+
category: "ConsoleLog",
|
|
82
|
+
file: filePath,
|
|
83
|
+
line,
|
|
84
|
+
column: path.node.loc.start.column,
|
|
85
|
+
title: `console.${method}() in production code`,
|
|
86
|
+
message: `console.${method}() call found at line ${line}`,
|
|
87
|
+
codeSnippet: snippet.substring(0, 60),
|
|
88
|
+
confidence: "high",
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Add summary if there are many
|
|
96
|
+
if (consoleCount > MAX_FINDINGS) {
|
|
97
|
+
findings.push({
|
|
98
|
+
type: "summary",
|
|
99
|
+
severity: "WARN",
|
|
100
|
+
category: "ConsoleLog",
|
|
101
|
+
file: filePath,
|
|
102
|
+
line: 0,
|
|
103
|
+
title: `${consoleCount} console.* statements found (${consoleCount - MAX_FINDINGS} more not shown)`,
|
|
104
|
+
message: `Found ${consoleCount} console statements in this file`,
|
|
105
|
+
confidence: "high",
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return findings;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = {
|
|
113
|
+
analyzeConsoleLogs,
|
|
114
|
+
parseCode,
|
|
115
|
+
};
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-File Analysis Engine
|
|
3
|
+
* Analyzes relationships between files: unused exports, circular dependencies, import consistency
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const path = require("path");
|
|
8
|
+
const { getAST } = require("./ast-cache");
|
|
9
|
+
const traverse = require("@babel/traverse").default;
|
|
10
|
+
const t = require("@babel/types");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Build export map for all files
|
|
14
|
+
*/
|
|
15
|
+
function buildExportMap(files, repoRoot) {
|
|
16
|
+
const exportMap = new Map(); // filePath -> Set<exportNames>
|
|
17
|
+
const importMap = new Map(); // filePath -> Map<importedFrom, Set<importedNames>>
|
|
18
|
+
|
|
19
|
+
for (const fileAbs of files) {
|
|
20
|
+
try {
|
|
21
|
+
const code = fs.readFileSync(fileAbs, "utf8");
|
|
22
|
+
const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
|
|
23
|
+
const ast = getAST(code, fileRel);
|
|
24
|
+
if (!ast) continue;
|
|
25
|
+
|
|
26
|
+
const exports = new Set();
|
|
27
|
+
const imports = new Map();
|
|
28
|
+
|
|
29
|
+
traverse(ast, {
|
|
30
|
+
// Collect exports
|
|
31
|
+
ExportNamedDeclaration(path) {
|
|
32
|
+
if (path.node.declaration) {
|
|
33
|
+
if (t.isFunctionDeclaration(path.node.declaration)) {
|
|
34
|
+
exports.add(path.node.declaration.id?.name);
|
|
35
|
+
} else if (t.isVariableDeclaration(path.node.declaration)) {
|
|
36
|
+
path.node.declaration.declarations.forEach(decl => {
|
|
37
|
+
if (t.isIdentifier(decl.id)) {
|
|
38
|
+
exports.add(decl.id.name);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
} else if (t.isClassDeclaration(path.node.declaration)) {
|
|
42
|
+
exports.add(path.node.declaration.id?.name);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (path.node.specifiers) {
|
|
46
|
+
path.node.specifiers.forEach(spec => {
|
|
47
|
+
if (t.isExportSpecifier(spec)) {
|
|
48
|
+
exports.add(spec.exported.name);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
ExportDefaultDeclaration(path) {
|
|
54
|
+
if (t.isIdentifier(path.node.declaration)) {
|
|
55
|
+
exports.add("default");
|
|
56
|
+
} else if (t.isFunctionDeclaration(path.node.declaration)) {
|
|
57
|
+
exports.add("default");
|
|
58
|
+
} else if (t.isClassDeclaration(path.node.declaration)) {
|
|
59
|
+
exports.add("default");
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
// Collect imports
|
|
64
|
+
ImportDeclaration(path) {
|
|
65
|
+
const source = path.node.source.value;
|
|
66
|
+
if (!source.startsWith(".") && !source.startsWith("/")) {
|
|
67
|
+
// External import, skip
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const importedNames = new Set();
|
|
72
|
+
path.node.specifiers.forEach(spec => {
|
|
73
|
+
if (t.isImportSpecifier(spec)) {
|
|
74
|
+
importedNames.add(spec.imported.name);
|
|
75
|
+
} else if (t.isImportDefaultSpecifier(spec)) {
|
|
76
|
+
importedNames.add("default");
|
|
77
|
+
} else if (t.isImportNamespaceSpecifier(spec)) {
|
|
78
|
+
importedNames.add("*");
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (importedNames.size > 0) {
|
|
83
|
+
imports.set(source, importedNames);
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
exportMap.set(fileRel, exports);
|
|
89
|
+
importMap.set(fileRel, imports);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
// Skip files that can't be analyzed
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return { exportMap, importMap };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Resolve import path to actual file path
|
|
101
|
+
*/
|
|
102
|
+
function resolveImportPath(importPath, fromFile, repoRoot) {
|
|
103
|
+
const dir = path.dirname(path.join(repoRoot, fromFile));
|
|
104
|
+
let resolved = path.resolve(dir, importPath);
|
|
105
|
+
|
|
106
|
+
// Try common extensions
|
|
107
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js", "/index.jsx"];
|
|
108
|
+
for (const ext of extensions) {
|
|
109
|
+
const candidate = resolved + ext;
|
|
110
|
+
if (fs.existsSync(candidate)) {
|
|
111
|
+
return path.relative(repoRoot, candidate).replace(/\\/g, "/");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Try without extension
|
|
116
|
+
if (fs.existsSync(resolved)) {
|
|
117
|
+
return path.relative(repoRoot, resolved).replace(/\\/g, "/");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Analyze cross-file issues
|
|
125
|
+
*/
|
|
126
|
+
function analyzeCrossFile(files, repoRoot) {
|
|
127
|
+
const findings = [];
|
|
128
|
+
const { exportMap, importMap } = buildExportMap(files, repoRoot);
|
|
129
|
+
|
|
130
|
+
// 1. Find unused exports
|
|
131
|
+
const usedExports = new Map(); // filePath -> Set<usedExportNames>
|
|
132
|
+
|
|
133
|
+
for (const [fileRel, imports] of importMap.entries()) {
|
|
134
|
+
for (const [importPath, importedNames] of imports.entries()) {
|
|
135
|
+
const targetFile = resolveImportPath(importPath, fileRel, repoRoot);
|
|
136
|
+
if (!targetFile || !exportMap.has(targetFile)) continue;
|
|
137
|
+
|
|
138
|
+
const exports = exportMap.get(targetFile);
|
|
139
|
+
if (!usedExports.has(targetFile)) {
|
|
140
|
+
usedExports.set(targetFile, new Set());
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
importedNames.forEach(name => {
|
|
144
|
+
if (name === "*") {
|
|
145
|
+
// Namespace import - mark all exports as used
|
|
146
|
+
exports.forEach(exp => usedExports.get(targetFile).add(exp));
|
|
147
|
+
} else {
|
|
148
|
+
usedExports.get(targetFile).add(name);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Find unused exports
|
|
155
|
+
for (const [fileRel, exports] of exportMap.entries()) {
|
|
156
|
+
const used = usedExports.get(fileRel) || new Set();
|
|
157
|
+
|
|
158
|
+
for (const exp of exports) {
|
|
159
|
+
if (!used.has(exp) && exp !== "default") {
|
|
160
|
+
// Check if it's a test file or config file (likely to have unused exports)
|
|
161
|
+
if (!fileRel.includes(".test.") && !fileRel.includes(".spec.") && !fileRel.includes("config")) {
|
|
162
|
+
findings.push({
|
|
163
|
+
type: "unused_export",
|
|
164
|
+
severity: "WARN",
|
|
165
|
+
category: "CodeQuality",
|
|
166
|
+
file: fileRel,
|
|
167
|
+
line: 0, // Would need to track line numbers
|
|
168
|
+
title: `Unused export: ${exp}`,
|
|
169
|
+
message: `Export "${exp}" from ${fileRel} is never imported`,
|
|
170
|
+
confidence: "med",
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 2. Detect circular dependencies
|
|
178
|
+
const visited = new Set();
|
|
179
|
+
const recursionStack = new Set();
|
|
180
|
+
|
|
181
|
+
function detectCycle(fileRel, path = []) {
|
|
182
|
+
if (recursionStack.has(fileRel)) {
|
|
183
|
+
// Found cycle!
|
|
184
|
+
const cycleStart = path.indexOf(fileRel);
|
|
185
|
+
const cycle = path.slice(cycleStart).concat(fileRel);
|
|
186
|
+
return cycle;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (visited.has(fileRel)) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
visited.add(fileRel);
|
|
194
|
+
recursionStack.add(fileRel);
|
|
195
|
+
|
|
196
|
+
const imports = importMap.get(fileRel);
|
|
197
|
+
if (imports) {
|
|
198
|
+
for (const [importPath] of imports.entries()) {
|
|
199
|
+
const targetFile = resolveImportPath(importPath, fileRel, repoRoot);
|
|
200
|
+
if (targetFile && exportMap.has(targetFile)) {
|
|
201
|
+
const cycle = detectCycle(targetFile, path.concat(fileRel));
|
|
202
|
+
if (cycle) {
|
|
203
|
+
recursionStack.delete(fileRel);
|
|
204
|
+
return cycle;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
recursionStack.delete(fileRel);
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
for (const fileRel of exportMap.keys()) {
|
|
215
|
+
if (!visited.has(fileRel)) {
|
|
216
|
+
const cycle = detectCycle(fileRel);
|
|
217
|
+
if (cycle && cycle.length > 2) {
|
|
218
|
+
findings.push({
|
|
219
|
+
type: "circular_dependency",
|
|
220
|
+
severity: "WARN",
|
|
221
|
+
category: "CodeQuality",
|
|
222
|
+
file: cycle[0],
|
|
223
|
+
line: 0,
|
|
224
|
+
title: `Circular dependency detected`,
|
|
225
|
+
message: `Circular dependency: ${cycle.join(" → ")}`,
|
|
226
|
+
confidence: "high",
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// 3. Check for inconsistent imports (same module imported differently)
|
|
233
|
+
const importStyles = new Map(); // modulePath -> Set<importStyles>
|
|
234
|
+
|
|
235
|
+
for (const [fileRel, imports] of importMap.entries()) {
|
|
236
|
+
for (const [importPath] of imports.entries()) {
|
|
237
|
+
const targetFile = resolveImportPath(importPath, fileRel, repoRoot);
|
|
238
|
+
if (!targetFile) continue;
|
|
239
|
+
|
|
240
|
+
if (!importStyles.has(targetFile)) {
|
|
241
|
+
importStyles.set(targetFile, new Set());
|
|
242
|
+
}
|
|
243
|
+
importStyles.get(targetFile).add(importPath);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
for (const [targetFile, styles] of importStyles.entries()) {
|
|
248
|
+
if (styles.size > 1) {
|
|
249
|
+
findings.push({
|
|
250
|
+
type: "inconsistent_imports",
|
|
251
|
+
severity: "WARN",
|
|
252
|
+
category: "CodeQuality",
|
|
253
|
+
file: targetFile,
|
|
254
|
+
line: 0,
|
|
255
|
+
title: `Inconsistent import paths`,
|
|
256
|
+
message: `Module imported with ${styles.size} different paths: ${Array.from(styles).join(", ")}`,
|
|
257
|
+
confidence: "low",
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return findings;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
module.exports = {
|
|
266
|
+
analyzeCrossFile,
|
|
267
|
+
buildExportMap,
|
|
268
|
+
};
|