@vibecheckai/cli 3.5.0 → 3.5.1

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 (326) hide show
  1. package/bin/registry.js +174 -449
  2. package/bin/runners/cli-utils.js +33 -2
  3. package/bin/runners/context/generators/cursor.js +2 -49
  4. package/bin/runners/context/generators/mcp.js +13 -15
  5. package/bin/runners/context/proof-context.js +1 -248
  6. package/bin/runners/lib/analysis-core.js +180 -198
  7. package/bin/runners/lib/analyzers.js +241 -2212
  8. package/bin/runners/lib/cli-output.js +210 -242
  9. package/bin/runners/lib/detectors-v2.js +785 -547
  10. package/bin/runners/lib/entitlements-v2.js +431 -161
  11. package/bin/runners/lib/error-handler.js +9 -16
  12. package/bin/runners/lib/global-flags.js +0 -37
  13. package/bin/runners/lib/html-proof-report.js +700 -350
  14. package/bin/runners/lib/missions/plan.js +6 -46
  15. package/bin/runners/lib/missions/templates.js +0 -232
  16. package/bin/runners/lib/route-truth.js +322 -1167
  17. package/bin/runners/lib/scan-output.js +467 -493
  18. package/bin/runners/lib/ship-output.js +27 -280
  19. package/bin/runners/lib/terminal-ui.js +700 -310
  20. package/bin/runners/lib/truth.js +321 -1004
  21. package/bin/runners/lib/unified-output.js +158 -162
  22. package/bin/runners/lib/upsell.js +204 -104
  23. package/bin/runners/runAIAgent.js +10 -5
  24. package/bin/runners/runAllowlist.js +324 -0
  25. package/bin/runners/runAuth.js +94 -344
  26. package/bin/runners/runCheckpoint.js +45 -43
  27. package/bin/runners/runContext.js +24 -139
  28. package/bin/runners/runDoctor.js +101 -136
  29. package/bin/runners/runEvidencePack.js +219 -0
  30. package/bin/runners/runFix.js +71 -82
  31. package/bin/runners/runGuard.js +119 -606
  32. package/bin/runners/runInit.js +60 -22
  33. package/bin/runners/runInstall.js +281 -0
  34. package/bin/runners/runLabs.js +341 -0
  35. package/bin/runners/runMcp.js +62 -139
  36. package/bin/runners/runPolish.js +83 -282
  37. package/bin/runners/runPromptFirewall.js +12 -5
  38. package/bin/runners/runProve.js +58 -33
  39. package/bin/runners/runReality.js +58 -81
  40. package/bin/runners/runReport.js +7 -34
  41. package/bin/runners/runRuntime.js +8 -5
  42. package/bin/runners/runScan.js +844 -219
  43. package/bin/runners/runShip.js +59 -721
  44. package/bin/runners/runValidate.js +11 -24
  45. package/bin/runners/runWatch.js +76 -131
  46. package/bin/vibecheck.js +69 -295
  47. package/mcp-server/ARCHITECTURE.md +339 -0
  48. package/mcp-server/__tests__/cache.test.ts +313 -0
  49. package/mcp-server/__tests__/executor.test.ts +239 -0
  50. package/mcp-server/__tests__/fixtures/exclusion-test/.cache/webpack/cache.pack +1 -0
  51. package/mcp-server/__tests__/fixtures/exclusion-test/.next/server/chunk.js +3 -0
  52. package/mcp-server/__tests__/fixtures/exclusion-test/.turbo/cache.json +3 -0
  53. package/mcp-server/__tests__/fixtures/exclusion-test/.venv/lib/env.py +3 -0
  54. package/mcp-server/__tests__/fixtures/exclusion-test/dist/bundle.js +3 -0
  55. package/mcp-server/__tests__/fixtures/exclusion-test/package.json +5 -0
  56. package/mcp-server/__tests__/fixtures/exclusion-test/src/app.ts +5 -0
  57. package/mcp-server/__tests__/fixtures/exclusion-test/venv/lib/config.py +4 -0
  58. package/mcp-server/__tests__/ids.test.ts +345 -0
  59. package/mcp-server/__tests__/integration/tools.test.ts +410 -0
  60. package/mcp-server/__tests__/registry.test.ts +365 -0
  61. package/mcp-server/__tests__/sandbox.test.ts +323 -0
  62. package/mcp-server/__tests__/schemas.test.ts +372 -0
  63. package/mcp-server/benchmarks/run-benchmarks.ts +304 -0
  64. package/mcp-server/examples/doctor.request.json +14 -0
  65. package/mcp-server/examples/doctor.response.json +53 -0
  66. package/mcp-server/examples/error.response.json +15 -0
  67. package/mcp-server/examples/scan.request.json +14 -0
  68. package/mcp-server/examples/scan.response.json +108 -0
  69. package/mcp-server/handlers/tool-handler.ts +671 -0
  70. package/mcp-server/index-v1.js +698 -0
  71. package/mcp-server/index-v3.ts +293 -0
  72. package/mcp-server/index.js +1080 -1757
  73. package/mcp-server/index.old.js +4137 -0
  74. package/mcp-server/lib/cache.ts +341 -0
  75. package/mcp-server/lib/errors.ts +346 -0
  76. package/mcp-server/lib/executor.ts +792 -0
  77. package/mcp-server/lib/ids.ts +238 -0
  78. package/mcp-server/lib/logger.ts +368 -0
  79. package/mcp-server/lib/metrics.ts +365 -0
  80. package/mcp-server/lib/sandbox.ts +337 -0
  81. package/mcp-server/lib/validator.ts +229 -0
  82. package/mcp-server/package-lock.json +165 -0
  83. package/mcp-server/package.json +32 -7
  84. package/mcp-server/premium-tools.js +2 -2
  85. package/mcp-server/registry/tools.json +476 -0
  86. package/mcp-server/schemas/error-envelope.schema.json +125 -0
  87. package/mcp-server/schemas/finding.schema.json +167 -0
  88. package/mcp-server/schemas/report-artifact.schema.json +88 -0
  89. package/mcp-server/schemas/run-request.schema.json +75 -0
  90. package/mcp-server/schemas/verdict.schema.json +168 -0
  91. package/mcp-server/tier-auth.d.ts +71 -0
  92. package/mcp-server/tier-auth.js +371 -183
  93. package/mcp-server/truth-context.js +90 -131
  94. package/mcp-server/truth-firewall-tools.js +1000 -1611
  95. package/mcp-server/tsconfig.json +34 -0
  96. package/mcp-server/vibecheck-tools.js +2 -2
  97. package/mcp-server/vitest.config.ts +16 -0
  98. package/package.json +3 -4
  99. package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +0 -474
  100. package/bin/runners/lib/agent-firewall/change-packet/builder.js +0 -488
  101. package/bin/runners/lib/agent-firewall/change-packet/schema.json +0 -228
  102. package/bin/runners/lib/agent-firewall/change-packet/store.js +0 -200
  103. package/bin/runners/lib/agent-firewall/claims/claim-types.js +0 -21
  104. package/bin/runners/lib/agent-firewall/claims/extractor.js +0 -303
  105. package/bin/runners/lib/agent-firewall/claims/patterns.js +0 -24
  106. package/bin/runners/lib/agent-firewall/critic/index.js +0 -151
  107. package/bin/runners/lib/agent-firewall/critic/judge.js +0 -432
  108. package/bin/runners/lib/agent-firewall/critic/prompts.js +0 -305
  109. package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +0 -88
  110. package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +0 -75
  111. package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +0 -127
  112. package/bin/runners/lib/agent-firewall/evidence/resolver.js +0 -102
  113. package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +0 -213
  114. package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +0 -145
  115. package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +0 -19
  116. package/bin/runners/lib/agent-firewall/fs-hook/installer.js +0 -87
  117. package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +0 -184
  118. package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +0 -163
  119. package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +0 -107
  120. package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +0 -68
  121. package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +0 -66
  122. package/bin/runners/lib/agent-firewall/interceptor/base.js +0 -304
  123. package/bin/runners/lib/agent-firewall/interceptor/cursor.js +0 -35
  124. package/bin/runners/lib/agent-firewall/interceptor/vscode.js +0 -35
  125. package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +0 -34
  126. package/bin/runners/lib/agent-firewall/lawbook/distributor.js +0 -465
  127. package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +0 -604
  128. package/bin/runners/lib/agent-firewall/lawbook/index.js +0 -304
  129. package/bin/runners/lib/agent-firewall/lawbook/registry.js +0 -514
  130. package/bin/runners/lib/agent-firewall/lawbook/schema.js +0 -420
  131. package/bin/runners/lib/agent-firewall/learning/learning-engine.js +0 -849
  132. package/bin/runners/lib/agent-firewall/logger.js +0 -141
  133. package/bin/runners/lib/agent-firewall/policy/default-policy.json +0 -90
  134. package/bin/runners/lib/agent-firewall/policy/engine.js +0 -103
  135. package/bin/runners/lib/agent-firewall/policy/loader.js +0 -451
  136. package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +0 -50
  137. package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +0 -50
  138. package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +0 -86
  139. package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +0 -162
  140. package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +0 -189
  141. package/bin/runners/lib/agent-firewall/policy/rules/scope.js +0 -93
  142. package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +0 -57
  143. package/bin/runners/lib/agent-firewall/policy/schema.json +0 -183
  144. package/bin/runners/lib/agent-firewall/policy/verdict.js +0 -54
  145. package/bin/runners/lib/agent-firewall/proposal/extractor.js +0 -394
  146. package/bin/runners/lib/agent-firewall/proposal/index.js +0 -212
  147. package/bin/runners/lib/agent-firewall/proposal/schema.js +0 -251
  148. package/bin/runners/lib/agent-firewall/proposal/validator.js +0 -386
  149. package/bin/runners/lib/agent-firewall/reality/index.js +0 -332
  150. package/bin/runners/lib/agent-firewall/reality/state.js +0 -625
  151. package/bin/runners/lib/agent-firewall/reality/watcher.js +0 -322
  152. package/bin/runners/lib/agent-firewall/risk/index.js +0 -173
  153. package/bin/runners/lib/agent-firewall/risk/scorer.js +0 -328
  154. package/bin/runners/lib/agent-firewall/risk/thresholds.js +0 -321
  155. package/bin/runners/lib/agent-firewall/risk/vectors.js +0 -421
  156. package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +0 -472
  157. package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +0 -346
  158. package/bin/runners/lib/agent-firewall/simulator/index.js +0 -181
  159. package/bin/runners/lib/agent-firewall/simulator/route-validator.js +0 -380
  160. package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +0 -661
  161. package/bin/runners/lib/agent-firewall/time-machine/index.js +0 -267
  162. package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +0 -436
  163. package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +0 -490
  164. package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +0 -530
  165. package/bin/runners/lib/agent-firewall/truthpack/index.js +0 -67
  166. package/bin/runners/lib/agent-firewall/truthpack/loader.js +0 -137
  167. package/bin/runners/lib/agent-firewall/unblock/planner.js +0 -337
  168. package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +0 -118
  169. package/bin/runners/lib/api-client.js +0 -269
  170. package/bin/runners/lib/audit-logger.js +0 -532
  171. package/bin/runners/lib/authority/authorities/architecture.js +0 -364
  172. package/bin/runners/lib/authority/authorities/compliance.js +0 -341
  173. package/bin/runners/lib/authority/authorities/human.js +0 -343
  174. package/bin/runners/lib/authority/authorities/quality.js +0 -420
  175. package/bin/runners/lib/authority/authorities/security.js +0 -228
  176. package/bin/runners/lib/authority/index.js +0 -293
  177. package/bin/runners/lib/authority-badge.js +0 -425
  178. package/bin/runners/lib/bundle/bundle-intelligence.js +0 -846
  179. package/bin/runners/lib/cli-charts.js +0 -368
  180. package/bin/runners/lib/cli-config-display.js +0 -405
  181. package/bin/runners/lib/cli-demo.js +0 -275
  182. package/bin/runners/lib/cli-errors.js +0 -438
  183. package/bin/runners/lib/cli-help-formatter.js +0 -439
  184. package/bin/runners/lib/cli-interactive-menu.js +0 -509
  185. package/bin/runners/lib/cli-prompts.js +0 -441
  186. package/bin/runners/lib/cli-scan-cards.js +0 -362
  187. package/bin/runners/lib/compliance-reporter.js +0 -710
  188. package/bin/runners/lib/conductor/index.js +0 -671
  189. package/bin/runners/lib/easy/README.md +0 -123
  190. package/bin/runners/lib/easy/index.js +0 -140
  191. package/bin/runners/lib/easy/interactive-wizard.js +0 -788
  192. package/bin/runners/lib/easy/one-click-firewall.js +0 -564
  193. package/bin/runners/lib/easy/zero-config-reality.js +0 -714
  194. package/bin/runners/lib/engines/accessibility-engine.js +0 -390
  195. package/bin/runners/lib/engines/api-consistency-engine.js +0 -467
  196. package/bin/runners/lib/engines/ast-cache.js +0 -99
  197. package/bin/runners/lib/engines/async-patterns-engine.js +0 -444
  198. package/bin/runners/lib/engines/bundle-size-engine.js +0 -433
  199. package/bin/runners/lib/engines/code-quality-engine.js +0 -255
  200. package/bin/runners/lib/engines/confidence-scoring.js +0 -276
  201. package/bin/runners/lib/engines/console-logs-engine.js +0 -115
  202. package/bin/runners/lib/engines/context-detection.js +0 -264
  203. package/bin/runners/lib/engines/cross-file-analysis-engine.js +0 -533
  204. package/bin/runners/lib/engines/database-patterns-engine.js +0 -429
  205. package/bin/runners/lib/engines/dead-code-engine.js +0 -198
  206. package/bin/runners/lib/engines/deprecated-api-engine.js +0 -226
  207. package/bin/runners/lib/engines/duplicate-code-engine.js +0 -354
  208. package/bin/runners/lib/engines/empty-catch-engine.js +0 -260
  209. package/bin/runners/lib/engines/env-variables-engine.js +0 -458
  210. package/bin/runners/lib/engines/error-handling-engine.js +0 -437
  211. package/bin/runners/lib/engines/false-positive-prevention.js +0 -630
  212. package/bin/runners/lib/engines/file-filter.js +0 -131
  213. package/bin/runners/lib/engines/framework-adapters/index.js +0 -607
  214. package/bin/runners/lib/engines/framework-detection.js +0 -508
  215. package/bin/runners/lib/engines/hardcoded-secrets-engine.js +0 -251
  216. package/bin/runners/lib/engines/import-order-engine.js +0 -429
  217. package/bin/runners/lib/engines/mock-data-engine.js +0 -315
  218. package/bin/runners/lib/engines/naming-conventions-engine.js +0 -544
  219. package/bin/runners/lib/engines/noise-reduction-engine.js +0 -452
  220. package/bin/runners/lib/engines/orchestrator.js +0 -334
  221. package/bin/runners/lib/engines/parallel-processor.js +0 -71
  222. package/bin/runners/lib/engines/performance-issues-engine.js +0 -405
  223. package/bin/runners/lib/engines/react-patterns-engine.js +0 -457
  224. package/bin/runners/lib/engines/security-vulnerabilities-engine.js +0 -571
  225. package/bin/runners/lib/engines/todo-fixme-engine.js +0 -115
  226. package/bin/runners/lib/engines/type-aware-engine.js +0 -376
  227. package/bin/runners/lib/engines/unsafe-regex-engine.js +0 -225
  228. package/bin/runners/lib/engines/vibecheck-engines/README.md +0 -53
  229. package/bin/runners/lib/engines/vibecheck-engines/index.js +0 -124
  230. package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +0 -806
  231. package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +0 -439
  232. package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +0 -577
  233. package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +0 -543
  234. package/bin/runners/lib/engines/vibecheck-engines/package.json +0 -13
  235. package/bin/runners/lib/engines/vibecheck-engines.js +0 -514
  236. package/bin/runners/lib/enhanced-features/index.js +0 -305
  237. package/bin/runners/lib/enhanced-output.js +0 -631
  238. package/bin/runners/lib/enterprise.js +0 -300
  239. package/bin/runners/lib/exit-codes.js +0 -275
  240. package/bin/runners/lib/fingerprint.js +0 -377
  241. package/bin/runners/lib/firewall/command-validator.js +0 -351
  242. package/bin/runners/lib/firewall/config.js +0 -341
  243. package/bin/runners/lib/firewall/content-validator.js +0 -519
  244. package/bin/runners/lib/firewall/index.js +0 -101
  245. package/bin/runners/lib/firewall/path-validator.js +0 -256
  246. package/bin/runners/lib/help-formatter.js +0 -413
  247. package/bin/runners/lib/intelligence/cross-repo-intelligence.js +0 -817
  248. package/bin/runners/lib/logger.js +0 -38
  249. package/bin/runners/lib/mcp-utils.js +0 -425
  250. package/bin/runners/lib/output/index.js +0 -1022
  251. package/bin/runners/lib/policy-engine.js +0 -652
  252. package/bin/runners/lib/polish/autofix/accessibility-fixes.js +0 -333
  253. package/bin/runners/lib/polish/autofix/async-handlers.js +0 -273
  254. package/bin/runners/lib/polish/autofix/dead-code.js +0 -280
  255. package/bin/runners/lib/polish/autofix/imports-optimizer.js +0 -344
  256. package/bin/runners/lib/polish/autofix/index.js +0 -200
  257. package/bin/runners/lib/polish/autofix/remove-consoles.js +0 -209
  258. package/bin/runners/lib/polish/autofix/strengthen-types.js +0 -245
  259. package/bin/runners/lib/polish/backend-checks.js +0 -148
  260. package/bin/runners/lib/polish/documentation-checks.js +0 -111
  261. package/bin/runners/lib/polish/frontend-checks.js +0 -168
  262. package/bin/runners/lib/polish/index.js +0 -71
  263. package/bin/runners/lib/polish/infrastructure-checks.js +0 -131
  264. package/bin/runners/lib/polish/library-detection.js +0 -175
  265. package/bin/runners/lib/polish/performance-checks.js +0 -100
  266. package/bin/runners/lib/polish/security-checks.js +0 -148
  267. package/bin/runners/lib/polish/utils.js +0 -203
  268. package/bin/runners/lib/prompt-builder.js +0 -540
  269. package/bin/runners/lib/proof-certificate.js +0 -634
  270. package/bin/runners/lib/reality/accessibility-audit.js +0 -946
  271. package/bin/runners/lib/reality/api-contract-validator.js +0 -1012
  272. package/bin/runners/lib/reality/chaos-engineering.js +0 -1084
  273. package/bin/runners/lib/reality/performance-tracker.js +0 -1077
  274. package/bin/runners/lib/reality/scenario-generator.js +0 -1404
  275. package/bin/runners/lib/reality/visual-regression.js +0 -852
  276. package/bin/runners/lib/reality-profiler.js +0 -717
  277. package/bin/runners/lib/replay/flight-recorder-viewer.js +0 -1160
  278. package/bin/runners/lib/review/ai-code-review.js +0 -832
  279. package/bin/runners/lib/rules/custom-rule-engine.js +0 -985
  280. package/bin/runners/lib/sbom-generator.js +0 -641
  281. package/bin/runners/lib/scan-output-enhanced.js +0 -512
  282. package/bin/runners/lib/security/owasp-scanner.js +0 -939
  283. package/bin/runners/lib/ship-output-enterprise.js +0 -239
  284. package/bin/runners/lib/unified-cli-output.js +0 -777
  285. package/bin/runners/lib/validators/contract-validator.js +0 -283
  286. package/bin/runners/lib/validators/dead-export-detector.js +0 -279
  287. package/bin/runners/lib/validators/dep-audit.js +0 -245
  288. package/bin/runners/lib/validators/env-validator.js +0 -319
  289. package/bin/runners/lib/validators/index.js +0 -120
  290. package/bin/runners/lib/validators/license-checker.js +0 -252
  291. package/bin/runners/lib/validators/route-validator.js +0 -290
  292. package/bin/runners/runAgent.d.ts +0 -5
  293. package/bin/runners/runAgent.js +0 -164
  294. package/bin/runners/runApprove.js +0 -1233
  295. package/bin/runners/runAuthority.js +0 -528
  296. package/bin/runners/runClassify.js +0 -862
  297. package/bin/runners/runConductor.js +0 -772
  298. package/bin/runners/runContainer.js +0 -366
  299. package/bin/runners/runContext.d.ts +0 -4
  300. package/bin/runners/runEasy.js +0 -410
  301. package/bin/runners/runFirewall.d.ts +0 -5
  302. package/bin/runners/runFirewall.js +0 -137
  303. package/bin/runners/runFirewallHook.d.ts +0 -5
  304. package/bin/runners/runFirewallHook.js +0 -59
  305. package/bin/runners/runIaC.js +0 -372
  306. package/bin/runners/runPolish.d.ts +0 -4
  307. package/bin/runners/runProof.zip +0 -0
  308. package/bin/runners/runTruth.d.ts +0 -5
  309. package/bin/runners/runTruth.js +0 -104
  310. package/bin/runners/runVibe.js +0 -791
  311. package/mcp-server/HARDENING_SUMMARY.md +0 -299
  312. package/mcp-server/agent-firewall-interceptor.js +0 -500
  313. package/mcp-server/authority-tools.js +0 -569
  314. package/mcp-server/conductor/conflict-resolver.js +0 -588
  315. package/mcp-server/conductor/execution-planner.js +0 -544
  316. package/mcp-server/conductor/index.js +0 -377
  317. package/mcp-server/conductor/lock-manager.js +0 -615
  318. package/mcp-server/conductor/request-queue.js +0 -550
  319. package/mcp-server/conductor/session-manager.js +0 -500
  320. package/mcp-server/conductor/tools.js +0 -510
  321. package/mcp-server/lib/api-client.cjs +0 -13
  322. package/mcp-server/lib/logger.cjs +0 -30
  323. package/mcp-server/logger.js +0 -173
  324. package/mcp-server/tools-v3.js +0 -1039
  325. package/mcp-server/tools.js +0 -495
  326. package/mcp-server/vibecheck-mcp-server-3.2.0.tgz +0 -0
