@vibecheckai/cli 3.4.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 (228) hide show
  1. package/bin/registry.js +154 -338
  2. package/bin/runners/context/generators/mcp.js +13 -15
  3. package/bin/runners/context/proof-context.js +1 -248
  4. package/bin/runners/lib/analysis-core.js +180 -198
  5. package/bin/runners/lib/analyzers.js +223 -1669
  6. package/bin/runners/lib/cli-output.js +210 -242
  7. package/bin/runners/lib/detectors-v2.js +785 -547
  8. package/bin/runners/lib/entitlements-v2.js +458 -96
  9. package/bin/runners/lib/error-handler.js +9 -16
  10. package/bin/runners/lib/global-flags.js +0 -37
  11. package/bin/runners/lib/route-truth.js +322 -1167
  12. package/bin/runners/lib/scan-output.js +469 -448
  13. package/bin/runners/lib/ship-output.js +27 -280
  14. package/bin/runners/lib/terminal-ui.js +733 -231
  15. package/bin/runners/lib/truth.js +321 -1004
  16. package/bin/runners/lib/unified-output.js +158 -162
  17. package/bin/runners/lib/upsell.js +204 -104
  18. package/bin/runners/runAllowlist.js +324 -0
  19. package/bin/runners/runAuth.js +95 -324
  20. package/bin/runners/runCheckpoint.js +21 -39
  21. package/bin/runners/runContext.js +24 -136
  22. package/bin/runners/runDoctor.js +67 -115
  23. package/bin/runners/runEvidencePack.js +219 -0
  24. package/bin/runners/runFix.js +5 -6
  25. package/bin/runners/runGuard.js +118 -212
  26. package/bin/runners/runInit.js +2 -14
  27. package/bin/runners/runInstall.js +281 -0
  28. package/bin/runners/runLabs.js +341 -0
  29. package/bin/runners/runMcp.js +52 -130
  30. package/bin/runners/runPolish.js +20 -43
  31. package/bin/runners/runProve.js +3 -13
  32. package/bin/runners/runReality.js +0 -14
  33. package/bin/runners/runReport.js +2 -3
  34. package/bin/runners/runScan.js +44 -511
  35. package/bin/runners/runShip.js +14 -28
  36. package/bin/runners/runValidate.js +2 -19
  37. package/bin/runners/runWatch.js +54 -118
  38. package/bin/vibecheck.js +41 -148
  39. package/mcp-server/ARCHITECTURE.md +339 -0
  40. package/mcp-server/__tests__/cache.test.ts +313 -0
  41. package/mcp-server/__tests__/executor.test.ts +239 -0
  42. package/mcp-server/__tests__/fixtures/exclusion-test/.cache/webpack/cache.pack +1 -0
  43. package/mcp-server/__tests__/fixtures/exclusion-test/.next/server/chunk.js +3 -0
  44. package/mcp-server/__tests__/fixtures/exclusion-test/.turbo/cache.json +3 -0
  45. package/mcp-server/__tests__/fixtures/exclusion-test/.venv/lib/env.py +3 -0
  46. package/mcp-server/__tests__/fixtures/exclusion-test/dist/bundle.js +3 -0
  47. package/mcp-server/__tests__/fixtures/exclusion-test/package.json +5 -0
  48. package/mcp-server/__tests__/fixtures/exclusion-test/src/app.ts +5 -0
  49. package/mcp-server/__tests__/fixtures/exclusion-test/venv/lib/config.py +4 -0
  50. package/mcp-server/__tests__/ids.test.ts +345 -0
  51. package/mcp-server/__tests__/integration/tools.test.ts +410 -0
  52. package/mcp-server/__tests__/registry.test.ts +365 -0
  53. package/mcp-server/__tests__/sandbox.test.ts +323 -0
  54. package/mcp-server/__tests__/schemas.test.ts +372 -0
  55. package/mcp-server/benchmarks/run-benchmarks.ts +304 -0
  56. package/mcp-server/examples/doctor.request.json +14 -0
  57. package/mcp-server/examples/doctor.response.json +53 -0
  58. package/mcp-server/examples/error.response.json +15 -0
  59. package/mcp-server/examples/scan.request.json +14 -0
  60. package/mcp-server/examples/scan.response.json +108 -0
  61. package/mcp-server/handlers/tool-handler.ts +671 -0
  62. package/mcp-server/index-v3.ts +293 -0
  63. package/mcp-server/index.js +1072 -1573
  64. package/mcp-server/index.old.js +4137 -0
  65. package/mcp-server/lib/cache.ts +341 -0
  66. package/mcp-server/lib/errors.ts +346 -0
  67. package/mcp-server/lib/executor.ts +792 -0
  68. package/mcp-server/lib/ids.ts +238 -0
  69. package/mcp-server/lib/logger.ts +368 -0
  70. package/mcp-server/lib/metrics.ts +365 -0
  71. package/mcp-server/lib/sandbox.ts +337 -0
  72. package/mcp-server/lib/validator.ts +229 -0
  73. package/mcp-server/package-lock.json +165 -0
  74. package/mcp-server/package.json +32 -7
  75. package/mcp-server/premium-tools.js +2 -2
  76. package/mcp-server/registry/tools.json +476 -0
  77. package/mcp-server/schemas/error-envelope.schema.json +125 -0
  78. package/mcp-server/schemas/finding.schema.json +167 -0
  79. package/mcp-server/schemas/report-artifact.schema.json +88 -0
  80. package/mcp-server/schemas/run-request.schema.json +75 -0
  81. package/mcp-server/schemas/verdict.schema.json +168 -0
  82. package/mcp-server/tier-auth.d.ts +71 -0
  83. package/mcp-server/tier-auth.js +371 -183
  84. package/mcp-server/truth-context.js +90 -131
  85. package/mcp-server/truth-firewall-tools.js +1000 -1611
  86. package/mcp-server/tsconfig.json +34 -0
  87. package/mcp-server/vibecheck-tools.js +2 -2
  88. package/mcp-server/vitest.config.ts +16 -0
  89. package/package.json +3 -4
  90. package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +0 -474
  91. package/bin/runners/lib/agent-firewall/change-packet/builder.js +0 -488
  92. package/bin/runners/lib/agent-firewall/change-packet/schema.json +0 -228
  93. package/bin/runners/lib/agent-firewall/change-packet/store.js +0 -200
  94. package/bin/runners/lib/agent-firewall/claims/claim-types.js +0 -21
  95. package/bin/runners/lib/agent-firewall/claims/extractor.js +0 -303
  96. package/bin/runners/lib/agent-firewall/claims/patterns.js +0 -24
  97. package/bin/runners/lib/agent-firewall/critic/index.js +0 -151
  98. package/bin/runners/lib/agent-firewall/critic/judge.js +0 -432
  99. package/bin/runners/lib/agent-firewall/critic/prompts.js +0 -305
  100. package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +0 -88
  101. package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +0 -75
  102. package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +0 -127
  103. package/bin/runners/lib/agent-firewall/evidence/resolver.js +0 -102
  104. package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +0 -213
  105. package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +0 -145
  106. package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +0 -19
  107. package/bin/runners/lib/agent-firewall/fs-hook/installer.js +0 -87
  108. package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +0 -184
  109. package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +0 -163
  110. package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +0 -107
  111. package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +0 -68
  112. package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +0 -66
  113. package/bin/runners/lib/agent-firewall/interceptor/base.js +0 -304
  114. package/bin/runners/lib/agent-firewall/interceptor/cursor.js +0 -35
  115. package/bin/runners/lib/agent-firewall/interceptor/vscode.js +0 -35
  116. package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +0 -34
  117. package/bin/runners/lib/agent-firewall/lawbook/distributor.js +0 -465
  118. package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +0 -604
  119. package/bin/runners/lib/agent-firewall/lawbook/index.js +0 -304
  120. package/bin/runners/lib/agent-firewall/lawbook/registry.js +0 -514
  121. package/bin/runners/lib/agent-firewall/lawbook/schema.js +0 -420
  122. package/bin/runners/lib/agent-firewall/logger.js +0 -141
  123. package/bin/runners/lib/agent-firewall/policy/default-policy.json +0 -90
  124. package/bin/runners/lib/agent-firewall/policy/engine.js +0 -103
  125. package/bin/runners/lib/agent-firewall/policy/loader.js +0 -451
  126. package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +0 -50
  127. package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +0 -50
  128. package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +0 -86
  129. package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +0 -162
  130. package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +0 -189
  131. package/bin/runners/lib/agent-firewall/policy/rules/scope.js +0 -93
  132. package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +0 -57
  133. package/bin/runners/lib/agent-firewall/policy/schema.json +0 -183
  134. package/bin/runners/lib/agent-firewall/policy/verdict.js +0 -54
  135. package/bin/runners/lib/agent-firewall/proposal/extractor.js +0 -394
  136. package/bin/runners/lib/agent-firewall/proposal/index.js +0 -212
  137. package/bin/runners/lib/agent-firewall/proposal/schema.js +0 -251
  138. package/bin/runners/lib/agent-firewall/proposal/validator.js +0 -386
  139. package/bin/runners/lib/agent-firewall/reality/index.js +0 -332
  140. package/bin/runners/lib/agent-firewall/reality/state.js +0 -625
  141. package/bin/runners/lib/agent-firewall/reality/watcher.js +0 -322
  142. package/bin/runners/lib/agent-firewall/risk/index.js +0 -173
  143. package/bin/runners/lib/agent-firewall/risk/scorer.js +0 -328
  144. package/bin/runners/lib/agent-firewall/risk/thresholds.js +0 -321
  145. package/bin/runners/lib/agent-firewall/risk/vectors.js +0 -421
  146. package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +0 -472
  147. package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +0 -346
  148. package/bin/runners/lib/agent-firewall/simulator/index.js +0 -181
  149. package/bin/runners/lib/agent-firewall/simulator/route-validator.js +0 -380
  150. package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +0 -661
  151. package/bin/runners/lib/agent-firewall/time-machine/index.js +0 -267
  152. package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +0 -436
  153. package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +0 -490
  154. package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +0 -530
  155. package/bin/runners/lib/agent-firewall/truthpack/index.js +0 -67
  156. package/bin/runners/lib/agent-firewall/truthpack/loader.js +0 -137
  157. package/bin/runners/lib/agent-firewall/unblock/planner.js +0 -337
  158. package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +0 -118
  159. package/bin/runners/lib/api-client.js +0 -269
  160. package/bin/runners/lib/authority-badge.js +0 -425
  161. package/bin/runners/lib/engines/accessibility-engine.js +0 -190
  162. package/bin/runners/lib/engines/api-consistency-engine.js +0 -162
  163. package/bin/runners/lib/engines/ast-cache.js +0 -99
  164. package/bin/runners/lib/engines/code-quality-engine.js +0 -255
  165. package/bin/runners/lib/engines/console-logs-engine.js +0 -115
  166. package/bin/runners/lib/engines/cross-file-analysis-engine.js +0 -268
  167. package/bin/runners/lib/engines/dead-code-engine.js +0 -198
  168. package/bin/runners/lib/engines/deprecated-api-engine.js +0 -226
  169. package/bin/runners/lib/engines/empty-catch-engine.js +0 -150
  170. package/bin/runners/lib/engines/file-filter.js +0 -131
  171. package/bin/runners/lib/engines/hardcoded-secrets-engine.js +0 -251
  172. package/bin/runners/lib/engines/mock-data-engine.js +0 -272
  173. package/bin/runners/lib/engines/parallel-processor.js +0 -71
  174. package/bin/runners/lib/engines/performance-issues-engine.js +0 -265
  175. package/bin/runners/lib/engines/security-vulnerabilities-engine.js +0 -243
  176. package/bin/runners/lib/engines/todo-fixme-engine.js +0 -115
  177. package/bin/runners/lib/engines/type-aware-engine.js +0 -152
  178. package/bin/runners/lib/engines/unsafe-regex-engine.js +0 -225
  179. package/bin/runners/lib/engines/vibecheck-engines/README.md +0 -53
  180. package/bin/runners/lib/engines/vibecheck-engines/index.js +0 -15
  181. package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +0 -164
  182. package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +0 -291
  183. package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +0 -83
  184. package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +0 -198
  185. package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +0 -275
  186. package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +0 -167
  187. package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +0 -217
  188. package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +0 -139
  189. package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +0 -140
  190. package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +0 -164
  191. package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +0 -234
  192. package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +0 -217
  193. package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +0 -78
  194. package/bin/runners/lib/engines/vibecheck-engines/package.json +0 -13
  195. package/bin/runners/lib/exit-codes.js +0 -275
  196. package/bin/runners/lib/fingerprint.js +0 -377
  197. package/bin/runners/lib/help-formatter.js +0 -413
  198. package/bin/runners/lib/logger.js +0 -38
  199. package/bin/runners/lib/ship-output-enterprise.js +0 -239
  200. package/bin/runners/lib/unified-cli-output.js +0 -604
  201. package/bin/runners/runAgent.d.ts +0 -5
  202. package/bin/runners/runAgent.js +0 -161
  203. package/bin/runners/runApprove.js +0 -1200
  204. package/bin/runners/runClassify.js +0 -859
  205. package/bin/runners/runContext.d.ts +0 -4
  206. package/bin/runners/runFirewall.d.ts +0 -5
  207. package/bin/runners/runFirewall.js +0 -134
  208. package/bin/runners/runFirewallHook.d.ts +0 -5
  209. package/bin/runners/runFirewallHook.js +0 -56
  210. package/bin/runners/runPolish.d.ts +0 -4
  211. package/bin/runners/runProof.zip +0 -0
  212. package/bin/runners/runTruth.d.ts +0 -5
  213. package/bin/runners/runTruth.js +0 -101
  214. package/mcp-server/HARDENING_SUMMARY.md +0 -299
  215. package/mcp-server/agent-firewall-interceptor.js +0 -500
  216. package/mcp-server/authority-tools.js +0 -569
  217. package/mcp-server/conductor/conflict-resolver.js +0 -588
  218. package/mcp-server/conductor/execution-planner.js +0 -544
  219. package/mcp-server/conductor/index.js +0 -377
  220. package/mcp-server/conductor/lock-manager.js +0 -615
  221. package/mcp-server/conductor/request-queue.js +0 -550
  222. package/mcp-server/conductor/session-manager.js +0 -500
  223. package/mcp-server/conductor/tools.js +0 -510
  224. package/mcp-server/lib/api-client.cjs +0 -13
  225. package/mcp-server/lib/logger.cjs +0 -30
  226. package/mcp-server/logger.js +0 -173
  227. package/mcp-server/tools-v3.js +0 -706
  228. package/mcp-server/vibecheck-mcp-server-3.2.0.tgz +0 -0
