korekt-cli 0.9.2 → 0.9.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "korekt-cli",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
4
4
  "description": "AI-powered code review CLI - Keep your kode korekt",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/scripts/azure.sh CHANGED
@@ -428,9 +428,6 @@ if [ -n "$BUILD_BUILDID" ]; then
428
428
  echo "" >> "$COMMENT_FILE"
429
429
  fi
430
430
 
431
- echo "---" >> "$COMMENT_FILE"
432
- echo "*Powered by [korekt-cli](https://github.com/korekt-ai/korekt-cli)*" >> "$COMMENT_FILE"
433
-
434
431
  # Delete old summaries
435
432
  delete_old_summary_comments
436
433
 
@@ -398,9 +398,6 @@ if [ -n "${BITBUCKET_BUILD_NUMBER:-}" ]; then
398
398
  echo "" >> "$COMMENT_FILE"
399
399
  fi
400
400
 
401
- echo "---" >> "$COMMENT_FILE"
402
- echo "*Powered by [korekt-cli](https://github.com/korekt-ai/korekt-cli)*" >> "$COMMENT_FILE"
403
-
404
401
  # Delete old summaries
405
402
  delete_old_summary_comments
406
403
 
package/scripts/github.sh CHANGED
@@ -389,9 +389,6 @@ else
389
389
  fi
390
390
  fi
391
391
 
392
- echo "---" >> "$COMMENT_FILE"
393
- echo "*Powered by [korekt-cli](https://github.com/korekt-ai/korekt-cli)*" >> "$COMMENT_FILE"
394
-
395
392
  # Delete old summaries
396
393
  delete_old_summary_comments
397
394
 
package/src/git-logic.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { execa } from 'execa';
2
2
  import chalk from 'chalk';
3
- import { detectCIProvider, getPrUrl } from './utils.js';
3
+ import { detectCIProvider, getPrUrl, getSourceBranchFromCI } from './utils.js';
4
4
 
