@vibecheckai/cli 3.2.2 → 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 +351 -271
- 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
|
@@ -1,72 +1,72 @@
|
|
|
1
|
-
// bin/runners/lib/enforcement.js
|
|
2
|
-
const fs = require("fs");
|
|
3
|
-
const path = require("path");
|
|
4
|
-
|
|
5
|
-
const ENFORCE_HINTS = [
|
|
6
|
-
"enforceFeature",
|
|
7
|
-
"enforceLimit",
|
|
8
|
-
"getEntitlements",
|
|
9
|
-
"requirePlan",
|
|
10
|
-
"requireTier",
|
|
11
|
-
"requireSubscription",
|
|
12
|
-
"checkAccess",
|
|
13
|
-
"entitlements",
|
|
14
|
-
"plan",
|
|
15
|
-
"tier",
|
|
16
|
-
"subscription",
|
|
17
|
-
"stripe",
|
|
18
|
-
"credits"
|
|
19
|
-
];
|
|
20
|
-
|
|
21
|
-
function looksPaidSurface(routePath) {
|
|
22
|
-
const p = String(routePath || "");
|
|
23
|
-
return (
|
|
24
|
-
p.includes("/api/ship") ||
|
|
25
|
-
p.includes("/api/verdict") ||
|
|
26
|
-
p.includes("/api/fix") ||
|
|
27
|
-
p.includes("/api/autopilot") ||
|
|
28
|
-
p.includes("/api/missions") ||
|
|
29
|
-
p.includes("/api/pr") ||
|
|
30
|
-
p.includes("/api/credits") ||
|
|
31
|
-
p.includes("/api/billing") ||
|
|
32
|
-
p.includes("/api/stripe") ||
|
|
33
|
-
p.includes("/api/entitlements")
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function handlerHasEnforcementSignal(repoRoot, handlerRel) {
|
|
38
|
-
const abs = path.join(repoRoot, handlerRel);
|
|
39
|
-
if (!fs.existsSync(abs)) return { ok: false, hits: [] };
|
|
40
|
-
|
|
41
|
-
const code = fs.readFileSync(abs, "utf8");
|
|
42
|
-
const hits = ENFORCE_HINTS.filter(k => code.includes(k));
|
|
43
|
-
return { ok: hits.length > 0, hits };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function buildEnforcementTruth(repoRoot, serverRoutes) {
|
|
47
|
-
const checked = [];
|
|
48
|
-
const routeList = serverRoutes || [];
|
|
49
|
-
|
|
50
|
-
for (const r of routeList) {
|
|
51
|
-
if (!r.handler) continue;
|
|
52
|
-
if (!looksPaidSurface(r.path)) continue;
|
|
53
|
-
|
|
54
|
-
const res = handlerHasEnforcementSignal(repoRoot, r.handler);
|
|
55
|
-
checked.push({
|
|
56
|
-
method: r.method,
|
|
57
|
-
path: r.path,
|
|
58
|
-
handler: r.handler,
|
|
59
|
-
enforced: res.ok,
|
|
60
|
-
hits: res.hits
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return {
|
|
65
|
-
checkedCount: checked.length,
|
|
66
|
-
enforcedCount: checked.filter(x => x.enforced).length,
|
|
67
|
-
missingCount: checked.filter(x => !x.enforced).length,
|
|
68
|
-
checks: checked
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
module.exports = { buildEnforcementTruth };
|
|
1
|
+
// bin/runners/lib/enforcement.js
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
|
|
5
|
+
const ENFORCE_HINTS = [
|
|
6
|
+
"enforceFeature",
|
|
7
|
+
"enforceLimit",
|
|
8
|
+
"getEntitlements",
|
|
9
|
+
"requirePlan",
|
|
10
|
+
"requireTier",
|
|
11
|
+
"requireSubscription",
|
|
12
|
+
"checkAccess",
|
|
13
|
+
"entitlements",
|
|
14
|
+
"plan",
|
|
15
|
+
"tier",
|
|
16
|
+
"subscription",
|
|
17
|
+
"stripe",
|
|
18
|
+
"credits"
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
function looksPaidSurface(routePath) {
|
|
22
|
+
const p = String(routePath || "");
|
|
23
|
+
return (
|
|
24
|
+
p.includes("/api/ship") ||
|
|
25
|
+
p.includes("/api/verdict") ||
|
|
26
|
+
p.includes("/api/fix") ||
|
|
27
|
+
p.includes("/api/autopilot") ||
|
|
28
|
+
p.includes("/api/missions") ||
|
|
29
|
+
p.includes("/api/pr") ||
|
|
30
|
+
p.includes("/api/credits") ||
|
|
31
|
+
p.includes("/api/billing") ||
|
|
32
|
+
p.includes("/api/stripe") ||
|
|
33
|
+
p.includes("/api/entitlements")
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function handlerHasEnforcementSignal(repoRoot, handlerRel) {
|
|
38
|
+
const abs = path.join(repoRoot, handlerRel);
|
|
39
|
+
if (!fs.existsSync(abs)) return { ok: false, hits: [] };
|
|
40
|
+
|
|
41
|
+
const code = fs.readFileSync(abs, "utf8");
|
|
42
|
+
const hits = ENFORCE_HINTS.filter(k => code.includes(k));
|
|
43
|
+
return { ok: hits.length > 0, hits };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function buildEnforcementTruth(repoRoot, serverRoutes) {
|
|
47
|
+
const checked = [];
|
|
48
|
+
const routeList = serverRoutes || [];
|
|
49
|
+
|
|
50
|
+
for (const r of routeList) {
|
|
51
|
+
if (!r.handler) continue;
|
|
52
|
+
if (!looksPaidSurface(r.path)) continue;
|
|
53
|
+
|
|
54
|
+
const res = handlerHasEnforcementSignal(repoRoot, r.handler);
|
|
55
|
+
checked.push({
|
|
56
|
+
method: r.method,
|
|
57
|
+
path: r.path,
|
|
58
|
+
handler: r.handler,
|
|
59
|
+
enforced: res.ok,
|
|
60
|
+
hits: res.hits
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
checkedCount: checked.length,
|
|
66
|
+
enforcedCount: checked.filter(x => x.enforced).length,
|
|
67
|
+
missingCount: checked.filter(x => !x.enforced).length,
|
|
68
|
+
checks: checked
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { buildEnforcementTruth };
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accessibility Analysis Engine
|
|
3
|
+
* Detects accessibility issues in React/JSX code
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { getAST } = require("./ast-cache");
|
|
7
|
+
const traverse = require("@babel/traverse").default;
|
|
8
|
+
const t = require("@babel/types");
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Analyze accessibility issues
|
|
12
|
+
*/
|
|
13
|
+
function analyzeAccessibility(code, filePath) {
|
|
14
|
+
const findings = [];
|
|
15
|
+
const ast = getAST(code, filePath);
|
|
16
|
+
if (!ast) return findings;
|
|
17
|
+
|
|
18
|
+
const lines = code.split("\n");
|
|
19
|
+
const isJSX = filePath.endsWith(".jsx") || filePath.endsWith(".tsx");
|
|
20
|
+
|
|
21
|
+
if (!isJSX) return findings; // Only analyze JSX files
|
|
22
|
+
|
|
23
|
+
traverse(ast, {
|
|
24
|
+
JSXElement(path) {
|
|
25
|
+
const node = path.node;
|
|
26
|
+
const openingElement = node.openingElement;
|
|
27
|
+
const tagName = openingElement.name.name;
|
|
28
|
+
|
|
29
|
+
// Missing alt text on images
|
|
30
|
+
if (tagName === "img") {
|
|
31
|
+
const hasAlt = openingElement.attributes.some(attr =>
|
|
32
|
+
t.isJSXAttribute(attr) && attr.name.name === "alt"
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
if (!hasAlt) {
|
|
36
|
+
const line = openingElement.loc.start.line;
|
|
37
|
+
findings.push({
|
|
38
|
+
type: "missing_alt_text",
|
|
39
|
+
severity: "BLOCK",
|
|
40
|
+
category: "Accessibility",
|
|
41
|
+
file: filePath,
|
|
42
|
+
line,
|
|
43
|
+
column: openingElement.loc.start.column,
|
|
44
|
+
title: "Image missing alt text",
|
|
45
|
+
message: "Images must have alt text for screen readers",
|
|
46
|
+
codeSnippet: lines[line - 1]?.trim(),
|
|
47
|
+
confidence: "high",
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Interactive elements without accessible labels
|
|
53
|
+
const interactiveElements = ["button", "a", "input", "select", "textarea"];
|
|
54
|
+
if (interactiveElements.includes(tagName)) {
|
|
55
|
+
const hasLabel = openingElement.attributes.some(attr =>
|
|
56
|
+
t.isJSXAttribute(attr) &&
|
|
57
|
+
(attr.name.name === "aria-label" ||
|
|
58
|
+
attr.name.name === "aria-labelledby" ||
|
|
59
|
+
attr.name.name === "title")
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// Check for associated label element
|
|
63
|
+
const hasAssociatedLabel = path.findParent(p => {
|
|
64
|
+
if (t.isJSXElement(p.node)) {
|
|
65
|
+
return p.node.openingElement.name.name === "label";
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (!hasLabel && !hasAssociatedLabel && tagName !== "a") {
|
|
71
|
+
const line = openingElement.loc.start.line;
|
|
72
|
+
findings.push({
|
|
73
|
+
type: "missing_accessible_label",
|
|
74
|
+
severity: "WARN",
|
|
75
|
+
category: "Accessibility",
|
|
76
|
+
file: filePath,
|
|
77
|
+
line,
|
|
78
|
+
column: openingElement.loc.start.column,
|
|
79
|
+
title: `${tagName} element missing accessible label`,
|
|
80
|
+
message: `Add aria-label, aria-labelledby, or wrap in <label>`,
|
|
81
|
+
codeSnippet: lines[line - 1]?.trim(),
|
|
82
|
+
confidence: "med",
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Missing form labels
|
|
88
|
+
if (tagName === "input" || tagName === "select" || tagName === "textarea") {
|
|
89
|
+
const inputType = openingElement.attributes.find(attr =>
|
|
90
|
+
t.isJSXAttribute(attr) && attr.name.name === "type"
|
|
91
|
+
);
|
|
92
|
+
const typeValue = inputType && t.isStringLiteral(inputType.value)
|
|
93
|
+
? inputType.value.value
|
|
94
|
+
: "text";
|
|
95
|
+
|
|
96
|
+
// Skip hidden inputs
|
|
97
|
+
if (typeValue === "hidden") return;
|
|
98
|
+
|
|
99
|
+
const hasLabel = openingElement.attributes.some(attr =>
|
|
100
|
+
t.isJSXAttribute(attr) &&
|
|
101
|
+
(attr.name.name === "aria-label" ||
|
|
102
|
+
attr.name.name === "aria-labelledby" ||
|
|
103
|
+
attr.name.name === "id")
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
if (!hasLabel) {
|
|
107
|
+
const line = openingElement.loc.start.line;
|
|
108
|
+
findings.push({
|
|
109
|
+
type: "missing_form_label",
|
|
110
|
+
severity: "WARN",
|
|
111
|
+
category: "Accessibility",
|
|
112
|
+
file: filePath,
|
|
113
|
+
line,
|
|
114
|
+
column: openingElement.loc.start.column,
|
|
115
|
+
title: "Form input missing label",
|
|
116
|
+
message: "Form inputs should have associated labels",
|
|
117
|
+
codeSnippet: lines[line - 1]?.trim(),
|
|
118
|
+
confidence: "med",
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Missing keyboard handlers on interactive elements
|
|
124
|
+
if (tagName === "div" || tagName === "span") {
|
|
125
|
+
const hasOnClick = openingElement.attributes.some(attr =>
|
|
126
|
+
t.isJSXAttribute(attr) &&
|
|
127
|
+
(attr.name.name === "onClick" || attr.name.name === "onKeyDown")
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const hasRole = openingElement.attributes.some(attr =>
|
|
131
|
+
t.isJSXAttribute(attr) && attr.name.name === "role"
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
if (hasOnClick && !hasRole) {
|
|
135
|
+
const line = openingElement.loc.start.line;
|
|
136
|
+
findings.push({
|
|
137
|
+
type: "missing_keyboard_handler",
|
|
138
|
+
severity: "WARN",
|
|
139
|
+
category: "Accessibility",
|
|
140
|
+
file: filePath,
|
|
141
|
+
line,
|
|
142
|
+
column: openingElement.loc.start.column,
|
|
143
|
+
title: "Interactive element missing keyboard support",
|
|
144
|
+
message: "Elements with onClick should have onKeyDown and proper role",
|
|
145
|
+
codeSnippet: lines[line - 1]?.trim(),
|
|
146
|
+
confidence: "med",
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Color contrast issues (heuristic: inline styles with low contrast colors)
|
|
152
|
+
const styleAttr = openingElement.attributes.find(attr =>
|
|
153
|
+
t.isJSXAttribute(attr) && attr.name.name === "style"
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
if (styleAttr && t.isJSXExpressionContainer(styleAttr.value)) {
|
|
157
|
+
const styleExpr = styleAttr.value.expression;
|
|
158
|
+
if (t.isObjectExpression(styleExpr)) {
|
|
159
|
+
const colorProp = styleExpr.properties.find(prop =>
|
|
160
|
+
t.isObjectProperty(prop) &&
|
|
161
|
+
t.isIdentifier(prop.key) &&
|
|
162
|
+
prop.key.name === "color"
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
if (colorProp) {
|
|
166
|
+
const line = openingElement.loc.start.line;
|
|
167
|
+
findings.push({
|
|
168
|
+
type: "potential_contrast_issue",
|
|
169
|
+
severity: "WARN",
|
|
170
|
+
category: "Accessibility",
|
|
171
|
+
file: filePath,
|
|
172
|
+
line,
|
|
173
|
+
column: openingElement.loc.start.column,
|
|
174
|
+
title: "Potential color contrast issue",
|
|
175
|
+
message: "Inline color styles may not meet WCAG contrast requirements - verify with contrast checker",
|
|
176
|
+
codeSnippet: lines[line - 1]?.trim(),
|
|
177
|
+
confidence: "low",
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return findings;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
module.exports = {
|
|
189
|
+
analyzeAccessibility,
|
|
190
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Consistency Engine
|
|
3
|
+
* Checks API route consistency, response formats, error handling patterns
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { getAST } = require("./ast-cache");
|
|
7
|
+
const traverse = require("@babel/traverse").default;
|
|
8
|
+
const t = require("@babel/types");
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Analyze API consistency issues
|
|
12
|
+
*/
|
|
13
|
+
function analyzeAPIConsistency(code, filePath) {
|
|
14
|
+
const findings = [];
|
|
15
|
+
const ast = getAST(code, filePath);
|
|
16
|
+
if (!ast) return findings;
|
|
17
|
+
|
|
18
|
+
const lines = code.split("\n");
|
|
19
|
+
const isAPIRoute = filePath.includes("/api/") || filePath.includes("/routes/");
|
|
20
|
+
|
|
21
|
+
if (!isAPIRoute) return findings;
|
|
22
|
+
|
|
23
|
+
const responseFormats = new Set();
|
|
24
|
+
const errorHandlingPatterns = new Set();
|
|
25
|
+
let hasErrorHandler = false;
|
|
26
|
+
|
|
27
|
+
traverse(ast, {
|
|
28
|
+
// Check response formats
|
|
29
|
+
CallExpression(path) {
|
|
30
|
+
const node = path.node;
|
|
31
|
+
|
|
32
|
+
// Next.js API routes
|
|
33
|
+
if (t.isMemberExpression(node.callee) &&
|
|
34
|
+
t.isIdentifier(node.callee.object, { name: "NextResponse" })) {
|
|
35
|
+
const method = node.callee.property.name;
|
|
36
|
+
if (["json", "redirect", "next"].includes(method)) {
|
|
37
|
+
responseFormats.add(`NextResponse.${method}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Express-style responses
|
|
42
|
+
if (t.isMemberExpression(node.callee)) {
|
|
43
|
+
const prop = node.callee.property;
|
|
44
|
+
if (t.isIdentifier(prop) &&
|
|
45
|
+
["json", "send", "status", "redirect"].includes(prop.name)) {
|
|
46
|
+
responseFormats.add(`res.${prop.name}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Error handling
|
|
51
|
+
if (t.isMemberExpression(node.callee) &&
|
|
52
|
+
t.isIdentifier(node.callee.property, { name: "catch" })) {
|
|
53
|
+
hasErrorHandler = true;
|
|
54
|
+
errorHandlingPatterns.add("promise.catch");
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
// Try-catch blocks
|
|
59
|
+
TryStatement(path) {
|
|
60
|
+
hasErrorHandler = true;
|
|
61
|
+
errorHandlingPatterns.add("try-catch");
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
// Check for inconsistent error responses
|
|
65
|
+
IfStatement(path) {
|
|
66
|
+
const test = path.node.test;
|
|
67
|
+
if (t.isBinaryExpression(test) &&
|
|
68
|
+
(test.operator === "===" || test.operator === "==")) {
|
|
69
|
+
const left = test.left;
|
|
70
|
+
const right = test.right;
|
|
71
|
+
|
|
72
|
+
// Check for error status checks
|
|
73
|
+
if ((t.isMemberExpression(left) &&
|
|
74
|
+
t.isIdentifier(left.property, { name: "status" })) ||
|
|
75
|
+
(t.isMemberExpression(left) &&
|
|
76
|
+
t.isIdentifier(left.property, { name: "ok" }))) {
|
|
77
|
+
const thenBlock = path.node.consequent;
|
|
78
|
+
const elseBlock = path.node.alternate;
|
|
79
|
+
|
|
80
|
+
// Check if error response is consistent
|
|
81
|
+
if (thenBlock && !elseBlock) {
|
|
82
|
+
const line = path.node.loc.start.line;
|
|
83
|
+
findings.push({
|
|
84
|
+
type: "missing_error_response",
|
|
85
|
+
severity: "WARN",
|
|
86
|
+
category: "APIConsistency",
|
|
87
|
+
file: filePath,
|
|
88
|
+
line,
|
|
89
|
+
column: path.node.loc.start.column,
|
|
90
|
+
title: "Missing error response handling",
|
|
91
|
+
message: "Error condition checked but no error response returned",
|
|
92
|
+
codeSnippet: lines[line - 1]?.trim(),
|
|
93
|
+
confidence: "med",
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Check for inconsistent response formats
|
|
102
|
+
if (responseFormats.size > 1) {
|
|
103
|
+
findings.push({
|
|
104
|
+
type: "inconsistent_response_format",
|
|
105
|
+
severity: "WARN",
|
|
106
|
+
category: "APIConsistency",
|
|
107
|
+
file: filePath,
|
|
108
|
+
line: 1,
|
|
109
|
+
column: 0,
|
|
110
|
+
title: "Inconsistent API response formats",
|
|
111
|
+
message: `Multiple response formats used: ${Array.from(responseFormats).join(", ")}`,
|
|
112
|
+
confidence: "low",
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Check for missing error handling
|
|
117
|
+
if (!hasErrorHandler && isAPIRoute) {
|
|
118
|
+
findings.push({
|
|
119
|
+
type: "missing_error_handling",
|
|
120
|
+
severity: "WARN",
|
|
121
|
+
category: "APIConsistency",
|
|
122
|
+
file: filePath,
|
|
123
|
+
line: 1,
|
|
124
|
+
column: 0,
|
|
125
|
+
title: "API route missing error handling",
|
|
126
|
+
message: "API route should have try-catch or promise error handling",
|
|
127
|
+
confidence: "med",
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Check for missing status codes
|
|
132
|
+
let hasStatusCode = false;
|
|
133
|
+
traverse(ast, {
|
|
134
|
+
CallExpression(path) {
|
|
135
|
+
const node = path.node;
|
|
136
|
+
if (t.isMemberExpression(node.callee) &&
|
|
137
|
+
t.isIdentifier(node.callee.property, { name: "status" })) {
|
|
138
|
+
hasStatusCode = true;
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
if (!hasStatusCode && responseFormats.size > 0) {
|
|
144
|
+
findings.push({
|
|
145
|
+
type: "missing_status_code",
|
|
146
|
+
severity: "WARN",
|
|
147
|
+
category: "APIConsistency",
|
|
148
|
+
file: filePath,
|
|
149
|
+
line: 1,
|
|
150
|
+
column: 0,
|
|
151
|
+
title: "API response missing explicit status code",
|
|
152
|
+
message: "API responses should explicitly set HTTP status codes",
|
|
153
|
+
confidence: "med",
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return findings;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = {
|
|
161
|
+
analyzeAPIConsistency,
|
|
162
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST Cache - Shared AST parsing cache for all engines
|
|
3
|
+
* Dramatically improves performance by parsing each file only once
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const parser = require("@babel/parser");
|
|
7
|
+
const traverse = require("@babel/traverse").default;
|
|
8
|
+
const t = require("@babel/types");
|
|
9
|
+
|
|
10
|
+
// Global AST cache: filePath -> { ast, code, timestamp }
|
|
11
|
+
const _AST_CACHE = new Map();
|
|
12
|
+
const _CACHE_MAX_SIZE = 10000; // Prevent memory issues in huge monorepos
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Parse code with comprehensive plugin support
|
|
16
|
+
*/
|
|
17
|
+
function parseCode(code, filePath = "") {
|
|
18
|
+
try {
|
|
19
|
+
return parser.parse(code, {
|
|
20
|
+
sourceType: "unambiguous",
|
|
21
|
+
errorRecovery: true,
|
|
22
|
+
allowReturnOutsideFunction: true,
|
|
23
|
+
plugins: [
|
|
24
|
+
"typescript",
|
|
25
|
+
"jsx",
|
|
26
|
+
"dynamicImport",
|
|
27
|
+
"topLevelAwait",
|
|
28
|
+
"classProperties",
|
|
29
|
+
"classPrivateProperties",
|
|
30
|
+
"decorators-legacy",
|
|
31
|
+
"exportDefaultFrom",
|
|
32
|
+
"exportNamespaceFrom",
|
|
33
|
+
"functionBind",
|
|
34
|
+
"nullishCoalescingOperator",
|
|
35
|
+
"optionalChaining",
|
|
36
|
+
"objectRestSpread",
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
} catch (err) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get AST from cache or parse and cache it
|
|
46
|
+
*/
|
|
47
|
+
function getAST(code, filePath) {
|
|
48
|
+
// Check cache first
|
|
49
|
+
if (_AST_CACHE.has(filePath)) {
|
|
50
|
+
const cached = _AST_CACHE.get(filePath);
|
|
51
|
+
// Verify code hasn't changed (simple hash check)
|
|
52
|
+
if (cached.code === code) {
|
|
53
|
+
return cached.ast;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Parse and cache
|
|
58
|
+
const ast = parseCode(code, filePath);
|
|
59
|
+
if (ast) {
|
|
60
|
+
// Evict oldest entries if cache is too large
|
|
61
|
+
if (_AST_CACHE.size >= _CACHE_MAX_SIZE) {
|
|
62
|
+
const firstKey = _AST_CACHE.keys().next().value;
|
|
63
|
+
_AST_CACHE.delete(firstKey);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
_AST_CACHE.set(filePath, {
|
|
67
|
+
ast,
|
|
68
|
+
code,
|
|
69
|
+
timestamp: Date.now(),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return ast;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Clear AST cache (call after scan completes)
|
|
78
|
+
*/
|
|
79
|
+
function clearASTCache() {
|
|
80
|
+
_AST_CACHE.clear();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get cache stats
|
|
85
|
+
*/
|
|
86
|
+
function getCacheStats() {
|
|
87
|
+
return {
|
|
88
|
+
size: _AST_CACHE.size,
|
|
89
|
+
maxSize: _CACHE_MAX_SIZE,
|
|
90
|
+
hitRate: 0, // Could track this if needed
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = {
|
|
95
|
+
getAST,
|
|
96
|
+
parseCode,
|
|
97
|
+
clearASTCache,
|
|
98
|
+
getCacheStats,
|
|
99
|
+
};
|