@maverick006/vibeguard 1.0.4 → 1.0.7
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/dist/formatter.js +69 -38
- package/package.json +1 -1
- package/src/formatter.ts +72 -38
package/dist/formatter.js
CHANGED
|
@@ -10,6 +10,31 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
10
10
|
const child_process_1 = require("child_process");
|
|
11
11
|
const types_1 = require("@maverick006/types");
|
|
12
12
|
const path_1 = __importDefault(require("path"));
|
|
13
|
+
function stripAnsi(str) {
|
|
14
|
+
return str.replace(/\u001b\[[0-9;]*m/g, '');
|
|
15
|
+
}
|
|
16
|
+
function visibleWidth(str) {
|
|
17
|
+
const plain = stripAnsi(str);
|
|
18
|
+
let width = 0;
|
|
19
|
+
for (const char of plain) {
|
|
20
|
+
const code = char.codePointAt(0) || 0;
|
|
21
|
+
if (code === 0xFE0F || code === 0xFE0E)
|
|
22
|
+
continue; // ignore variation selectors
|
|
23
|
+
// Emojis and certain symbols take 2 terminal columns
|
|
24
|
+
if (code > 0x1F000 || (code >= 0x2600 && code <= 0x27BF)) {
|
|
25
|
+
width += 2;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
width += 1;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return width;
|
|
32
|
+
}
|
|
33
|
+
function padVisible(str, targetWidth) {
|
|
34
|
+
const current = visibleWidth(str);
|
|
35
|
+
const diff = Math.max(0, targetWidth - current);
|
|
36
|
+
return str + ' '.repeat(diff);
|
|
37
|
+
}
|
|
13
38
|
function getGitInfo(cwd = process.cwd()) {
|
|
14
39
|
let name = path_1.default.basename(cwd);
|
|
15
40
|
let branch = 'main';
|
|
@@ -95,39 +120,44 @@ function renderDashboard(options) {
|
|
|
95
120
|
' \\ \\ / / ',
|
|
96
121
|
' `-------\' '
|
|
97
122
|
];
|
|
98
|
-
// Title ASCII Art
|
|
123
|
+
// Title ASCII Art (VIBEGUARD)
|
|
99
124
|
const title = [
|
|
100
|
-
'
|
|
101
|
-
'\\ \\
|
|
102
|
-
' \\
|
|
103
|
-
'
|
|
104
|
-
'
|
|
125
|
+
' __ __ ___ ____ _____ ____ _ _ _ ____ ____ ',
|
|
126
|
+
'\\ \\ / /|_ _| __ )| ____/ ___| | | | / \\ | _ \\| _ \\ ',
|
|
127
|
+
' \\ \\ / / | || _ \\| _|| | _| | | |/ _ \\| |_) | | | |',
|
|
128
|
+
' \\ V / | || |_) | |___| |_| |_| / ___ \\ _ <| |_| |',
|
|
129
|
+
' \\_/ |___|____/|_____|\\____|\\___/_/ \\_\\_| \\_\\____/'
|
|
105
130
|
];
|
|
106
131
|
const scoreColor = stats.score >= 85 ? green : stats.score >= 65 ? yellow : red;
|
|
107
132
|
const riskColor = stats.riskLevel === 'LOW RISK' ? green : stats.riskLevel === 'MEDIUM RISK' ? yellow : red;
|
|
108
133
|
console.log('\n');
|
|
109
134
|
// Print Top Row: Clock aligned right
|
|
110
135
|
console.log(' '.repeat(65) + cyan.bold(timeStr));
|
|
111
|
-
// Header
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
136
|
+
// 1. Header (Shield + VIBEGUARD)
|
|
137
|
+
console.log(`${cyan(shield[0])} ${cyan.bold(title[0])}`);
|
|
138
|
+
console.log(`${cyan(shield[1])} ${cyan.bold(title[1])}`);
|
|
139
|
+
console.log(`${cyan(shield[2])} ${cyan.bold(title[2])}`);
|
|
140
|
+
console.log(`${cyan(shield[3])} ${cyan.bold(title[3])}`);
|
|
141
|
+
console.log(`${cyan(shield[4])} ${cyan.bold(title[4])}`);
|
|
142
|
+
console.log(`${cyan(shield[5])} ${gray('AI-Powered DevSecOps Orchestrator')}`);
|
|
143
|
+
console.log(` ${cyan('Scanning. Analyzing. Protecting.')}\n`);
|
|
144
|
+
// 2. Risk Score Box (Placed cleanly BELOW VIBEGUARD Header)
|
|
145
|
+
const boxWidth = 74;
|
|
146
|
+
console.log(darkBorder(`┌${'─'.repeat(boxWidth)}┐`));
|
|
147
|
+
console.log(`${darkBorder('│')} ${cyan.bold('OVERALL RISK SCORE')}${' '.repeat(boxWidth - 20)}${darkBorder('│')}`);
|
|
148
|
+
console.log(`${darkBorder('│')}${' '.repeat(boxWidth)}${darkBorder('│')}`);
|
|
149
|
+
const scoreLine1 = ` ${scoreColor.bold(String(stats.score))} ${gray('/100')} ${red('CRITICAL')} ${String(stats.critical).padEnd(2, ' ')} ${orange('HIGH')} ${String(stats.high).padEnd(2, ' ')}`;
|
|
150
|
+
const scoreLine2 = ` ${riskColor.bold(stats.riskLevel.padEnd(13, ' '))} ${yellow('MEDIUM')} ${String(stats.medium).padEnd(2, ' ')} ${cyan('LOW')} ${String(stats.low).padEnd(2, ' ')}`;
|
|
151
|
+
console.log(`${darkBorder('│')}${padVisible(scoreLine1, boxWidth)}${darkBorder('│')}`);
|
|
152
|
+
console.log(`${darkBorder('│')}${padVisible(scoreLine2, boxWidth)}${darkBorder('│')}`);
|
|
153
|
+
console.log(darkBorder(`└${'─'.repeat(boxWidth)}┘`));
|
|
124
154
|
console.log('');
|
|
125
|
-
// 3
|
|
155
|
+
// 3. Multi-panel Grid (Pipeline & Findings)
|
|
126
156
|
const pipelineRows = [
|
|
127
|
-
|
|
157
|
+
`</> SAST (Semgrep) ${green('✓ OK')}`,
|
|
128
158
|
`📦 Dependency Check (Trivy) ${green('✓ OK')}`,
|
|
129
159
|
`🔑 Secrets Scan (Gitleaks) ${green('✓ OK')}`,
|
|
130
|
-
|
|
160
|
+
`🔒 IaC Scan (Checkov) ${green('✓ OK')}`,
|
|
131
161
|
`🐳 Container Scan (Trivy) ${green('✓ OK')}`,
|
|
132
162
|
`📑 Code Quality (ESLint) ${green('✓ OK')}`,
|
|
133
163
|
``,
|
|
@@ -140,7 +170,7 @@ function renderDashboard(options) {
|
|
|
140
170
|
];
|
|
141
171
|
// Table of findings (top 10)
|
|
142
172
|
const topFindings = findings.slice(0, 10);
|
|
143
|
-
const tableHeader = `${dimGray('ID'
|
|
173
|
+
const tableHeader = `${dimGray(padVisible('ID', 13))} ${dimGray(padVisible('SEVERITY', 10))} ${dimGray(padVisible('TITLE', 28))} ${dimGray('FILE:LINE')}`;
|
|
144
174
|
const findingRows = [tableHeader];
|
|
145
175
|
if (topFindings.length === 0) {
|
|
146
176
|
findingRows.push(green(' ✓ No security vulnerabilities detected. Codebase is clean!'));
|
|
@@ -156,22 +186,25 @@ function renderDashboard(options) {
|
|
|
156
186
|
sevFormatted = orange.bold('HIGH ');
|
|
157
187
|
else if (sev === 'MEDIUM')
|
|
158
188
|
sevFormatted = yellow('MEDIUM ');
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
189
|
+
const rawTitle = f.title || 'Security Finding';
|
|
190
|
+
const titleTruncated = rawTitle.length > 26 ? rawTitle.slice(0, 24) + '..' : rawTitle;
|
|
191
|
+
const titleFormatted = padVisible(white(titleTruncated), 28);
|
|
162
192
|
const fileLine = `${f.file || 'unknown'}:${f.line || 1}`;
|
|
163
|
-
|
|
193
|
+
const idFormatted = padVisible(cyan(id), 13);
|
|
194
|
+
findingRows.push(`${idFormatted} ${sevFormatted} ${titleFormatted} ${dimGray(fileLine)}`);
|
|
164
195
|
}
|
|
165
196
|
}
|
|
166
|
-
// Print Section Headers
|
|
167
|
-
|
|
197
|
+
// Print Section Headers with ANSI-safe padding
|
|
198
|
+
const headerCol1 = padVisible(cyan('▶ SCANNER PIPELINE'), 36);
|
|
199
|
+
const headerCol2 = cyan('▶ TOP FINDINGS');
|
|
200
|
+
console.log(`${headerCol1} ${headerCol2}`);
|
|
168
201
|
const maxRows = Math.max(pipelineRows.length, findingRows.length);
|
|
169
202
|
for (let i = 0; i < maxRows; i++) {
|
|
170
|
-
const left = (pipelineRows[i] || ''
|
|
203
|
+
const left = padVisible(pipelineRows[i] || '', 36);
|
|
171
204
|
const right = findingRows[i] || '';
|
|
172
205
|
console.log(`${left} ${right}`);
|
|
173
206
|
}
|
|
174
|
-
// AI Remediation Section
|
|
207
|
+
// 4. AI Remediation Section
|
|
175
208
|
if (remediation) {
|
|
176
209
|
console.log(`\n${cyan('▶ AI REMEDIATION (VG-AI)')}\n`);
|
|
177
210
|
console.log(`${cyan('Finding:')} ${red.bold(remediation.findingId)}\n`);
|
|
@@ -191,20 +224,18 @@ function renderDashboard(options) {
|
|
|
191
224
|
styled = dimGray(d);
|
|
192
225
|
else
|
|
193
226
|
styled = white(d);
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const padLen = Math.max(0, 58 - plain.length);
|
|
197
|
-
console.log(`${darkBorder('│')} ${styled}${' '.repeat(padLen)} ${darkBorder('│')}`);
|
|
227
|
+
const paddedLine = padVisible(styled, 58);
|
|
228
|
+
console.log(`${darkBorder('│')} ${paddedLine} ${darkBorder('│')}`);
|
|
198
229
|
}
|
|
199
230
|
console.log(darkBorder('└────────────────────────────────────────────────────────────┘'));
|
|
200
231
|
console.log(`\n${cyan('Confidence:')} ${green.bold(remediation.confidence + '%')}`);
|
|
201
232
|
}
|
|
202
|
-
// Summary Bar (bottom capsule)
|
|
233
|
+
// 5. Summary Bar (bottom capsule)
|
|
203
234
|
console.log(`\n${cyan('▶ SUMMARY')}`);
|
|
204
235
|
const summaryText = `🛡️ Scan completed in ${duration} │ ${stats.total} findings │ 6 passed`;
|
|
205
|
-
const barTop = `┌${'─'.repeat(summaryText
|
|
236
|
+
const barTop = `┌${'─'.repeat(visibleWidth(summaryText) + 4)}┐`;
|
|
206
237
|
const barMid = `│ ${summaryText} │`;
|
|
207
|
-
const barBot = `└${'─'.repeat(summaryText
|
|
238
|
+
const barBot = `└${'─'.repeat(visibleWidth(summaryText) + 4)}┘`;
|
|
208
239
|
console.log(cyan(barTop));
|
|
209
240
|
console.log(cyan(barMid));
|
|
210
241
|
console.log(cyan(barBot));
|
package/package.json
CHANGED
package/src/formatter.ts
CHANGED
|
@@ -13,6 +13,32 @@ export interface ScanStats {
|
|
|
13
13
|
riskLevel: 'LOW RISK' | 'MEDIUM RISK' | 'HIGH RISK' | 'CRITICAL RISK';
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
function stripAnsi(str: string): string {
|
|
17
|
+
return str.replace(/\u001b\[[0-9;]*m/g, '');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function visibleWidth(str: string): number {
|
|
21
|
+
const plain = stripAnsi(str);
|
|
22
|
+
let width = 0;
|
|
23
|
+
for (const char of plain) {
|
|
24
|
+
const code = char.codePointAt(0) || 0;
|
|
25
|
+
if (code === 0xFE0F || code === 0xFE0E) continue; // ignore variation selectors
|
|
26
|
+
// Emojis and certain symbols take 2 terminal columns
|
|
27
|
+
if (code > 0x1F000 || (code >= 0x2600 && code <= 0x27BF)) {
|
|
28
|
+
width += 2;
|
|
29
|
+
} else {
|
|
30
|
+
width += 1;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return width;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function padVisible(str: string, targetWidth: number): string {
|
|
37
|
+
const current = visibleWidth(str);
|
|
38
|
+
const diff = Math.max(0, targetWidth - current);
|
|
39
|
+
return str + ' '.repeat(diff);
|
|
40
|
+
}
|
|
41
|
+
|
|
16
42
|
export function getGitInfo(cwd: string = process.cwd()) {
|
|
17
43
|
let name = path.basename(cwd);
|
|
18
44
|
let branch = 'main';
|
|
@@ -112,13 +138,13 @@ export function renderDashboard(options: {
|
|
|
112
138
|
' `-------\' '
|
|
113
139
|
];
|
|
114
140
|
|
|
115
|
-
// Title ASCII Art
|
|
141
|
+
// Title ASCII Art (VIBEGUARD)
|
|
116
142
|
const title = [
|
|
117
|
-
'
|
|
118
|
-
'\\ \\
|
|
119
|
-
' \\
|
|
120
|
-
'
|
|
121
|
-
'
|
|
143
|
+
' __ __ ___ ____ _____ ____ _ _ _ ____ ____ ',
|
|
144
|
+
'\\ \\ / /|_ _| __ )| ____/ ___| | | | / \\ | _ \\| _ \\ ',
|
|
145
|
+
' \\ \\ / / | || _ \\| _|| | _| | | |/ _ \\| |_) | | | |',
|
|
146
|
+
' \\ V / | || |_) | |___| |_| |_| / ___ \\ _ <| |_| |',
|
|
147
|
+
' \\_/ |___|____/|_____|\\____|\\___/_/ \\_\\_| \\_\\____/'
|
|
122
148
|
];
|
|
123
149
|
|
|
124
150
|
const scoreColor = stats.score >= 85 ? green : stats.score >= 65 ? yellow : red;
|
|
@@ -129,28 +155,35 @@ export function renderDashboard(options: {
|
|
|
129
155
|
// Print Top Row: Clock aligned right
|
|
130
156
|
console.log(' '.repeat(65) + cyan.bold(timeStr));
|
|
131
157
|
|
|
132
|
-
// Header
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
` ${cyan('Scanning. Analyzing. Protecting.')} ${darkBorder('└───────────────────────────────────────┘')}`
|
|
141
|
-
];
|
|
158
|
+
// 1. Header (Shield + VIBEGUARD)
|
|
159
|
+
console.log(`${cyan(shield[0])} ${cyan.bold(title[0])}`);
|
|
160
|
+
console.log(`${cyan(shield[1])} ${cyan.bold(title[1])}`);
|
|
161
|
+
console.log(`${cyan(shield[2])} ${cyan.bold(title[2])}`);
|
|
162
|
+
console.log(`${cyan(shield[3])} ${cyan.bold(title[3])}`);
|
|
163
|
+
console.log(`${cyan(shield[4])} ${cyan.bold(title[4])}`);
|
|
164
|
+
console.log(`${cyan(shield[5])} ${gray('AI-Powered DevSecOps Orchestrator')}`);
|
|
165
|
+
console.log(` ${cyan('Scanning. Analyzing. Protecting.')}\n`);
|
|
142
166
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
167
|
+
// 2. Risk Score Box (Placed cleanly BELOW VIBEGUARD Header)
|
|
168
|
+
const boxWidth = 74;
|
|
169
|
+
console.log(darkBorder(`┌${'─'.repeat(boxWidth)}┐`));
|
|
170
|
+
console.log(`${darkBorder('│')} ${cyan.bold('OVERALL RISK SCORE')}${' '.repeat(boxWidth - 20)}${darkBorder('│')}`);
|
|
171
|
+
console.log(`${darkBorder('│')}${' '.repeat(boxWidth)}${darkBorder('│')}`);
|
|
172
|
+
|
|
173
|
+
const scoreLine1 = ` ${scoreColor.bold(String(stats.score))} ${gray('/100')} ${red('CRITICAL')} ${String(stats.critical).padEnd(2, ' ')} ${orange('HIGH')} ${String(stats.high).padEnd(2, ' ')}`;
|
|
174
|
+
const scoreLine2 = ` ${riskColor.bold(stats.riskLevel.padEnd(13, ' '))} ${yellow('MEDIUM')} ${String(stats.medium).padEnd(2, ' ')} ${cyan('LOW')} ${String(stats.low).padEnd(2, ' ')}`;
|
|
175
|
+
|
|
176
|
+
console.log(`${darkBorder('│')}${padVisible(scoreLine1, boxWidth)}${darkBorder('│')}`);
|
|
177
|
+
console.log(`${darkBorder('│')}${padVisible(scoreLine2, boxWidth)}${darkBorder('│')}`);
|
|
178
|
+
console.log(darkBorder(`└${'─'.repeat(boxWidth)}┘`));
|
|
146
179
|
console.log('');
|
|
147
180
|
|
|
148
|
-
// 3
|
|
181
|
+
// 3. Multi-panel Grid (Pipeline & Findings)
|
|
149
182
|
const pipelineRows = [
|
|
150
|
-
|
|
183
|
+
`</> SAST (Semgrep) ${green('✓ OK')}`,
|
|
151
184
|
`📦 Dependency Check (Trivy) ${green('✓ OK')}`,
|
|
152
185
|
`🔑 Secrets Scan (Gitleaks) ${green('✓ OK')}`,
|
|
153
|
-
|
|
186
|
+
`🔒 IaC Scan (Checkov) ${green('✓ OK')}`,
|
|
154
187
|
`🐳 Container Scan (Trivy) ${green('✓ OK')}`,
|
|
155
188
|
`📑 Code Quality (ESLint) ${green('✓ OK')}`,
|
|
156
189
|
``,
|
|
@@ -164,7 +197,7 @@ export function renderDashboard(options: {
|
|
|
164
197
|
|
|
165
198
|
// Table of findings (top 10)
|
|
166
199
|
const topFindings = findings.slice(0, 10);
|
|
167
|
-
const tableHeader = `${dimGray('ID'
|
|
200
|
+
const tableHeader = `${dimGray(padVisible('ID', 13))} ${dimGray(padVisible('SEVERITY', 10))} ${dimGray(padVisible('TITLE', 28))} ${dimGray('FILE:LINE')}`;
|
|
168
201
|
|
|
169
202
|
const findingRows: string[] = [tableHeader];
|
|
170
203
|
|
|
@@ -179,26 +212,29 @@ export function renderDashboard(options: {
|
|
|
179
212
|
else if (sev === 'HIGH') sevFormatted = orange.bold('HIGH ');
|
|
180
213
|
else if (sev === 'MEDIUM') sevFormatted = yellow('MEDIUM ');
|
|
181
214
|
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
215
|
+
const rawTitle = f.title || 'Security Finding';
|
|
216
|
+
const titleTruncated = rawTitle.length > 26 ? rawTitle.slice(0, 24) + '..' : rawTitle;
|
|
217
|
+
const titleFormatted = padVisible(white(titleTruncated), 28);
|
|
185
218
|
|
|
186
219
|
const fileLine = `${f.file || 'unknown'}:${f.line || 1}`;
|
|
187
|
-
|
|
220
|
+
const idFormatted = padVisible(cyan(id), 13);
|
|
221
|
+
findingRows.push(`${idFormatted} ${sevFormatted} ${titleFormatted} ${dimGray(fileLine)}`);
|
|
188
222
|
}
|
|
189
223
|
}
|
|
190
224
|
|
|
191
|
-
// Print Section Headers
|
|
192
|
-
|
|
225
|
+
// Print Section Headers with ANSI-safe padding
|
|
226
|
+
const headerCol1 = padVisible(cyan('▶ SCANNER PIPELINE'), 36);
|
|
227
|
+
const headerCol2 = cyan('▶ TOP FINDINGS');
|
|
228
|
+
console.log(`${headerCol1} ${headerCol2}`);
|
|
193
229
|
|
|
194
230
|
const maxRows = Math.max(pipelineRows.length, findingRows.length);
|
|
195
231
|
for (let i = 0; i < maxRows; i++) {
|
|
196
|
-
const left = (pipelineRows[i] || ''
|
|
232
|
+
const left = padVisible(pipelineRows[i] || '', 36);
|
|
197
233
|
const right = findingRows[i] || '';
|
|
198
234
|
console.log(`${left} ${right}`);
|
|
199
235
|
}
|
|
200
236
|
|
|
201
|
-
// AI Remediation Section
|
|
237
|
+
// 4. AI Remediation Section
|
|
202
238
|
if (remediation) {
|
|
203
239
|
console.log(`\n${cyan('▶ AI REMEDIATION (VG-AI)')}\n`);
|
|
204
240
|
console.log(`${cyan('Finding:')} ${red.bold(remediation.findingId)}\n`);
|
|
@@ -216,22 +252,20 @@ export function renderDashboard(options: {
|
|
|
216
252
|
else if (d.trim().startsWith('#')) styled = dimGray(d);
|
|
217
253
|
else styled = white(d);
|
|
218
254
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const padLen = Math.max(0, 58 - plain.length);
|
|
222
|
-
console.log(`${darkBorder('│')} ${styled}${' '.repeat(padLen)} ${darkBorder('│')}`);
|
|
255
|
+
const paddedLine = padVisible(styled, 58);
|
|
256
|
+
console.log(`${darkBorder('│')} ${paddedLine} ${darkBorder('│')}`);
|
|
223
257
|
}
|
|
224
258
|
console.log(darkBorder('└────────────────────────────────────────────────────────────┘'));
|
|
225
259
|
|
|
226
260
|
console.log(`\n${cyan('Confidence:')} ${green.bold(remediation.confidence + '%')}`);
|
|
227
261
|
}
|
|
228
262
|
|
|
229
|
-
// Summary Bar (bottom capsule)
|
|
263
|
+
// 5. Summary Bar (bottom capsule)
|
|
230
264
|
console.log(`\n${cyan('▶ SUMMARY')}`);
|
|
231
265
|
const summaryText = `🛡️ Scan completed in ${duration} │ ${stats.total} findings │ 6 passed`;
|
|
232
|
-
const barTop = `┌${'─'.repeat(summaryText
|
|
266
|
+
const barTop = `┌${'─'.repeat(visibleWidth(summaryText) + 4)}┐`;
|
|
233
267
|
const barMid = `│ ${summaryText} │`;
|
|
234
|
-
const barBot = `└${'─'.repeat(summaryText
|
|
268
|
+
const barBot = `└${'─'.repeat(visibleWidth(summaryText) + 4)}┘`;
|
|
235
269
|
console.log(cyan(barTop));
|
|
236
270
|
console.log(cyan(barMid));
|
|
237
271
|
console.log(cyan(barBot));
|