@@ -1,571 +1,545 @@
1
1
  /**
2
- * Enterprise Scan Output - Premium Format
3
- * Features:
4
- * - "SCAN" ASCII Art
5
- * - Fixed alignment tables
6
- * - "Redacted" Upsell section for Scan context
2
+ * Scan Output - Premium Scan Results Display
3
+ *
4
+ * Handles all scan output formatting:
5
+ * - Layer status display
6
+ * - Findings grouped by category
7
+ * - Coverage maps
8
+ * - JSON/SARIF export
7
9
  */
8
10
 
9
- // Use ANSI codes directly (chalk v5 is ESM-only, this is CommonJS)
10
- const ESC = '\x1b';
11
- const chalk = {
12
- reset: `${ESC}[0m`,
13
- bold: `${ESC}[1m`,
14
- dim: `${ESC}[2m`,
15
- red: `${ESC}[31m`,
16
- green: `${ESC}[32m`,
17
- yellow: `${ESC}[33m`,
18
- cyan: `${ESC}[36m`,
19
- magenta: `${ESC}[35m`,
20
- white: `${ESC}[37m`,
21
- gray: `${ESC}[90m`,
22
- };
11
+ const {
12
+ ansi,
13
+ colors,
14
+ box,
15
+ icons,
16
+ renderScoreCard,
17
+ renderFindingsList,
18
+ renderSection,
19
+ renderDivider,
20
+ renderTable,
21
+ formatDuration,
22
+ formatNumber,
23
+ truncate,
24
+ } = require('./terminal-ui');
23
25
 