@@ -1,33 +1,34 @@
1
1
  /**
2
- * Terminal UI - The Vibecheck Design System
3
- * * Centralizes all visual primitives to ensure "World Class" consistency.
4
- * * features:
5
- * - Global Grid: 76 chars width
6
- * - Border Style: Double Outer / Single Inner
7
- * - Components: Spinners, Progress Bars, Tables, Headers
2
+ * Terminal UI - Premium CLI Components
3
+ *
4
+ * Reusable across all vibecheck commands:
5
+ * - Advanced spinners with phases
6
+ * - Multi-line progress displays
7
+ * - Score visualizations
8
+ * - Tables and cards
9
+ * - Color utilities
10
+ *
11
+ * Zero dependencies - pure ANSI escape codes
8
12
  */
9
13
 
10
- "use strict";
11
-
12
14
  // ═══════════════════════════════════════════════════════════════════════════════
13
- // 1. CORE CONSTANTS & ANSI
15
+ // ANSI ESCAPE CODES
14
16
  // ═══════════════════════════════════════════════════════════════════════════════
15
17
 
16
- const WIDTH = 76;
17
-
18
18
  const ESC = '\x1b';
19
- const SUPPORTS_TRUECOLOR = process.env.COLORTERM === 'truecolor' ||
20
- process.env.TERM_PROGRAM === 'iTerm.app' ||
21
- process.env.TERM_PROGRAM === 'Apple_Terminal' ||
22
- process.env.WT_SESSION;
23
19
 