5
5
  /**
6
6
  * Truncate content to a maximum number of lines using "head and tail".
@@ -319,7 +319,16 @@ export async function runLocalReview(targetBranch = null, ignorePatterns = null)
319
319
  // 1. Get Repo URL, current branch name, and repository root
320
320
  const { stdout: repoUrl } = await execa('git', ['remote', 'get-url', 'origin']);
321
321
  const { stdout: sourceBranch } = await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD']);
322
- const branchName = sourceBranch.trim();
322
+ let branchName = sourceBranch.trim();
323
+
324
+ // Handle detached HEAD state (common in CI pipelines)
325
+ if (branchName === 'HEAD') {
326
+ const ciBranch = getSourceBranchFromCI();
327
+ if (ciBranch) {
328
+ branchName = ciBranch;
329
+ console.error(chalk.gray(`Detected source branch from CI: ${branchName}`));
330
+ }
331
+ }
323
332
 
324
333
  // Get the repository root directory - we'll run all git commands from there
325
334
  const { stdout: repoRoot } = await execa('git', ['rev-parse', '--show-toplevel']);
@@ -9,7 +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
+ import { detectCIProvider, getSourceBranchFromCI } from './utils.js';
13
13
 
14
14
  describe('parseNameStatus', () => {
15
15
  it('should correctly parse M, A, and D statuses', () => {
@@ -838,6 +838,7 @@ describe('is_ci flag in payload', () => {
838
838
  vi.mock('./utils.js', () => ({
839
839
  detectCIProvider: vi.fn(),
840
840
  getPrUrl: vi.fn().mockReturnValue(null),
841
+ getSourceBranchFromCI: vi.fn().mockReturnValue(null),
841
842
  }));
842
843
  });
843
844
 
@@ -1011,3 +1012,176 @@ describe('is_ci flag in payload', () => {
1011
1012
  expect(result.is_ci).toBe(false);
1012
1013
  });
1013
1014
  });
1015
+
1016
+ describe('getSourceBranchFromCI', () => {
1017
+ const originalEnv = process.env;
1018
+
1019
+ beforeEach(() => {
1020
+ vi.doUnmock('./utils.js');
1021
+ process.env = { ...originalEnv };
1022
+ // Clear any CI-related env vars
1023
+ delete process.env.SYSTEM_PULLREQUEST_SOURCEBRANCH;
1024
+ delete process.env.GITHUB_HEAD_REF;
1025
+ delete process.env.BITBUCKET_BRANCH;
1026
+ });
1027
+
1028
+ afterEach(() => {
1029
+ process.env = originalEnv;
1030
+ });
1031
+
1032
+ it('should return null when not in CI environment', async () => {
1033
+ const { getSourceBranchFromCI: fn } = await import('./utils.js');
1034
+ expect(fn()).toBeNull();
1035
+ });
1036
+
1037
+ it('should extract branch name from Azure DevOps with refs/heads/ prefix', async () => {
1038
+ process.env.SYSTEM_PULLREQUEST_SOURCEBRANCH = 'refs/heads/feature/my-branch';
1039
+ const { getSourceBranchFromCI: fn } = await import('./utils.js');
1040
+ expect(fn()).toBe('feature/my-branch');
1041
+ });
1042
+
1043
+ it('should handle Azure DevOps branch without refs/heads/ prefix', async () => {
1044
+ process.env.SYSTEM_PULLREQUEST_SOURCEBRANCH = 'feature/my-branch';
1045
+ const { getSourceBranchFromCI: fn } = await import('./utils.js');
1046
+ expect(fn()).toBe('feature/my-branch');
1047
+ });
1048
+
1049
+ it('should return GitHub head ref directly', async () => {
1050
+ process.env.GITHUB_HEAD_REF = 'feature/github-branch';
1051
+ const { getSourceBranchFromCI: fn } = await import('./utils.js');
1052
+ expect(fn()).toBe('feature/github-branch');
1053
+ });
1054
+
1055
+ it('should return Bitbucket branch directly', async () => {
1056
+ process.env.BITBUCKET_BRANCH = 'feature/bitbucket-branch';
1057
+ const { getSourceBranchFromCI: fn } = await import('./utils.js');
1058
+ expect(fn()).toBe('feature/bitbucket-branch');
1059
+ });
1060
+
1061
+ it('should prefer Azure DevOps over GitHub when both are set', async () => {
1062
+ process.env.SYSTEM_PULLREQUEST_SOURCEBRANCH = 'refs/heads/azure-branch';
1063
+ process.env.GITHUB_HEAD_REF = 'github-branch';
1064
+ const { getSourceBranchFromCI: fn } = await import('./utils.js');
1065
+ expect(fn()).toBe('azure-branch');
1066
+ });
1067
+ });
1068
+
1069
+ describe('runLocalReview - detached HEAD handling', () => {
1070
+ beforeEach(() => {
1071
+ vi.mock('execa');
1072
+ vi.mock('./utils.js', () => ({
1073
+ detectCIProvider: vi.fn().mockReturnValue(null),
1074
+ getPrUrl: vi.fn().mockReturnValue(null),
1075
+ getSourceBranchFromCI: vi.fn(),
1076
+ }));
1077
+ vi.spyOn(console, 'error').mockImplementation(() => {});
1078
+ });
1079
+
1080
+ afterEach(() => {
1081
+ vi.restoreAllMocks();
1082
+ });
1083
+
1084
+ it('should use CI environment variable when git returns HEAD (detached HEAD state)', async () => {
1085
+ // Use the statically imported getSourceBranchFromCI (line 12)
1086
+ // which is automatically mocked by vi.mock() in beforeEach
1087
+ vi.mocked(getSourceBranchFromCI).mockReturnValue('feature/ci-branch');
1088
+
1089
+ vi.mocked(execa).mockImplementation(async (cmd, args) => {
1090
+ const command = [cmd, ...args].join(' ');
1091
+
1092
+ if (command.includes('remote get-url origin')) {
1093
+ return { stdout: 'https://github.com/user/repo.git' };
1094
+ }
1095
+ // Simulate detached HEAD state
1096
+ if (command.includes('rev-parse --abbrev-ref HEAD')) {
1097
+ return { stdout: 'HEAD' };
1098
+ }
1099
+ if (command.includes('rev-parse --show-toplevel')) {
1100
+ return { stdout: '/path/to/repo' };
1101
+ }
1102
+ if (command.includes('rev-parse --verify main')) {
1103
+ return { stdout: 'commit-hash' };
1104
+ }
1105
+ if (command === 'git fetch origin main') {
1106
+ return { stdout: '' };
1107
+ }
1108
+ if (command.includes('merge-base origin/main HEAD')) {
1109
+ return { stdout: 'abc123' };
1110
+ }
1111
+ if (command.includes('log --no-merges --pretty=%B---EOC---')) {
1112
+ return { stdout: 'feat: add feature---EOC---' };
1113
+ }
1114
+ if (command.includes('log --no-merges --format=%ae|%an')) {
1115
+ return { stdout: 'user@example.com|User Name' };
1116
+ }
1117
+ if (command.includes('diff --name-status')) {
1118
+ return { stdout: 'M\tfile.js' };
1119
+ }
1120
+ if (command.includes('diff -U15')) {
1121
+ return { stdout: 'diff content' };
1122
+ }
1123
+ if (command.includes('show abc123:file.js')) {
1124
+ return { stdout: 'original content' };
1125
+ }
1126
+
1127
+ return { stdout: '' };
1128
+ });
1129
+
1130
+ const result = await runLocalReview('main');
1131
+
1132
+ expect(result).toBeDefined();
1133
+ expect(result.source_branch).toBe('feature/ci-branch');
1134
+ expect(getSourceBranchFromCI).toHaveBeenCalled();
1135
+ });
1136
+
1137
+ it('should keep HEAD as source_branch when in detached HEAD but no CI env var', async () => {
1138
+ // getSourceBranchFromCI returns null by default (set in beforeEach mock)
1139
+ // so no need to explicitly set it here
1140
+
1141
+ vi.mocked(execa).mockImplementation(async (cmd, args) => {
1142
+ const command = [cmd, ...args].join(' ');
1143
+
1144
+ if (command.includes('remote get-url origin')) {
1145
+ return { stdout: 'https://github.com/user/repo.git' };
1146
+ }
1147
+ // Simulate detached HEAD state
1148
+ if (command.includes('rev-parse --abbrev-ref HEAD')) {
1149
+ return { stdout: 'HEAD' };
1150
+ }
1151
+ if (command.includes('rev-parse --show-toplevel')) {
1152
+ return { stdout: '/path/to/repo' };
1153
+ }
1154
+ if (command.includes('rev-parse --verify main')) {
1155
+ return { stdout: 'commit-hash' };
1156
+ }
1157
+ if (command === 'git fetch origin main') {
1158
+ return { stdout: '' };
1159
+ }
1160
+ if (command.includes('merge-base origin/main HEAD')) {
1161
+ return { stdout: 'abc123' };
1162
+ }
1163
+ if (command.includes('log --no-merges --pretty=%B---EOC---')) {
1164
+ return { stdout: 'feat: add feature---EOC---' };
1165
+ }
1166
+ if (command.includes('log --no-merges --format=%ae|%an')) {
1167
+ return { stdout: 'user@example.com|User Name' };
1168
+ }
1169
+ if (command.includes('diff --name-status')) {
1170
+ return { stdout: 'M\tfile.js' };
1171
+ }
1172
+ if (command.includes('diff -U15')) {
1173
+ return { stdout: 'diff content' };
1174
+ }
1175
+ if (command.includes('show abc123:file.js')) {
1176
+ return { stdout: 'original content' };
1177
+ }
1178
+
1179
+ return { stdout: '' };
1180
+ });
1181
+
1182
+ const result = await runLocalReview('main');
1183
+
1184
+ expect(result).toBeDefined();
1185
+ expect(result.source_branch).toBe('HEAD');
1186
+ });
1187
+ });
package/src/utils.js CHANGED
@@ -15,6 +15,27 @@ export function detectCIProvider() {
15
15
  return null;
16
16
  }
17
17
 
18
+ /**
19
+ * Get source branch name from CI environment variables
20
+ * Useful when git is in detached HEAD state (common in CI pipelines)
21
+ * @returns {string|null} Branch name or null if not in CI context
22
+ */
23
+ export function getSourceBranchFromCI() {
24
+ // Azure DevOps: refs/heads/feature/my-branch -> feature/my-branch
25
+ if (process.env.SYSTEM_PULLREQUEST_SOURCEBRANCH) {
26
+ return process.env.SYSTEM_PULLREQUEST_SOURCEBRANCH.replace(/^refs\/heads\//, '');
27
+ }
28
+ // GitHub Actions: already just the branch name
29
+ if (process.env.GITHUB_HEAD_REF) {
30
+ return process.env.GITHUB_HEAD_REF;
31
+ }
32
+ // Bitbucket Pipelines: already just the branch name
33
+ if (process.env.BITBUCKET_BRANCH) {
34
+ return process.env.BITBUCKET_BRANCH;
35
+ }
36
+ return null;
37
+ }
38
+
18
39
  /**
19
40
  * Build PR URL from CI environment variables
20
41
  * @returns {string|null} Full PR URL or null if not in CI PR context