codeant-cli 0.3.1 → 0.3.3
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/178.index.js +1 -1
- package/dist/239.index.js +1 -1
- package/dist/701.index.js +1 -1
- package/dist/880.index.js +1 -1
- package/dist/935.index.js +1 -1
- package/dist/966.index.js +1 -1
- package/dist/index.js +5 -5
- package/dist/review/239.index.js +1 -0
- package/dist/review/index.js +8 -0
- package/dist/review/package.json +3 -0
- package/package.json +5 -6
- package/src/commands/getBaseUrl.js +0 -21
- package/src/commands/login.js +0 -125
- package/src/commands/logout.js +0 -30
- package/src/commands/review.js +0 -294
- package/src/commands/secrets.js +0 -148
- package/src/commands/setBaseUrl.js +0 -37
- package/src/components/ReviewUI.js +0 -410
- package/src/components/SecretsUI.js +0 -366
- package/src/components/Welcome.js +0 -144
- package/src/index.js +0 -349
- package/src/reviewHeadless.js +0 -198
- package/src/scm/azure/client.js +0 -21
- package/src/scm/azure/getCodeReview.js +0 -20
- package/src/scm/azure/getPullRequest.js +0 -30
- package/src/scm/azure/index.js +0 -6
- package/src/scm/azure/listCodeReviews.js +0 -17
- package/src/scm/azure/listPullRequestComments.js +0 -41
- package/src/scm/azure/listPullRequests.js +0 -26
- package/src/scm/azure/searchComments.js +0 -35
- package/src/scm/bitbucket/client.js +0 -18
- package/src/scm/bitbucket/getCodeReview.js +0 -3
- package/src/scm/bitbucket/getPullRequest.js +0 -26
- package/src/scm/bitbucket/index.js +0 -6
- package/src/scm/bitbucket/listCodeReviews.js +0 -17
- package/src/scm/bitbucket/listPullRequestComments.js +0 -33
- package/src/scm/bitbucket/listPullRequests.js +0 -33
- package/src/scm/bitbucket/searchComments.js +0 -29
- package/src/scm/github/client.js +0 -17
- package/src/scm/github/getCodeReview.js +0 -26
- package/src/scm/github/getPullRequest.js +0 -27
- package/src/scm/github/index.js +0 -6
- package/src/scm/github/listCodeReviews.js +0 -17
- package/src/scm/github/listPullRequestComments.js +0 -51
- package/src/scm/github/listPullRequests.js +0 -32
- package/src/scm/github/searchComments.js +0 -30
- package/src/scm/gitlab/client.js +0 -16
- package/src/scm/gitlab/getCodeReview.js +0 -18
- package/src/scm/gitlab/getPullRequest.js +0 -24
- package/src/scm/gitlab/index.js +0 -6
- package/src/scm/gitlab/listCodeReviews.js +0 -21
- package/src/scm/gitlab/listPullRequestComments.js +0 -44
- package/src/scm/gitlab/listPullRequests.js +0 -26
- package/src/scm/gitlab/searchComments.js +0 -28
- package/src/scm/index.js +0 -96
- package/src/tools/executeTool.js +0 -31
- package/src/tools/globTool.js +0 -9
- package/src/tools/grepTool.js +0 -18
- package/src/tools/lsTool.js +0 -7
- package/src/tools/readTool.js +0 -12
- package/src/utils/analytics.js +0 -45
- package/src/utils/baseUrl.js +0 -11
- package/src/utils/commonApiHelper.js +0 -143
- package/src/utils/config.js +0 -42
- package/src/utils/fetchApi.js +0 -39
- package/src/utils/gitDiffHelper.js +0 -1203
- package/src/utils/reviewApiHelper.js +0 -275
- package/src/utils/scmAuth.js +0 -136
- package/src/utils/secretsApiHelper.js +0 -242
|
@@ -1,410 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import { Box, Text } from 'ink';
|
|
3
|
-
|
|
4
|
-
// ── Constants ────────────────────────────────────────────────────────────────
|
|
5
|
-
|
|
6
|
-
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
7
|
-
const DIVIDER = '─'.repeat(55);
|
|
8
|
-
const STEPS = ['Init', 'Fetch', 'Analyze', 'Reflect'];
|
|
9
|
-
|
|
10
|
-
const SEVERITY_COLORS = {
|
|
11
|
-
BLOCKER: 'red',
|
|
12
|
-
CRITICAL: 'red',
|
|
13
|
-
MAJOR: 'yellow',
|
|
14
|
-
MINOR: 'cyan',
|
|
15
|
-
INFO: 'gray',
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
19
|
-
|
|
20
|
-
export function mapLabelToSeverity(label) {
|
|
21
|
-
const mapping = {
|
|
22
|
-
'security': 'CRITICAL',
|
|
23
|
-
'performance': 'MAJOR',
|
|
24
|
-
'maintainability': 'MINOR',
|
|
25
|
-
'code quality': 'MINOR',
|
|
26
|
-
'best practice': 'MINOR',
|
|
27
|
-
'bug': 'MAJOR',
|
|
28
|
-
};
|
|
29
|
-
// Guard against non-string labels from model output to avoid runtime crash
|
|
30
|
-
if (typeof label !== 'string') return 'MINOR';
|
|
31
|
-
return mapping[label.toLowerCase().replace(/[^\w\s]/g, '')] || 'MINOR';
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function getSeverityColor(sev) {
|
|
35
|
-
return SEVERITY_COLORS[sev] || 'gray';
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function padEnd(str, len) {
|
|
39
|
-
return str + ' '.repeat(Math.max(0, len - str.length));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function formatElapsed(startTime) {
|
|
43
|
-
return ((Date.now() - startTime) / 1000).toFixed(1) + 's';
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// ── Animated Components ──────────────────────────────────────────────────────
|
|
47
|
-
|
|
48
|
-
function Spinner({ color = 'cyan' }) {
|
|
49
|
-
const [frame, setFrame] = useState(0);
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
const timer = setInterval(() => {
|
|
52
|
-
setFrame(f => (f + 1) % SPINNER_FRAMES.length);
|
|
53
|
-
}, 80);
|
|
54
|
-
return () => clearInterval(timer);
|
|
55
|
-
}, []);
|
|
56
|
-
return React.createElement(Text, { color }, SPINNER_FRAMES[frame]);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function ElapsedTime({ startTime }) {
|
|
60
|
-
const [, setTick] = useState(0);
|
|
61
|
-
useEffect(() => {
|
|
62
|
-
const timer = setInterval(() => setTick(t => t + 1), 100);
|
|
63
|
-
return () => clearInterval(timer);
|
|
64
|
-
}, []);
|
|
65
|
-
return React.createElement(Text, { color: 'gray' }, formatElapsed(startTime));
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function StepProgress({ currentStep }) {
|
|
69
|
-
const items = STEPS.map((step, i) => {
|
|
70
|
-
if (i < currentStep) {
|
|
71
|
-
return React.createElement(Text, { key: step, color: 'green' }, `✓ ${step}`);
|
|
72
|
-
} else if (i === currentStep) {
|
|
73
|
-
return React.createElement(
|
|
74
|
-
Box, { key: step },
|
|
75
|
-
React.createElement(Spinner, { color: 'cyan' }),
|
|
76
|
-
React.createElement(Text, { color: 'cyan' }, ` ${step}`),
|
|
77
|
-
);
|
|
78
|
-
} else {
|
|
79
|
-
return React.createElement(Text, { key: step, color: 'gray', dimColor: true }, `· ${step}`);
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
return React.createElement(Box, { gap: 2 }, ...items);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// ── Loading Screen ───────────────────────────────────────────────────────────
|
|
86
|
-
|
|
87
|
-
function ReviewLoading({ message, step, startTime, meta }) {
|
|
88
|
-
const els = [
|
|
89
|
-
React.createElement(
|
|
90
|
-
Box, { key: 'status', gap: 1 },
|
|
91
|
-
React.createElement(Spinner, {}),
|
|
92
|
-
React.createElement(Text, { color: 'white', bold: true }, message),
|
|
93
|
-
startTime != null && React.createElement(ElapsedTime, { startTime }),
|
|
94
|
-
),
|
|
95
|
-
React.createElement(
|
|
96
|
-
Box, { key: 'steps', marginTop: 1 },
|
|
97
|
-
React.createElement(StepProgress, { currentStep: step }),
|
|
98
|
-
),
|
|
99
|
-
];
|
|
100
|
-
|
|
101
|
-
// Show file list and coverage info when we have metadata (during analyze/reflect)
|
|
102
|
-
if (meta && step >= 2) {
|
|
103
|
-
const reviewed = meta.reviewed_files || [];
|
|
104
|
-
const total = meta.total_changed || 0;
|
|
105
|
-
const skipped = meta.skipped || [];
|
|
106
|
-
|
|
107
|
-
els.push(React.createElement(Text, { key: 'meta-sp' }, ''));
|
|
108
|
-
els.push(React.createElement(
|
|
109
|
-
Text, { key: 'meta-hdr', color: 'gray', dimColor: true }, DIVIDER,
|
|
110
|
-
));
|
|
111
|
-
|
|
112
|
-
// Coverage line
|
|
113
|
-
if (total > 0) {
|
|
114
|
-
const coverageColor = reviewed.length === total ? 'green' : 'yellow';
|
|
115
|
-
els.push(React.createElement(
|
|
116
|
-
Text, { key: 'coverage', color: coverageColor },
|
|
117
|
-
`Reviewing ${reviewed.length} of ${total} changed file${total !== 1 ? 's' : ''}`,
|
|
118
|
-
));
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// File list
|
|
122
|
-
if (reviewed.length > 0) {
|
|
123
|
-
els.push(React.createElement(Text, { key: 'files-sp' }, ''));
|
|
124
|
-
reviewed.forEach((file, i) => {
|
|
125
|
-
const isLast = i === reviewed.length - 1;
|
|
126
|
-
const branch = isLast ? '└─' : '├─';
|
|
127
|
-
els.push(React.createElement(
|
|
128
|
-
Text, { key: `file-${i}`, color: 'white' },
|
|
129
|
-
` ${branch} ${file}`,
|
|
130
|
-
));
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Skipped summary
|
|
135
|
-
if (skipped.length > 0) {
|
|
136
|
-
els.push(React.createElement(Text, { key: 'skip-sp' }, ''));
|
|
137
|
-
// Group skip reasons
|
|
138
|
-
const reasons = {};
|
|
139
|
-
skipped.forEach(s => {
|
|
140
|
-
const r = s.reason.split(' (')[0]; // normalize "too large (1234 lines...)" → "too large"
|
|
141
|
-
reasons[r] = (reasons[r] || 0) + 1;
|
|
142
|
-
});
|
|
143
|
-
const parts = Object.entries(reasons).map(([r, n]) => `${n} ${r}`);
|
|
144
|
-
els.push(React.createElement(
|
|
145
|
-
Text, { key: 'skipped', color: 'gray', dimColor: true },
|
|
146
|
-
`Skipped: ${parts.join(' · ')}`,
|
|
147
|
-
));
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Cap warning
|
|
151
|
-
if (meta.capped) {
|
|
152
|
-
els.push(React.createElement(
|
|
153
|
-
Text, { key: 'cap', color: 'yellow', dimColor: true },
|
|
154
|
-
`Note: max ${meta.max_files} files per review`,
|
|
155
|
-
));
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return React.createElement(
|
|
160
|
-
Box, { flexDirection: 'column', paddingX: 1, paddingY: 1 }, ...els,
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// ── Public Render Functions ──────────────────────────────────────────────────
|
|
165
|
-
|
|
166
|
-
export function renderInitializing(startTime) {
|
|
167
|
-
return React.createElement(ReviewLoading, {
|
|
168
|
-
message: 'Initializing...',
|
|
169
|
-
step: 0,
|
|
170
|
-
startTime,
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export function renderFetchingDiff(startTime) {
|
|
175
|
-
return React.createElement(ReviewLoading, {
|
|
176
|
-
message: 'Fetching changes...',
|
|
177
|
-
step: 1,
|
|
178
|
-
startTime,
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export function renderReviewing(currentMessage, startTime, meta) {
|
|
183
|
-
const step = currentMessage?.toLowerCase().includes('reflector') ? 3 : 2;
|
|
184
|
-
return React.createElement(ReviewLoading, {
|
|
185
|
-
message: currentMessage || 'Analyzing code...',
|
|
186
|
-
step,
|
|
187
|
-
startTime,
|
|
188
|
-
meta,
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
export function renderNoFiles(scanType, lastNCommits) {
|
|
193
|
-
const hints = {
|
|
194
|
-
'staged-only': 'Stage some changes first with "git add".',
|
|
195
|
-
'branch-diff': 'No changes found compared to the base branch.',
|
|
196
|
-
'uncommitted': 'No uncommitted changes found.',
|
|
197
|
-
'last-commit': 'No files found in the last commit.',
|
|
198
|
-
'last-n-commits': `No changes found in the last ${lastNCommits} commit(s).`,
|
|
199
|
-
'all': 'No changes found between your branch and the base.',
|
|
200
|
-
'committed': 'No unpushed commits found.',
|
|
201
|
-
};
|
|
202
|
-
return React.createElement(
|
|
203
|
-
Box, { flexDirection: 'column', paddingX: 1, paddingY: 1 },
|
|
204
|
-
React.createElement(Text, { color: 'cyan', bold: true }, 'CodeAnt Review'),
|
|
205
|
-
React.createElement(Text, { color: 'gray', dimColor: true }, DIVIDER),
|
|
206
|
-
React.createElement(Text, {}, ''),
|
|
207
|
-
React.createElement(Text, { color: 'yellow' }, ' No changes to review.'),
|
|
208
|
-
React.createElement(Text, { color: 'gray' }, ` ${hints[scanType] || 'No files found.'}`),
|
|
209
|
-
React.createElement(Text, {}, ''),
|
|
210
|
-
);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
export function renderError(error) {
|
|
214
|
-
return React.createElement(
|
|
215
|
-
Box, { flexDirection: 'column', paddingX: 1, paddingY: 1 },
|
|
216
|
-
React.createElement(Text, { color: 'cyan', bold: true }, 'CodeAnt Review'),
|
|
217
|
-
React.createElement(Text, { color: 'gray', dimColor: true }, DIVIDER),
|
|
218
|
-
React.createElement(Text, {}, ''),
|
|
219
|
-
React.createElement(Text, { color: 'red', bold: true }, ` ✗ ${error}`),
|
|
220
|
-
React.createElement(Text, {}, ''),
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
export function renderNotLoggedIn() {
|
|
225
|
-
return React.createElement(
|
|
226
|
-
Box, { flexDirection: 'column', paddingX: 1, paddingY: 1 },
|
|
227
|
-
React.createElement(Text, { color: 'cyan', bold: true }, 'CodeAnt Review'),
|
|
228
|
-
React.createElement(Text, { color: 'gray', dimColor: true }, DIVIDER),
|
|
229
|
-
React.createElement(Text, {}, ''),
|
|
230
|
-
React.createElement(Text, { color: 'red', bold: true }, ' ✗ Not logged in'),
|
|
231
|
-
React.createElement(Text, { color: 'gray' }, ' Run "codeant login" to authenticate.'),
|
|
232
|
-
React.createElement(Text, {}, ''),
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
export function renderDone(issues, summary, failOn, startTime, fileCount, meta) {
|
|
237
|
-
const severityOrder = ['INFO', 'MINOR', 'MAJOR', 'CRITICAL', 'BLOCKER'];
|
|
238
|
-
const failOnIndex = severityOrder.indexOf(failOn);
|
|
239
|
-
|
|
240
|
-
if (failOnIndex === -1) {
|
|
241
|
-
return React.createElement(
|
|
242
|
-
Box, { flexDirection: 'column', paddingX: 1, paddingY: 1 },
|
|
243
|
-
React.createElement(Text, { color: 'red', bold: true }, `✗ Invalid failOn value: ${failOn}`),
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const blockingIssues = issues.filter(i =>
|
|
248
|
-
severityOrder.indexOf(mapLabelToSeverity(i.label)) >= failOnIndex
|
|
249
|
-
);
|
|
250
|
-
const hasBlocking = blockingIssues.length > 0;
|
|
251
|
-
const elapsed = startTime ? formatElapsed(startTime) : null;
|
|
252
|
-
|
|
253
|
-
// Count by severity
|
|
254
|
-
const counts = {};
|
|
255
|
-
issues.forEach(i => {
|
|
256
|
-
const sev = mapLabelToSeverity(i.label);
|
|
257
|
-
counts[sev] = (counts[sev] || 0) + 1;
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
// Group issues by file
|
|
261
|
-
const grouped = {};
|
|
262
|
-
issues.forEach(i => {
|
|
263
|
-
const file = i.relevant_file || 'Unknown';
|
|
264
|
-
(grouped[file] ??= []).push(i);
|
|
265
|
-
});
|
|
266
|
-
const sortedFiles = Object.keys(grouped).sort();
|
|
267
|
-
sortedFiles.forEach(f =>
|
|
268
|
-
grouped[f].sort((a, b) => (a.start_line || 0) - (b.start_line || 0))
|
|
269
|
-
);
|
|
270
|
-
|
|
271
|
-
const els = [];
|
|
272
|
-
|
|
273
|
-
// ── Header ──
|
|
274
|
-
els.push(React.createElement(
|
|
275
|
-
Box, { key: 'hdr', gap: 1 },
|
|
276
|
-
React.createElement(Text, { color: 'cyan', bold: true }, 'CodeAnt Review'),
|
|
277
|
-
elapsed && React.createElement(Text, { color: 'gray' }, elapsed),
|
|
278
|
-
));
|
|
279
|
-
els.push(React.createElement(
|
|
280
|
-
Text, { key: 'div1', color: 'gray', dimColor: true }, DIVIDER,
|
|
281
|
-
));
|
|
282
|
-
|
|
283
|
-
if (issues.length === 0) {
|
|
284
|
-
// ── Clean pass ──
|
|
285
|
-
els.push(React.createElement(Text, { key: 'sp' }, ''));
|
|
286
|
-
els.push(React.createElement(
|
|
287
|
-
Text, { key: 'ok', color: 'green', bold: true }, ' ✓ No issues found',
|
|
288
|
-
));
|
|
289
|
-
els.push(React.createElement(Text, { key: 'sp2' }, ''));
|
|
290
|
-
} else {
|
|
291
|
-
// ── Issues grouped by file ──
|
|
292
|
-
els.push(React.createElement(Text, { key: 'sp1' }, ''));
|
|
293
|
-
|
|
294
|
-
sortedFiles.forEach((file, fi) => {
|
|
295
|
-
const fileIssues = grouped[file];
|
|
296
|
-
|
|
297
|
-
els.push(React.createElement(
|
|
298
|
-
Text, { key: `f-${fi}`, color: 'white', bold: true }, ` ${file}`,
|
|
299
|
-
));
|
|
300
|
-
|
|
301
|
-
fileIssues.forEach((issue, ii) => {
|
|
302
|
-
const sev = mapLabelToSeverity(issue.label);
|
|
303
|
-
const sevColor = getSeverityColor(sev);
|
|
304
|
-
const isLast = ii === fileIssues.length - 1;
|
|
305
|
-
const branch = isLast ? '└─' : '├─';
|
|
306
|
-
const cont = isLast ? ' ' : '│ ';
|
|
307
|
-
const lineStr = issue.start_line ? `L${issue.start_line}` : '';
|
|
308
|
-
|
|
309
|
-
els.push(React.createElement(
|
|
310
|
-
Box, { key: `i-${fi}-${ii}` },
|
|
311
|
-
React.createElement(Text, { color: 'gray' }, ` ${branch} `),
|
|
312
|
-
React.createElement(Text, {
|
|
313
|
-
color: sevColor,
|
|
314
|
-
bold: sev === 'CRITICAL' || sev === 'BLOCKER',
|
|
315
|
-
}, padEnd(sev, 9)),
|
|
316
|
-
React.createElement(Text, { color: 'gray' }, padEnd(lineStr, 6)),
|
|
317
|
-
React.createElement(Text, { color: 'white', dimColor: true }, issue.label),
|
|
318
|
-
));
|
|
319
|
-
|
|
320
|
-
// Safely coerce issue_content — API may return null/non-string, which would crash .trim()
|
|
321
|
-
const content = String(issue.issue_content || '').trim();
|
|
322
|
-
if (content) {
|
|
323
|
-
els.push(React.createElement(
|
|
324
|
-
Text, { key: `d-${fi}-${ii}`, color: 'gray' },
|
|
325
|
-
` ${cont} ${content}`,
|
|
326
|
-
));
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
els.push(React.createElement(Text, { key: `g-${fi}` }, ''));
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// ── Footer ──
|
|
335
|
-
els.push(React.createElement(
|
|
336
|
-
Text, { key: 'div2', color: 'gray', dimColor: true }, DIVIDER,
|
|
337
|
-
));
|
|
338
|
-
|
|
339
|
-
// Coverage line
|
|
340
|
-
const totalChanged = meta?.total_changed || 0;
|
|
341
|
-
const reviewedCount = meta?.reviewed_files?.length || fileCount;
|
|
342
|
-
if (totalChanged > 0) {
|
|
343
|
-
const coverageColor = reviewedCount === totalChanged ? 'green' : 'yellow';
|
|
344
|
-
els.push(React.createElement(
|
|
345
|
-
Text, { key: 'coverage', color: coverageColor },
|
|
346
|
-
`Reviewed ${reviewedCount} of ${totalChanged} changed file${totalChanged !== 1 ? 's' : ''}`,
|
|
347
|
-
));
|
|
348
|
-
} else if (fileCount) {
|
|
349
|
-
els.push(React.createElement(
|
|
350
|
-
Text, { key: 'coverage', color: 'gray' },
|
|
351
|
-
`${fileCount} file${fileCount !== 1 ? 's' : ''} reviewed`,
|
|
352
|
-
));
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// Skipped files summary
|
|
356
|
-
const skipped = meta?.skipped || [];
|
|
357
|
-
if (skipped.length > 0) {
|
|
358
|
-
const reasons = {};
|
|
359
|
-
skipped.forEach(s => {
|
|
360
|
-
const r = s.reason.split(' (')[0];
|
|
361
|
-
reasons[r] = (reasons[r] || 0) + 1;
|
|
362
|
-
});
|
|
363
|
-
const parts = Object.entries(reasons).map(([r, n]) => `${n} ${r}`);
|
|
364
|
-
els.push(React.createElement(
|
|
365
|
-
Text, { key: 'skipped', color: 'gray', dimColor: true },
|
|
366
|
-
`Skipped: ${parts.join(' · ')}`,
|
|
367
|
-
));
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
if (meta?.capped) {
|
|
371
|
-
els.push(React.createElement(
|
|
372
|
-
Text, { key: 'cap', color: 'yellow', dimColor: true },
|
|
373
|
-
`Note: max ${meta.max_files} files per review`,
|
|
374
|
-
));
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// Issue stats line
|
|
378
|
-
const stats = [];
|
|
379
|
-
if (issues.length > 0) {
|
|
380
|
-
stats.push(`${issues.length} issue${issues.length !== 1 ? 's' : ''}`);
|
|
381
|
-
for (const sev of ['CRITICAL', 'MAJOR', 'MINOR', 'INFO']) {
|
|
382
|
-
if (counts[sev]) stats.push(`${counts[sev]} ${sev.toLowerCase()}`);
|
|
383
|
-
}
|
|
384
|
-
els.push(React.createElement(
|
|
385
|
-
Text, { key: 'stats', color: 'gray' }, stats.join(' · '),
|
|
386
|
-
));
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// Status line
|
|
390
|
-
if (hasBlocking) {
|
|
391
|
-
els.push(React.createElement(
|
|
392
|
-
Text, { key: 'status', color: 'red', bold: true },
|
|
393
|
-
`✗ ${blockingIssues.length} blocking issue${blockingIssues.length !== 1 ? 's' : ''} — fix before proceeding`,
|
|
394
|
-
));
|
|
395
|
-
} else if (issues.length > 0) {
|
|
396
|
-
els.push(React.createElement(
|
|
397
|
-
Text, { key: 'status', color: 'green', bold: true },
|
|
398
|
-
'✓ All issues are non-blocking',
|
|
399
|
-
));
|
|
400
|
-
} else {
|
|
401
|
-
els.push(React.createElement(
|
|
402
|
-
Text, { key: 'status', color: 'green', bold: true },
|
|
403
|
-
'✓ All clear',
|
|
404
|
-
));
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
return React.createElement(
|
|
408
|
-
Box, { flexDirection: 'column', paddingX: 1, paddingY: 1 }, ...els,
|
|
409
|
-
);
|
|
410
|
-
}
|