korekt-cli 0.8.4 โ†’ 0.8.5

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "korekt-cli",
3
- "version": "0.8.4",
3
+ "version": "0.8.5",
4
4
  "description": "AI-powered code review CLI - Keep your kode korekt",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/formatter.js CHANGED
@@ -29,6 +29,8 @@ const CATEGORY_ICONS = {
29
29
  documentation: '๐Ÿ“',
30
30
  test_coverage: '๐Ÿงช',
31
31
  readability: '๐Ÿ“–',
32
+ architectural: '๐Ÿ—๏ธ',
33
+ file_structure: '๐Ÿ“',
32
34
  default: 'โš™๏ธ', // Default icon
33
35
  };
34
36
 
package/src/git-logic.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { execa } from 'execa';
2
2
  import chalk from 'chalk';
3
+ import { detectCIProvider } from './utils.js';
3
4
 
4
5
  /**
5
6
  * Truncate content to a maximum number of lines using "head and tail".
@@ -244,6 +245,7 @@ export async function runUncommittedReview(mode = 'unstaged') {
244
245
  changed_files: changedFiles,
245
246
  source_branch: branchName,
246
247
  changed_lines: calculateChangedLines(changedFiles),
248
+ is_ci: detectCIProvider() !== null,
247
249
  };
248
250
  } catch (error) {
249
251
  console.error(chalk.red('Failed to analyze uncommitted changes:'), error.message);
@@ -524,6 +526,7 @@ export async function runLocalReview(targetBranch = null, ignorePatterns = null)
524
526
  author_name,
525
527
  contributors,
526
528
  changed_lines: calculateChangedLines(changedFiles),
529
+ is_ci: detectCIProvider() !== null,
527
530
  };
528
531
  } catch (error) {
529
532
  console.error(chalk.red('Failed to run local review analysis:'), error.message);
@@ -9,6 +9,7 @@ import {
9
9
  getContributors,
10
10
  } from './git-logic.js';
11
11
  import { execa } from 'execa';
12
+ import { detectCIProvider } from './utils.js';
12
13
 
13
14
  describe('parseNameStatus', () => {
14
15
  it('should correctly parse M, A, and D statuses', () => {
@@ -830,3 +831,182 @@ describe('changed_lines calculation', () => {
830
831
  expect(result.changed_lines).toBe(0);
831
832
  });
832
833
  });
834
+
835
+ describe('is_ci flag in payload', () => {
836
+ beforeEach(() => {
837
+ vi.mock('execa');
838
+ vi.mock('./utils.js', () => ({
839
+ detectCIProvider: vi.fn(),
840
+ }));
841
+ });
842
+
843
+ afterEach(() => {
844
+ vi.restoreAllMocks();
845
+ });
846
+
847
+ it('should set is_ci to true when detectCIProvider returns a provider', async () => {
848
+ vi.mocked(detectCIProvider).mockReturnValue('github');
849
+
850
+ vi.mocked(execa).mockImplementation(async (cmd, args) => {
851
+ const command = [cmd, ...args].join(' ');
852
+
853
+ if (command.includes('remote get-url origin')) {
854
+ return { stdout: 'https://github.com/user/repo.git' };
855
+ }
856
+ if (command.includes('rev-parse --abbrev-ref HEAD')) {
857
+ return { stdout: 'feature-branch' };
858
+ }
859
+ if (command.includes('rev-parse --show-toplevel')) {
860
+ return { stdout: '/fake/repo/path' };
861
+ }
862
+ if (command.includes('diff --cached --name-status')) {
863
+ return { stdout: 'M\tfile.js' };
864
+ }
865
+ if (command.includes('diff --cached -U15 -- file.js')) {
866
+ return { stdout: 'diff --git a/file.js b/file.js\n+new line' };
867
+ }
868
+ if (command.includes('show HEAD:file.js')) {
869
+ return { stdout: 'old content' };
870
+ }
871
+
872
+ throw new Error(`Unmocked command: ${command}`);
873
+ });
874
+
875
+ const result = await runUncommittedReview('staged');
876
+
877
+ expect(result).toBeDefined();
878
+ expect(result.is_ci).toBe(true);
879
+ });
880
+
881
+ it('should set is_ci to false when detectCIProvider returns null', async () => {
882
+ vi.mocked(detectCIProvider).mockReturnValue(null);
883
+
884
+ vi.mocked(execa).mockImplementation(async (cmd, args) => {
885
+ const command = [cmd, ...args].join(' ');
886
+
887
+ if (command.includes('remote get-url origin')) {
888
+ return { stdout: 'https://github.com/user/repo.git' };
889
+ }
890
+ if (command.includes('rev-parse --abbrev-ref HEAD')) {
891
+ return { stdout: 'feature-branch' };
892
+ }
893
+ if (command.includes('rev-parse --show-toplevel')) {
894
+ return { stdout: '/fake/repo/path' };
895
+ }
896
+ if (command.includes('diff --cached --name-status')) {
897
+ return { stdout: 'M\tfile.js' };
898
+ }
899
+ if (command.includes('diff --cached -U15 -- file.js')) {
900
+ return { stdout: 'diff --git a/file.js b/file.js\n+new line' };
901
+ }
902
+ if (command.includes('show HEAD:file.js')) {
903
+ return { stdout: 'old content' };
904
+ }
905
+
906
+ throw new Error(`Unmocked command: ${command}`);
907
+ });
908
+
909
+ const result = await runUncommittedReview('staged');
910
+
911
+ expect(result).toBeDefined();
912
+ expect(result.is_ci).toBe(false);
913
+ });
914
+
915
+ it('should include is_ci in runLocalReview payload', async () => {
916
+ vi.mocked(detectCIProvider).mockReturnValue('azure');
917
+
918
+ vi.mocked(execa).mockImplementation(async (cmd, args) => {
919
+ const command = [cmd, ...args].join(' ');
920
+
921
+ if (command.includes('remote get-url origin')) {
922
+ return { stdout: 'https://github.com/user/repo.git' };
923
+ }
924
+ if (command.includes('rev-parse --abbrev-ref HEAD')) {
925
+ return { stdout: 'feature-branch' };
926
+ }
927
+ if (command.includes('rev-parse --show-toplevel')) {
928
+ return { stdout: '/path/to/repo' };
929
+ }
930
+ if (command.includes('rev-parse --verify main')) {
931
+ return { stdout: 'commit-hash' };
932
+ }
933
+ if (command === 'git fetch origin main') {
934
+ return { stdout: '' };
935
+ }
936
+ if (command.includes('merge-base origin/main HEAD')) {
937
+ return { stdout: 'abc123' };
938
+ }
939
+ if (command.includes('log --no-merges --pretty=%B---EOC---')) {
940
+ return { stdout: 'feat: add feature---EOC---' };
941
+ }
942
+ if (command.includes('log --no-merges --format=%ae|%an')) {
943
+ return { stdout: 'user@example.com|User Name' };
944
+ }
945
+ if (command.includes('diff --name-status')) {
946
+ return { stdout: 'M\tfile.js' };
947
+ }
948
+ if (command.includes('diff -U15')) {
949
+ return { stdout: 'diff content' };
950
+ }
951
+ if (command.includes('show abc123:file.js')) {
952
+ return { stdout: 'original content' };
953
+ }
954
+
955
+ return { stdout: '' };
956
+ });
957
+
958
+ const result = await runLocalReview('main');
959
+
960
+ expect(result).toBeDefined();
961
+ expect(result.is_ci).toBe(true);
962
+ });
963
+
964
+ it('should set is_ci to false in runLocalReview when not in CI', async () => {
965
+ vi.mocked(detectCIProvider).mockReturnValue(null);
966
+
967
+ vi.mocked(execa).mockImplementation(async (cmd, args) => {
968
+ const command = [cmd, ...args].join(' ');
969
+
970
+ if (command.includes('remote get-url origin')) {
971
+ return { stdout: 'https://github.com/user/repo.git' };
972
+ }
973
+ if (command.includes('rev-parse --abbrev-ref HEAD')) {
974
+ return { stdout: 'feature-branch' };
975
+ }
976
+ if (command.includes('rev-parse --show-toplevel')) {
977
+ return { stdout: '/path/to/repo' };
978
+ }
979
+ if (command.includes('rev-parse --verify main')) {
980
+ return { stdout: 'commit-hash' };
981
+ }
982
+ if (command === 'git fetch origin main') {
983
+ return { stdout: '' };
984
+ }
985
+ if (command.includes('merge-base origin/main HEAD')) {
986
+ return { stdout: 'abc123' };
987
+ }
988
+ if (command.includes('log --no-merges --pretty=%B---EOC---')) {
989
+ return { stdout: 'feat: add feature---EOC---' };
990
+ }
991
+ if (command.includes('log --no-merges --format=%ae|%an')) {
992
+ return { stdout: 'user@example.com|User Name' };
993
+ }
994
+ if (command.includes('diff --name-status')) {
995
+ return { stdout: 'M\tfile.js' };
996
+ }
997
+ if (command.includes('diff -U15')) {
998
+ return { stdout: 'diff content' };
999
+ }
1000
+ if (command.includes('show abc123:file.js')) {
1001
+ return { stdout: 'original content' };
1002
+ }
1003
+
1004
+ return { stdout: '' };
1005
+ });
1006
+
1007
+ const result = await runLocalReview('main');
1008
+
1009
+ expect(result).toBeDefined();
1010
+ expect(result.is_ci).toBe(false);
1011
+ });
1012
+ });
package/src/index.js CHANGED
@@ -14,6 +14,10 @@ import { tmpdir } from 'os';
14
14
  import { runLocalReview } from './git-logic.js';
15
15
  import { getApiKey, setApiKey, getApiEndpoint, setApiEndpoint } from './config.js';
16
16
  import { formatReviewOutput } from './formatter.js';
17
+ import { detectCIProvider, truncateFileData, formatErrorOutput } from './utils.js';
18
+
19
+ // Re-export utilities for backward compatibility
20
+ export { detectCIProvider, truncateFileData, formatErrorOutput } from './utils.js';
17
21
 
18
22
  const require = createRequire(import.meta.url);
19
23
  const { version } = require('../package.json');
@@ -26,44 +30,6 @@ const { version } = require('../package.json');
26
30
  const log = (msg) => process.stderr.write(msg + '\n');
27
31
  const output = (msg) => process.stdout.write(msg + '\n');
28
32
 
29
- /**
30
- * Truncates file data (diff and content) for display purposes
31
- * @param {Object} file - File object with path, status, diff, content, etc.
32
- * @param {number} maxLength - Maximum length before truncation (default: 500)
33
- * @returns {Object} File object with truncated diff and content
34
- */
35
- export function truncateFileData(file, maxLength = 500) {
36
- return {
37
- path: file.path,
38
- status: file.status,
39
- ...(file.old_path && { old_path: file.old_path }),
40
- diff:
41
- file.diff.length > maxLength
42
- ? `${file.diff.substring(0, maxLength)}... [truncated ${file.diff.length - maxLength} chars]`
43
- : file.diff,
44
- content:
45
- file.content.length > maxLength
46
- ? `${file.content.substring(0, maxLength)}... [truncated ${file.content.length - maxLength} chars]`
47
- : file.content,
48
- };
49
- }
50
-
51
- /**
52
- * Formats error object for JSON output
53
- * @param {Error} error - Error object from axios or other source
54
- * @returns {Object} Formatted error output with success: false
55
- */
56
- export function formatErrorOutput(error) {
57
- return {
58
- success: false,
59
- error: error.message,
60
- ...(error.response && {
61
- status: error.response.status,
62
- data: error.response.data,
63
- }),
64
- };
65
- }
66
-
67
33
  /**
68
34
  * Ask for user confirmation before proceeding
69
35
  */
