korekt-cli 0.9.6 ā 0.10.0
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/package.json +1 -1
- package/scripts/azure.sh +17 -0
- package/scripts/bitbucket.sh +17 -0
- package/scripts/github.sh +17 -0
- package/src/formatter.js +19 -1
- package/src/formatter.test.js +96 -0
- package/src/git-logic.test.js +58 -0
- package/src/index.js +6 -0
package/package.json
CHANGED
package/scripts/azure.sh
CHANGED
|
@@ -232,6 +232,9 @@ post_review_thread() {
|
|
|
232
232
|
TOTAL_ISSUES=$(jq -r '.data.summary.total_issues // 0' "$RESULTS_FILE")
|
|
233
233
|
TOTAL_PRAISES=$(jq -r '.data.summary.total_praises // 0' "$RESULTS_FILE")
|
|
234
234
|
CRITICAL_ISSUES=$(jq -r '.data.summary.critical // 0' "$RESULTS_FILE")
|
|
235
|
+
CHANGE_SUMMARY=$(jq -r '.data.change_classification?.summary // ""' "$RESULTS_FILE")
|
|
236
|
+
CHANGE_INTENT=$(jq -r '.data.change_classification?.intent // ""' "$RESULTS_FILE")
|
|
237
|
+
CHANGE_ASPECTS=$(jq -r '.data.change_classification?.aspects // [] | join(", ")' "$RESULTS_FILE")
|
|
235
238
|
|
|
236
239
|
# Post inline comments for issues (excluding low severity)
|
|
237
240
|
if [ "$TOTAL_ISSUES" -gt 0 ] && [ "$POST_INLINE_COMMENTS" = "true" ]; then
|
|
@@ -318,6 +321,20 @@ else
|
|
|
318
321
|
echo "š¤ **Automated Code Review Results**" >> "$COMMENT_FILE"
|
|
319
322
|
echo "" >> "$COMMENT_FILE"
|
|
320
323
|
|
|
324
|
+
# Change Summary section
|
|
325
|
+
if [ -n "$CHANGE_SUMMARY" ]; then
|
|
326
|
+
echo "### š Change Summary" >> "$COMMENT_FILE"
|
|
327
|
+
echo "$CHANGE_SUMMARY" >> "$COMMENT_FILE"
|
|
328
|
+
echo "" >> "$COMMENT_FILE"
|
|
329
|
+
|
|
330
|
+
# Build metadata line
|
|
331
|
+
META=""
|
|
332
|
+
[ -n "$CHANGE_INTENT" ] && META="Intent: $CHANGE_INTENT"
|
|
333
|
+
[ -n "$CHANGE_ASPECTS" ] && { [ -n "$META" ] && META="$META | "; META="${META}Aspects: $CHANGE_ASPECTS"; }
|
|
334
|
+
[ -n "$META" ] && echo "_${META}_" >> "$COMMENT_FILE"
|
|
335
|
+
echo "" >> "$COMMENT_FILE"
|
|
336
|
+
fi
|
|
337
|
+
|
|
321
338
|
# Praises section
|
|
322
339
|
if [ "$TOTAL_PRAISES" -gt 0 ]; then
|
|
323
340
|
echo "### ⨠Praises ($TOTAL_PRAISES)" >> "$COMMENT_FILE"
|
package/scripts/bitbucket.sh
CHANGED
|
@@ -205,6 +205,9 @@ post_inline_comment() {
|
|
|
205
205
|
TOTAL_ISSUES=$(jq -r '.data.summary.total_issues // 0' "$RESULTS_FILE")
|
|
206
206
|
TOTAL_PRAISES=$(jq -r '.data.summary.total_praises // 0' "$RESULTS_FILE")
|
|
207
207
|
CRITICAL_ISSUES=$(jq -r '.data.summary.critical // 0' "$RESULTS_FILE")
|
|
208
|
+
CHANGE_SUMMARY=$(jq -r '.data.change_classification?.summary // ""' "$RESULTS_FILE")
|
|
209
|
+
CHANGE_INTENT=$(jq -r '.data.change_classification?.intent // ""' "$RESULTS_FILE")
|
|
210
|
+
CHANGE_ASPECTS=$(jq -r '.data.change_classification?.aspects // [] | join(", ")' "$RESULTS_FILE")
|
|
208
211
|
|
|
209
212
|
# Post inline comments for issues (excluding low severity)
|
|
210
213
|
if [ "$TOTAL_ISSUES" -gt 0 ] && [ "$POST_INLINE_COMMENTS" = "true" ]; then
|
|
@@ -288,6 +291,20 @@ else
|
|
|
288
291
|
echo "š¤ **Automated Code Review Results**" >> "$COMMENT_FILE"
|
|
289
292
|
echo "" >> "$COMMENT_FILE"
|
|
290
293
|
|
|
294
|
+
# Change Summary section
|
|
295
|
+
if [ -n "$CHANGE_SUMMARY" ]; then
|
|
296
|
+
echo "### š Change Summary" >> "$COMMENT_FILE"
|
|
297
|
+
echo "$CHANGE_SUMMARY" >> "$COMMENT_FILE"
|
|
298
|
+
echo "" >> "$COMMENT_FILE"
|
|
299
|
+
|
|
300
|
+
# Build metadata line
|
|
301
|
+
META=""
|
|
302
|
+
[ -n "$CHANGE_INTENT" ] && META="Intent: $CHANGE_INTENT"
|
|
303
|
+
[ -n "$CHANGE_ASPECTS" ] && { [ -n "$META" ] && META="$META | "; META="${META}Aspects: $CHANGE_ASPECTS"; }
|
|
304
|
+
[ -n "$META" ] && echo "_${META}_" >> "$COMMENT_FILE"
|
|
305
|
+
echo "" >> "$COMMENT_FILE"
|
|
306
|
+
fi
|
|
307
|
+
|
|
291
308
|
# Praises section
|
|
292
309
|
if [ "$TOTAL_PRAISES" -gt 0 ]; then
|
|
293
310
|
echo "### ⨠Praises ($TOTAL_PRAISES)" >> "$COMMENT_FILE"
|
package/scripts/github.sh
CHANGED
|
@@ -204,6 +204,9 @@ post_review_comment() {
|
|
|
204
204
|
TOTAL_ISSUES=$(jq -r '.data.summary.total_issues // 0' "$RESULTS_FILE")
|
|
205
205
|
TOTAL_PRAISES=$(jq -r '.data.summary.total_praises // 0' "$RESULTS_FILE")
|
|
206
206
|
CRITICAL_ISSUES=$(jq -r '.data.summary.critical // 0' "$RESULTS_FILE")
|
|
207
|
+
CHANGE_SUMMARY=$(jq -r '.data.change_classification?.summary // ""' "$RESULTS_FILE")
|
|
208
|
+
CHANGE_INTENT=$(jq -r '.data.change_classification?.intent // ""' "$RESULTS_FILE")
|
|
209
|
+
CHANGE_ASPECTS=$(jq -r '.data.change_classification?.aspects // [] | join(", ")' "$RESULTS_FILE")
|
|
207
210
|
|
|
208
211
|
# Post inline comments for issues
|
|
209
212
|
if [ "$TOTAL_ISSUES" -gt 0 ]; then
|
|
@@ -287,6 +290,20 @@ else
|
|
|
287
290
|
echo "š¤ **Automated Code Review Results**" >> "$COMMENT_FILE"
|
|
288
291
|
echo "" >> "$COMMENT_FILE"
|
|
289
292
|
|
|
293
|
+
# Change Summary section
|
|
294
|
+
if [ -n "$CHANGE_SUMMARY" ]; then
|
|
295
|
+
echo "### š Change Summary" >> "$COMMENT_FILE"
|
|
296
|
+
echo "$CHANGE_SUMMARY" >> "$COMMENT_FILE"
|
|
297
|
+
echo "" >> "$COMMENT_FILE"
|
|
298
|
+
|
|
299
|
+
# Build metadata line
|
|
300
|
+
META=""
|
|
301
|
+
[ -n "$CHANGE_INTENT" ] && META="Intent: $CHANGE_INTENT"
|
|
302
|
+
[ -n "$CHANGE_ASPECTS" ] && { [ -n "$META" ] && META="$META | "; META="${META}Aspects: $CHANGE_ASPECTS"; }
|
|
303
|
+
[ -n "$META" ] && echo "_${META}_" >> "$COMMENT_FILE"
|
|
304
|
+
echo "" >> "$COMMENT_FILE"
|
|
305
|
+
fi
|
|
306
|
+
|
|
290
307
|
# Praises section
|
|
291
308
|
if [ "$TOTAL_PRAISES" -gt 0 ]; then
|
|
292
309
|
echo "### ⨠Praises ($TOTAL_PRAISES)" >> "$COMMENT_FILE"
|
package/src/formatter.js
CHANGED
|
@@ -76,10 +76,28 @@ function toAbsolutePath(filePath) {
|
|
|
76
76
|
* @param {Object} data - The API response data
|
|
77
77
|
*/
|
|
78
78
|
export function formatReviewOutput(data) {
|
|
79
|
-
const { review, summary } = data.data;
|
|
79
|
+
const { review, summary, change_classification: changeClassification } = data.data;
|
|
80
80
|
|
|
81
81
|
console.log(chalk.bold.blue('š¤ Automated Code Review Results\n'));
|
|
82
82
|
|
|
83
|
+
// --- Change Summary Section ---
|
|
84
|
+
if (changeClassification && changeClassification.summary) {
|
|
85
|
+
console.log(chalk.bold.cyan('š Change Summary\n'));
|
|
86
|
+
console.log(` ${changeClassification.summary}\n`);
|
|
87
|
+
|
|
88
|
+
const meta = [];
|
|
89
|
+
if (changeClassification.intent) {
|
|
90
|
+
meta.push(`Intent: ${changeClassification.intent}`);
|
|
91
|
+
}
|
|
92
|
+
if (changeClassification.aspects && changeClassification.aspects.length > 0) {
|
|
93
|
+
meta.push(`Aspects: ${changeClassification.aspects.join(', ')}`);
|
|
94
|
+
}
|
|
95
|
+
if (meta.length > 0) {
|
|
96
|
+
console.log(chalk.gray(` ${meta.join(' | ')}`));
|
|
97
|
+
}
|
|
98
|
+
console.log(); // Spacing after change summary
|
|
99
|
+
}
|
|
100
|
+
|
|
83
101
|
// --- Praises Section ---
|
|
84
102
|
if (review && review.praises && review.praises.length > 0) {
|
|
85
103
|
console.log(chalk.bold.magenta(`⨠Praises (${summary.total_praises})`));
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { formatReviewOutput } from './formatter.js';
|
|
3
|
+
|
|
4
|
+
describe('formatReviewOutput', () => {
|
|
5
|
+
let consoleSpy;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
vi.restoreAllMocks();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe('change_classification output', () => {
|
|
16
|
+
it('should display change summary when change_classification.summary is present', () => {
|
|
17
|
+
const data = {
|
|
18
|
+
data: {
|
|
19
|
+
review: { issues: [], praises: [] },
|
|
20
|
+
summary: { total_issues: 0, total_praises: 0 },
|
|
21
|
+
change_classification: {
|
|
22
|
+
intent: 'fix',
|
|
23
|
+
aspects: ['security', 'tests'],
|
|
24
|
+
summary: 'Fix authentication bypass vulnerability',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
formatReviewOutput(data);
|
|
30
|
+
|
|
31
|
+
const allCalls = consoleSpy.mock.calls.map((call) => call[0]);
|
|
32
|
+
expect(allCalls.some((call) => call.includes('Change Summary'))).toBe(true);
|
|
33
|
+
expect(
|
|
34
|
+
allCalls.some((call) => call.includes('Fix authentication bypass vulnerability'))
|
|
35
|
+
).toBe(true);
|
|
36
|
+
expect(allCalls.some((call) => call.includes('Intent: fix'))).toBe(true);
|
|
37
|
+
expect(allCalls.some((call) => call.includes('Aspects: security, tests'))).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should not display change_classification section when summary is missing', () => {
|
|
41
|
+
const data = {
|
|
42
|
+
data: {
|
|
43
|
+
review: { issues: [], praises: [] },
|
|
44
|
+
summary: { total_issues: 0, total_praises: 0 },
|
|
45
|
+
change_classification: null,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
formatReviewOutput(data);
|
|
50
|
+
|
|
51
|
+
const allCalls = consoleSpy.mock.calls.map((call) => call[0]);
|
|
52
|
+
expect(allCalls.some((call) => call && call.includes('Change Summary'))).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should handle change_classification with only summary (no intent/aspects)', () => {
|
|
56
|
+
const data = {
|
|
57
|
+
data: {
|
|
58
|
+
review: { issues: [], praises: [] },
|
|
59
|
+
summary: { total_issues: 0, total_praises: 0 },
|
|
60
|
+
change_classification: {
|
|
61
|
+
summary: 'Add new feature',
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
formatReviewOutput(data);
|
|
67
|
+
|
|
68
|
+
const allCalls = consoleSpy.mock.calls.map((call) => call[0]);
|
|
69
|
+
expect(allCalls.some((call) => call.includes('Change Summary'))).toBe(true);
|
|
70
|
+
expect(allCalls.some((call) => call.includes('Add new feature'))).toBe(true);
|
|
71
|
+
// Should not have metadata line when no intent/aspects
|
|
72
|
+
expect(allCalls.some((call) => call && call.includes('Intent:'))).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should handle change_classification with empty aspects array', () => {
|
|
76
|
+
const data = {
|
|
77
|
+
data: {
|
|
78
|
+
review: { issues: [], praises: [] },
|
|
79
|
+
summary: { total_issues: 0, total_praises: 0 },
|
|
80
|
+
change_classification: {
|
|
81
|
+
intent: 'feature',
|
|
82
|
+
aspects: [],
|
|
83
|
+
summary: 'Add user dashboard',
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
formatReviewOutput(data);
|
|
89
|
+
|
|
90
|
+
const allCalls = consoleSpy.mock.calls.map((call) => call[0]);
|
|
91
|
+
expect(allCalls.some((call) => call.includes('Add user dashboard'))).toBe(true);
|
|
92
|
+
expect(allCalls.some((call) => call && call.includes('Intent: feature'))).toBe(true);
|
|
93
|
+
expect(allCalls.some((call) => call && call.includes('Aspects:'))).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
package/src/git-logic.test.js
CHANGED
|
@@ -259,6 +259,64 @@ describe('runLocalReview - branch fetching', () => {
|
|
|
259
259
|
});
|
|
260
260
|
});
|
|
261
261
|
|
|
262
|
+
describe('runLocalReview - no files changed', () => {
|
|
263
|
+
beforeEach(() => {
|
|
264
|
+
vi.mock('execa');
|
|
265
|
+
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
afterEach(() => {
|
|
269
|
+
vi.restoreAllMocks();
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('should return payload with empty changed_files when diff has no files', async () => {
|
|
273
|
+
vi.mocked(execa).mockImplementation(async (cmd, args) => {
|
|
274
|
+
const command = [cmd, ...args].join(' ');
|
|
275
|
+
|
|
276
|
+
if (command.includes('remote get-url origin')) {
|
|
277
|
+
return { stdout: 'https://github.com/user/repo.git' };
|
|
278
|
+
}
|
|
279
|
+
if (command.includes('rev-parse --abbrev-ref HEAD')) {
|
|
280
|
+
return { stdout: 'feature-branch' };
|
|
281
|
+
}
|
|
282
|
+
if (command === 'git rev-parse HEAD') {
|
|
283
|
+
return { stdout: 'abc123def456789' };
|
|
284
|
+
}
|
|
285
|
+
if (command.includes('rev-parse --show-toplevel')) {
|
|
286
|
+
return { stdout: '/path/to/repo' };
|
|
287
|
+
}
|
|
288
|
+
if (command.includes('rev-parse --verify main')) {
|
|
289
|
+
return { stdout: 'commit-hash' };
|
|
290
|
+
}
|
|
291
|
+
if (command === 'git fetch origin main') {
|
|
292
|
+
return { stdout: '' };
|
|
293
|
+
}
|
|
294
|
+
if (command.includes('merge-base origin/main HEAD')) {
|
|
295
|
+
return { stdout: 'abc123' };
|
|
296
|
+
}
|
|
297
|
+
if (command.includes('log --no-merges --pretty=%B---EOC---')) {
|
|
298
|
+
return { stdout: 'feat: some commit---EOC---' };
|
|
299
|
+
}
|
|
300
|
+
if (command.includes('log --no-merges --format=%ae|%an')) {
|
|
301
|
+
return { stdout: 'user@example.com|User Name' };
|
|
302
|
+
}
|
|
303
|
+
// No files changed
|
|
304
|
+
if (command.includes('diff --name-status')) {
|
|
305
|
+
return { stdout: '' };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return { stdout: '' };
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
const result = await runLocalReview('main');
|
|
312
|
+
|
|
313
|
+
expect(result).not.toBeNull();
|
|
314
|
+
expect(result.changed_files).toEqual([]);
|
|
315
|
+
expect(result.source_branch).toBe('feature-branch');
|
|
316
|
+
expect(result.destination_branch).toBe('main');
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
262
320
|
describe('runLocalReview - fork point detection', () => {
|
|
263
321
|
beforeEach(() => {
|
|
264
322
|
vi.mock('execa');
|
package/src/index.js
CHANGED
|
@@ -165,6 +165,12 @@ program
|
|
|
165
165
|
process.exit(1);
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
// Exit successfully if no files to review (common in CI when PR has no code changes)
|
|
169
|
+
if (payload.changed_files.length === 0) {
|
|
170
|
+
log(chalk.green('ā No files changed to review. Exiting successfully.'));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
168
174
|
// If dry-run, just show the payload and exit
|
|
169
175
|
if (options.dryRun) {
|
|
170
176
|
log(chalk.yellow('\nš Dry Run - Payload that would be sent:\n'));
|