korekt-cli 0.8.2 → 0.8.4
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/src/git-logic.js +26 -0
- package/src/git-logic.test.js +163 -0
- package/src/index.js +6 -0
package/package.json
CHANGED
package/src/git-logic.js
CHANGED
|
@@ -98,6 +98,30 @@ export function shouldIgnoreFile(filePath, patterns) {
|
|
|
98
98
|
return false;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Calculate total changed lines (additions + deletions) from changed files
|
|
103
|
+
* @param {Array} changedFiles - Array of file objects with diff property
|
|
104
|
+
* @returns {number} - Total number of changed lines
|
|
105
|
+
*/
|
|
106
|
+
export function calculateChangedLines(changedFiles) {
|
|
107
|
+
let changedLines = 0;
|
|
108
|
+
for (const file of changedFiles) {
|
|
109
|
+
if (file.diff) {
|
|
110
|
+
const lines = file.diff.split('\n');
|
|
111
|
+
for (const line of lines) {
|
|
112
|
+
if (
|
|
113
|
+
(line.startsWith('+') || line.startsWith('-')) &&
|
|
114
|
+
!line.startsWith('+++') &&
|
|
115
|
+
!line.startsWith('---')
|
|
116
|
+
) {
|
|
117
|
+
changedLines++;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return changedLines;
|
|
123
|
+
}
|
|
124
|
+
|
|
101
125
|
/**
|
|
102
126
|
* Helper function to parse the complex output of git diff --name-status
|
|
103
127
|
*/
|
|
@@ -219,6 +243,7 @@ export async function runUncommittedReview(mode = 'unstaged') {
|
|
|
219
243
|
commit_messages: [], // No commits for uncommitted changes
|
|
220
244
|
changed_files: changedFiles,
|
|
221
245
|
source_branch: branchName,
|
|
246
|
+
changed_lines: calculateChangedLines(changedFiles),
|
|
222
247
|
};
|
|
223
248
|
} catch (error) {
|
|
224
249
|
console.error(chalk.red('Failed to analyze uncommitted changes:'), error.message);
|
|
@@ -498,6 +523,7 @@ export async function runLocalReview(targetBranch = null, ignorePatterns = null)
|
|
|
498
523
|
author_email,
|
|
499
524
|
author_name,
|
|
500
525
|
contributors,
|
|
526
|
+
changed_lines: calculateChangedLines(changedFiles),
|
|
501
527
|
};
|
|
502
528
|
} catch (error) {
|
|
503
529
|
console.error(chalk.red('Failed to run local review analysis:'), error.message);
|
package/src/git-logic.test.js
CHANGED
|
@@ -667,3 +667,166 @@ describe('getContributors', () => {
|
|
|
667
667
|
expect(result.contributors[2].commits).toBe(1);
|
|
668
668
|
});
|
|
669
669
|
});
|
|
670
|
+
|
|
671
|
+
describe('changed_lines calculation', () => {
|
|
672
|
+
beforeEach(() => {
|
|
673
|
+
vi.mock('execa');
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
afterEach(() => {
|
|
677
|
+
vi.restoreAllMocks();
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
it('should calculate changed_lines for uncommitted changes', async () => {
|
|
681
|
+
vi.mocked(execa).mockImplementation(async (cmd, args) => {
|
|
682
|
+
const command = [cmd, ...args].join(' ');
|
|
683
|
+
|
|
684
|
+
if (command.includes('remote get-url origin')) {
|
|
685
|
+
return { stdout: 'https://github.com/user/repo.git' };
|
|
686
|
+
}
|
|
687
|
+
if (command.includes('rev-parse --abbrev-ref HEAD')) {
|
|
688
|
+
return { stdout: 'feature-branch' };
|
|
689
|
+
}
|
|
690
|
+
if (command.includes('rev-parse --show-toplevel')) {
|
|
691
|
+
return { stdout: '/fake/repo/path' };
|
|
692
|
+
}
|
|
693
|
+
if (command.includes('diff --cached --name-status')) {
|
|
694
|
+
return { stdout: 'M\tfile.js' };
|
|
695
|
+
}
|
|
696
|
+
if (command.includes('diff --cached -U15 -- file.js')) {
|
|
697
|
+
// Simulate a diff with 5 additions and 3 deletions (8 total changed lines)
|
|
698
|
+
return {
|
|
699
|
+
stdout:
|
|
700
|
+
'diff --git a/file.js b/file.js\n' +
|
|
701
|
+
'--- a/file.js\n' +
|
|
702
|
+
'+++ b/file.js\n' +
|
|
703
|
+
'@@ -1,5 +1,10 @@\n' +
|
|
704
|
+
'+new line 1\n' +
|
|
705
|
+
'+new line 2\n' +
|
|
706
|
+
' existing line\n' +
|
|
707
|
+
'-old line 1\n' +
|
|
708
|
+
'-old line 2\n' +
|
|
709
|
+
'-old line 3\n' +
|
|
710
|
+
'+new line 3\n' +
|
|
711
|
+
'+new line 4\n' +
|
|
712
|
+
'+new line 5\n',
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
if (command.includes('show HEAD:file.js')) {
|
|
716
|
+
return { stdout: 'old content' };
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
throw new Error(`Unmocked command: ${command}`);
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
const result = await runUncommittedReview('staged');
|
|
723
|
+
|
|
724
|
+
expect(result).toBeDefined();
|
|
725
|
+
expect(result.changed_lines).toBe(8); // 5 additions + 3 deletions
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
it('should calculate changed_lines for local review', async () => {
|
|
729
|
+
vi.mocked(execa).mockImplementation(async (cmd, args) => {
|
|
730
|
+
const command = [cmd, ...args].join(' ');
|
|
731
|
+
|
|
732
|
+
if (command.includes('remote get-url origin')) {
|
|
733
|
+
return { stdout: 'https://github.com/user/repo.git' };
|
|
734
|
+
}
|
|
735
|
+
if (command.includes('rev-parse --abbrev-ref HEAD')) {
|
|
736
|
+
return { stdout: 'feature-branch' };
|
|
737
|
+
}
|
|
738
|
+
if (command.includes('rev-parse --show-toplevel')) {
|
|
739
|
+
return { stdout: '/path/to/repo' };
|
|
740
|
+
}
|
|
741
|
+
if (command.includes('rev-parse --verify main')) {
|
|
742
|
+
return { stdout: 'commit-hash' };
|
|
743
|
+
}
|
|
744
|
+
if (command === 'git fetch origin main') {
|
|
745
|
+
return { stdout: '' };
|
|
746
|
+
}
|
|
747
|
+
if (command.includes('merge-base origin/main HEAD')) {
|
|
748
|
+
return { stdout: 'abc123' };
|
|
749
|
+
}
|
|
750
|
+
if (command.includes('log --no-merges --pretty=%B---EOC---')) {
|
|
751
|
+
return { stdout: 'feat: add feature---EOC---' };
|
|
752
|
+
}
|
|
753
|
+
if (command.includes('diff --name-status')) {
|
|
754
|
+
return { stdout: 'M\tfile1.js\nA\tfile2.js' };
|
|
755
|
+
}
|
|
756
|
+
if (command.includes('diff -U15') && command.includes('file1.js')) {
|
|
757
|
+
// 10 lines changed in file1.js
|
|
758
|
+
return {
|
|
759
|
+
stdout:
|
|
760
|
+
'diff --git a/file1.js b/file1.js\n' +
|
|
761
|
+
'+line1\n' +
|
|
762
|
+
'+line2\n' +
|
|
763
|
+
'-line3\n' +
|
|
764
|
+
'-line4\n' +
|
|
765
|
+
'+line5\n' +
|
|
766
|
+
'+line6\n' +
|
|
767
|
+
'+line7\n' +
|
|
768
|
+
'-line8\n' +
|
|
769
|
+
'+line9\n' +
|
|
770
|
+
'+line10\n',
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
if (command.includes('diff -U15') && command.includes('file2.js')) {
|
|
774
|
+
// 5 lines added in file2.js (new file)
|
|
775
|
+
return {
|
|
776
|
+
stdout:
|
|
777
|
+
'diff --git a/file2.js b/file2.js\n' +
|
|
778
|
+
'+line1\n' +
|
|
779
|
+
'+line2\n' +
|
|
780
|
+
'+line3\n' +
|
|
781
|
+
'+line4\n' +
|
|
782
|
+
'+line5\n',
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
if (command.includes('show abc123:file1.js')) {
|
|
786
|
+
return { stdout: 'original content' };
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
return { stdout: '' };
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
const result = await runLocalReview('main');
|
|
793
|
+
|
|
794
|
+
expect(result).toBeDefined();
|
|
795
|
+
expect(result.changed_lines).toBe(15); // 10 from file1.js + 5 from file2.js
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
it('should handle diffs with no changes', async () => {
|
|
799
|
+
vi.mocked(execa).mockImplementation(async (cmd, args) => {
|
|
800
|
+
const command = [cmd, ...args].join(' ');
|
|
801
|
+
|
|
802
|
+
if (command.includes('remote get-url origin')) {
|
|
803
|
+
return { stdout: 'https://github.com/user/repo.git' };
|
|
804
|
+
}
|
|
805
|
+
if (command.includes('rev-parse --abbrev-ref HEAD')) {
|
|
806
|
+
return { stdout: 'feature-branch' };
|
|
807
|
+
}
|
|
808
|
+
if (command.includes('rev-parse --show-toplevel')) {
|
|
809
|
+
return { stdout: '/fake/repo/path' };
|
|
810
|
+
}
|
|
811
|
+
if (command.includes('diff --cached --name-status')) {
|
|
812
|
+
return { stdout: 'M\tfile.js' };
|
|
813
|
+
}
|
|
814
|
+
if (command.includes('diff --cached -U15 -- file.js')) {
|
|
815
|
+
// Empty diff (no actual changes, just headers)
|
|
816
|
+
return {
|
|
817
|
+
stdout: 'diff --git a/file.js b/file.js\n--- a/file.js\n+++ b/file.js\n',
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
if (command.includes('show HEAD:file.js')) {
|
|
821
|
+
return { stdout: 'content' };
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
throw new Error(`Unmocked command: ${command}`);
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
const result = await runUncommittedReview('staged');
|
|
828
|
+
|
|
829
|
+
expect(result).toBeDefined();
|
|
830
|
+
expect(result.changed_lines).toBe(0);
|
|
831
|
+
});
|
|
832
|
+
});
|
package/src/index.js
CHANGED
|
@@ -187,6 +187,7 @@ program
|
|
|
187
187
|
)
|
|
188
188
|
.option('--json', 'Output raw API response as JSON')
|
|
189
189
|
.option('--comment', 'Post review results as PR comments (auto-detects CI provider)')
|
|
190
|
+
.option('--post-ticket', 'Post review results to linked ticket (e.g., JIRA)')
|
|
190
191
|
.action(async (targetBranch, options) => {
|
|
191
192
|
const reviewTarget = targetBranch ? `against '${targetBranch}'` : '(auto-detecting fork point)';
|
|
192
193
|
|
|
@@ -269,6 +270,11 @@ program
|
|
|
269
270
|
spinner.text = `Submitting review to the AI... ${elapsed}s`;
|
|
270
271
|
}, 1000);
|
|
271
272
|
|
|
273
|
+
// Add post_to_ticket flag if requested
|
|
274
|
+
if (options.postTicket) {
|
|
275
|
+
payload.post_to_ticket = true;
|
|
276
|
+
}
|
|
277
|
+
|
|
272
278
|
try {
|
|
273
279
|
const response = await axios.post(apiEndpoint, payload, {
|
|
274
280
|
headers: {
|