24
26
  // ═══════════════════════════════════════════════════════════════════════════════
25
- // CONFIGURATION
27
+ // LAYER STATUS DISPLAY
26
28
  // ═══════════════════════════════════════════════════════════════════════════════
27
29
 
28
- const WIDTH = 76;
29
-
30
- const BOX = {
31
- topLeft: '', topRight: '╗', bottomLeft: '╚', bottomRight: '╝',
32
- horizontal: '═', vertical: '║',
33
- teeRight: '╠', teeLeft: '╣',
34
-
35
- // Table Borders (Light)
36
- tTopLeft: '┌', tTopRight: '', tBottomLeft: '', tBottomRight: '',
37
- tHorizontal: '', tVertical: '',
38
- tTeeTop: '┬', tTeeBottom: '', tTee: '', tTeeLeft: '', tTeeRight: '┤'
39
- };
40
-
41
- const LOGO_SCAN = `
42
- █████████ █████████ █████████ █████████
43
- ███░░░░░███ ███░░░░░███ ███░░░░░███ ███░░░░░███
44
- ░███ ░░░ ░███ ░░░ ░███ ░███ ░███ ░███
45
- ░░█████████ ░███ ░███████████ ░███ ░███
46
- ░░░░░░░░███░███ ░███░░░░░███ ░███ ░███
47
- ███ ░███░███ ███ ░███ ░███ ░███ ░███
48
- ░░█████████ ░░█████████ ░███ ░███ ░███ ░███
49
- ░░░░░░░░░ ░░░░░░░░░ ░░░░ ░░░░ ░░░░ ░░░░
50
- `;
51
-
52
- // Column widths for the table (Must correspond to math below)
53
- const COL_1 = 10; // Severity
54
- const COL_2 = 13; // Component
55
- const COL_3 = 41; // Message
30
+ function renderLayers(layers) {
31
+ const lines = [];
32
+ lines.push(renderSection('ANALYSIS LAYERS', '⚡'));
33
+ lines.push('');
34
+
35
+ const layerConfig = {
36
+ ast: { name: 'AST Analysis', icon: '🔍', desc: 'Static code parsing' },
37
+ truth: { name: 'Build Truth', icon: '📦', desc: 'Manifest verification' },
38
+ reality: { name: 'Reality Check', icon: '🎭', desc: 'Playwright runtime' },
39
+ realitySniff: { name: 'Reality Sniff', icon: '🔬', desc: 'AI artifact detection' },
40
+ detection: { name: 'Detection Engines', icon: '🛡️', desc: 'Security patterns' },
41
+ };
42
+
43
+ for (const layer of layers) {
44
+ const config = layerConfig[layer.name] || { name: layer.name, icon: '○', desc: '' };
45
+
46
+ let status, statusColor;
47
+ if (layer.skipped) {
48
+ status = `${ansi.dim}○ skipped${ansi.reset}`;
49
+ } else if (layer.error) {
50
+ status = `${colors.error}${icons.error} error${ansi.reset}`;
51
+ } else {
52
+ status = `${colors.success}${icons.success}${ansi.reset}`;
53
+ }
54
+
55
+ const duration = layer.duration ? `${ansi.dim}${layer.duration}ms${ansi.reset}` : '';
56
+ const findings = layer.findings !== undefined ? `${colors.accent}${layer.findings}${ansi.reset} ${ansi.dim}findings${ansi.reset}` : '';
57
+
58
+ lines.push(` ${status} ${config.icon} ${config.name.padEnd(20)} ${duration.padEnd(15)} ${findings}`);
59
+ }
60
+
61
+ return lines.join('\n');
62
+ }
56
63
 