24
20
  const ansi = {
21
+ // Text styles
25
22
  reset: `${ESC}[0m`,
26
23
  bold: `${ESC}[1m`,
27
24
  dim: `${ESC}[2m`,
28
25
  italic: `${ESC}[3m`,
29
26
  underline: `${ESC}[4m`,
30
- // Colors
27
+ inverse: `${ESC}[7m`,
28
+ strikethrough: `${ESC}[9m`,
29
+
30
+ // Standard colors
31
+ black: `${ESC}[30m`,
31
32
  red: `${ESC}[31m`,
32
33
  green: `${ESC}[32m`,
33
34
  yellow: `${ESC}[33m`,
@@ -35,294 +36,785 @@ const ansi = {
35
36
  magenta: `${ESC}[35m`,
36
37
  cyan: `${ESC}[36m`,
37
38
  white: `${ESC}[37m`,
39
+
40
+ // Bright colors
38
41
  gray: `${ESC}[90m`,
39
- // RGB colors (truecolor support)
40
- rgb: (r, g, b) => SUPPORTS_TRUECOLOR ? `${ESC}[38;2;${r};${g};${b}m` : '',
41
- bgRgb: (r, g, b) => SUPPORTS_TRUECOLOR ? `${ESC}[48;2;${r};${g};${b}m` : '',
42
- // Backgrounds
42
+ brightRed: `${ESC}[91m`,
43
+ brightGreen: `${ESC}[92m`,
44
+ brightYellow: `${ESC}[93m`,
45
+ brightBlue: `${ESC}[94m`,
46
+ brightMagenta: `${ESC}[95m`,
47
+ brightCyan: `${ESC}[96m`,
48
+ brightWhite: `${ESC}[97m`,
49
+
50
+ // Background colors
51
+ bgBlack: `${ESC}[40m`,
43
52
  bgRed: `${ESC}[41m`,
44
53
  bgGreen: `${ESC}[42m`,
45
54
  bgYellow: `${ESC}[43m`,
46
55
  bgBlue: `${ESC}[44m`,
47
56
  bgMagenta: `${ESC}[45m`,
48
57
  bgCyan: `${ESC}[46m`,
49
- // Cursor
58
+ bgWhite: `${ESC}[47m`,
59
+
60
+ // Cursor control
50
61
  hideCursor: `${ESC}[?25l`,
51
62
  showCursor: `${ESC}[?25h`,
63
+ saveCursor: `${ESC}[s`,
64
+ restoreCursor: `${ESC}[u`,
52
65
  clearLine: `${ESC}[2K`,
66
+ clearScreen: `${ESC}[2J`,
53
67
  cursorUp: (n = 1) => `${ESC}[${n}A`,
68
+ cursorDown: (n = 1) => `${ESC}[${n}B`,
69
+ cursorRight: (n = 1) => `${ESC}[${n}C`,
70
+ cursorLeft: (n = 1) => `${ESC}[${n}D`,
71
+ cursorTo: (x, y) => `${ESC}[${y};${x}H`,
72
+
73
+ // 24-bit color (truecolor)
74
+ rgb: (r, g, b) => `${ESC}[38;2;${r};${g};${b}m`,
75
+ bgRgb: (r, g, b) => `${ESC}[48;2;${r};${g};${b}m`,
76
+ };
77
+
78
+ // ═══════════════════════════════════════════════════════════════════════════════
79
+ // COLOR PALETTE - Electric Blue Theme
80
+ // ═══════════════════════════════════════════════════════════════════════════════
81
+
82
+ const colors = {
83
+ // Primary brand colors
84
+ primary: ansi.rgb(99, 102, 241), // Indigo
85
+ secondary: ansi.rgb(139, 92, 246), // Purple
86
+ accent: ansi.rgb(6, 182, 212), // Cyan
87
+
88
+ // Semantic colors
89
+ success: ansi.rgb(16, 185, 129), // Emerald
90
+ warning: ansi.rgb(245, 158, 11), // Amber
91
+ error: ansi.rgb(239, 68, 68), // Red
92
+ info: ansi.rgb(59, 130, 246), // Blue
93
+
94
+ // Severity colors
95
+ critical: ansi.rgb(220, 38, 38), // Red-600
96
+ high: ansi.rgb(234, 88, 12), // Orange-600
97
+ medium: ansi.rgb(202, 138, 4), // Yellow-600
98
+ low: ansi.rgb(37, 99, 235), // Blue-600
99
+
100
+ // Gradient stops
101
+ gradient: {
102
+ cyan: ansi.rgb(0, 255, 255),
103
+ blue: ansi.rgb(100, 149, 237),
104
+ purple: ansi.rgb(138, 43, 226),
105
+ pink: ansi.rgb(236, 72, 153),
106
+ orange: ansi.rgb(251, 146, 60),
107
+ },
108
+
109
+ // Background variants
110
+ bg: {
111
+ success: ansi.bgRgb(16, 185, 129),
112
+ warning: ansi.bgRgb(245, 158, 11),
113
+ error: ansi.bgRgb(220, 38, 38),
114
+ info: ansi.bgRgb(59, 130, 246),
115
+ muted: ansi.bgRgb(39, 39, 42),
116
+ },
54
117
  };
55
118
 
56
- // Semantic Palette
57
- const style = {
58
- success: (t) => `${ansi.green}${t}${ansi.reset}`,
59
- error: (t) => `${ansi.red}${t}${ansi.reset}`,
60
- warning: (t) => `${ansi.yellow}${t}${ansi.reset}`,
61
- info: (t) => `${ansi.cyan}${t}${ansi.reset}`,
62
- subtle: (t) => `${ansi.gray}${t}${ansi.reset}`,
63
- highlight: (t) => `${ansi.bold}${ansi.white}${t}${ansi.reset}`,
64
- label: (t) => `${ansi.blue}${t}${ansi.reset}`,
65
- // Backgrounds for badges
66
- bgSuccess: (t) => `${ansi.bgGreen}${ansi.bold}${ansi.white} ${t} ${ansi.reset}`,
67
- bgError: (t) => `${ansi.bgRed}${ansi.bold}${ansi.white} ${t} ${ansi.reset}`,
68
- bgWarn: (t) => `${ansi.bgYellow}${ansi.bold}${ansi.white} ${t} ${ansi.reset}`,
119
+ // ═══════════════════════════════════════════════════════════════════════════════
120
+ // BOX DRAWING CHARACTERS
121
+ // ═══════════════════════════════════════════════════════════════════════════════
122
+
123
+ const box = {
124
+ // Rounded corners
125
+ topLeft: '╭',
126
+ topRight: '╮',
127
+ bottomLeft: '╰',
128
+ bottomRight: '╯',
129
+ horizontal: '─',
130
+ vertical: '│',
131
+
132
+ // Sharp corners
133
+ sharpTopLeft: '┌',
134
+ sharpTopRight: '┐',
135
+ sharpBottomLeft: '└',
136
+ sharpBottomRight: '┘',
137
+
138
+ // Double lines
139
+ doubleHorizontal: '═',
140
+ doubleVertical: '║',
141
+ doubleTopLeft: '╔',
142
+ doubleTopRight: '╗',
143
+ doubleBottomLeft: '╚',
144
+ doubleBottomRight: '╝',
145
+
146
+ // Connectors
147
+ teeRight: '├',
148
+ teeLeft: '┤',
149
+ teeDown: '┬',
150
+ teeUp: '┴',
151
+ cross: '┼',
152
+
153
+ // Block elements
154
+ fullBlock: '█',
155
+ lightShade: '░',
156
+ mediumShade: '▒',
157
+ darkShade: '▓',
158
+ upperHalf: '▀',
159
+ lowerHalf: '▄',
160
+ leftHalf: '▌',
161
+ rightHalf: '▐',
69
162
  };
70
163
 
164
+ // ═══════════════════════════════════════════════════════════════════════════════
165
+ // ICONS & SYMBOLS
166
+ // ═══════════════════════════════════════════════════════════════════════════════
167
+
71
168
  const icons = {
169
+ // Status
72
170
  success: '✓',
73
171
  error: '✗',
74
172
  warning: '⚠',
75
173
  info: 'ℹ',
76
- bullet: '',
77
- pointer: '❯',
174
+ question: '?',
175
+
176
+ // Arrows
78
177
  arrowRight: '→',
79
- line: '',
178
+ arrowLeft: '',
179
+ arrowUp: '↑',
180
+ arrowDown: '↓',
181
+ arrowBoth: '↔',
182
+
183
+ // Pointers
184
+ pointer: '❯',
185
+ pointerSmall: '›',
186
+ bullet: '•',
187
+ dot: '·',
188
+
189
+ // Misc
190
+ star: '★',
191
+ heart: '♥',
192
+ lightning: '⚡',
193
+ fire: '🔥',
194
+ rocket: '🚀',
195
+ check: '☑',
80
196
  radioOn: '◉',
81
197
  radioOff: '○',
82
- lock: '🔒',
198
+
199
+ // Severity badges
200
+ critical: '🚨',
201
+ high: '🔴',
202
+ medium: '🟡',
203
+ low: '🔵',
83
204
  };
84
205
 
85
206
  // ═══════════════════════════════════════════════════════════════════════════════
86
- // 2. LAYOUT PRIMITIVES (The "Grid")
207
+ // SPINNER - Advanced Multi-Phase Spinner
87
208
  // ═══════════════════════════════════════════════════════════════════════════════
88
209
 
89
- const BOX = {
90
- // Outer Frame (Double)
91
- tl: '', tr: '', bl: '', br: '', h: '═', v: '║',
92
- trT: '', tlT: '',
93
- // Inner Dividers (Single)
94
- ltl: '', ltr: '', lbl: '', lbr: '', lh: '', lv: '',
95
- lt: '', lb: '', lx: '', ltrT: '', ltlT: ''
210
+ const SPINNER_FRAMES = {
211
+ dots: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],
212
+ line: ['|', '/', '-', '\\'],
213
+ arc: ['', '◠', '', '◞', '◡', '◟'],
214
+ circle: ['◐', '◓', '◑', '◒'],
215
+ bounce: ['', '⠂', '', '⡀', '', '', '', ''],
216
+ pulse: ['', '▓', '', '', '', ''],
96
217
  };
97
218
 
98
- /**
99
- * Centers text within the standard width, accounting for ANSI codes
100
- */
101
- function padCenter(str, width = WIDTH - 2) {
102
- const visibleLen = str.replace(/\u001b\[\d+m/g, '').length;
103
- const padding = Math.max(0, width - visibleLen);
104
- const left = Math.floor(padding / 2);
105
- return ' '.repeat(left) + str + ' '.repeat(padding - left);
219
+ class Spinner {
220
+ constructor(options = {}) {
221
+ this.frames = SPINNER_FRAMES[options.type || 'dots'];
222
+ this.interval = options.interval || 80;
223
+ this.stream = options.stream || process.stdout;
224
+ this.color = options.color || colors.primary;
225
+ this.frameIndex = 0;
226
+ this.timer = null;
227
+ this.text = '';
228
+ this.phases = [];
229
+ this.currentPhase = 0;
230
+ this.lines = 1;
231
+ }
232
+
233
+ start(text) {
234
+ this.text = text;
235
+ this.stream.write(ansi.hideCursor);
236
+ this._render();
237
+ this.timer = setInterval(() => this._render(), this.interval);
238
+ return this;
239
+ }
240
+
241
+ update(text) {
242
+ this.text = text;
243
+ return this;
244
+ }
245
+
246
+ setPhases(phases) {
247
+ this.phases = phases;
248
+ this.currentPhase = 0;
249
+ return this;
250
+ }
251
+
252
+ nextPhase() {
253
+ if (this.currentPhase < this.phases.length - 1) {
254
+ this.currentPhase++;
255
+ this.text = this.phases[this.currentPhase];
256
+ }
257
+ return this;
258
+ }
259
+
260
+ succeed(text) {
261
+ this._stop();
262
+ this._clear();
263
+ const msg = text || this.text;
264
+ this.stream.write(` ${colors.success}${icons.success}${ansi.reset} ${msg}\n`);
265
+ this.stream.write(ansi.showCursor);
266
+ return this;
267
+ }
268
+
269
+ fail(text) {
270
+ this._stop();
271
+ this._clear();
272
+ const msg = text || this.text;
273
+ this.stream.write(` ${colors.error}${icons.error}${ansi.reset} ${msg}\n`);
274
+ this.stream.write(ansi.showCursor);
275
+ return this;
276
+ }
277
+
278
+ warn(text) {
279
+ this._stop();
280
+ this._clear();
281
+ const msg = text || this.text;
282
+ this.stream.write(` ${colors.warning}${icons.warning}${ansi.reset} ${msg}\n`);
283
+ this.stream.write(ansi.showCursor);
284
+ return this;
285
+ }
286
+
287
+ info(text) {
288
+ this._stop();
289
+ this._clear();
290
+ const msg = text || this.text;
291
+ this.stream.write(` ${colors.info}${icons.info}${ansi.reset} ${msg}\n`);
292
+ this.stream.write(ansi.showCursor);
293
+ return this;
294
+ }
295
+
296
+ stop() {
297
+ this._stop();
298
+ this._clear();
299
+ this.stream.write(ansi.showCursor);
300
+ return this;
301
+ }
302
+
303
+ _render() {
304
+ const frame = this.frames[this.frameIndex];
305
+ this.frameIndex = (this.frameIndex + 1) % this.frames.length;
306
+
307
+ this._clear();
308
+
309
+ let output = ` ${this.color}${frame}${ansi.reset} ${this.text}`;
310
+
311
+ // Add phase indicator if phases are set
312
+ if (this.phases.length > 0) {
313
+ const phaseIndicator = this.phases.map((_, i) =>
314
+ i < this.currentPhase ? `${colors.success}●${ansi.reset}` :
315
+ i === this.currentPhase ? `${this.color}●${ansi.reset}` :
316
+ `${ansi.dim}○${ansi.reset}`
317
+ ).join(' ');
318
+ output += ` ${ansi.dim}[${ansi.reset}${phaseIndicator}${ansi.dim}]${ansi.reset}`;
319
+ }
320
+
321
+ this.stream.write(`\r${output}`);
322
+ }
323
+
324
+ _stop() {
325
+ if (this.timer) {
326
+ clearInterval(this.timer);
327
+ this.timer = null;
328
+ }
329
+ }
330
+
331
+ _clear() {
332
+ this.stream.write(`\r${ansi.clearLine}`);
333
+ }
106
334
  }
107
335
 
108
- /**
109
- * Pads text to the right, accounting for ANSI codes
110
- */
111
- function padRight(str, len) {
112
- const visibleLen = str.replace(/\u001b\[\d+m/g, '').length;
113
- const truncated = visibleLen > len ? str.substring(0, len - 3) + '...' : str;
114
- const finalLen = truncated.replace(/\u001b\[\d+m/g, '').length;
115
- return truncated + ' '.repeat(Math.max(0, len - finalLen));
116
- }
336
+ // ═══════════════════════════════════════════════════════════════════════════════
337
+ // PROGRESS BAR
338
+ // ═══════════════════════════════════════════════════════════════════════════════
117
339
 
118
- /**
119
- * Truncates text with ellipsis
120
- */
121
- function truncate(str, len) {
122
- if (!str) return '';
123
- const clean = str.replace(/\u001b\[\d+m/g, '');
124
- if (clean.length <= len) return str;
125
- return clean.substring(0, len - 3) + '...';
340
+ class ProgressBar {
341
+ constructor(options = {}) {
342
+ this.total = options.total || 100;
343
+ this.width = options.width || 40;
344
+ this.complete = options.complete || '';
345
+ this.incomplete = options.incomplete || '';
346
+ this.stream = options.stream || process.stdout;
347
+ this.current = 0;
348
+ this.startTime = Date.now();
349
+ }
350
+
351
+ update(current, label = '') {
352
+ this.current = current;
353
+ const percent = Math.min(100, Math.round((current / this.total) * 100));
354
+ const filled = Math.round((percent / 100) * this.width);
355
+ const empty = this.width - filled;
356
+
357
+ const color = percent >= 80 ? colors.success : percent >= 50 ? colors.warning : colors.error;
358
+ const bar = `${color}${this.complete.repeat(filled)}${ansi.dim}${this.incomplete.repeat(empty)}${ansi.reset}`;
359
+
360
+ const elapsed = ((Date.now() - this.startTime) / 1000).toFixed(1);
361
+ const eta = current > 0 ? (((this.total - current) / current) * (Date.now() - this.startTime) / 1000).toFixed(1) : '?';
362
+
363
+ this.stream.write(`\r${ansi.clearLine} ${bar} ${ansi.bold}${percent}%${ansi.reset} ${ansi.dim}${label} (${elapsed}s / ~${eta}s)${ansi.reset}`);
364
+ }
365
+
366
+ complete(label = 'Complete') {
367
+ this.update(this.total, label);
368
+ this.stream.write('\n');
369
+ }
126
370
  }
127
371
 
128
372
  // ═══════════════════════════════════════════════════════════════════════════════
129
- // 3. COMPONENT: SPINNER
373
+ // MULTI-LINE PROGRESS - Phase-Based Progress Display
130
374
  // ═══════════════════════════════════════════════════════════════════════════════
131
375
 
132
- const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
133
-
134
- class Spinner {
135
- constructor(text = '', colorStr = ansi.cyan) {
136
- this.text = text;
137
- this.colorStr = colorStr;
138
- this.timer = null;
139
- this.frameIndex = 0;
376
+ class PhaseProgress {
377
+ constructor(phases, options = {}) {
378
+ this.phases = phases.map(p => ({
379
+ name: p.name || p,
380
+ status: 'pending', // pending, running, success, error, skipped
381
+ message: '',
382
+ duration: null,
383
+ }));
384
+ this.currentPhase = -1;
385
+ this.stream = options.stream || process.stdout;
386
+ this.startTime = null;
387
+ this.phaseStartTime = null;
388
+ this.spinner = new Spinner({ color: colors.primary });
140
389
  }
141
-
142
- start(text) {
143
- if (text) this.text = text;
144
- process.stdout.write(ansi.hideCursor);
145
- this.timer = setInterval(() => {
146
- const frame = SPINNER_FRAMES[this.frameIndex];
147
- this.frameIndex = (this.frameIndex + 1) % SPINNER_FRAMES.length;
148
- process.stdout.write(`\r ${this.colorStr}${frame}${ansi.reset} ${this.text}`);
149
- }, 80);
390
+
391
+ start() {
392
+ this.startTime = Date.now();
393
+ this.stream.write(ansi.hideCursor);
394
+ this._render();
150
395
  return this;
151
396
  }
152
-
153
- stop(symbol = icons.success, color = ansi.green, finalMsg) {
154
- if (this.timer) clearInterval(this.timer);
155
- process.stdout.write(`\r${ansi.clearLine}`);
156
- if (finalMsg !== null) { // Pass null to clear completely
157
- const msg = finalMsg || this.text;
158
- console.log(` ${color}${symbol}${ansi.reset} ${msg}`);
397
+
398
+ startPhase(index, message = '') {
399
+ if (this.currentPhase >= 0 && this.phases[this.currentPhase].status === 'running') {
400
+ this.phases[this.currentPhase].status = 'success';
401
+ this.phases[this.currentPhase].duration = Date.now() - this.phaseStartTime;
159
402
  }
160
- process.stdout.write(ansi.showCursor);
403
+
404
+ this.currentPhase = index;
405
+ this.phaseStartTime = Date.now();
406
+ this.phases[index].status = 'running';
407
+ this.phases[index].message = message;
408
+ this._render();
161
409
  return this;
162
410
  }
163
-
164
- succeed(text) { return this.stop(icons.success, ansi.green, text); }
165
- fail(text) { return this.stop(icons.error, ansi.red, text); }
166
- warn(text) { return this.stop(icons.warning, ansi.yellow, text); }
411
+
412
+ updatePhase(message) {
413
+ if (this.currentPhase >= 0) {
414
+ this.phases[this.currentPhase].message = message;
415
+ this._render();
416
+ }
417
+ return this;
418
+ }
419
+
420
+ succeedPhase(message = '') {
421
+ if (this.currentPhase >= 0) {
422
+ this.phases[this.currentPhase].status = 'success';
423
+ this.phases[this.currentPhase].duration = Date.now() - this.phaseStartTime;
424
+ if (message) this.phases[this.currentPhase].message = message;
425
+ this._render();
426
+ }
427
+ return this;
428
+ }
429
+
430
+ failPhase(message = '') {
431
+ if (this.currentPhase >= 0) {
432
+ this.phases[this.currentPhase].status = 'error';
433
+ this.phases[this.currentPhase].duration = Date.now() - this.phaseStartTime;
434
+ if (message) this.phases[this.currentPhase].message = message;
435
+ this._render();
436
+ }
437
+ return this;
438
+ }
439
+
440
+ skipPhase(message = 'Skipped') {
441
+ if (this.currentPhase >= 0) {
442
+ this.phases[this.currentPhase].status = 'skipped';
443
+ this.phases[this.currentPhase].message = message;
444
+ this._render();
445
+ }
446
+ return this;
447
+ }
448
+
449
+ finish() {
450
+ if (this.currentPhase >= 0 && this.phases[this.currentPhase].status === 'running') {
451
+ this.phases[this.currentPhase].status = 'success';
452
+ this.phases[this.currentPhase].duration = Date.now() - this.phaseStartTime;
453
+ }
454
+ this._render();
455
+ this.stream.write(ansi.showCursor);
456
+ return this;
457
+ }
458
+
459
+ _render() {
460
+ // Move cursor up to clear previous render
461
+ if (this._rendered) {
462
+ this.stream.write(ansi.cursorUp(this.phases.length + 1));
463
+ }
464
+ this._rendered = true;
465
+
466
+ const totalDuration = Date.now() - this.startTime;
467
+
468
+ for (const phase of this.phases) {
469
+ this.stream.write(ansi.clearLine);
470
+
471
+ let statusIcon, statusColor;
472
+ switch (phase.status) {
473
+ case 'success':
474
+ statusIcon = icons.success;
475
+ statusColor = colors.success;
476
+ break;
477
+ case 'error':
478
+ statusIcon = icons.error;
479
+ statusColor = colors.error;
480
+ break;
481
+ case 'running':
482
+ statusIcon = this.spinner.frames[this.spinner.frameIndex];
483
+ statusColor = colors.primary;
484
+ this.spinner.frameIndex = (this.spinner.frameIndex + 1) % this.spinner.frames.length;
485
+ break;
486
+ case 'skipped':
487
+ statusIcon = '○';
488
+ statusColor = ansi.dim;
489
+ break;
490
+ default:
491
+ statusIcon = '○';
492
+ statusColor = ansi.dim;
493
+ }
494
+
495
+ const duration = phase.duration ? `${ansi.dim}${phase.duration}ms${ansi.reset}` : '';
496
+ const message = phase.message ? `${ansi.dim}${phase.message}${ansi.reset}` : '';
497
+
498
+ this.stream.write(` ${statusColor}${statusIcon}${ansi.reset} ${phase.name.padEnd(25)} ${duration.padEnd(15)} ${message}\n`);
499
+ }
500
+
501
+ // Total line
502
+ this.stream.write(ansi.clearLine);
503
+ this.stream.write(` ${ansi.dim}${'─'.repeat(60)}${ansi.reset}\n`);
504
+
505
+ // Keep spinner running for active phase
506
+ if (this.phases.some(p => p.status === 'running')) {
507
+ setTimeout(() => this._render(), 80);
508
+ }
509
+ }
167
510
  }
168
511
 
169
512
  // ═══════════════════════════════════════════════════════════════════════════════
170
- // 4. COMPONENT: PROGRESS BAR
513
+ // SCORE DISPLAY - Animated Score Card
171
514
  // ═══════════════════════════════════════════════════════════════════════════════
172
515
 
173
- function renderProgressBar(percentage, width = 20, color = ansi.green) {
174
- const filled = Math.round((percentage / 100) * width);
175
- return `${color}${'█'.repeat(filled)}${ansi.gray}${'░'.repeat(width - filled)}${ansi.reset}`;
516
+ function getScoreColor(score) {
517
+ if (score >= 90) return colors.success;
518
+ if (score >= 70) return colors.warning;
519
+ if (score >= 50) return ansi.rgb(251, 146, 60);
520
+ return colors.error;
521
+ }
522
+
523
+ function getGrade(score) {
524
+ if (score >= 90) return 'A';
525
+ if (score >= 80) return 'B';
526
+ if (score >= 70) return 'C';
527
+ if (score >= 60) return 'D';
528
+ return 'F';
529
+ }
530
+
531
+ function renderScoreCard(score, options = {}) {
532
+ const {
533
+ verdict = score >= 80 ? 'SHIP' : score >= 60 ? 'WARN' : 'BLOCK',
534
+ findings = { critical: 0, high: 0, medium: 0, low: 0 },
535
+ duration = null,
536
+ cached = false,
537
+ } = options;
538
+
539
+ const scoreColor = getScoreColor(score);
540
+ const grade = getGrade(score);
541
+ const gradeColor = scoreColor;
542
+
543
+ const verdictConfig = {
544
+ SHIP: { bg: colors.bg.success, text: ' ✓ SHIP ', desc: 'Ready to ship' },
545
+ WARN: { bg: colors.bg.warning, text: ' ⚠ WARN ', desc: 'Review before shipping' },
546
+ BLOCK: { bg: colors.bg.error, text: ' ✗ BLOCK ', desc: 'Fix issues before shipping' },
547
+ PASS: { bg: colors.bg.success, text: ' ✓ PASS ', desc: 'All checks passed' },
548
+ FAIL: { bg: colors.bg.error, text: ' ✗ FAIL ', desc: 'Checks failed' },
549
+ };
550
+ const v = verdictConfig[verdict] || verdictConfig.WARN;
551
+
552
+ // Build progress bar
553
+ const barWidth = 40;
554
+ const filled = Math.round((score / 100) * barWidth);
555
+ const bar = `${scoreColor}${'█'.repeat(filled)}${ansi.dim}${'░'.repeat(barWidth - filled)}${ansi.reset}`;
556
+
557
+ const lines = [];
558
+ lines.push('');
559
+ lines.push(` ${ansi.dim}${box.topLeft}${'─'.repeat(66)}${box.topRight}${ansi.reset}`);
560
+ lines.push(` ${ansi.dim}${box.vertical}${ansi.reset}${' '.repeat(66)}${ansi.dim}${box.vertical}${ansi.reset}`);
561
+
562
+ // Score + Grade row
563
+ const scoreStr = String(score).padStart(3);
564
+ lines.push(` ${ansi.dim}${box.vertical}${ansi.reset} ${ansi.dim}SCORE${ansi.reset} ${scoreColor}${ansi.bold}${scoreStr}${ansi.reset}${ansi.dim}/100${ansi.reset} ${ansi.dim}GRADE${ansi.reset} ${gradeColor}${ansi.bold}${grade}${ansi.reset} ${cached ? `${ansi.dim}(cached)${ansi.reset}` : ''} ${ansi.dim}${box.vertical}${ansi.reset}`);
565
+ lines.push(` ${ansi.dim}${box.vertical}${ansi.reset}${' '.repeat(66)}${ansi.dim}${box.vertical}${ansi.reset}`);
566
+
567
+ // Progress bar
568
+ lines.push(` ${ansi.dim}${box.vertical}${ansi.reset} ${bar} ${ansi.dim}${box.vertical}${ansi.reset}`);
569
+ lines.push(` ${ansi.dim}${box.vertical}${ansi.reset}${' '.repeat(66)}${ansi.dim}${box.vertical}${ansi.reset}`);
570
+
571
+ // Verdict badge
572
+ const verdictPad = ' '.repeat(Math.max(0, 23 - v.text.length));
573
+ lines.push(` ${ansi.dim}${box.vertical}${ansi.reset}${' '.repeat(20)}${v.bg}${ansi.bold}${v.text}${ansi.reset}${verdictPad}${ansi.dim}${v.desc}${ansi.reset} ${ansi.dim}${box.vertical}${ansi.reset}`);
574
+ lines.push(` ${ansi.dim}${box.vertical}${ansi.reset}${' '.repeat(66)}${ansi.dim}${box.vertical}${ansi.reset}`);
575
+
576
+ // Findings summary
577
+ const criticalStr = `${colors.critical}${findings.critical || 0}${ansi.reset} critical`;
578
+ const highStr = `${colors.high}${findings.high || 0}${ansi.reset} high`;
579
+ const mediumStr = `${colors.medium}${findings.medium || 0}${ansi.reset} medium`;
580
+ const lowStr = `${colors.low}${findings.low || 0}${ansi.reset} low`;
581
+ lines.push(` ${ansi.dim}${box.vertical}${ansi.reset} ${criticalStr} ${ansi.dim}│${ansi.reset} ${highStr} ${ansi.dim}│${ansi.reset} ${mediumStr} ${ansi.dim}│${ansi.reset} ${lowStr} ${ansi.dim}${box.vertical}${ansi.reset}`);
582
+ lines.push(` ${ansi.dim}${box.vertical}${ansi.reset}${' '.repeat(66)}${ansi.dim}${box.vertical}${ansi.reset}`);
583
+
584
+ // Duration if provided
585
+ if (duration) {
586
+ const durationStr = typeof duration === 'number' ? `${duration}ms` : duration;
587
+ lines.push(` ${ansi.dim}${box.vertical}${ansi.reset} ${ansi.dim}Completed in ${durationStr}${ansi.reset}${' '.repeat(Math.max(0, 46 - durationStr.length))}${ansi.dim}${box.vertical}${ansi.reset}`);
588
+ lines.push(` ${ansi.dim}${box.vertical}${ansi.reset}${' '.repeat(66)}${ansi.dim}${box.vertical}${ansi.reset}`);
589
+ }
590
+
591
+ lines.push(` ${ansi.dim}${box.bottomLeft}${'─'.repeat(66)}${box.bottomRight}${ansi.reset}`);
592
+ lines.push('');
593
+
594
+ return lines.join('\n');
176
595
  }
177
596
 
178
597
  // ═══════════════════════════════════════════════════════════════════════════════
179
- // 5. STANDARD RENDERERS (The "Look")
598
+ // FINDINGS LIST - Premium Findings Display
180
599
  // ═══════════════════════════════════════════════════════════════════════════════
181
600
 
182
- /**
183
- * Renders the Standard Double-Border Header with Logo
184
- */
185
- function renderScreenHeader(logoAscii, title, logoColor = (t) => t, context) {
601
+ function renderFindingsList(findings, options = {}) {
602
+ const { maxItems = 10, showCode = false, groupBySeverity = true } = options;
603
+
604
+ if (!findings || findings.length === 0) {
605
+ return `\n ${colors.success}${icons.success}${ansi.reset} ${ansi.bold}No issues found${ansi.reset}\n`;
606
+ }
607
+
186
608
  const lines = [];
187
609
 
188
- // Top Frame
189
- lines.push(ansi.gray + BOX.tl + BOX.h.repeat(WIDTH - 2) + BOX.tr + ansi.reset);
190
- lines.push(ansi.gray + BOX.v + ansi.reset + ' '.repeat(WIDTH - 2) + ansi.gray + BOX.v + ansi.reset);
191
-
192
- // Logo Processing
193
- logoAscii.trim().split('\n').forEach(line => {
194
- const cleanLine = line.replace(/\r/g, '');
195
- const maxLen = Math.max(...logoAscii.split('\n').map(l => l.length));
196
- const padding = ' '.repeat(Math.floor((WIDTH - 2 - maxLen) / 2));
197
- const rightPad = ' '.repeat(WIDTH - 2 - padding.length - cleanLine.length);
610
+ if (groupBySeverity) {
611
+ const groups = {
612
+ critical: findings.filter(f => f.severity === 'critical' || f.severity === 'BLOCK'),
613
+ high: findings.filter(f => f.severity === 'high'),
614
+ medium: findings.filter(f => f.severity === 'medium' || f.severity === 'WARN' || f.severity === 'warning'),
615
+ low: findings.filter(f => f.severity === 'low' || f.severity === 'INFO' || f.severity === 'info'),
616
+ };
198
617
 
199
- lines.push(ansi.gray + BOX.v + ansi.reset + padding + logoColor(cleanLine) + rightPad + ansi.gray + BOX.v + ansi.reset);
200
- });
201
-
202
- // Title
203
- lines.push(ansi.gray + BOX.v + ansi.reset + ' '.repeat(WIDTH - 2) + ansi.gray + BOX.v + ansi.reset);
204
- lines.push(ansi.gray + BOX.v + ansi.reset + padCenter(ansi.bold + ansi.white + title + ansi.reset, WIDTH - 2) + ansi.gray + BOX.v + ansi.reset);
205
- lines.push(ansi.gray + BOX.v + ansi.reset + ' '.repeat(WIDTH - 2) + ansi.gray + BOX.v + ansi.reset);
206
-
207
- // Context Bar (Optional)
208
- if (context) {
209
- lines.push(ansi.gray + BOX.trT + BOX.h.repeat(WIDTH - 2) + BOX.tlT + ansi.reset);
210
- const ctxStr = `${ansi.gray}${context.label}: ${ansi.reset}${ansi.cyan}${context.value}${ansi.reset}`;
211
- lines.push(ansi.gray + BOX.v + ansi.reset + padCenter(ctxStr, WIDTH - 2) + ansi.gray + BOX.v + ansi.reset);
212
- lines.push(ansi.gray + BOX.trT + BOX.h.repeat(WIDTH - 2) + BOX.tlT + ansi.reset);
618
+ for (const [severity, items] of Object.entries(groups)) {
619
+ if (items.length === 0) continue;
620
+
621
+ const color = colors[severity] || ansi.dim;
622
+ const icon = icons[severity] || icons.bullet;
623
+
624
+ lines.push('');
625
+ lines.push(` ${color}${ansi.bold}${severity.toUpperCase()} (${items.length})${ansi.reset}`);
626
+ lines.push(` ${ansi.dim}${'─'.repeat(40)}${ansi.reset}`);
627
+
628
+ for (const finding of items.slice(0, Math.ceil(maxItems / 4))) {
629
+ lines.push(...renderFinding(finding, { showCode, color }));
630
+ }
631
+
632
+ if (items.length > Math.ceil(maxItems / 4)) {
633
+ lines.push(` ${ansi.dim} ... and ${items.length - Math.ceil(maxItems / 4)} more ${severity} findings${ansi.reset}`);
634
+ }
635
+ }
213
636
  } else {
214
- lines.push(ansi.gray + BOX.trT + BOX.h.repeat(WIDTH - 2) + BOX.tlT + ansi.reset);
637
+ for (const finding of findings.slice(0, maxItems)) {
638
+ lines.push(...renderFinding(finding, { showCode }));
639
+ }
640
+
641
+ if (findings.length > maxItems) {
642
+ lines.push('');
643
+ lines.push(` ${ansi.dim}... and ${findings.length - maxItems} more findings${ansi.reset}`);
644
+ }
215
645
  }
216
-
217
- console.log(lines.join('\n'));
646
+
647
+ return lines.join('\n');
218
648
  }
219
649
 
220
- /**
221
- * Renders the Standard Footer
222
- */
223
- function renderScreenFooter() {
224
- console.log(ansi.gray + BOX.bl + BOX.h.repeat(WIDTH - 2) + BOX.br + ansi.reset);
650
+ function renderFinding(finding, options = {}) {
651
+ const { showCode = false, color = ansi.dim } = options;
652
+ const lines = [];
653
+
654
+ const severityColor = {
655
+ critical: colors.critical,
656
+ BLOCK: colors.critical,
657
+ high: colors.high,
658
+ medium: colors.medium,
659
+ WARN: colors.medium,
660
+ warning: colors.medium,
661
+ low: colors.low,
662
+ INFO: colors.low,
663
+ info: colors.low,
664
+ }[finding.severity] || ansi.dim;
665
+
666
+ const title = finding.title || finding.message || 'Unknown issue';
667
+ lines.push(` ${severityColor}${icons.pointer}${ansi.reset} ${ansi.bold}${truncate(title, 60)}${ansi.reset}`);
668
+
669
+ if (finding.file) {
670
+ const fileStr = finding.file + (finding.line ? `:${finding.line}` : '');
671
+ lines.push(` ${ansi.dim}${truncate(fileStr, 55)}${ansi.reset}`);
672
+ }
673
+
674
+ if (finding.fix || finding.fixSuggestion) {
675
+ lines.push(` ${colors.success}${icons.arrowRight}${ansi.reset} ${ansi.dim}${truncate(finding.fix || finding.fixSuggestion, 50)}${ansi.reset}`);
676
+ }
677
+
678
+ if (showCode && finding.codeSnippet) {
679
+ lines.push(` ${ansi.dim}┌──────────────────────────────────────${ansi.reset}`);
680
+ for (const line of finding.codeSnippet.split('\n').slice(0, 3)) {
681
+ lines.push(` ${ansi.dim}│${ansi.reset} ${truncate(line, 50)}`);
682
+ }
683
+ lines.push(` ${ansi.dim}└──────────────────────────────────────${ansi.reset}`);
684
+ }
685
+
686
+ lines.push('');
687
+ return lines;
225
688
  }
226
689
 
227
- /**
228
- * Renders a Standard Verdict/Telemetry Table
229
- */
230
- function renderVerdictTable(stats, score, findings) {
231
- const lines = [];
690
+ // ═══════════════════════════════════════════════════════════════════════════════
691
+ // TABLE RENDERING
692
+ // ═══════════════════════════════════════════════════════════════════════════════
232
693
 
233
- // Telemetry Row
234
- const heapMB = Math.round(stats.heap / 1024 / 1024);
235
- const statsStr = `📡 TELEMETRY │ ⏱ ${stats.duration}ms │ 📂 ${stats.files || '?'} Files │ 📦 ${heapMB}MB`;
236
- lines.push(ansi.gray + BOX.v + ansi.reset + padCenter(statsStr, WIDTH - 2) + ansi.gray + BOX.v + ansi.reset);
237
- lines.push(ansi.gray + BOX.trT + BOX.h.repeat(WIDTH - 2) + BOX.tlT + ansi.reset);
238
-
239
- // Score Row
240
- lines.push(ansi.gray + BOX.v + ansi.reset + ' '.repeat(WIDTH - 2) + ansi.gray + BOX.v + ansi.reset);
241
- const bar = renderProgressBar(score, 20);
242
- lines.push(ansi.gray + BOX.v + ansi.reset + padCenter(`HEALTH SCORE [${bar}] ${score} / 100`, WIDTH + 18) + ansi.gray + BOX.v + ansi.reset);
243
- lines.push(ansi.gray + BOX.v + ansi.reset + ' '.repeat(WIDTH - 2) + ansi.gray + BOX.v + ansi.reset);
244
-
245
- if (findings.length > 0) {
246
- // Table Header
247
- const C1=10, C2=13, C3=41;
248
- const tTop = ` ${BOX.ltl}${BOX.lh.repeat(C1)}${BOX.lt}${BOX.lh.repeat(C2)}${BOX.lt}${BOX.lh.repeat(C3)}${BOX.ltr} `;
249
- const header = ` ${BOX.lv}${padRight(' SEVERITY', C1)}${BOX.lv}${padRight(' TYPE', C2)}${BOX.lv}${padRight(' FINDING', C3)}${BOX.lv} `;
250
- const tBot = ` ${BOX.lbl}${BOX.lh.repeat(C1)}${BOX.lb}${BOX.lh.repeat(C2)}${BOX.lb}${BOX.lh.repeat(C3)}${BOX.lbr} `;
251
-
252
- lines.push(ansi.gray + BOX.v + ansi.reset + ansi.gray + tTop + ansi.reset + ansi.gray + BOX.v + ansi.reset);
253
- lines.push(ansi.gray + BOX.v + ansi.reset + ansi.bold + header + ansi.reset + ansi.gray + BOX.v + ansi.reset);
254
-
255
- // Rows
256
- findings.slice(0, 5).forEach(f => {
257
- let sev = style.subtle(' INFO ');
258
- if (f.severity === 'critical' || f.severity === 'BLOCK') sev = style.error('🛑 CRIT ');
259
- else if (f.severity === 'high' || f.severity === 'WARN') sev = style.warning('🟡 WARN ');
260
-
261
- const row = ` ${ansi.gray}${BOX.lv}${ansi.reset}${sev}${ansi.gray}${BOX.lv}${ansi.reset}${padRight(' '+(f.category||'General'), C2)}${ansi.gray}${BOX.lv}${ansi.reset}${padRight(' '+(f.message||f.title), C3)}${ansi.gray}${BOX.lv}${ansi.reset} `;
262
- lines.push(ansi.gray + BOX.v + ansi.reset + row + ansi.gray + BOX.v + ansi.reset);
694
+ function renderTable(headers, rows, options = {}) {
695
+ const { padding = 2, headerColor = colors.primary } = options;
696
+
697
+ // Calculate column widths
698
+ const colWidths = headers.map((h, i) => {
699
+ const maxData = Math.max(...rows.map(r => stripAnsi(String(r[i] || '')).length));
700
+ return Math.max(stripAnsi(h).length, maxData) + padding;
701
+ });
702
+
703
+ const lines = [];
704
+ const totalWidth = colWidths.reduce((a, b) => a + b, 0) + colWidths.length + 1;
705
+
706
+ // Header
707
+ lines.push(` ${ansi.dim}${box.topLeft}${'─'.repeat(totalWidth - 2)}${box.topRight}${ansi.reset}`);
708
+ lines.push(` ${ansi.dim}${box.vertical}${ansi.reset}${headers.map((h, i) => `${headerColor}${ansi.bold}${h.padEnd(colWidths[i])}${ansi.reset}`).join(`${ansi.dim}│${ansi.reset}`)}${ansi.dim}${box.vertical}${ansi.reset}`);
709
+ lines.push(` ${ansi.dim}${box.teeRight}${'─'.repeat(totalWidth - 2)}${box.teeLeft}${ansi.reset}`);
710
+
711
+ // Rows
712
+ for (const row of rows) {
713
+ const cells = row.map((cell, i) => {
714
+ const str = String(cell || '');
715
+ const visibleLen = stripAnsi(str).length;
716
+ return str + ' '.repeat(Math.max(0, colWidths[i] - visibleLen));
263
717
  });
264
-
265
- lines.push(ansi.gray + BOX.v + ansi.reset + ansi.gray + tBot + ansi.reset + ansi.gray + BOX.v + ansi.reset);
266
- } else {
267
- lines.push(ansi.gray + BOX.v + ansi.reset + padCenter(style.success('✅ NO ISSUES FOUND'), WIDTH - 2) + ansi.gray + BOX.v + ansi.reset);
718
+ lines.push(` ${ansi.dim}${box.vertical}${ansi.reset}${cells.join(`${ansi.dim}│${ansi.reset}`)}${ansi.dim}${box.vertical}${ansi.reset}`);
268
719
  }
269
-
270
- lines.push(ansi.gray + BOX.v + ansi.reset + ' '.repeat(WIDTH - 2) + ansi.gray + BOX.v + ansi.reset);
271
- console.log(lines.join('\n'));
720
+
721
+ lines.push(` ${ansi.dim}${box.bottomLeft}${''.repeat(totalWidth - 2)}${box.bottomRight}${ansi.reset}`);
722
+
723
+ return lines.join('\n');
272
724
  }
273
725
 
274
726
  // ═══════════════════════════════════════════════════════════════════════════════
275
- // COLORS OBJECT (for compatibility)
727
+ // SECTION HEADERS
276
728
  // ═══════════════════════════════════════════════════════════════════════════════
277
729
 
278
- const colors = {
279
- success: ansi.green,
280
- error: ansi.red,
281
- warning: ansi.yellow,
282
- info: ansi.cyan,
283
- accent: ansi.cyan,
284
- muted: ansi.gray,
285
- highlight: ansi.white,
286
- };
730
+ function renderSection(title, icon = '◆') {
731
+ return `\n ${colors.accent}${icon}${ansi.reset} ${ansi.bold}${title}${ansi.reset}\n ${ansi.dim}${'─'.repeat(60)}${ansi.reset}`;
732
+ }
733
+
734
+ function renderDivider(char = '─', width = 60) {
735
+ return ` ${ansi.dim}${char.repeat(width)}${ansi.reset}`;
736
+ }
287
737
 
288
738
  // ═══════════════════════════════════════════════════════════════════════════════
289
739
  // UTILITY FUNCTIONS
290
740
  // ═══════════════════════════════════════════════════════════════════════════════
291
741
 
292
- /**
293
- * Format duration in milliseconds to human-readable string
294
- */
742
+ function truncate(str, len) {
743
+ if (!str) return '';
744
+ str = String(str);
745
+ const visible = stripAnsi(str);
746
+ if (visible.length <= len) return str;
747
+ return str.slice(0, len - 3) + '...';
748
+ }
749
+
750
+ function stripAnsi(str) {
751
+ return str.replace(/\x1b\[[0-9;]*m/g, '');
752
+ }
753
+
754
+ function formatNumber(num) {
755
+ return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
756
+ }
757
+
295
758
  function formatDuration(ms) {
296
759
  if (ms < 1000) return `${ms}ms`;
297
760
  if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
298
- const minutes = Math.floor(ms / 60000);
299
- const seconds = Math.floor((ms % 60000) / 1000);
300
- return `${minutes}m ${seconds}s`;
761
+ return `${Math.floor(ms / 60000)}m ${Math.round((ms % 60000) / 1000)}s`;
301
762
  }
302
763
 
303
- /**
304
- * Render a section header
305
- */
306
- function renderSection(title, icon = '') {
307
- return `\n ${ansi.cyan}${icon}${ansi.reset} ${ansi.bold}${title}${ansi.reset}\n ${ansi.gray}${BOX.lh.repeat(WIDTH - 4)}${ansi.reset}`;
764
+ function formatBytes(bytes) {
765
+ if (bytes === 0) return '0 B';
766
+ const k = 1024;
767
+ const sizes = ['B', 'KB', 'MB', 'GB'];
768
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
769
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
308
770
  }
309
771
 
310
- /**
311
- * Render banner (placeholder - actual banner is in runScan.js)
312
- */
313
- function renderBanner() {
314
- // Banner is handled in individual command files
315
- return '';
316
- }
772
+ // ═══════════════════════════════════════════════════════════════════════════════
773
+ // BANNER GENERATOR
774
+ // ═══════════════════════════════════════════════════════════════════════════════
317
775
 
318
- /**
319
- * PhaseProgress class (placeholder)
320
- */
321
- class PhaseProgress {
322
- constructor() {}
323
- start() { return this; }
324
- update() { return this; }
325
- stop() { return this; }
776
+ function renderBanner(name = 'VIBECHECK', subtitle = '') {
777
+ const gradient = [
778
+ ansi.rgb(0, 200, 255),
779
+ ansi.rgb(30, 180, 255),
780
+ ansi.rgb(60, 160, 255),
781
+ ansi.rgb(90, 140, 255),
782
+ ansi.rgb(120, 120, 255),
783
+ ansi.rgb(150, 100, 255),
784
+ ];
785
+
786
+ const asciiArt = {
787
+ V: ['██╗ ██╗', '██║ ██║', '██║ ██║', '╚██╗ ██╔╝', ' ╚████╔╝ ', ' ╚═══╝ '],
788
+ I: ['██╗', '██║', '██║', '██║', '██║', '╚═╝'],
789
+ B: ['██████╗ ', '██╔══██╗', '██████╔╝', '██╔══██╗', '██████╔╝', '╚═════╝ '],
790
+ E: ['███████╗', '██╔════╝', '█████╗ ', '██╔══╝ ', '███████╗', '╚══════╝'],
791
+ C: [' ██████╗', '██╔════╝', '██║ ', '██║ ', '╚██████╗', ' ╚═════╝'],
792
+ H: ['██╗ ██╗', '██║ ██║', '███████║', '██╔══██║', '██║ ██║', '╚═╝ ╚═╝'],
793
+ K: ['██╗ ██╗', '██║ ██╔╝', '█████╔╝ ', '██╔═██╗ ', '██║ ██╗', '╚═╝ ╚═╝'],
794
+ };
795
+
796
+ const lines = [];
797
+ lines.push('');
798
+
799
+ for (let row = 0; row < 6; row++) {
800
+ let line = ' ';
801
+ for (const char of name) {
802
+ if (asciiArt[char]) {
803
+ line += asciiArt[char][row];
804
+ }
805
+ }
806
+ lines.push(`${gradient[row]}${line}${ansi.reset}`);
807
+ }
808
+
809
+ if (subtitle) {
810
+ lines.push('');
811
+ lines.push(` ${ansi.dim}${box.topLeft}${'─'.repeat(subtitle.length + 4)}${box.topRight}${ansi.reset}`);
812
+ lines.push(` ${ansi.dim}${box.vertical}${ansi.reset} ${subtitle} ${ansi.dim}${box.vertical}${ansi.reset}`);
813
+ lines.push(` ${ansi.dim}${box.bottomLeft}${'─'.repeat(subtitle.length + 4)}${box.bottomRight}${ansi.reset}`);
814
+ }
815
+
816
+ lines.push('');
817
+ return lines.join('\n');
326
818
  }
327
819
 
328
820
  // ═══════════════════════════════════════════════════════════════════════════════
@@ -330,22 +822,32 @@ class PhaseProgress {
330
822
  // ═══════════════════════════════════════════════════════════════════════════════
331
823
 
332
824
  module.exports = {
333
- WIDTH,
825
+ // ANSI codes
334
826
  ansi,
335
827
  colors,
336
- style,
828
+ box,
337
829
  icons,
338
- BOX,
339
- padCenter,
340
- padRight,
341
- truncate,
830
+
831
+ // Components
342
832
  Spinner,
833
+ ProgressBar,
343
834
  PhaseProgress,
344
- renderProgressBar,
345
- renderScreenHeader,
346
- renderScreenFooter,
347
- renderVerdictTable,
835
+
836
+ // Renderers
837
+ renderScoreCard,
838
+ renderFindingsList,
839
+ renderFinding,
840
+ renderTable,
348
841
  renderSection,
842
+ renderDivider,
349
843
  renderBanner,
844
+
845
+ // Utilities
846
+ truncate,
847
+ stripAnsi,
848
+ formatNumber,
350
849
  formatDuration,
351
- };
850
+ formatBytes,
851
+ getScoreColor,
852
+ getGrade,
853
+ };