@@ -84,23 +50,6 @@ async function confirmAction(message) {
84
50
  });
85
51
  }
86
52
 
87
- /**
88
- * Detect CI provider from environment variables
89
- * @returns {string|null} Provider name or null if not detected
90
- */
91
- export function detectCIProvider() {
92
- if (process.env.GITHUB_TOKEN && process.env.GITHUB_REPOSITORY) {
93
- return 'github';
94
- }
95
- if (process.env.SYSTEM_ACCESSTOKEN && process.env.SYSTEM_PULLREQUEST_PULLREQUESTID) {
96
- return 'azure';
97
- }
98
- if (process.env.BITBUCKET_REPO_SLUG && process.env.BITBUCKET_PR_ID) {
99
- return 'bitbucket';
100
- }
101
- return null;
102
- }
103
-
104
53
  /**
105
54
  * Run the CI integration script to post comments
106
55
  * @param {string} provider - CI provider (github, azure, bitbucket)
package/src/utils.js ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Detect CI provider from environment variables
3
+ * @returns {string|null} Provider name or null if not detected
4
+ */
5
+ export function detectCIProvider() {
6
+ if (process.env.GITHUB_TOKEN && process.env.GITHUB_REPOSITORY) {
7
+ return 'github';
8
+ }
9
+ if (process.env.SYSTEM_ACCESSTOKEN && process.env.SYSTEM_PULLREQUEST_PULLREQUESTID) {
10
+ return 'azure';
11
+ }
12
+ if (process.env.BITBUCKET_REPO_SLUG && process.env.BITBUCKET_PR_ID) {
13
+ return 'bitbucket';
14
+ }
15
+ return null;
16
+ }
17
+
18
+ /**
19
+ * Truncates file data (diff and content) for display purposes
20
+ * @param {Object} file - File object with path, status, diff, content, etc.
21
+ * @param {number} maxLength - Maximum length before truncation (default: 500)
22
+ * @returns {Object} File object with truncated diff and content
23
+ */
24
+ export function truncateFileData(file, maxLength = 500) {
25
+ return {
26
+ path: file.path,
27
+ status: file.status,
28
+ ...(file.old_path && { old_path: file.old_path }),
29
+ diff:
30
+ file.diff.length > maxLength
31
+ ? `${file.diff.substring(0, maxLength)}... [truncated ${file.diff.length - maxLength} chars]`
32
+ : file.diff,
33
+ content:
34
+ file.content.length > maxLength
35
+ ? `${file.content.substring(0, maxLength)}... [truncated ${file.content.length - maxLength} chars]`
36
+ : file.content,
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Formats error object for JSON output
42
+ * @param {Error} error - Error object from axios or other source
43
+ * @returns {Object} Formatted error output with success: false
44
+ */
45
+ export function formatErrorOutput(error) {
46
+ return {
47
+ success: false,
48
+ error: error.message,
49
+ ...(error.response && {
50
+ status: error.response.status,
51
+ data: error.response.data,
52
+ }),
53
+ };
54
+ }