57
64
  // ═══════════════════════════════════════════════════════════════════════════════
58
- // UTILITIES
65
+ // COVERAGE MAP DISPLAY
59
66
  // ═══════════════════════════════════════════════════════════════════════════════
60
67
 
61
- function padCenter(str, width) {
62
- const visibleLen = str.replace(/\u001b\[\d+m/g, '').length;
63
- const padding = Math.max(0, width - visibleLen);
64
- const left = Math.floor(padding / 2);
65
- const right = padding - left;
66
- return ' '.repeat(left) + str + ' '.repeat(right);
67
- }
68
-
69
- function padRight(str, len) {
70
- const visibleLen = str.length; // Simplified for this usage
71
- const truncated = visibleLen > len ? str.substring(0, len - 3) + '...' : str;
72
- return truncated + ' '.repeat(Math.max(0, len - truncated.length));
68
+ function renderCoverageMap(coverage) {
69
+ if (!coverage) return '';
70
+
71
+ const lines = [];
72
+ lines.push(renderSection('ROUTE COVERAGE', '🗺️'));
73
+ lines.push('');
74
+
75
+ const pct = coverage.coveragePercent || 0;
76
+ const color = pct >= 80 ? colors.success : pct >= 60 ? colors.warning : colors.error;
77
+
78
+ // Coverage bar
79
+ const barWidth = 50;
80
+ const filled = Math.round((pct / 100) * barWidth);
81
+ const bar = `${color}${'█'.repeat(filled)}${ansi.dim}${'░'.repeat(barWidth - filled)}${ansi.reset}`;
82
+
83
+ lines.push(` ${color}${ansi.bold}${pct}%${ansi.reset} ${ansi.dim}of routes reachable from${ansi.reset} ${colors.accent}/${ansi.reset}`);
84
+ lines.push(` ${bar}`);
85
+ lines.push('');
86
+
87
+ // Stats
88
+ const stats = [
89
+ ['Total Routes', coverage.totalRoutes || 0],
90
+ ['Reachable', coverage.reachableFromRoot || 0],
91
+ ['Orphaned', coverage.orphanedRoutes || 0],
92
+ ['Dead Links', coverage.deadLinks || 0],
93
+ ];
94
+
95
+ for (const [label, value] of stats) {
96
+ const valueColor = label === 'Orphaned' || label === 'Dead Links'
97
+ ? (value > 0 ? colors.error : colors.success)
98
+ : ansi.reset;
99
+ lines.push(` ${ansi.dim}${label}:${ansi.reset} ${valueColor}${ansi.bold}${value}${ansi.reset}`);
100
+ }
101
+
102
+ // Isolated clusters
103
+ if (coverage.isolatedClusters?.length > 0) {
104
+ lines.push('');
105
+ lines.push(` ${colors.warning}${icons.warning}${ansi.reset} ${ansi.dim}Isolated clusters:${ansi.reset}`);
106
+ for (const cluster of coverage.isolatedClusters.slice(0, 3)) {
107
+ const auth = cluster.requiresAuth ? ` ${ansi.dim}(auth required)${ansi.reset}` : '';
108
+ lines.push(` ${ansi.dim}├─${ansi.reset} ${ansi.bold}${cluster.name}${ansi.reset}${auth} ${ansi.dim}(${cluster.nodeIds?.length || 0} routes)${ansi.reset}`);
109
+ }
110
+ if (coverage.isolatedClusters.length > 3) {
111
+ lines.push(` ${ansi.dim}└─ ... and ${coverage.isolatedClusters.length - 3} more clusters${ansi.reset}`);
112
+ }
113
+ }
114
+
115
+ // Unreachable routes
116
+ if (coverage.unreachableRoutes?.length > 0) {
117
+ lines.push('');
118
+ lines.push(` ${colors.error}${icons.error}${ansi.reset} ${ansi.dim}Unreachable routes:${ansi.reset}`);
119
+ for (const route of coverage.unreachableRoutes.slice(0, 5)) {
120
+ lines.push(` ${ansi.dim}├─${ansi.reset} ${colors.error}${route}${ansi.reset}`);
121
+ }
122
+ if (coverage.unreachableRoutes.length > 5) {
123
+ lines.push(` ${ansi.dim}└─ ... and ${coverage.unreachableRoutes.length - 5} more${ansi.reset}`);
124
+ }
125
+ }
126
+
127
+ return lines.join('\n');
73
128
  }
74
129
 
75
- function padLogoBlock(ascii, width) {
76
- const lines = ascii.trim().split('\n');
77
- const maxContentWidth = Math.max(...lines.map(l => l.length));
78
-
79
- return lines.map(line => {
80
- const solidLine = line + ' '.repeat(maxContentWidth - line.length);
81
- return padCenter(solidLine, width);
82
- });
83
- }
130
+ // ═══════════════════════════════════════════════════════════════════════════════
131
+ // BREAKDOWN DISPLAY
132
+ // ═══════════════════════════════════════════════════════════════════════════════
84
133
 
85
- function renderProgressBar(score, width = 20) {
86
- const filled = Math.round((score / 100) * width);
87
- const bar = `${chalk.green}█${chalk.reset}`.repeat(filled) + `${chalk.gray}░${chalk.reset}`.repeat(width - filled);
88
- return bar;
134
+ function renderBreakdown(breakdown) {
135
+ if (!breakdown) return '';
136
+
137
+ const lines = [];
138
+ lines.push(renderSection('SCORE BREAKDOWN', '📊'));
139
+ lines.push('');
140
+
141
+ const items = [
142
+ { key: 'deadLinks', label: 'Dead Links', icon: '🔗' },
143
+ { key: 'orphanRoutes', label: 'Orphan Routes', icon: '👻' },
144
+ { key: 'runtimeFailures', label: 'Runtime 404s', icon: '💥' },
145
+ { key: 'unresolvedDynamic', label: 'Unresolved Dynamic', icon: '❓' },
146
+ { key: 'placeholders', label: 'Placeholders', icon: '📝' },
147
+ { key: 'secretsExposed', label: 'Secrets Exposed', icon: '🔐' },
148
+ { key: 'authBypass', label: 'Auth Bypass', icon: '🚪' },
149
+ { key: 'mockData', label: 'Mock Data', icon: '🎭' },
150
+ ];
151
+
152
+ for (const item of items) {
153
+ const data = breakdown[item.key];
154
+ if (!data && data !== 0) continue;
155
+
156
+ const count = typeof data === 'object' ? data.count : data;
157
+ const penalty = typeof data === 'object' ? data.penalty : 0;
158
+
159
+ const status = count === 0 ? `${colors.success}${icons.success}${ansi.reset}` : `${colors.error}${icons.error}${ansi.reset}`;
160
+ const countColor = count === 0 ? colors.success : colors.error;
161
+ const penaltyStr = penalty > 0 ? `${ansi.dim}-${penalty} pts${ansi.reset}` : `${ansi.dim} ---${ansi.reset}`;
162
+
163
+ lines.push(` ${status} ${item.icon} ${item.label.padEnd(22)} ${countColor}${ansi.bold}${String(count).padStart(3)}${ansi.reset} ${penaltyStr}`);
164
+ }
165
+
166
+ return lines.join('\n');
89
167
  }
90
168
 
91
169
  // ═══════════════════════════════════════════════════════════════════════════════
92
- // MAIN FORMATTER
170
+ // BLOCKERS DISPLAY
93
171
  // ═══════════════════════════════════════════════════════════════════════════════
94
172
 
95
- function formatScanOutput(result, options = {}) {
96
- // Extract data from various possible structures
97
- let score = 0;
98
- let findings = [];
99
- let duration = 0;
100
- let scannedFiles = 0;
101
-
102
- // Helper function to calculate score from findings
103
- function calculateScoreFromFindings(findings) {
104
- if (!findings || findings.length === 0) return 100;
105
-
106
- // Count by severity (handle various severity formats)
107
- const severityCounts = {
108
- critical: 0,
109
- high: 0,
110
- medium: 0,
111
- low: 0,
112
- info: 0,
113
- };
173
+ function renderBlockers(blockers, options = {}) {
174
+ const { maxItems = 8 } = options;
175
+
176
+ if (!blockers || blockers.length === 0) {
177
+ const lines = [];
178
+ lines.push(renderSection('SHIP BLOCKERS', '🚀'));
179
+ lines.push('');
180
+ lines.push(` ${colors.success}${ansi.bold}${icons.success} No blockers! You're clear to ship.${ansi.reset}`);
181
+ return lines.join('\n');
182
+ }
183
+
184
+ const lines = [];
185
+ lines.push(renderSection(`SHIP BLOCKERS (${blockers.length})`, '🚨'));
186
+ lines.push('');
187
+
188
+ for (const blocker of blockers.slice(0, maxItems)) {
189
+ const sevColor = blocker.severity === 'critical' || blocker.severity === 'BLOCK'
190
+ ? colors.bg.error
191
+ : colors.bg.warning;
192
+ const sevLabel = blocker.severity === 'critical' || blocker.severity === 'BLOCK'
193
+ ? 'CRITICAL'
194
+ : ' HIGH ';
114
195
 
115
- findings.forEach(f => {
116
- const sev = (f.severity || '').toLowerCase();
117
- if (sev === 'critical' || sev === 'block') {
118
- severityCounts.critical++;
119
- } else if (sev === 'high') {
120
- severityCounts.high++;
121
- } else if (sev === 'medium' || sev === 'warn' || sev === 'warning') {
122
- severityCounts.medium++;
123
- } else if (sev === 'low') {
124
- severityCounts.low++;
125
- } else if (sev === 'info') {
126
- severityCounts.info++;
127
- } else {
128
- // Default unknown severities to medium
129
- severityCounts.medium++;
130
- }
131
- });
196
+ lines.push(` ${sevColor}${ansi.bold} ${sevLabel} ${ansi.reset} ${ansi.bold}${truncate(blocker.title || blocker.message, 45)}${ansi.reset}`);
132
197
 
133
- // Calculate score with proper weights
134
- // Critical: 25 points each, High: 15, Medium: 5, Low: 2, Info: 0
135
- const deductions =
136
- (severityCounts.critical * 25) +
137
- (severityCounts.high * 15) +
138
- (severityCounts.medium * 5) +
139
- (severityCounts.low * 2);
198
+ if (blocker.description) {
199
+ lines.push(` ${ansi.dim}${truncate(blocker.description, 55)}${ansi.reset}`);
200
+ }
140
201
 
141
- // Cap deductions to prevent negative scores, but allow score to go to 0
142
- const calculatedScore = Math.max(0, 100 - deductions);
202
+ if (blocker.file) {
203
+ const fileDisplay = blocker.file + (blocker.line ? `:${blocker.line}` : '');
204
+ lines.push(` ${colors.accent}${truncate(fileDisplay, 50)}${ansi.reset}`);
205
+ }
143
206
 
144
- // If we have findings but score is still 100, something's wrong - recalculate more aggressively
145
- if (findings.length > 0 && calculatedScore === 100 && deductions === 0) {
146
- // All findings were info or unknown - still deduct something
147
- return Math.max(50, 100 - (findings.length * 0.1));
207
+ if (blocker.fix || blocker.fixSuggestion) {
208
+ lines.push(` ${colors.success}→ ${truncate(blocker.fix || blocker.fixSuggestion, 50)}${ansi.reset}`);
148
209
  }
149
210
 
150
- return Math.round(calculatedScore);
211
+ lines.push('');
151
212
  }
152
213
 
153
- // Handle different input structures
154
- if (result.verdict) {
155
- // New unified output structure
156
- if (typeof result.verdict === 'object') {
157
- findings = result.findings || [];
158
-
159
- // Always recalculate score from findings to ensure accuracy
160
- score = calculateScoreFromFindings(findings);
161
-
162
- // Only use provided score if it seems reasonable (not 100 when we have findings)
163
- if (result.verdict.score && findings.length === 0) {
164
- score = result.verdict.score;
165
- } else if (result.verdict.score && result.verdict.score < score) {
166
- // Use the lower (worse) score if provided score is worse
167
- score = result.verdict.score;
168
- }
169
-
170
- duration = result.timings?.total || result.duration || 0;
171
- scannedFiles = result.timings?.filesScanned || result.scannedFiles || findings.length || 0;
172
- } else {
173
- // Legacy structure where verdict is just a string
174
- findings = result.findings || [];
175
- score = calculateScoreFromFindings(findings);
176
-
177
- // Fallback to verdict string if no findings
178
- if (!score && findings.length === 0) {
179
- const verdictStr = result.verdict;
180
- if (verdictStr === 'PASS' || verdictStr === 'SHIP') score = 100;
181
- else if (verdictStr === 'WARN') score = 70;
182
- else if (verdictStr === 'FAIL' || verdictStr === 'BLOCK') score = 40;
183
- }
184
-
185
- duration = result.timings?.total || result.duration || 0;
186
- scannedFiles = result.timings?.filesScanned || result.scannedFiles || findings.length || 0;
187
- }
188
- } else {
189
- // Direct structure
190
- findings = result.findings || [];
191
- score = calculateScoreFromFindings(findings);
192
-
193
- // Only use provided score if it seems reasonable
194
- if (result.score && findings.length === 0) {
195
- score = result.score;
196
- } else if (result.score && result.score < score) {
197
- score = result.score; // Use worse score
214
+ if (blockers.length > maxItems) {
215
+ lines.push(` ${ansi.dim}... and ${blockers.length - maxItems} more blockers${ansi.reset}`);
216
+ lines.push('');
217
+ }
218
+
219
+ return lines.join('\n');
220
+ }
221
+
222
+ // ═══════════════════════════════════════════════════════════════════════════════
223
+ // CATEGORY SUMMARY
224
+ // ═══════════════════════════════════════════════════════════════════════════════
225
+
226
+ function renderCategorySummary(findings) {
227
+ if (!findings || findings.length === 0) return '';
228
+
229
+ // Group by category
230
+ const categories = {};
231
+ for (const f of findings) {
232
+ const cat = f.category || f.ruleId?.split('/')[0] || 'other';
233
+ if (!categories[cat]) {
234
+ categories[cat] = { critical: 0, high: 0, medium: 0, low: 0, total: 0 };
198
235
  }
236
+ categories[cat].total++;
199
237
 
200
- duration = result.duration || result.timings?.total || 0;
201
- scannedFiles = result.scannedFiles || result.timings?.filesScanned || 0;
238
+ const sev = (f.severity || '').toLowerCase();
239
+ if (sev === 'critical' || sev === 'block') categories[cat].critical++;
240
+ else if (sev === 'high') categories[cat].high++;
241
+ else if (sev === 'medium' || sev === 'warn' || sev === 'warning') categories[cat].medium++;
242
+ else categories[cat].low++;
202
243
  }
203
-
204
- const hasIssues = findings.length > 0;
205
- const heapMB = Math.round(process.memoryUsage().heapUsed / 1024 / 1024);
244
+
206
245
  const lines = [];
207
-
208
- // 1. OUTER FRAME TOP
209
- lines.push(`${chalk.gray}${BOX.topLeft}${BOX.horizontal.repeat(WIDTH - 2)}${BOX.topRight}${chalk.reset}`);
210
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${' '.repeat(WIDTH - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
211
-
212
- // 2. LOGO (Cyan if clean, Red if issues)
213
- const logoColorCode = hasIssues ? chalk.red : chalk.cyan;
214
- padLogoBlock(LOGO_SCAN, WIDTH - 2).forEach(line => {
215
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${logoColorCode}${line}${chalk.reset}${chalk.gray}${BOX.vertical}${chalk.reset}`);
216
- });
217
-
218
- const subTitle = hasIssues ? 'INTEGRITY SCAN • ISSUES DETECTED' : 'INTEGRITY SCAN • CLEAN';
219
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${padCenter(`${chalk.bold}${chalk.white}${subTitle}${chalk.reset}`, WIDTH - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
220
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${' '.repeat(WIDTH - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
221
-
222
- // 3. TELEMETRY
223
- lines.push(`${chalk.gray}${BOX.teeRight}${BOX.horizontal.repeat(WIDTH - 2)}${BOX.teeLeft}${chalk.reset}`);
224
- const stats = `📡 TELEMETRY │ ⏱ ${duration}ms │ 📂 ${scannedFiles} Files │ 📦 ${heapMB}MB`;
225
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${padCenter(stats, WIDTH - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
226
- lines.push(`${chalk.gray}${BOX.teeRight}${BOX.horizontal.repeat(WIDTH - 2)}${BOX.teeLeft}${chalk.reset}`);
227
-
228
- if (!hasIssues) {
229
- // -- CLEAN STATE --
230
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${' '.repeat(WIDTH - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
231
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${padCenter(`${chalk.green}✅ NO STATIC ISSUES FOUND${chalk.reset}`, WIDTH - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
232
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${' '.repeat(WIDTH - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
233
- } else {
234
- // -- ISSUES STATE --
235
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${' '.repeat(WIDTH - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
236
- const scoreBar = renderProgressBar(score, 20);
237
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${padCenter(`HEALTH SCORE [${scoreBar}] ${score} / 100`, WIDTH - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
238
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${' '.repeat(WIDTH - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
239
-
240
- // 4. FINDINGS SUMMARY BY CATEGORY
241
- const categoryCounts = {};
242
- findings.forEach(f => {
243
- const cat = f.category || f.type || f.ruleId || 'Other';
244
- categoryCounts[cat] = (categoryCounts[cat] || 0) + 1;
246
+ lines.push(renderSection('FINDINGS BY CATEGORY', '📁'));
247
+ lines.push('');
248
+
249
+ const categoryIcons = {
250
+ ROUTE: '🔗',
251
+ AUTH: '🔐',
252
+ SECRET: '🔑',
253
+ BILLING: '💳',
254
+ MOCK: '🎭',
255
+ DEAD_UI: '👻',
256
+ FAKE_SUCCESS: '✨',
257
+ REALITY: '🔬',
258
+ QUALITY: '📋',
259
+ CONFIG: '⚙️',
260
+ };
261
+
262
+ const sortedCategories = Object.entries(categories)
263
+ .sort((a, b) => {
264
+ // Sort by criticality: critical > high > medium > low
265
+ const aCrit = a[1].critical * 1000 + a[1].high * 100 + a[1].medium * 10;
266
+ const bCrit = b[1].critical * 1000 + b[1].high * 100 + b[1].medium * 10;
267
+ return bCrit - aCrit;
245
268
  });
269
+
270
+ for (const [cat, counts] of sortedCategories) {
271
+ const icon = categoryIcons[cat.toUpperCase()] || '•';
272
+ const critStr = counts.critical > 0 ? `${colors.critical}${counts.critical}C${ansi.reset} ` : '';
273
+ const highStr = counts.high > 0 ? `${colors.high}${counts.high}H${ansi.reset} ` : '';
274
+ const medStr = counts.medium > 0 ? `${colors.medium}${counts.medium}M${ansi.reset} ` : '';
275
+ const lowStr = counts.low > 0 ? `${colors.low}${counts.low}L${ansi.reset}` : '';
246
276
 
247
- const categoryLabels = {
248
- 'EnvContract': '🔐 Env',
249
- 'MissingRoute': '🔗 Routes',
250
- 'GhostAuth': '🛡️ Auth',
251
- 'FakeSuccess': '⚠️ Fake',
252
- 'MockData': '🎭 Mocks',
253
- 'Secrets': '🔑 Secrets',
254
- 'ConsoleLog': '📝 Logs',
255
- 'TodoFixme': '📋 TODOs',
256
- 'CodeQuality': '✨ Quality',
257
- 'Security': '⚡ Security',
258
- 'Performance': '⚡ Perf',
259
- };
260
-
261
- const summaryItems = Object.entries(categoryCounts)
262
- .sort((a, b) => b[1] - a[1])
263
- .slice(0, 6)
264
- .map(([cat, count]) => {
265
- const label = categoryLabels[cat] || cat;
266
- return `${label}: ${chalk.bold}${count}${chalk.reset}`;
267
- });
268
-
269
- if (summaryItems.length > 0) {
270
- // Simple left-aligned format with consistent spacing
271
- const indent = ' ';
272
-
273
- // Always split into 2 rows of 3 for clean alignment
274
- const row1 = summaryItems.slice(0, 3).join(' ');
275
- const row2 = summaryItems.slice(3, 6).join(' ');
276
-
277
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${indent}${chalk.dim}FINDINGS:${chalk.reset} ${row1}${' '.repeat(Math.max(0, WIDTH - row1.length - 14))}${chalk.gray}${BOX.vertical}${chalk.reset}`);
278
- if (row2) {
279
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${indent}${' '.repeat(10)}${row2}${' '.repeat(Math.max(0, WIDTH - row2.length - 14))}${chalk.gray}${BOX.vertical}${chalk.reset}`);
280
- }
281
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${' '.repeat(WIDTH - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
282
- }
283
-
284
- // 5. FINDINGS TABLE
285
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${padCenter('DETECTED VULNERABILITIES (STATIC)', WIDTH - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
286
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${' '.repeat(WIDTH - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
277
+ lines.push(` ${icon} ${cat.padEnd(15)} ${ansi.dim}${String(counts.total).padStart(3)} total${ansi.reset} ${critStr}${highStr}${medStr}${lowStr}`);
278
+ }
279
+
280
+ return lines.join('\n');
281
+ }
287
282
 
288
- // Table Construction - Fixed alignment with proper spacing
289
- const C1 = COL_1, C2 = COL_2, C3 = COL_3;
290
- const tableContentWidth = C1 + C2 + C3 + 7; // 7 = borders (3 vertical + 4 spaces)
291
- const tableLeftPad = Math.floor((WIDTH - 2 - tableContentWidth) / 2);
292
- const tablePad = ' '.repeat(Math.max(1, tableLeftPad)); // At least 1 space padding
293
-
294
- // Table borders without padding (we'll add padding when inserting into frame)
295
- const tTop = `${BOX.tTopLeft}${BOX.tHorizontal.repeat(C1)}${BOX.tTeeTop}${BOX.tHorizontal.repeat(C2)}${BOX.tTeeTop}${BOX.tHorizontal.repeat(C3)}${BOX.tTopRight}`;
296
- const tMid = `${BOX.tTeeLeft}${BOX.tHorizontal.repeat(C1)}${BOX.tTee}${BOX.tHorizontal.repeat(C2)}${BOX.tTee}${BOX.tHorizontal.repeat(C3)}${BOX.tTeeRight}`;
297
- const tBot = `${BOX.tBottomLeft}${BOX.tHorizontal.repeat(C1)}${BOX.tTeeBottom}${BOX.tHorizontal.repeat(C2)}${BOX.tTeeBottom}${BOX.tHorizontal.repeat(C3)}${BOX.tBottomRight}`;
283
+ // ═══════════════════════════════════════════════════════════════════════════════
284
+ // FULL SCAN OUTPUT
285
+ // ═══════════════════════════════════════════════════════════════════════════════
298
286
 
299
- // Calculate right padding to fill the line
300
- const totalTableWidth = tablePad.length + tableContentWidth;
301
- const rightPad = WIDTH - 2 - totalTableWidth;
302
- const rightPadStr = ' '.repeat(Math.max(0, rightPad));
287
+ function formatScanOutput(result, options = {}) {
288
+ const { verbose = false, json = false } = options;
289
+
290
+ if (json) {
291
+ return JSON.stringify(result, null, 2);
292
+ }
293
+
294
+ const { verdict, findings = [], layers = [], coverage, breakdown, timings = {} } = result;
295
+
296
+ // Count findings by severity
297
+ const severityCounts = {
298
+ critical: findings.filter(f => f.severity === 'critical' || f.severity === 'BLOCK').length,
299
+ high: findings.filter(f => f.severity === 'high').length,
300
+ medium: findings.filter(f => f.severity === 'medium' || f.severity === 'WARN' || f.severity === 'warning').length,
301
+ low: findings.filter(f => f.severity === 'low' || f.severity === 'INFO' || f.severity === 'info').length,
302
+ };
303
+
304
+ // Calculate score
305
+ const score = verdict?.score ?? calculateScore(severityCounts);
306
+ const verdictStatus = verdict?.verdict || (severityCounts.critical > 0 ? 'BLOCK' : severityCounts.high > 0 ? 'WARN' : 'SHIP');
307
+
308
+ const lines = [];
309
+
310
+ // Score card
311
+ lines.push(renderScoreCard(score, {
312
+ verdict: verdictStatus,
313
+ findings: severityCounts,
314
+ duration: timings.total,
315
+ cached: result.cached,
316
+ }));
317
+
318
+ // Blockers (critical + high severity findings)
319
+ const blockers = findings.filter(f =>
320
+ f.severity === 'critical' || f.severity === 'BLOCK' || f.severity === 'high'
321
+ );
322
+ lines.push(renderBlockers(blockers));
323
+
324
+ // Category summary
325
+ if (findings.length > 0) {
326
+ lines.push(renderCategorySummary(findings));
327
+ }
328
+
329
+ // Verbose output
330
+ if (verbose) {
331
+ // Coverage map
332
+ if (coverage) {
333
+ lines.push('');
334
+ lines.push(renderCoverageMap(coverage));
335
+ }
303
336
 
304
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${tablePad}${chalk.gray}${tTop}${chalk.reset}${rightPadStr}${chalk.gray}${BOX.vertical}${chalk.reset}`);
305
- const header = `${tablePad}${BOX.tVertical}${padRight(' SEVERITY', C1)}${BOX.tVertical}${padRight(' TYPE', C2)}${BOX.tVertical}${padRight(' FINDING', C3)}${BOX.tVertical}`;
306
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${chalk.bold}${header}${chalk.reset}${rightPadStr}${chalk.gray}${BOX.vertical}${chalk.reset}`);
307
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${tablePad}${chalk.gray}${tMid}${chalk.reset}${rightPadStr}${chalk.gray}${BOX.vertical}${chalk.reset}`);
308
-
309
- // Rows - map findings to display format (show top 10)
310
- const topItems = findings.slice(0, 10);
311
- topItems.forEach(item => {
312
- // Handle different finding structures
313
- const severityText = item.severity === 'critical' || item.severity === 'BLOCK' || item.category === 'critical'
314
- ? '🛑 CRIT '
315
- : item.severity === 'warning' || item.severity === 'WARN' || item.category === 'warning'
316
- ? '🟡 WARN '
317
- : '🟡 WARN ';
318
-
319
- const severityColor = item.severity === 'critical' || item.severity === 'BLOCK' || item.category === 'critical'
320
- ? chalk.red
321
- : chalk.yellow;
322
-
323
- const severity = `${severityColor}${severityText}${chalk.reset}`;
324
-
325
- // Map category to readable type
326
- const categoryMap = {
327
- 'EnvContract': 'EnvVar',
328
- 'MissingRoute': 'Route',
329
- 'GhostAuth': 'Auth',
330
- 'FakeSuccess': 'FakeSuccess',
331
- 'MockData': 'MockData',
332
- 'Secrets': 'Secret',
333
- 'ConsoleLog': 'Console',
334
- 'TodoFixme': 'TODO',
335
- };
336
- const typeName = categoryMap[item.category] || item.category || item.type || item.ruleId || 'Unknown';
337
- const type = padRight(' ' + typeName, C2);
338
-
339
- // Truncate description if too long
340
- let desc = item.message || item.title || item.description || '';
341
- if (desc.length > C3 - 1) {
342
- desc = desc.substring(0, C3 - 4) + '...';
343
- }
344
- desc = padRight(' ' + desc, C3);
345
-
346
- const row = `${tablePad}${chalk.gray}${BOX.tVertical}${chalk.reset}${padRight(severity, C1)}${chalk.gray}${BOX.tVertical}${chalk.reset}${type}${chalk.gray}${BOX.tVertical}${chalk.reset}${desc}${chalk.gray}${BOX.tVertical}${chalk.reset}`;
347
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${row}${rightPadStr}${chalk.gray}${BOX.vertical}${chalk.reset}`);
348
- });
349
-
350
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${tablePad}${chalk.gray}${tBot}${chalk.reset}${rightPadStr}${chalk.gray}${BOX.vertical}${chalk.reset}`);
337
+ // Breakdown
338
+ if (breakdown) {
339
+ lines.push('');
340
+ lines.push(renderBreakdown(breakdown));
341
+ }
351
342
 
352
- // Show count if more findings exist
353
- if (findings.length > 10) {
354
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${padCenter(`${chalk.dim}... and ${findings.length - 10} more findings${chalk.reset}`, WIDTH - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
343
+ // Layers
344
+ if (layers.length > 0) {
345
+ lines.push('');
346
+ lines.push(renderLayers(layers));
355
347
  }
356
348
 
357
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${' '.repeat(WIDTH - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
349
+ // All findings
350
+ if (findings.length > 0) {
351
+ lines.push('');
352
+ lines.push(renderFindingsList(findings, { maxItems: 20, groupBySeverity: true }));
353
+ }
358
354
  }
359
-
360
- // 6. UPSELL SECTION - Autofix & Mission Packs
361
- lines.push(`${chalk.gray}${BOX.teeRight}${BOX.horizontal.repeat(WIDTH - 2)}${BOX.teeLeft}${chalk.reset}`);
362
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${' '.repeat(WIDTH - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
363
-
364
- // Autofix Upsell
365
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${padCenter(`${chalk.bold}${chalk.cyan}🚀 AUTO-FIX AVAILABLE${chalk.reset}`, WIDTH - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
366
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${padCenter(`${chalk.dim}Fix ${findings.length} issues automatically with AI-powered autofix${chalk.reset}`, WIDTH - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
367
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${padCenter(`Run ${chalk.cyan}vibecheck scan --autofix${chalk.reset} to apply fixes`, WIDTH - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
368
-
369
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${' '.repeat(WIDTH - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
370
355
 
371
- // Mission Packs Upsell
372
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${padCenter(`${chalk.bold}${chalk.magenta}📦 MISSION PACKS${chalk.reset}`, WIDTH - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
373
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${padCenter(`${chalk.dim}Get AI-generated fix plans grouped by feature area${chalk.reset}`, WIDTH - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
374
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${padCenter(`Run ${chalk.cyan}vibecheck fix --packs${chalk.reset} to generate mission packs`, WIDTH - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
375
-
376
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${' '.repeat(WIDTH - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
356
+ // Timing summary
357
+ if (timings.total) {
358
+ lines.push('');
359
+ lines.push(` ${ansi.dim}Completed in ${formatDuration(timings.total)}${ansi.reset}`);
360
+ }
377
361
 
378
- // Upgrade CTA
379
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${padCenter(`${chalk.bold}★ Upgrade for unlimited scans + auto-fix${chalk.reset} → ${chalk.cyan}https://vibecheckai.dev${chalk.reset}`, WIDTH - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
362
+ lines.push('');
363
+ return lines.join('\n');
364
+ }
380
365
 
381
- // BOTTOM FRAME
382
- lines.push(`${chalk.gray}${BOX.vertical}${chalk.reset}${' '.repeat(WIDTH - 2)}${chalk.gray}${BOX.vertical}${chalk.reset}`);
383
- lines.push(`${chalk.gray}${BOX.bottomLeft}${BOX.horizontal.repeat(WIDTH - 2)}${BOX.bottomRight}${chalk.reset}`);
366
+ // ═══════════════════════════════════════════════════════════════════════════════
367
+ // SCORE CALCULATION
368
+ // ═══════════════════════════════════════════════════════════════════════════════
384
369
 
385
- return lines.join('\n');
370
+ function calculateScore(severityCounts) {
371
+ const deductions =
372
+ (severityCounts.critical || 0) * 25 +
373
+ (severityCounts.high || 0) * 15 +
374
+ (severityCounts.medium || 0) * 5 +
375
+ (severityCounts.low || 0) * 1;
376
+
377
+ return Math.max(0, 100 - deductions);
386
378
  }
387
379
 
388
380
  // ═══════════════════════════════════════════════════════════════════════════════
389
- // ADDITIONAL EXPORTS (for compatibility with runScan.js)
381
+ // EXIT CODE DETERMINATION
390
382
  // ═══════════════════════════════════════════════════════════════════════════════
391
383
 
392
- // Exit codes
393
384
  const EXIT_CODES = {
394
- SHIP: 0,
395
- WARN: 1,
396
- BLOCK: 2,
397
- ERROR: 1,
398
- MISCONFIG: 3,
399
- INTERNAL: 1,
400
- };
401
-
402
- // Severity weights for scoring
403
- const SEVERITY_WEIGHTS = {
404
- critical: 25,
405
- high: 15,
406
- medium: 5,
407
- low: 1,
408
- info: 0,
385
+ SUCCESS: 0,
386
+ WARNING: 1,
387
+ FAILURE: 2,
388
+ ERROR: 3,
409
389
  };
410
390
 
411
- /**
412
- * Calculate overall score from severity counts
413
- */
414
- function calculateScore(severityCounts, totalFindings = 0) {
415
- if (totalFindings === 0) return 100;
416
-
417
- const deductions =
418
- (severityCounts.critical || 0) * SEVERITY_WEIGHTS.critical +
419
- (severityCounts.high || 0) * SEVERITY_WEIGHTS.high +
420
- (severityCounts.medium || 0) * SEVERITY_WEIGHTS.medium +
421
- (severityCounts.low || 0) * SEVERITY_WEIGHTS.low;
422
-
423
- // Cap deductions at 100
424
- const score = Math.max(0, 100 - deductions);
425
- return Math.round(score);
426
- }
427
-
428
- /**
429
- * Get exit code from verdict
430
- */
431
391
  function getExitCode(verdict) {
432
- if (!verdict) return EXIT_CODES.BLOCK;
433
- const v = typeof verdict === 'object' ? verdict.verdict : verdict;
434
- if (v === 'SHIP' || v === 'PASS') return EXIT_CODES.SHIP;
435
- if (v === 'WARN') return EXIT_CODES.WARN;
436
- return EXIT_CODES.BLOCK;
392
+ if (!verdict) return EXIT_CODES.ERROR;
393
+
394
+ const status = verdict.verdict || verdict;
395
+
396
+ switch (status) {
397
+ case 'PASS':
398
+ case 'SHIP':
399
+ return EXIT_CODES.SUCCESS;
400
+ case 'WARN':
401
+ return EXIT_CODES.WARNING;
402
+ case 'FAIL':
403
+ case 'BLOCK':
404
+ return EXIT_CODES.FAILURE;
405
+ default:
406
+ return EXIT_CODES.ERROR;
407
+ }
437
408
  }
438
409
 
439
- /**
440
- * Print error message
441
- */
410
+ // ═══════════════════════════════════════════════════════════════════════════════
411
+ // ERROR DISPLAY
412
+ // ═══════════════════════════════════════════════════════════════════════════════
413
+
442
414
  function printError(error, context = '') {
443
- const isConfig = error.message && (
444
- error.message.includes('config') ||
445
- error.message.includes('missing') ||
446
- error.message.includes('not found')
447
- );
448
- const exitCode = isConfig ? EXIT_CODES.MISCONFIG : EXIT_CODES.INTERNAL;
415
+ const prefix = context ? `${context}: ` : '';
416
+
417
+ console.error('');
418
+ console.error(` ${colors.error}${icons.error}${ansi.reset} ${ansi.bold}${prefix}${error.message || error}${ansi.reset}`);
449
419
 
450
- console.error(`${chalk.red}✗ Error${chalk.reset}`);
451
- if (context) console.error(` ${chalk.dim}${context}${chalk.reset}`);
452
- console.error(` ${error.message || error}`);
453
- if (error.stack && process.env.VIBECHECK_DEBUG) {
454
- console.error(`${chalk.dim}${error.stack}${chalk.reset}`);
420
+ if (error.code) {
421
+ console.error(` ${ansi.dim}Error code: ${error.code}${ansi.reset}`);
455
422
  }
456
423
 
457
- return exitCode;
424
+ if (error.suggestion || error.fix) {
425
+ console.error(` ${colors.success}${icons.arrowRight}${ansi.reset} ${error.suggestion || error.fix}`);
426
+ }
427
+
428
+ if (error.docs || error.helpUrl) {
429
+ console.error(` ${ansi.dim}See: ${error.docs || error.helpUrl}${ansi.reset}`);
430
+ }
431
+
432
+ console.error('');
433
+
434
+ // Return appropriate exit code
435
+ if (error.code === 'VALIDATION_ERROR') return EXIT_CODES.FAILURE;
436
+ if (error.code === 'LIMIT_EXCEEDED') return EXIT_CODES.WARNING;
437
+ return EXIT_CODES.ERROR;
458
438
  }
459
439
 
460
- /**
461
- * Format SARIF output (placeholder - full implementation in report-engine.js)
462
- */
440
+ // ═══════════════════════════════════════════════════════════════════════════════
441
+ // SARIF OUTPUT
442
+ // ═══════════════════════════════════════════════════════════════════════════════
443
+
463
444
  function formatSARIF(findings, options = {}) {
464
- // Basic SARIF structure - full implementation should be in report-engine.js
465
- return JSON.stringify({
466
- version: "2.1.0",
467
- $schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema.json",
445
+ const { projectPath = '.', version = '1.0.0' } = options;
446
+
447
+ const rules = new Map();
448
+ const results = [];
449
+
450
+ for (const f of findings) {
451
+ const ruleId = f.ruleId || f.id || `vibecheck/${f.category || 'general'}`;
452
+
453
+ if (!rules.has(ruleId)) {
454
+ rules.set(ruleId, {
455
+ id: ruleId,
456
+ name: f.category || 'general',
457
+ shortDescription: { text: f.title || f.message },
458
+ defaultConfiguration: {
459
+ level: sarifLevel(f.severity),
460
+ },
461
+ helpUri: 'https://vibecheck.dev/docs/rules/' + ruleId,
462
+ });
463
+ }
464
+
465
+ const result = {
466
+ ruleId,
467
+ level: sarifLevel(f.severity),
468
+ message: { text: f.message || f.title },
469
+ locations: [],
470
+ };
471
+
472
+ if (f.file) {
473
+ result.locations.push({
474
+ physicalLocation: {
475
+ artifactLocation: { uri: f.file },
476
+ region: f.line ? { startLine: f.line, startColumn: f.column || 1 } : undefined,
477
+ },
478
+ });
479
+ }
480
+
481
+ if (f.fix) {
482
+ result.fixes = [{ description: { text: f.fix } }];
483
+ }
484
+
485
+ results.push(result);
486
+ }
487
+
488
+ return {
489
+ $schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json',
490
+ version: '2.1.0',
468
491
  runs: [{
469
492
  tool: {
470
493
  driver: {
471
- name: "vibecheck",
472
- version: options.version || "1.0.0"
473
- }
474
- },
475
- results: findings.map((f, i) => ({
476
- ruleId: f.ruleId || f.category || `rule-${i}`,
477
- message: {
478
- text: f.message || f.title || ""
494
+ name: 'vibecheck',
495
+ version,
496
+ informationUri: 'https://vibecheck.dev',
497
+ rules: Array.from(rules.values()),
479
498
  },
480
- level: f.severity === 'critical' ? 'error' : f.severity === 'warning' ? 'warning' : 'note',
481
- locations: f.file ? [{
482
- physicalLocation: {
483
- artifactLocation: {
484
- uri: f.file
485
- },
486
- region: f.line ? {
487
- startLine: f.line
488
- } : undefined
489
- }
490
- }] : []
491
- }))
492
- }]
493
- }, null, 2);
494
- }
495
-
496
- // Placeholder functions for compatibility
497
- function renderCoverageMap(coverageMap) {
498
- return ''; // Implement if needed
499
- }
500
-
501
- function renderBreakdown(breakdown) {
502
- return ''; // Implement if needed
503
- }
504
-
505
- function renderBlockers(blockers) {
506
- return ''; // Implement if needed
499
+ },
500
+ results,
501
+ invocations: [{
502
+ executionSuccessful: true,
503
+ endTimeUtc: new Date().toISOString(),
504
+ }],
505
+ }],
506
+ };
507
507
  }
508
508
 
509
- function renderLayers(layers) {
510
- return ''; // Implement if needed
509
+ function sarifLevel(severity) {
510
+ const levels = {
511
+ critical: 'error',
512
+ BLOCK: 'error',
513
+ high: 'error',
514
+ medium: 'warning',
515
+ WARN: 'warning',
516
+ warning: 'warning',
517
+ low: 'note',
518
+ INFO: 'note',
519
+ info: 'none',
520
+ };
521
+ return levels[severity] || 'warning';
511
522
  }
512
523
 
513
524
  // ═══════════════════════════════════════════════════════════════════════════════
514
- // ENHANCED OUTPUT (with charts and Pro upselling)
525
+ // EXPORTS
515
526
  // ═══════════════════════════════════════════════════════════════════════════════
516
527
 
517
- let enhancedOutput;
518
- try {
519
- enhancedOutput = require('./scan-output-enhanced');
520
- } catch (e) {
521
- enhancedOutput = null;
522
- }
523
-
524
- /**
525
- * Format scan output with enhanced visuals (charts, upsell)
526
- * Falls back to standard output if enhanced module not available
527
- */
528
- function formatScanOutputEnhanced(result, options = {}) {
529
- if (enhancedOutput && !options.legacy) {
530
- enhancedOutput.renderEnhancedScanOutput(result, options);
531
- return ''; // Output is printed directly
532
- }
533
- return formatScanOutput(result, options);
534
- }
535
-
536
- /**
537
- * Get Pro upsell prompt for current context
538
- */
539
- function getProUpsellPrompt(context, data = {}) {
540
- if (enhancedOutput) {
541
- return enhancedOutput.getProPrompt(context, data);
542
- }
543
- return null;
544
- }
545
-
546
- /**
547
- * Render inline Pro upsell
548
- */
549
- function renderProUpsell(context, data = {}) {
550
- if (enhancedOutput) {
551
- return enhancedOutput.renderInlineProPrompt(context, data);
552
- }
553
- return '';
554
- }
555
-
556
- module.exports = {
528
+ module.exports = {
529
+ // Main formatters
557
530
  formatScanOutput,
558
- formatScanOutputEnhanced,
559
- calculateScore,
560
- getExitCode,
561
- printError,
562
531
  formatSARIF,
563
- EXIT_CODES,
532
+
533
+ // Component renderers
534
+ renderLayers,
564
535
  renderCoverageMap,
565
536
  renderBreakdown,
566
537
  renderBlockers,
567
- renderLayers,
568
- // Pro upsell
569
- getProUpsellPrompt,
570
- renderProUpsell,
571
- };
538
+ renderCategorySummary,
539
+
540
+ // Utilities
541
+ calculateScore,
542
+ getExitCode,
543
+ printError,
544
+ EXIT_CODES,
545
+ };