@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.
Files changed (170) hide show
  1. package/bin/.generated +25 -25
  2. package/bin/dev/run-v2-torture.js +30 -30
  3. package/bin/runners/ENHANCEMENT_GUIDE.md +121 -121
  4. package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -295
  5. package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +474 -0
  6. package/bin/runners/lib/agent-firewall/claims/extractor.js +117 -28
  7. package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +23 -14
  8. package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +72 -1
  9. package/bin/runners/lib/agent-firewall/interceptor/base.js +2 -2
  10. package/bin/runners/lib/agent-firewall/policy/default-policy.json +6 -0
  11. package/bin/runners/lib/agent-firewall/policy/engine.js +34 -3
  12. package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +29 -4
  13. package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +12 -0
  14. package/bin/runners/lib/agent-firewall/truthpack/loader.js +21 -0
  15. package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +118 -0
  16. package/bin/runners/lib/analyzers.js +606 -325
  17. package/bin/runners/lib/auth-truth.js +193 -193
  18. package/bin/runners/lib/backup.js +62 -62
  19. package/bin/runners/lib/billing.js +107 -107
  20. package/bin/runners/lib/claims.js +118 -118
  21. package/bin/runners/lib/cli-ui.js +540 -540
  22. package/bin/runners/lib/contracts/auth-contract.js +202 -202
  23. package/bin/runners/lib/contracts/env-contract.js +181 -181
  24. package/bin/runners/lib/contracts/external-contract.js +206 -206
  25. package/bin/runners/lib/contracts/guard.js +168 -168
  26. package/bin/runners/lib/contracts/index.js +89 -89
  27. package/bin/runners/lib/contracts/plan-validator.js +311 -311
  28. package/bin/runners/lib/contracts/route-contract.js +199 -199
  29. package/bin/runners/lib/contracts.js +804 -804
  30. package/bin/runners/lib/detect.js +89 -89
  31. package/bin/runners/lib/doctor/autofix.js +254 -254
  32. package/bin/runners/lib/doctor/index.js +37 -37
  33. package/bin/runners/lib/doctor/modules/dependencies.js +325 -325
  34. package/bin/runners/lib/doctor/modules/index.js +46 -46
  35. package/bin/runners/lib/doctor/modules/network.js +250 -250
  36. package/bin/runners/lib/doctor/modules/project.js +312 -312
  37. package/bin/runners/lib/doctor/modules/runtime.js +224 -224
  38. package/bin/runners/lib/doctor/modules/security.js +348 -348
  39. package/bin/runners/lib/doctor/modules/system.js +213 -213
  40. package/bin/runners/lib/doctor/modules/vibecheck.js +394 -394
  41. package/bin/runners/lib/doctor/reporter.js +262 -262
  42. package/bin/runners/lib/doctor/service.js +262 -262
  43. package/bin/runners/lib/doctor/types.js +113 -113
  44. package/bin/runners/lib/doctor/ui.js +263 -263
  45. package/bin/runners/lib/doctor-v2.js +608 -608
  46. package/bin/runners/lib/drift.js +425 -425
  47. package/bin/runners/lib/enforcement.js +72 -72
  48. package/bin/runners/lib/engines/accessibility-engine.js +190 -0
  49. package/bin/runners/lib/engines/api-consistency-engine.js +162 -0
  50. package/bin/runners/lib/engines/ast-cache.js +99 -0
  51. package/bin/runners/lib/engines/code-quality-engine.js +255 -0
  52. package/bin/runners/lib/engines/console-logs-engine.js +115 -0
  53. package/bin/runners/lib/engines/cross-file-analysis-engine.js +268 -0
  54. package/bin/runners/lib/engines/dead-code-engine.js +198 -0
  55. package/bin/runners/lib/engines/deprecated-api-engine.js +226 -0
  56. package/bin/runners/lib/engines/empty-catch-engine.js +150 -0
  57. package/bin/runners/lib/engines/file-filter.js +131 -0
  58. package/bin/runners/lib/engines/hardcoded-secrets-engine.js +251 -0
  59. package/bin/runners/lib/engines/mock-data-engine.js +272 -0
  60. package/bin/runners/lib/engines/parallel-processor.js +71 -0
  61. package/bin/runners/lib/engines/performance-issues-engine.js +265 -0
  62. package/bin/runners/lib/engines/security-vulnerabilities-engine.js +243 -0
  63. package/bin/runners/lib/engines/todo-fixme-engine.js +115 -0
  64. package/bin/runners/lib/engines/type-aware-engine.js +152 -0
  65. package/bin/runners/lib/engines/unsafe-regex-engine.js +225 -0
  66. package/bin/runners/lib/engines/vibecheck-engines/README.md +53 -0
  67. package/bin/runners/lib/engines/vibecheck-engines/index.js +15 -0
  68. package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
  69. package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
  70. package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
  71. package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
  72. package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
  73. package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
  74. package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
  75. package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +139 -0
  76. package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
  77. package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
  78. package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
  79. package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
  80. package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
  81. package/bin/runners/lib/engines/vibecheck-engines/package.json +13 -0
  82. package/bin/runners/lib/enterprise-detect.js +603 -603
  83. package/bin/runners/lib/enterprise-init.js +942 -942
  84. package/bin/runners/lib/env-resolver.js +417 -417
  85. package/bin/runners/lib/env-template.js +66 -66
  86. package/bin/runners/lib/env.js +189 -189
  87. package/bin/runners/lib/extractors/client-calls.js +990 -990
  88. package/bin/runners/lib/extractors/fastify-route-dump.js +573 -573
  89. package/bin/runners/lib/extractors/fastify-routes.js +426 -426
  90. package/bin/runners/lib/extractors/index.js +363 -363
  91. package/bin/runners/lib/extractors/next-routes.js +524 -524
  92. package/bin/runners/lib/extractors/proof-graph.js +431 -431
  93. package/bin/runners/lib/extractors/route-matcher.js +451 -451
  94. package/bin/runners/lib/extractors/truthpack-v2.js +377 -377
  95. package/bin/runners/lib/extractors/ui-bindings.js +547 -547
  96. package/bin/runners/lib/findings-schema.js +281 -281
  97. package/bin/runners/lib/firewall-prompt.js +50 -50
  98. package/bin/runners/lib/global-flags.js +213 -213
  99. package/bin/runners/lib/graph/graph-builder.js +265 -265
  100. package/bin/runners/lib/graph/html-renderer.js +413 -413
  101. package/bin/runners/lib/graph/index.js +32 -32
  102. package/bin/runners/lib/graph/runtime-collector.js +215 -215
  103. package/bin/runners/lib/graph/static-extractor.js +518 -518
  104. package/bin/runners/lib/html-report.js +650 -650
  105. package/bin/runners/lib/interactive-menu.js +1496 -1496
  106. package/bin/runners/lib/llm.js +75 -75
  107. package/bin/runners/lib/meter.js +61 -61
  108. package/bin/runners/lib/missions/evidence.js +126 -126
  109. package/bin/runners/lib/patch.js +40 -40
  110. package/bin/runners/lib/permissions/auth-model.js +213 -213
  111. package/bin/runners/lib/permissions/idor-prover.js +205 -205
  112. package/bin/runners/lib/permissions/index.js +45 -45
  113. package/bin/runners/lib/permissions/matrix-builder.js +198 -198
  114. package/bin/runners/lib/pkgjson.js +28 -28
  115. package/bin/runners/lib/policy.js +295 -295
  116. package/bin/runners/lib/preflight.js +142 -142
  117. package/bin/runners/lib/reality/correlation-detectors.js +359 -359
  118. package/bin/runners/lib/reality/index.js +318 -318
  119. package/bin/runners/lib/reality/request-hashing.js +416 -416
  120. package/bin/runners/lib/reality/request-mapper.js +453 -453
  121. package/bin/runners/lib/reality/safety-rails.js +463 -463
  122. package/bin/runners/lib/reality/semantic-snapshot.js +408 -408
  123. package/bin/runners/lib/reality/toast-detector.js +393 -393
  124. package/bin/runners/lib/reality-findings.js +84 -84
  125. package/bin/runners/lib/receipts.js +179 -179
  126. package/bin/runners/lib/redact.js +29 -29
  127. package/bin/runners/lib/replay/capsule-manager.js +154 -154
  128. package/bin/runners/lib/replay/index.js +263 -263
  129. package/bin/runners/lib/replay/player.js +348 -348
  130. package/bin/runners/lib/replay/recorder.js +331 -331
  131. package/bin/runners/lib/report-output.js +187 -187
  132. package/bin/runners/lib/report.js +135 -135
  133. package/bin/runners/lib/route-detection.js +1140 -1140
  134. package/bin/runners/lib/sandbox/index.js +59 -59
  135. package/bin/runners/lib/sandbox/proof-chain.js +399 -399
  136. package/bin/runners/lib/sandbox/sandbox-runner.js +205 -205
  137. package/bin/runners/lib/sandbox/worktree.js +174 -174
  138. package/bin/runners/lib/scan-output.js +525 -190
  139. package/bin/runners/lib/schema-validator.js +350 -350
  140. package/bin/runners/lib/schemas/contracts.schema.json +160 -160
  141. package/bin/runners/lib/schemas/finding.schema.json +100 -100
  142. package/bin/runners/lib/schemas/mission-pack.schema.json +206 -206
  143. package/bin/runners/lib/schemas/proof-graph.schema.json +176 -176
  144. package/bin/runners/lib/schemas/reality-report.schema.json +162 -162
  145. package/bin/runners/lib/schemas/share-pack.schema.json +180 -180
  146. package/bin/runners/lib/schemas/ship-report.schema.json +117 -117
  147. package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -303
  148. package/bin/runners/lib/schemas/validator.js +438 -438
  149. package/bin/runners/lib/score-history.js +282 -282
  150. package/bin/runners/lib/share-pack.js +239 -239
  151. package/bin/runners/lib/snippets.js +67 -67
  152. package/bin/runners/lib/status-output.js +253 -253
  153. package/bin/runners/lib/terminal-ui.js +351 -271
  154. package/bin/runners/lib/upsell.js +510 -510
  155. package/bin/runners/lib/usage.js +153 -153
  156. package/bin/runners/lib/validate-patch.js +156 -156
  157. package/bin/runners/lib/verdict-engine.js +628 -628
  158. package/bin/runners/reality/engine.js +917 -917
  159. package/bin/runners/reality/flows.js +122 -122
  160. package/bin/runners/reality/report.js +378 -378
  161. package/bin/runners/reality/session.js +193 -193
  162. package/bin/runners/runGuard.js +168 -168
  163. package/bin/runners/runProof.zip +0 -0
  164. package/bin/runners/runProve.js +8 -0
  165. package/bin/runners/runReality.js +14 -0
  166. package/bin/runners/runScan.js +17 -1
  167. package/bin/runners/runTruth.js +15 -3
  168. package/mcp-server/tier-auth.js +4 -4
  169. package/mcp-server/tools/index.js +72 -72
  170. 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
+ };