@vibecheckai/cli 3.5.0 → 3.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/registry.js +214 -237
- package/bin/runners/cli-utils.js +33 -2
- package/bin/runners/context/analyzer.js +52 -1
- package/bin/runners/context/generators/cursor.js +2 -49
- package/bin/runners/context/git-context.js +3 -1
- package/bin/runners/context/team-conventions.js +33 -7
- package/bin/runners/lib/analysis-core.js +25 -5
- package/bin/runners/lib/analyzers.js +431 -481
- package/bin/runners/lib/default-config.js +127 -0
- package/bin/runners/lib/doctor/modules/security.js +3 -1
- package/bin/runners/lib/engine/ast-cache.js +210 -0
- package/bin/runners/lib/engine/auth-extractor.js +211 -0
- package/bin/runners/lib/engine/billing-extractor.js +112 -0
- package/bin/runners/lib/engine/enforcement-extractor.js +100 -0
- package/bin/runners/lib/engine/env-extractor.js +207 -0
- package/bin/runners/lib/engine/express-extractor.js +208 -0
- package/bin/runners/lib/engine/extractors.js +849 -0
- package/bin/runners/lib/engine/index.js +207 -0
- package/bin/runners/lib/engine/repo-index.js +514 -0
- package/bin/runners/lib/engine/types.js +124 -0
- package/bin/runners/lib/engines/accessibility-engine.js +18 -218
- package/bin/runners/lib/engines/api-consistency-engine.js +30 -335
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +27 -292
- package/bin/runners/lib/engines/empty-catch-engine.js +17 -127
- package/bin/runners/lib/engines/mock-data-engine.js +10 -53
- package/bin/runners/lib/engines/performance-issues-engine.js +36 -176
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +54 -382
- package/bin/runners/lib/engines/type-aware-engine.js +39 -263
- package/bin/runners/lib/engines/vibecheck-engines/index.js +13 -122
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +73 -373
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
- package/bin/runners/lib/entitlements-v2.js +73 -97
- package/bin/runners/lib/error-handler.js +44 -3
- package/bin/runners/lib/error-messages.js +289 -0
- package/bin/runners/lib/evidence-pack.js +7 -1
- package/bin/runners/lib/finding-id.js +69 -0
- package/bin/runners/lib/finding-sorter.js +89 -0
- package/bin/runners/lib/html-proof-report.js +700 -350
- package/bin/runners/lib/missions/plan.js +6 -46
- package/bin/runners/lib/missions/templates.js +0 -232
- package/bin/runners/lib/next-action.js +560 -0
- package/bin/runners/lib/prerequisites.js +149 -0
- package/bin/runners/lib/route-detection.js +137 -68
- package/bin/runners/lib/scan-output.js +91 -76
- package/bin/runners/lib/scan-runner.js +135 -0
- package/bin/runners/lib/schemas/ajv-validator.js +464 -0
- package/bin/runners/lib/schemas/error-envelope.schema.json +105 -0
- package/bin/runners/lib/schemas/finding-v3.schema.json +151 -0
- package/bin/runners/lib/schemas/report-artifact.schema.json +120 -0
- package/bin/runners/lib/schemas/run-request.schema.json +108 -0
- package/bin/runners/lib/schemas/validator.js +27 -0
- package/bin/runners/lib/schemas/verdict.schema.json +140 -0
- package/bin/runners/lib/ship-output-enterprise.js +23 -23
- package/bin/runners/lib/ship-output.js +75 -31
- package/bin/runners/lib/terminal-ui.js +6 -113
- package/bin/runners/lib/truth.js +351 -10
- package/bin/runners/lib/unified-cli-output.js +430 -603
- package/bin/runners/lib/unified-output.js +13 -9
- package/bin/runners/runAIAgent.js +10 -5
- package/bin/runners/runAgent.js +0 -3
- package/bin/runners/runAllowlist.js +389 -0
- package/bin/runners/runApprove.js +0 -33
- package/bin/runners/runAuth.js +73 -45
- package/bin/runners/runCheckpoint.js +51 -11
- package/bin/runners/runClassify.js +85 -21
- package/bin/runners/runContext.js +0 -3
- package/bin/runners/runDoctor.js +41 -28
- package/bin/runners/runEvidencePack.js +362 -0
- package/bin/runners/runFirewall.js +0 -3
- package/bin/runners/runFirewallHook.js +0 -3
- package/bin/runners/runFix.js +66 -76
- package/bin/runners/runGuard.js +18 -411
- package/bin/runners/runInit.js +113 -30
- package/bin/runners/runLabs.js +424 -0
- package/bin/runners/runMcp.js +19 -25
- package/bin/runners/runPolish.js +64 -240
- package/bin/runners/runPromptFirewall.js +12 -5
- package/bin/runners/runProve.js +57 -22
- package/bin/runners/runQuickstart.js +531 -0
- package/bin/runners/runReality.js +59 -68
- package/bin/runners/runReport.js +38 -33
- package/bin/runners/runRuntime.js +8 -5
- package/bin/runners/runScan.js +1413 -190
- package/bin/runners/runShip.js +113 -719
- package/bin/runners/runTruth.js +0 -3
- package/bin/runners/runValidate.js +13 -9
- package/bin/runners/runWatch.js +23 -14
- package/bin/scan.js +6 -1
- package/bin/vibecheck.js +204 -185
- package/mcp-server/deprecation-middleware.js +282 -0
- package/mcp-server/handlers/index.ts +15 -0
- package/mcp-server/handlers/tool-handler.ts +554 -0
- package/mcp-server/index-v1.js +698 -0
- package/mcp-server/index.js +210 -238
- package/mcp-server/lib/cache-wrapper.cjs +383 -0
- package/mcp-server/lib/error-envelope.js +138 -0
- package/mcp-server/lib/executor.ts +499 -0
- package/mcp-server/lib/index.ts +19 -0
- package/mcp-server/lib/rate-limiter.js +166 -0
- package/mcp-server/lib/sandbox.test.ts +519 -0
- package/mcp-server/lib/sandbox.ts +395 -0
- package/mcp-server/lib/types.ts +267 -0
- package/mcp-server/package.json +12 -3
- package/mcp-server/registry/tool-registry.js +794 -0
- package/mcp-server/registry/tools.json +605 -0
- package/mcp-server/registry.test.ts +334 -0
- package/mcp-server/tests/tier-gating.test.js +297 -0
- package/mcp-server/tier-auth.js +378 -45
- package/mcp-server/tools-v3.js +353 -442
- package/mcp-server/tsconfig.json +37 -0
- package/mcp-server/vibecheck-2.0-tools.js +14 -1
- package/package.json +1 -1
- package/bin/runners/lib/agent-firewall/learning/learning-engine.js +0 -849
- package/bin/runners/lib/audit-logger.js +0 -532
- package/bin/runners/lib/authority/authorities/architecture.js +0 -364
- package/bin/runners/lib/authority/authorities/compliance.js +0 -341
- package/bin/runners/lib/authority/authorities/human.js +0 -343
- package/bin/runners/lib/authority/authorities/quality.js +0 -420
- package/bin/runners/lib/authority/authorities/security.js +0 -228
- package/bin/runners/lib/authority/index.js +0 -293
- package/bin/runners/lib/bundle/bundle-intelligence.js +0 -846
- package/bin/runners/lib/cli-charts.js +0 -368
- package/bin/runners/lib/cli-config-display.js +0 -405
- package/bin/runners/lib/cli-demo.js +0 -275
- package/bin/runners/lib/cli-errors.js +0 -438
- package/bin/runners/lib/cli-help-formatter.js +0 -439
- package/bin/runners/lib/cli-interactive-menu.js +0 -509
- package/bin/runners/lib/cli-prompts.js +0 -441
- package/bin/runners/lib/cli-scan-cards.js +0 -362
- package/bin/runners/lib/compliance-reporter.js +0 -710
- package/bin/runners/lib/conductor/index.js +0 -671
- package/bin/runners/lib/easy/README.md +0 -123
- package/bin/runners/lib/easy/index.js +0 -140
- package/bin/runners/lib/easy/interactive-wizard.js +0 -788
- package/bin/runners/lib/easy/one-click-firewall.js +0 -564
- package/bin/runners/lib/easy/zero-config-reality.js +0 -714
- package/bin/runners/lib/engines/async-patterns-engine.js +0 -444
- package/bin/runners/lib/engines/bundle-size-engine.js +0 -433
- package/bin/runners/lib/engines/confidence-scoring.js +0 -276
- package/bin/runners/lib/engines/context-detection.js +0 -264
- package/bin/runners/lib/engines/database-patterns-engine.js +0 -429
- package/bin/runners/lib/engines/duplicate-code-engine.js +0 -354
- package/bin/runners/lib/engines/env-variables-engine.js +0 -458
- package/bin/runners/lib/engines/error-handling-engine.js +0 -437
- package/bin/runners/lib/engines/false-positive-prevention.js +0 -630
- package/bin/runners/lib/engines/framework-adapters/index.js +0 -607
- package/bin/runners/lib/engines/framework-detection.js +0 -508
- package/bin/runners/lib/engines/import-order-engine.js +0 -429
- package/bin/runners/lib/engines/naming-conventions-engine.js +0 -544
- package/bin/runners/lib/engines/noise-reduction-engine.js +0 -452
- package/bin/runners/lib/engines/orchestrator.js +0 -334
- package/bin/runners/lib/engines/react-patterns-engine.js +0 -457
- package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +0 -806
- package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +0 -577
- package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +0 -543
- package/bin/runners/lib/engines/vibecheck-engines.js +0 -514
- package/bin/runners/lib/enhanced-features/index.js +0 -305
- package/bin/runners/lib/enhanced-output.js +0 -631
- package/bin/runners/lib/enterprise.js +0 -300
- package/bin/runners/lib/firewall/command-validator.js +0 -351
- package/bin/runners/lib/firewall/config.js +0 -341
- package/bin/runners/lib/firewall/content-validator.js +0 -519
- package/bin/runners/lib/firewall/index.js +0 -101
- package/bin/runners/lib/firewall/path-validator.js +0 -256
- package/bin/runners/lib/intelligence/cross-repo-intelligence.js +0 -817
- package/bin/runners/lib/mcp-utils.js +0 -425
- package/bin/runners/lib/output/index.js +0 -1022
- package/bin/runners/lib/policy-engine.js +0 -652
- package/bin/runners/lib/polish/autofix/accessibility-fixes.js +0 -333
- package/bin/runners/lib/polish/autofix/async-handlers.js +0 -273
- package/bin/runners/lib/polish/autofix/dead-code.js +0 -280
- package/bin/runners/lib/polish/autofix/imports-optimizer.js +0 -344
- package/bin/runners/lib/polish/autofix/index.js +0 -200
- package/bin/runners/lib/polish/autofix/remove-consoles.js +0 -209
- package/bin/runners/lib/polish/autofix/strengthen-types.js +0 -245
- package/bin/runners/lib/polish/backend-checks.js +0 -148
- package/bin/runners/lib/polish/documentation-checks.js +0 -111
- package/bin/runners/lib/polish/frontend-checks.js +0 -168
- package/bin/runners/lib/polish/index.js +0 -71
- package/bin/runners/lib/polish/infrastructure-checks.js +0 -131
- package/bin/runners/lib/polish/library-detection.js +0 -175
- package/bin/runners/lib/polish/performance-checks.js +0 -100
- package/bin/runners/lib/polish/security-checks.js +0 -148
- package/bin/runners/lib/polish/utils.js +0 -203
- package/bin/runners/lib/prompt-builder.js +0 -540
- package/bin/runners/lib/proof-certificate.js +0 -634
- package/bin/runners/lib/reality/accessibility-audit.js +0 -946
- package/bin/runners/lib/reality/api-contract-validator.js +0 -1012
- package/bin/runners/lib/reality/chaos-engineering.js +0 -1084
- package/bin/runners/lib/reality/performance-tracker.js +0 -1077
- package/bin/runners/lib/reality/scenario-generator.js +0 -1404
- package/bin/runners/lib/reality/visual-regression.js +0 -852
- package/bin/runners/lib/reality-profiler.js +0 -717
- package/bin/runners/lib/replay/flight-recorder-viewer.js +0 -1160
- package/bin/runners/lib/review/ai-code-review.js +0 -832
- package/bin/runners/lib/rules/custom-rule-engine.js +0 -985
- package/bin/runners/lib/sbom-generator.js +0 -641
- package/bin/runners/lib/scan-output-enhanced.js +0 -512
- package/bin/runners/lib/security/owasp-scanner.js +0 -939
- package/bin/runners/lib/validators/contract-validator.js +0 -283
- package/bin/runners/lib/validators/dead-export-detector.js +0 -279
- package/bin/runners/lib/validators/dep-audit.js +0 -245
- package/bin/runners/lib/validators/env-validator.js +0 -319
- package/bin/runners/lib/validators/index.js +0 -120
- package/bin/runners/lib/validators/license-checker.js +0 -252
- package/bin/runners/lib/validators/route-validator.js +0 -290
- package/bin/runners/runAuthority.js +0 -528
- package/bin/runners/runConductor.js +0 -772
- package/bin/runners/runContainer.js +0 -366
- package/bin/runners/runEasy.js +0 -410
- package/bin/runners/runIaC.js +0 -372
- package/bin/runners/runVibe.js +0 -791
- package/mcp-server/tools.js +0 -495
|
@@ -1,1022 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OutputManager - Unified CLI Output System for Vibecheck
|
|
3
|
-
*
|
|
4
|
-
* Central output management with consistent styling, JSON mode, and progress indicators.
|
|
5
|
-
* All CLI runners should import and use this module for output consistency.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* import { output } from './lib/output/index.js';
|
|
9
|
-
* output.setMode({ json: opts.json, quiet: opts.quiet });
|
|
10
|
-
* output.header('scan', { target: projectPath });
|
|
11
|
-
* output.startProgress('Analyzing codebase...');
|
|
12
|
-
* // ... work ...
|
|
13
|
-
* output.succeedProgress('Analysis complete');
|
|
14
|
-
* output.findingsList(findings);
|
|
15
|
-
* output.summaryCard({ score, files, duration });
|
|
16
|
-
* output.flush();
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
"use strict";
|
|
20
|
-
|
|
21
|
-
const { ansi, BOX, icons, Spinner, padRight, truncate, formatDuration, WIDTH, colors, style } = require('../terminal-ui.js');
|
|
22
|
-
|
|
23
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
24
|
-
// CONSTANTS
|
|
25
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
26
|
-
|
|
27
|
-
const BOX_WIDTH = 76;
|
|
28
|
-
|
|
29
|
-
const SUPPORTS_COLOR = process.stdout.isTTY &&
|
|
30
|
-
!process.env.NO_COLOR &&
|
|
31
|
-
process.env.TERM !== 'dumb';
|
|
32
|
-
|
|
33
|
-
const SUPPORTS_UNICODE = (() => {
|
|
34
|
-
if (process.env.VIBECHECK_NO_UNICODE === '1') return false;
|
|
35
|
-
if (process.platform === 'win32') {
|
|
36
|
-
return Boolean(
|
|
37
|
-
process.env.CI ||
|
|
38
|
-
process.env.WT_SESSION ||
|
|
39
|
-
process.env.TERMINAL_EMULATOR === 'JetBrains-JediTerm' ||
|
|
40
|
-
(process.env.TERM || '').includes('256color') ||
|
|
41
|
-
(process.env.LANG || '').toLowerCase().includes('utf')
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
return process.env.TERM !== 'linux';
|
|
45
|
-
})();
|
|
46
|
-
|
|
47
|
-
// Command icons
|
|
48
|
-
const CMD_ICONS = SUPPORTS_UNICODE ? {
|
|
49
|
-
scan: '🔍',
|
|
50
|
-
ship: '🚀',
|
|
51
|
-
fix: '🔧',
|
|
52
|
-
polish: '✨',
|
|
53
|
-
prove: '📜',
|
|
54
|
-
reality: '🎭',
|
|
55
|
-
doctor: '🩺',
|
|
56
|
-
watch: '👁️',
|
|
57
|
-
report: '📊',
|
|
58
|
-
auth: '🔑',
|
|
59
|
-
init: '⚙️',
|
|
60
|
-
guard: '🛡️',
|
|
61
|
-
validate: '✓',
|
|
62
|
-
checkpoint: '📍',
|
|
63
|
-
mcp: '🔌',
|
|
64
|
-
runtime: '⏱️',
|
|
65
|
-
classify: '🏷️',
|
|
66
|
-
context: '📂',
|
|
67
|
-
default: '✨'
|
|
68
|
-
} : {
|
|
69
|
-
scan: '[S]',
|
|
70
|
-
ship: '^',
|
|
71
|
-
fix: '[F]',
|
|
72
|
-
polish: '*',
|
|
73
|
-
prove: '[P]',
|
|
74
|
-
reality: '[R]',
|
|
75
|
-
doctor: '[D]',
|
|
76
|
-
watch: '[W]',
|
|
77
|
-
report: '[#]',
|
|
78
|
-
auth: '[A]',
|
|
79
|
-
init: '[I]',
|
|
80
|
-
guard: '[G]',
|
|
81
|
-
validate: '[V]',
|
|
82
|
-
checkpoint: '[C]',
|
|
83
|
-
mcp: '[M]',
|
|
84
|
-
runtime: '[T]',
|
|
85
|
-
classify: '[L]',
|
|
86
|
-
context: '[X]',
|
|
87
|
-
default: '*'
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
// Severity icons
|
|
91
|
-
const SEV_ICONS = SUPPORTS_UNICODE ? {
|
|
92
|
-
critical: '🔴',
|
|
93
|
-
high: '🟠',
|
|
94
|
-
medium: '🟡',
|
|
95
|
-
low: '🔵',
|
|
96
|
-
info: 'ℹ️'
|
|
97
|
-
} : {
|
|
98
|
-
critical: '[!]',
|
|
99
|
-
high: '[H]',
|
|
100
|
-
medium: '[M]',
|
|
101
|
-
low: '[L]',
|
|
102
|
-
info: '[i]'
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
// Box characters for headers (double-line)
|
|
106
|
-
const D_BOX = SUPPORTS_UNICODE ? {
|
|
107
|
-
tl: '╔', tr: '╗', bl: '╚', br: '╝', h: '═', v: '║'
|
|
108
|
-
} : {
|
|
109
|
-
tl: '+', tr: '+', bl: '+', br: '+', h: '=', v: '|'
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
// Box characters for findings (single-line)
|
|
113
|
-
const S_BOX = SUPPORTS_UNICODE ? {
|
|
114
|
-
tl: '┌', tr: '┐', bl: '└', br: '┘', h: '─', v: '│'
|
|
115
|
-
} : {
|
|
116
|
-
tl: '+', tr: '+', bl: '+', br: '+', h: '-', v: '|'
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
// Box characters for summary (rounded)
|
|
120
|
-
const R_BOX = SUPPORTS_UNICODE ? {
|
|
121
|
-
tl: '╭', tr: '╮', bl: '╰', br: '╯', h: '─', v: '│'
|
|
122
|
-
} : {
|
|
123
|
-
tl: '+', tr: '+', bl: '+', br: '+', h: '-', v: '|'
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
// Progress bar characters
|
|
127
|
-
const PROG = SUPPORTS_UNICODE ? {
|
|
128
|
-
filled: '█',
|
|
129
|
-
empty: '░'
|
|
130
|
-
} : {
|
|
131
|
-
filled: '#',
|
|
132
|
-
empty: '-'
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
136
|
-
// UTILITY FUNCTIONS
|
|
137
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Get visible length of string (excluding ANSI codes)
|
|
141
|
-
*/
|
|
142
|
-
function visibleLength(str) {
|
|
143
|
-
return (str || '').replace(/\x1b\[[0-9;]*m/g, '').replace(/[\u{1F300}-\u{1F9FF}]/gu, ' ').length;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Pad string to right with visible length awareness
|
|
148
|
-
*/
|
|
149
|
-
function padToRight(str, width, char = ' ') {
|
|
150
|
-
const visible = visibleLength(str);
|
|
151
|
-
if (visible >= width) return str;
|
|
152
|
-
return str + char.repeat(width - visible);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Pad string to left with visible length awareness
|
|
157
|
-
*/
|
|
158
|
-
function padToLeft(str, width, char = ' ') {
|
|
159
|
-
const visible = visibleLength(str);
|
|
160
|
-
if (visible >= width) return str;
|
|
161
|
-
return char.repeat(width - visible) + str;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Center string within width
|
|
166
|
-
*/
|
|
167
|
-
function centerText(str, width) {
|
|
168
|
-
const visible = visibleLength(str);
|
|
169
|
-
if (visible >= width) return str;
|
|
170
|
-
const left = Math.floor((width - visible) / 2);
|
|
171
|
-
const right = width - visible - left;
|
|
172
|
-
return ' '.repeat(left) + str + ' '.repeat(right);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Truncate string with ellipsis
|
|
177
|
-
*/
|
|
178
|
-
function truncateStr(str, maxLen) {
|
|
179
|
-
if (!str) return '';
|
|
180
|
-
const visible = visibleLength(str);
|
|
181
|
-
if (visible <= maxLen) return str;
|
|
182
|
-
// For strings with ANSI, we need to be more careful
|
|
183
|
-
const clean = str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
184
|
-
if (clean.length <= maxLen) return str;
|
|
185
|
-
return clean.substring(0, maxLen - 3) + '...';
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Format timestamp
|
|
190
|
-
*/
|
|
191
|
-
function formatTimestamp() {
|
|
192
|
-
const now = new Date();
|
|
193
|
-
const pad = (n) => String(n).padStart(2, '0');
|
|
194
|
-
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Format duration in ms to readable string
|
|
199
|
-
*/
|
|
200
|
-
function formatDur(ms) {
|
|
201
|
-
if (ms < 1000) return `${ms}ms`;
|
|
202
|
-
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
203
|
-
const mins = Math.floor(ms / 60000);
|
|
204
|
-
const secs = Math.floor((ms % 60000) / 1000);
|
|
205
|
-
return `${mins}m ${secs}s`;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Get severity color
|
|
210
|
-
*/
|
|
211
|
-
function getSeverityColor(severity) {
|
|
212
|
-
switch ((severity || '').toLowerCase()) {
|
|
213
|
-
case 'critical':
|
|
214
|
-
case 'block':
|
|
215
|
-
return colors.blockRed || ansi.red;
|
|
216
|
-
case 'high':
|
|
217
|
-
case 'warn':
|
|
218
|
-
return colors.warnAmber || ansi.yellow;
|
|
219
|
-
case 'medium':
|
|
220
|
-
return ansi.yellow;
|
|
221
|
-
case 'low':
|
|
222
|
-
return ansi.blue;
|
|
223
|
-
default:
|
|
224
|
-
return ansi.gray;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
229
|
-
// OUTPUT MANAGER CLASS
|
|
230
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
231
|
-
|
|
232
|
-
class OutputManager {
|
|
233
|
-
constructor() {
|
|
234
|
-
this._json = false;
|
|
235
|
-
this._quiet = false;
|
|
236
|
-
this._ci = false;
|
|
237
|
-
this._buffer = {
|
|
238
|
-
command: null,
|
|
239
|
-
findings: [],
|
|
240
|
-
summary: null,
|
|
241
|
-
errors: [],
|
|
242
|
-
warnings: [],
|
|
243
|
-
info: []
|
|
244
|
-
};
|
|
245
|
-
this._spinner = null;
|
|
246
|
-
this._startTime = null;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
250
|
-
// MODE CONFIGURATION
|
|
251
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Set output mode
|
|
255
|
-
* @param {Object} options - { json, quiet, ci }
|
|
256
|
-
*/
|
|
257
|
-
setMode({ json = false, quiet = false, ci = false } = {}) {
|
|
258
|
-
this._json = json;
|
|
259
|
-
this._quiet = quiet;
|
|
260
|
-
this._ci = ci || Boolean(process.env.CI);
|
|
261
|
-
return this;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Check if in JSON mode
|
|
266
|
-
*/
|
|
267
|
-
get isJson() {
|
|
268
|
-
return this._json;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Check if in quiet mode
|
|
273
|
-
*/
|
|
274
|
-
get isQuiet() {
|
|
275
|
-
return this._quiet;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Check if in CI mode
|
|
280
|
-
*/
|
|
281
|
-
get isCi() {
|
|
282
|
-
return this._ci;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
286
|
-
// HEADER
|
|
287
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Render command header
|
|
291
|
-
* @param {string} command - Command name (scan, ship, fix, etc.)
|
|
292
|
-
* @param {Object} meta - Metadata like { target, tier }
|
|
293
|
-
*/
|
|
294
|
-
header(command, meta = {}) {
|
|
295
|
-
this._buffer.command = command;
|
|
296
|
-
this._startTime = Date.now();
|
|
297
|
-
|
|
298
|
-
if (this._json || this._quiet) return this;
|
|
299
|
-
|
|
300
|
-
const cmdIcon = CMD_ICONS[command] || CMD_ICONS.default;
|
|
301
|
-
const title = `VIBECHECK ${command.toUpperCase()}`;
|
|
302
|
-
const timestamp = formatTimestamp();
|
|
303
|
-
const target = meta.target ? truncateStr(meta.target, 40) : '';
|
|
304
|
-
|
|
305
|
-
const innerWidth = BOX_WIDTH - 2;
|
|
306
|
-
|
|
307
|
-
// Build header box
|
|
308
|
-
const lines = [];
|
|
309
|
-
|
|
310
|
-
// Top border
|
|
311
|
-
lines.push(`${ansi.gray}${D_BOX.tl}${D_BOX.h.repeat(innerWidth)}${D_BOX.tr}${ansi.reset}`);
|
|
312
|
-
|
|
313
|
-
// Title line (centered)
|
|
314
|
-
const titleWithIcon = `${cmdIcon} ${title}`;
|
|
315
|
-
lines.push(`${ansi.gray}${D_BOX.v}${ansi.reset}${centerText(`${ansi.bold}${titleWithIcon}${ansi.reset}`, innerWidth)}${ansi.gray}${D_BOX.v}${ansi.reset}`);
|
|
316
|
-
|
|
317
|
-
// Meta line (target left, timestamp right)
|
|
318
|
-
if (target || timestamp) {
|
|
319
|
-
const targetStr = target ? `Target: ${target}` : '';
|
|
320
|
-
const leftPad = 2;
|
|
321
|
-
const rightPad = 2;
|
|
322
|
-
const availWidth = innerWidth - leftPad - rightPad - visibleLength(timestamp);
|
|
323
|
-
const metaLine = ' '.repeat(leftPad) +
|
|
324
|
-
padToRight(targetStr, availWidth) +
|
|
325
|
-
`${ansi.gray}${timestamp}${ansi.reset}` +
|
|
326
|
-
' '.repeat(rightPad);
|
|
327
|
-
lines.push(`${ansi.gray}${D_BOX.v}${ansi.reset}${metaLine}${ansi.gray}${D_BOX.v}${ansi.reset}`);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Bottom border
|
|
331
|
-
lines.push(`${ansi.gray}${D_BOX.bl}${D_BOX.h.repeat(innerWidth)}${D_BOX.br}${ansi.reset}`);
|
|
332
|
-
|
|
333
|
-
console.log('');
|
|
334
|
-
console.log(lines.join('\n'));
|
|
335
|
-
console.log('');
|
|
336
|
-
|
|
337
|
-
return this;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
341
|
-
// PROGRESS
|
|
342
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Start progress spinner
|
|
346
|
-
* @param {string} text - Progress message
|
|
347
|
-
*/
|
|
348
|
-
startProgress(text) {
|
|
349
|
-
if (this._json) return this;
|
|
350
|
-
|
|
351
|
-
if (this._ci) {
|
|
352
|
-
console.log(`::group::${text}`);
|
|
353
|
-
return this;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
if (this._quiet) return this;
|
|
357
|
-
|
|
358
|
-
this._spinner = new Spinner(text);
|
|
359
|
-
this._spinner.start();
|
|
360
|
-
return this;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* Update progress message
|
|
365
|
-
* @param {string} text - New progress message
|
|
366
|
-
*/
|
|
367
|
-
updateProgress(text) {
|
|
368
|
-
if (this._json || this._ci || this._quiet) return this;
|
|
369
|
-
|
|
370
|
-
if (this._spinner) {
|
|
371
|
-
this._spinner.text = text;
|
|
372
|
-
}
|
|
373
|
-
return this;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* Complete progress with success
|
|
378
|
-
* @param {string} text - Success message
|
|
379
|
-
*/
|
|
380
|
-
succeedProgress(text) {
|
|
381
|
-
if (this._json) return this;
|
|
382
|
-
|
|
383
|
-
if (this._ci) {
|
|
384
|
-
console.log(`::endgroup::`);
|
|
385
|
-
console.log(`✓ ${text || 'Complete'}`);
|
|
386
|
-
return this;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
if (this._quiet) return this;
|
|
390
|
-
|
|
391
|
-
if (this._spinner) {
|
|
392
|
-
this._spinner.succeed(text);
|
|
393
|
-
this._spinner = null;
|
|
394
|
-
} else {
|
|
395
|
-
console.log(` ${ansi.green}${icons.success}${ansi.reset} ${text}`);
|
|
396
|
-
}
|
|
397
|
-
return this;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
/**
|
|
401
|
-
* Complete progress with failure
|
|
402
|
-
* @param {string} text - Failure message
|
|
403
|
-
*/
|
|
404
|
-
failProgress(text) {
|
|
405
|
-
if (this._json) return this;
|
|
406
|
-
|
|
407
|
-
if (this._ci) {
|
|
408
|
-
console.log(`::endgroup::`);
|
|
409
|
-
console.log(`✗ ${text || 'Failed'}`);
|
|
410
|
-
return this;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
if (this._quiet) return this;
|
|
414
|
-
|
|
415
|
-
if (this._spinner) {
|
|
416
|
-
this._spinner.fail(text);
|
|
417
|
-
this._spinner = null;
|
|
418
|
-
} else {
|
|
419
|
-
console.log(` ${ansi.red}${icons.error}${ansi.reset} ${text}`);
|
|
420
|
-
}
|
|
421
|
-
return this;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
/**
|
|
425
|
-
* Complete progress with warning
|
|
426
|
-
* @param {string} text - Warning message
|
|
427
|
-
*/
|
|
428
|
-
warnProgress(text) {
|
|
429
|
-
if (this._json) return this;
|
|
430
|
-
|
|
431
|
-
if (this._ci) {
|
|
432
|
-
console.log(`::endgroup::`);
|
|
433
|
-
console.log(`! ${text || 'Warning'}`);
|
|
434
|
-
return this;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
if (this._quiet) return this;
|
|
438
|
-
|
|
439
|
-
if (this._spinner) {
|
|
440
|
-
this._spinner.warn(text);
|
|
441
|
-
this._spinner = null;
|
|
442
|
-
} else {
|
|
443
|
-
console.log(` ${ansi.yellow}${icons.warning}${ansi.reset} ${text}`);
|
|
444
|
-
}
|
|
445
|
-
return this;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
449
|
-
// FINDINGS
|
|
450
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
451
|
-
|
|
452
|
-
/**
|
|
453
|
-
* Display single finding
|
|
454
|
-
* @param {Object} finding - Finding object with severity, ruleId, file, line, message, fix
|
|
455
|
-
*/
|
|
456
|
-
finding(f) {
|
|
457
|
-
this._buffer.findings.push(f);
|
|
458
|
-
|
|
459
|
-
if (this._json || this._quiet) return this;
|
|
460
|
-
|
|
461
|
-
if (this._ci) {
|
|
462
|
-
// GitHub Actions annotation format
|
|
463
|
-
const level = f.severity === 'critical' || f.severity === 'high' ? 'error' : 'warning';
|
|
464
|
-
console.log(`::${level} file=${f.file || ''},line=${f.line || 1}::${f.message || f.title}`);
|
|
465
|
-
return this;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
const innerWidth = BOX_WIDTH - 4;
|
|
469
|
-
const sevIcon = SEV_ICONS[f.severity] || SEV_ICONS.info;
|
|
470
|
-
const sevColor = getSeverityColor(f.severity);
|
|
471
|
-
const sevLabel = (f.severity || 'INFO').toUpperCase().padEnd(8);
|
|
472
|
-
const ruleId = f.ruleId || f.category || 'general';
|
|
473
|
-
|
|
474
|
-
const lines = [];
|
|
475
|
-
|
|
476
|
-
// Top border
|
|
477
|
-
lines.push(` ${ansi.gray}${S_BOX.tl}${S_BOX.h.repeat(innerWidth)}${S_BOX.tr}${ansi.reset}`);
|
|
478
|
-
|
|
479
|
-
// Severity + Rule line
|
|
480
|
-
const headerLine = `${sevColor}${sevIcon} ${sevLabel}${ansi.reset} ${ansi.gray}${ruleId}${ansi.reset}`;
|
|
481
|
-
lines.push(` ${ansi.gray}${S_BOX.v}${ansi.reset} ${padToRight(headerLine, innerWidth - 2)} ${ansi.gray}${S_BOX.v}${ansi.reset}`);
|
|
482
|
-
|
|
483
|
-
// Location line
|
|
484
|
-
if (f.file) {
|
|
485
|
-
const location = f.line ? `${f.file}:${f.line}` : f.file;
|
|
486
|
-
lines.push(` ${ansi.gray}${S_BOX.v} ${truncateStr(location, innerWidth - 2)}${padToRight('', innerWidth - visibleLength(location) - 1)}${S_BOX.v}${ansi.reset}`);
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// Message line
|
|
490
|
-
const message = f.message || f.title || '';
|
|
491
|
-
lines.push(` ${ansi.gray}${S_BOX.v}${ansi.reset} ${padToRight(truncateStr(message, innerWidth - 2), innerWidth - 2)} ${ansi.gray}${S_BOX.v}${ansi.reset}`);
|
|
492
|
-
|
|
493
|
-
// Fix suggestion line
|
|
494
|
-
if (f.fix) {
|
|
495
|
-
lines.push(` ${ansi.gray}${S_BOX.v} ${ansi.cyan}Fix:${ansi.reset} ${padToRight(truncateStr(f.fix, innerWidth - 7), innerWidth - 7)} ${ansi.gray}${S_BOX.v}${ansi.reset}`);
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
// Bottom border
|
|
499
|
-
lines.push(` ${ansi.gray}${S_BOX.bl}${S_BOX.h.repeat(innerWidth)}${S_BOX.br}${ansi.reset}`);
|
|
500
|
-
|
|
501
|
-
console.log(lines.join('\n'));
|
|
502
|
-
return this;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
/**
|
|
506
|
-
* Display list of findings
|
|
507
|
-
* @param {Array} findings - Array of finding objects
|
|
508
|
-
* @param {Object} options - { maxDisplay, groupBy }
|
|
509
|
-
*/
|
|
510
|
-
findingsList(findings, options = {}) {
|
|
511
|
-
const { maxDisplay = 10, showAll = false } = options;
|
|
512
|
-
|
|
513
|
-
if (!findings || findings.length === 0) {
|
|
514
|
-
if (!this._json && !this._quiet) {
|
|
515
|
-
console.log(` ${ansi.green}${icons.success}${ansi.reset} No issues found!`);
|
|
516
|
-
}
|
|
517
|
-
return this;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
// Sort by severity
|
|
521
|
-
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
|
|
522
|
-
const sorted = [...findings].sort((a, b) =>
|
|
523
|
-
(severityOrder[a.severity] || 4) - (severityOrder[b.severity] || 4)
|
|
524
|
-
);
|
|
525
|
-
|
|
526
|
-
// Buffer all findings for JSON
|
|
527
|
-
this._buffer.findings = sorted;
|
|
528
|
-
|
|
529
|
-
if (this._json || this._quiet) return this;
|
|
530
|
-
|
|
531
|
-
// Display findings
|
|
532
|
-
const displayCount = showAll ? sorted.length : Math.min(sorted.length, maxDisplay);
|
|
533
|
-
|
|
534
|
-
console.log('');
|
|
535
|
-
console.log(` ${ansi.bold}Issues Found (${findings.length})${ansi.reset}`);
|
|
536
|
-
console.log(` ${ansi.gray}${S_BOX.h.repeat(BOX_WIDTH - 4)}${ansi.reset}`);
|
|
537
|
-
console.log('');
|
|
538
|
-
|
|
539
|
-
for (let i = 0; i < displayCount; i++) {
|
|
540
|
-
this.finding(sorted[i]);
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
if (sorted.length > displayCount) {
|
|
544
|
-
console.log(` ${ansi.gray}... and ${sorted.length - displayCount} more issues${ansi.reset}`);
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
return this;
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
551
|
-
// SUMMARY CARD
|
|
552
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
553
|
-
|
|
554
|
-
/**
|
|
555
|
-
* Display summary card with score, stats
|
|
556
|
-
* @param {Object} data - { score, files, findings, duration, blockers, warnings }
|
|
557
|
-
*/
|
|
558
|
-
summaryCard(data) {
|
|
559
|
-
this._buffer.summary = data;
|
|
560
|
-
|
|
561
|
-
if (this._json) return this;
|
|
562
|
-
|
|
563
|
-
if (this._ci) {
|
|
564
|
-
// CI key=value output
|
|
565
|
-
console.log(`score=${data.score || 0}`);
|
|
566
|
-
console.log(`files=${data.files || 0}`);
|
|
567
|
-
console.log(`findings=${data.findings || 0}`);
|
|
568
|
-
console.log(`duration=${data.duration || 0}`);
|
|
569
|
-
return this;
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
if (this._quiet) return this;
|
|
573
|
-
|
|
574
|
-
const innerWidth = BOX_WIDTH - 4;
|
|
575
|
-
const score = data.score || 0;
|
|
576
|
-
const files = data.files || 0;
|
|
577
|
-
const findingCount = data.findings || 0;
|
|
578
|
-
const duration = typeof data.duration === 'number' ? formatDur(data.duration) : (data.duration || '0ms');
|
|
579
|
-
|
|
580
|
-
// Score color
|
|
581
|
-
const scoreColor = score >= 80 ? ansi.green : score >= 60 ? ansi.yellow : ansi.red;
|
|
582
|
-
|
|
583
|
-
// Progress bar
|
|
584
|
-
const barWidth = 24;
|
|
585
|
-
const filled = Math.round((score / 100) * barWidth);
|
|
586
|
-
const bar = `${scoreColor}${PROG.filled.repeat(filled)}${ansi.gray}${PROG.empty.repeat(barWidth - filled)}${ansi.reset}`;
|
|
587
|
-
|
|
588
|
-
const lines = [];
|
|
589
|
-
|
|
590
|
-
// Top border (rounded)
|
|
591
|
-
lines.push(` ${ansi.gray}${R_BOX.tl}${R_BOX.h.repeat(innerWidth)}${R_BOX.tr}${ansi.reset}`);
|
|
592
|
-
|
|
593
|
-
// Score line
|
|
594
|
-
const scoreLine = ` SCORE: ${scoreColor}${ansi.bold}${score}${ansi.reset}/100 ${bar}`;
|
|
595
|
-
lines.push(` ${ansi.gray}${R_BOX.v}${ansi.reset}${padToRight(scoreLine, innerWidth)} ${ansi.gray}${R_BOX.v}${ansi.reset}`);
|
|
596
|
-
|
|
597
|
-
// Stats line
|
|
598
|
-
const statsLine = ` Files: ${files} ${ansi.gray}|${ansi.reset} Findings: ${findingCount} ${ansi.gray}|${ansi.reset} Duration: ${duration}`;
|
|
599
|
-
lines.push(` ${ansi.gray}${R_BOX.v}${ansi.reset}${padToRight(statsLine, innerWidth)} ${ansi.gray}${R_BOX.v}${ansi.reset}`);
|
|
600
|
-
|
|
601
|
-
// Bottom border (rounded)
|
|
602
|
-
lines.push(` ${ansi.gray}${R_BOX.bl}${R_BOX.h.repeat(innerWidth)}${R_BOX.br}${ansi.reset}`);
|
|
603
|
-
|
|
604
|
-
console.log('');
|
|
605
|
-
console.log(lines.join('\n'));
|
|
606
|
-
|
|
607
|
-
return this;
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
611
|
-
// VERDICT (SHIP/WARN/BLOCK/PASS/FAIL)
|
|
612
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
613
|
-
|
|
614
|
-
/**
|
|
615
|
-
* Display verdict card (for ship/gate commands)
|
|
616
|
-
* @param {string} type - SHIP, WARN, BLOCK, PASS, FAIL
|
|
617
|
-
* @param {Object} data - { score, blockers, warnings, message }
|
|
618
|
-
*/
|
|
619
|
-
verdict(type, data = {}) {
|
|
620
|
-
if (this._json) {
|
|
621
|
-
this._buffer.verdict = { type, ...data };
|
|
622
|
-
return this;
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
if (this._ci) {
|
|
626
|
-
if (type === 'BLOCK' || type === 'FAIL') {
|
|
627
|
-
console.log(`::error::${type}: ${data.message || 'Check failed'}`);
|
|
628
|
-
} else if (type === 'WARN') {
|
|
629
|
-
console.log(`::warning::${type}: ${data.message || 'Review recommended'}`);
|
|
630
|
-
} else {
|
|
631
|
-
console.log(`::notice::${type}: ${data.message || 'Check passed'}`);
|
|
632
|
-
}
|
|
633
|
-
return this;
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
if (this._quiet) {
|
|
637
|
-
const exitWord = type === 'SHIP' || type === 'PASS' ? 'OK' : type;
|
|
638
|
-
console.log(exitWord);
|
|
639
|
-
return this;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
const innerWidth = BOX_WIDTH - 2;
|
|
643
|
-
|
|
644
|
-
const configs = {
|
|
645
|
-
SHIP: {
|
|
646
|
-
bg: ansi.bgGreen,
|
|
647
|
-
fg: ansi.white,
|
|
648
|
-
icon: '✅',
|
|
649
|
-
label: 'SHIP IT',
|
|
650
|
-
message: data.message || 'All checks passed'
|
|
651
|
-
},
|
|
652
|
-
PASS: {
|
|
653
|
-
bg: ansi.bgGreen,
|
|
654
|
-
fg: ansi.white,
|
|
655
|
-
icon: '✓',
|
|
656
|
-
label: 'PASS',
|
|
657
|
-
message: data.message || 'All checks passed'
|
|
658
|
-
},
|
|
659
|
-
WARN: {
|
|
660
|
-
bg: ansi.bgYellow,
|
|
661
|
-
fg: ansi.black,
|
|
662
|
-
icon: '⚠️',
|
|
663
|
-
label: 'WARN',
|
|
664
|
-
message: data.message || 'Review recommended'
|
|
665
|
-
},
|
|
666
|
-
BLOCK: {
|
|
667
|
-
bg: ansi.bgRed,
|
|
668
|
-
fg: ansi.white,
|
|
669
|
-
icon: '❌',
|
|
670
|
-
label: 'BLOCK',
|
|
671
|
-
message: data.message || 'Critical issues found'
|
|
672
|
-
},
|
|
673
|
-
FAIL: {
|
|
674
|
-
bg: ansi.bgRed,
|
|
675
|
-
fg: ansi.white,
|
|
676
|
-
icon: '✗',
|
|
677
|
-
label: 'FAIL',
|
|
678
|
-
message: data.message || 'Scan failed'
|
|
679
|
-
}
|
|
680
|
-
};
|
|
681
|
-
|
|
682
|
-
const config = configs[type] || configs.WARN;
|
|
683
|
-
|
|
684
|
-
const lines = [];
|
|
685
|
-
|
|
686
|
-
// Top border
|
|
687
|
-
lines.push(`${ansi.gray}${D_BOX.tl}${D_BOX.h.repeat(innerWidth)}${D_BOX.tr}${ansi.reset}`);
|
|
688
|
-
|
|
689
|
-
// Empty line
|
|
690
|
-
lines.push(`${ansi.gray}${D_BOX.v}${ansi.reset}${' '.repeat(innerWidth)}${ansi.gray}${D_BOX.v}${ansi.reset}`);
|
|
691
|
-
|
|
692
|
-
// Verdict line (centered, with badge)
|
|
693
|
-
const verdictStr = `${config.icon} ${config.label}`;
|
|
694
|
-
lines.push(`${ansi.gray}${D_BOX.v}${ansi.reset}${centerText(`${config.bg}${config.fg}${ansi.bold} ${config.label} ${ansi.reset}`, innerWidth)}${ansi.gray}${D_BOX.v}${ansi.reset}`);
|
|
695
|
-
|
|
696
|
-
// Message line
|
|
697
|
-
lines.push(`${ansi.gray}${D_BOX.v}${ansi.reset}${centerText(config.message, innerWidth)}${ansi.gray}${D_BOX.v}${ansi.reset}`);
|
|
698
|
-
|
|
699
|
-
// Empty line
|
|
700
|
-
lines.push(`${ansi.gray}${D_BOX.v}${ansi.reset}${' '.repeat(innerWidth)}${ansi.gray}${D_BOX.v}${ansi.reset}`);
|
|
701
|
-
|
|
702
|
-
// Stats line if provided
|
|
703
|
-
if (data.score !== undefined || data.blockers !== undefined || data.warnings !== undefined) {
|
|
704
|
-
const parts = [];
|
|
705
|
-
if (data.score !== undefined) parts.push(`Score: ${data.score}/100`);
|
|
706
|
-
if (data.blockers !== undefined) parts.push(`Blockers: ${data.blockers}`);
|
|
707
|
-
if (data.warnings !== undefined) parts.push(`Warnings: ${data.warnings}`);
|
|
708
|
-
const statsLine = parts.join(' | ');
|
|
709
|
-
lines.push(`${ansi.gray}${D_BOX.v}${ansi.reset}${centerText(`${ansi.gray}${statsLine}${ansi.reset}`, innerWidth)}${ansi.gray}${D_BOX.v}${ansi.reset}`);
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
// Bottom border
|
|
713
|
-
lines.push(`${ansi.gray}${D_BOX.bl}${D_BOX.h.repeat(innerWidth)}${D_BOX.br}${ansi.reset}`);
|
|
714
|
-
|
|
715
|
-
console.log('');
|
|
716
|
-
console.log(lines.join('\n'));
|
|
717
|
-
console.log('');
|
|
718
|
-
|
|
719
|
-
return this;
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
723
|
-
// STATUS MESSAGES
|
|
724
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
725
|
-
|
|
726
|
-
/**
|
|
727
|
-
* Display info message
|
|
728
|
-
*/
|
|
729
|
-
info(message) {
|
|
730
|
-
this._buffer.info.push(message);
|
|
731
|
-
if (this._json || this._quiet) return this;
|
|
732
|
-
|
|
733
|
-
if (this._ci) {
|
|
734
|
-
console.log(`ℹ ${message}`);
|
|
735
|
-
return this;
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
console.log(` ${ansi.cyan}${icons.info}${ansi.reset} ${message}`);
|
|
739
|
-
return this;
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
/**
|
|
743
|
-
* Display warning message
|
|
744
|
-
*/
|
|
745
|
-
warn(message) {
|
|
746
|
-
this._buffer.warnings.push(message);
|
|
747
|
-
if (this._json) return this;
|
|
748
|
-
|
|
749
|
-
if (this._ci) {
|
|
750
|
-
console.log(`::warning::${message}`);
|
|
751
|
-
return this;
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
if (this._quiet) return this;
|
|
755
|
-
|
|
756
|
-
console.log(` ${ansi.yellow}${icons.warning}${ansi.reset} ${message}`);
|
|
757
|
-
return this;
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
/**
|
|
761
|
-
* Display error message
|
|
762
|
-
*/
|
|
763
|
-
error(message) {
|
|
764
|
-
this._buffer.errors.push(message);
|
|
765
|
-
if (this._json) return this;
|
|
766
|
-
|
|
767
|
-
if (this._ci) {
|
|
768
|
-
console.log(`::error::${message}`);
|
|
769
|
-
return this;
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
// Always show errors even in quiet mode
|
|
773
|
-
console.log(` ${ansi.red}${icons.error}${ansi.reset} ${message}`);
|
|
774
|
-
return this;
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
/**
|
|
778
|
-
* Display success message
|
|
779
|
-
*/
|
|
780
|
-
success(message) {
|
|
781
|
-
if (this._json || this._quiet) return this;
|
|
782
|
-
|
|
783
|
-
if (this._ci) {
|
|
784
|
-
console.log(`✓ ${message}`);
|
|
785
|
-
return this;
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
console.log(` ${ansi.green}${icons.success}${ansi.reset} ${message}`);
|
|
789
|
-
return this;
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
/**
|
|
793
|
-
* Display bullet point
|
|
794
|
-
*/
|
|
795
|
-
bullet(message) {
|
|
796
|
-
if (this._json || this._quiet) return this;
|
|
797
|
-
console.log(` ${ansi.gray}${icons.bullet}${ansi.reset} ${message}`);
|
|
798
|
-
return this;
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
802
|
-
// TABLE
|
|
803
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
804
|
-
|
|
805
|
-
/**
|
|
806
|
-
* Display a table
|
|
807
|
-
* @param {Object} options - { title, columns: [{ header, key, width, format, align }], data: [], maxRows }
|
|
808
|
-
*/
|
|
809
|
-
table(options) {
|
|
810
|
-
if (this._json || this._quiet) return this;
|
|
811
|
-
|
|
812
|
-
const { title, columns, data, maxRows = 20 } = options;
|
|
813
|
-
|
|
814
|
-
// Calculate column widths
|
|
815
|
-
const colWidths = columns.map((col) => {
|
|
816
|
-
const headerLen = visibleLength(col.header);
|
|
817
|
-
const maxDataLen = data.reduce((max, row) => {
|
|
818
|
-
const formatted = col.format ? col.format(row[col.key], row) : String(row[col.key] || '');
|
|
819
|
-
return Math.max(max, visibleLength(formatted));
|
|
820
|
-
}, 0);
|
|
821
|
-
return col.width || Math.min(Math.max(headerLen, maxDataLen) + 2, 40);
|
|
822
|
-
});
|
|
823
|
-
|
|
824
|
-
// Title
|
|
825
|
-
if (title) {
|
|
826
|
-
console.log('');
|
|
827
|
-
console.log(` ${ansi.bold}${title}${ansi.reset}`);
|
|
828
|
-
console.log(` ${ansi.gray}${S_BOX.h.repeat(Math.min(BOX_WIDTH - 4, 72))}${ansi.reset}`);
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
// Header row
|
|
832
|
-
const headerBorder = ` ${S_BOX.tl}${colWidths.map(w => S_BOX.h.repeat(w)).join(S_BOX.h)}${S_BOX.tr}`;
|
|
833
|
-
console.log(ansi.gray + headerBorder + ansi.reset);
|
|
834
|
-
|
|
835
|
-
let headerRow = ` ${ansi.gray}${S_BOX.v}${ansi.reset}`;
|
|
836
|
-
columns.forEach((col, i) => {
|
|
837
|
-
headerRow += `${ansi.bold}${centerText(col.header, colWidths[i])}${ansi.reset}${ansi.gray}${S_BOX.v}${ansi.reset}`;
|
|
838
|
-
});
|
|
839
|
-
console.log(headerRow);
|
|
840
|
-
|
|
841
|
-
// Separator
|
|
842
|
-
const sep = ` ${S_BOX.v}${colWidths.map(w => S_BOX.h.repeat(w)).join(S_BOX.h)}${S_BOX.v}`;
|
|
843
|
-
console.log(ansi.gray + sep + ansi.reset);
|
|
844
|
-
|
|
845
|
-
// Data rows
|
|
846
|
-
const displayData = data.slice(0, maxRows);
|
|
847
|
-
displayData.forEach((row) => {
|
|
848
|
-
let dataRow = ` ${ansi.gray}${S_BOX.v}${ansi.reset}`;
|
|
849
|
-
columns.forEach((col, i) => {
|
|
850
|
-
const value = col.format ? col.format(row[col.key], row) : String(row[col.key] || '');
|
|
851
|
-
const aligned = col.align === 'right'
|
|
852
|
-
? padToLeft(truncateStr(value, colWidths[i] - 1), colWidths[i])
|
|
853
|
-
: col.align === 'center'
|
|
854
|
-
? centerText(truncateStr(value, colWidths[i] - 1), colWidths[i])
|
|
855
|
-
: padToRight(' ' + truncateStr(value, colWidths[i] - 2), colWidths[i]);
|
|
856
|
-
dataRow += `${aligned}${ansi.gray}${S_BOX.v}${ansi.reset}`;
|
|
857
|
-
});
|
|
858
|
-
console.log(dataRow);
|
|
859
|
-
});
|
|
860
|
-
|
|
861
|
-
// Footer border
|
|
862
|
-
const footerBorder = ` ${S_BOX.bl}${colWidths.map(w => S_BOX.h.repeat(w)).join(S_BOX.h)}${S_BOX.br}`;
|
|
863
|
-
console.log(ansi.gray + footerBorder + ansi.reset);
|
|
864
|
-
|
|
865
|
-
if (data.length > maxRows) {
|
|
866
|
-
console.log(` ${ansi.gray}... and ${data.length - maxRows} more rows${ansi.reset}`);
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
return this;
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
873
|
-
// SECTION HEADERS
|
|
874
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
875
|
-
|
|
876
|
-
/**
|
|
877
|
-
* Display section header
|
|
878
|
-
*/
|
|
879
|
-
section(title, icon) {
|
|
880
|
-
if (this._json || this._quiet) return this;
|
|
881
|
-
|
|
882
|
-
const iconStr = icon ? `${icon} ` : '';
|
|
883
|
-
console.log('');
|
|
884
|
-
console.log(` ${ansi.cyan}${iconStr}${ansi.bold}${title}${ansi.reset}`);
|
|
885
|
-
console.log(` ${ansi.gray}${S_BOX.h.repeat(BOX_WIDTH - 4)}${ansi.reset}`);
|
|
886
|
-
return this;
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
/**
|
|
890
|
-
* Display divider line
|
|
891
|
-
*/
|
|
892
|
-
divider() {
|
|
893
|
-
if (this._json || this._quiet) return this;
|
|
894
|
-
console.log(` ${ansi.gray}${S_BOX.h.repeat(BOX_WIDTH - 4)}${ansi.reset}`);
|
|
895
|
-
return this;
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
/**
|
|
899
|
-
* Display blank line
|
|
900
|
-
*/
|
|
901
|
-
blank() {
|
|
902
|
-
if (this._json || this._quiet) return this;
|
|
903
|
-
console.log('');
|
|
904
|
-
return this;
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
908
|
-
// NEXT STEPS / FOOTER
|
|
909
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
910
|
-
|
|
911
|
-
/**
|
|
912
|
-
* Display next steps
|
|
913
|
-
* @param {Array} steps - [{ cmd, desc }]
|
|
914
|
-
*/
|
|
915
|
-
nextSteps(steps) {
|
|
916
|
-
if (this._json || this._quiet || !steps || steps.length === 0) return this;
|
|
917
|
-
|
|
918
|
-
console.log('');
|
|
919
|
-
console.log(` ${ansi.gray}Next Steps:${ansi.reset}`);
|
|
920
|
-
steps.forEach(({ cmd, desc }) => {
|
|
921
|
-
console.log(` ${ansi.cyan}${cmd}${ansi.reset} ${ansi.gray}${desc}${ansi.reset}`);
|
|
922
|
-
});
|
|
923
|
-
return this;
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
/**
|
|
927
|
-
* Display upsell message for Pro features
|
|
928
|
-
*/
|
|
929
|
-
upsell(tier = 'free') {
|
|
930
|
-
if (this._json || this._quiet || tier !== 'free') return this;
|
|
931
|
-
|
|
932
|
-
console.log('');
|
|
933
|
-
console.log(` ${ansi.gray}★${ansi.reset} ${ansi.magenta}PRO${ansi.reset}${ansi.gray}: auto-fix + AI explanations + badge generation${ansi.reset}`);
|
|
934
|
-
console.log(` ${ansi.gray} Upgrade → https://vibecheckai.dev${ansi.reset}`);
|
|
935
|
-
return this;
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
939
|
-
// FLUSH (JSON OUTPUT)
|
|
940
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
941
|
-
|
|
942
|
-
/**
|
|
943
|
-
* Flush buffered output (for JSON mode)
|
|
944
|
-
*/
|
|
945
|
-
flush() {
|
|
946
|
-
if (!this._json) return this;
|
|
947
|
-
|
|
948
|
-
const duration = this._startTime ? Date.now() - this._startTime : 0;
|
|
949
|
-
|
|
950
|
-
const output = {
|
|
951
|
-
command: this._buffer.command,
|
|
952
|
-
findings: this._buffer.findings,
|
|
953
|
-
summary: this._buffer.summary || {},
|
|
954
|
-
duration
|
|
955
|
-
};
|
|
956
|
-
|
|
957
|
-
if (this._buffer.verdict) {
|
|
958
|
-
output.verdict = this._buffer.verdict;
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
if (this._buffer.errors.length > 0) {
|
|
962
|
-
output.errors = this._buffer.errors;
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
if (this._buffer.warnings.length > 0) {
|
|
966
|
-
output.warnings = this._buffer.warnings;
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
console.log(JSON.stringify(output, null, 2));
|
|
970
|
-
|
|
971
|
-
// Reset buffer
|
|
972
|
-
this._buffer = {
|
|
973
|
-
command: null,
|
|
974
|
-
findings: [],
|
|
975
|
-
summary: null,
|
|
976
|
-
errors: [],
|
|
977
|
-
warnings: [],
|
|
978
|
-
info: []
|
|
979
|
-
};
|
|
980
|
-
|
|
981
|
-
return this;
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
/**
|
|
985
|
-
* Reset state (for reuse)
|
|
986
|
-
*/
|
|
987
|
-
reset() {
|
|
988
|
-
this._buffer = {
|
|
989
|
-
command: null,
|
|
990
|
-
findings: [],
|
|
991
|
-
summary: null,
|
|
992
|
-
errors: [],
|
|
993
|
-
warnings: [],
|
|
994
|
-
info: []
|
|
995
|
-
};
|
|
996
|
-
this._spinner = null;
|
|
997
|
-
this._startTime = null;
|
|
998
|
-
return this;
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1003
|
-
// SINGLETON INSTANCE
|
|
1004
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1005
|
-
|
|
1006
|
-
const output = new OutputManager();
|
|
1007
|
-
|
|
1008
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1009
|
-
// EXPORTS
|
|
1010
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1011
|
-
|
|
1012
|
-
module.exports = {
|
|
1013
|
-
OutputManager,
|
|
1014
|
-
output,
|
|
1015
|
-
// Re-export utilities for convenience
|
|
1016
|
-
formatDuration: formatDur,
|
|
1017
|
-
visibleLength,
|
|
1018
|
-
truncate: truncateStr,
|
|
1019
|
-
padRight: padToRight,
|
|
1020
|
-
padLeft: padToLeft,
|
|
1021
|
-
center: centerText
|
|
1022
|
-
};
|