korekt-cli 0.5.0 → 0.7.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/README.md CHANGED
@@ -12,7 +12,7 @@ AI-powered code review CLI - Keep your kode korekt
12
12
 
13
13
  * **AI-Powered Analysis**: Get instant, intelligent code reviews with severity levels, categories, and actionable suggestions
14
14
  * **Local Git Integration**: Works with committed changes, staged changes, and unstaged modifications
15
- * **Ticket System Integration**: Automatically extracts ticket IDs from branch names and commit messages (Jira & Azure DevOps)
15
+ * **Ticket Context Enrichment**: Server-side ticket extraction from branch names and commit messages (Jira & Azure DevOps)
16
16
  * **Beautiful Output**: Color-coded issues with severity indicators, file locations, and suggested fixes
17
17
  * **Ultra-Fast**: Short command syntax (`kk`) for maximum developer efficiency
18
18
 
@@ -28,7 +28,7 @@ Configure the CLI with your API credentials:
28
28
 
29
29
  ```bash
30
30
  kk config --key YOUR_API_KEY
31
- kk config --endpoint https://api.korekt.ai/review/local
31
+ kk config --endpoint https://api.korekt.ai/api/review
32
32
  ```
33
33
 
34
34
  Run your first review:
@@ -43,8 +43,7 @@ kk stg
43
43
  # Review only unstaged changes
44
44
  kk diff
45
45
 
46
- # Review all uncommitted changes (staged + unstaged)
47
- kk all
46
+
48
47
  ```
49
48
 
50
49
  ## Usage
@@ -56,10 +55,7 @@ kk all
56
55
  kk config --key YOUR_API_KEY
57
56
 
58
57
  # Set API endpoint
59
- kk config --endpoint https://api.korekt.ai/review/local
60
-
61
- # Set default ticket system (jira or ado)
62
- kk config --ticket-system jira
58
+ kk config --endpoint https://api.korekt.ai/api/review
63
59
 
64
60
  # Show current configuration
65
61
  kk config --show
@@ -74,9 +70,6 @@ kk review
74
70
  # Review against specific branch
75
71
  kk review main
76
72
 
77
- # Review with ticket system override
78
- kk review main --ticket-system ado
79
-
80
73
  # Review with ignored files
81
74
  kk review main --ignore "*.lock" "dist/*"
82
75
 
@@ -93,16 +86,9 @@ kk stg
93
86
  # Review unstaged changes only
94
87
  kk diff
95
88
 
96
- # Review all uncommitted changes
97
- kk all
98
-
99
- # Include untracked files
100
- kk all --untracked
101
-
102
89
  # JSON output works with all review commands
103
90
  kk stg --json
104
91
  kk diff --json
105
- kk all --json
106
92
  ```
107
93
 
108
94
  ### Alternative Command
@@ -119,8 +105,7 @@ You can also configure using environment variables:
119
105
 
120
106
  ```bash
121
107
  export KOREKT_API_KEY="your-api-key"
122
- export KOREKT_API_ENDPOINT="https://api.korekt.ai/review/local"
123
- export KOREKT_TICKET_SYSTEM="jira"
108
+ export KOREKT_API_ENDPOINT="https://api.korekt.ai/api/review"
124
109
  ```
125
110
 
126
111
  Note: Config file takes precedence over environment variables.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "korekt-cli",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "AI-powered code review CLI - Keep your kode korekt",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/config.js CHANGED
@@ -35,7 +35,7 @@ export function getApiEndpoint() {
35
35
  const configEndpoint = config.get('apiEndpoint');
36
36
  if (configEndpoint) return configEndpoint;
37
37
 
38
- return process.env.KOREKT_API_ENDPOINT || 'https://api.korekt.ai/api/review/local';
38
+ return process.env.KOREKT_API_ENDPOINT || 'https://api.korekt.ai/api/review';
39
39
  }
40
40
 
41
41
  /**
@@ -45,24 +45,6 @@ export function setApiEndpoint(endpoint) {
45
45
  config.set('apiEndpoint', endpoint);
46
46
  }
47
47
 
48
- /**
49
- * Get the ticket system from config or environment
50
- * Priority: 1) config store, 2) .env file
51
- */
52
- export function getTicketSystem() {
53
- const configTicketSystem = config.get('ticketSystem');
54
- if (configTicketSystem) return configTicketSystem;
55
-
56
- return process.env.KOREKT_TICKET_SYSTEM || null;
57
- }
58
-
59
- /**
60
- * Set the ticket system in config store
61
- */
62
- export function setTicketSystem(system) {
63
- config.set('ticketSystem', system);
64
- }
65
-
66
48
  /**
67
49
  * Get all configuration
68
50
  */
@@ -70,6 +52,5 @@ export function getConfig() {
70
52
  return {
71
53
  apiKey: getApiKey(),
72
54
  apiEndpoint: getApiEndpoint(),
73
- ticketSystem: getTicketSystem(),
74
55
  };
75
56
  }
package/src/git-logic.js CHANGED
@@ -1,7 +1,5 @@
1
1
  import { execa } from 'execa';
2
2
  import chalk from 'chalk';
3
- import fs from 'fs';
4
- import path from 'path';
5
3
 
6
4
  /**
7
5
  * Truncate content to a maximum number of lines using "head and tail".
@@ -136,16 +134,11 @@ export function parseNameStatus(output) {
136
134
  }
137
135
 
138
136
  /**
139
- * Analyze uncommitted changes (staged, unstaged, or all)
140
- * @param {string} mode - 'staged', 'unstaged', or 'all'
141
- * @param {string|null} ticketSystem - The ticket system to use (jira or ado), or null to skip ticket extraction
137
+ * Analyze uncommitted changes (staged or unstaged)
138
+ * @param {string} mode - 'staged' or 'unstaged'
142
139
  * @returns {Object|null} - The payload object ready for API submission, or null on error
143
140
  */
144
- export async function runUncommittedReview(
145
- mode = 'all',
146
- _ticketSystem = null,
147
- includeUntracked = false
148
- ) {
141
+ export async function runUncommittedReview(mode = 'unstaged') {
149
142
  try {
150
143
  // 1. Get Repo URL, current branch name, and repository root
151
144
  const { stdout: repoUrl } = await execa('git', ['remote', 'get-url', 'origin']);
@@ -167,67 +160,23 @@ export async function runUncommittedReview(
167
160
  if (mode === 'staged') {
168
161
  nameStatusOutput = await git('diff', '--cached', '--name-status');
169
162
  console.error(chalk.gray('Analyzing staged changes...'));
170
- } else if (mode === 'unstaged') {
163
+ } else {
171
164
  nameStatusOutput = await git('diff', '--name-status');
172
165
  console.error(chalk.gray('Analyzing unstaged changes...'));
173
- } else {
174
- // mode === 'all': combine staged and unstaged
175
- const staged = await git('diff', '--cached', '--name-status');
176
- const unstaged = await git('diff', '--name-status');
177
- nameStatusOutput = [staged, unstaged].filter(Boolean).join('\n');
178
- console.error(chalk.gray('Analyzing all uncommitted changes...'));
179
166
  }
180
167
 
181
168
  const fileList = parseNameStatus(nameStatusOutput);
182
169
  const changedFiles = [];
183
170
 
184
- // Handle untracked files if requested
185
- if (includeUntracked) {
186
- console.error(chalk.gray('Analyzing untracked files...'));
187
- const untrackedFilesOutput = await git('ls-files', '--others', '--exclude-standard');
188
- const untrackedFiles = untrackedFilesOutput.split('\n').filter(Boolean);
189
-
190
- for (const file of untrackedFiles) {
191
- const fullPath = path.join(repoRootPath, file);
192
- const content = fs.readFileSync(fullPath, 'utf-8');
193
- const diff = content
194
- .split('\n')
195
- .map((line) => `+${line}`)
196
- .join('\n');
197
- changedFiles.push({
198
- path: file,
199
- status: 'A', // Untracked files are always additions
200
- diff: diff,
201
- content: '', // No old content
202
- });
203
- // Add to fileList to prevent duplication if it's also in nameStatusOutput (edge case)
204
- fileList.push({ status: 'A', path: file, oldPath: file });
205
- }
206
- }
207
-
208
- // Deduplicate file list before processing diffs
209
- const processedPaths = new Set(changedFiles.map((f) => f.path));
210
- const uniqueFileList = fileList.filter((file) => !processedPaths.has(file.path));
211
-
212
- for (const file of uniqueFileList) {
171
+ for (const file of fileList) {
213
172
  const { status, path, oldPath } = file;
214
173
 
215
174
  // Get diff for this file
216
175
  let diff;
217
176
  if (mode === 'staged') {
218
177
  diff = await git('diff', '--cached', '-U15', '--', path);
219
- } else if (mode === 'unstaged') {
220
- diff = await git('diff', '-U15', '--', path);
221
178
  } else {
222
- // For 'all', try staged first, then unstaged
223
- try {
224
- diff = await git('diff', '--cached', '-U15', '--', path);
225
- if (!diff) {
226
- diff = await git('diff', '-U15', '--', path);
227
- }
228
- } catch {
229
- diff = await git('diff', '-U15', '--', path);
230
- }
179
+ diff = await git('diff', '-U15', '--', path);
231
180
  }
232
181
 
233
182
  // Get current content from HEAD (before changes)
@@ -280,18 +229,62 @@ export async function runUncommittedReview(
280
229
  }
281
230
  }
282
231
 
232
+ /**
233
+ * Extract contributors from git commits in a range
234
+ * Returns the author (most commits) and full list of contributors
235
+ * @param {string} diffRange - The git range to analyze (e.g., "abc123..HEAD")
236
+ * @param {string} repoRootPath - The repository root directory
237
+ * @returns {Object} - { author_email, author_name, contributors[] }
238
+ */
239
+ export async function getContributors(diffRange, repoRootPath) {
240
+ try {
241
+ // Get all commit authors with email and name
242
+ const { stdout: authorOutput } = await execa('git', ['log', '--format=%ae|%an', diffRange], {
243
+ cwd: repoRootPath,
244
+ });
245
+
246
+ if (!authorOutput.trim()) {
247
+ return { author_email: null, author_name: null, contributors: [] };
248
+ }
249
+
250
+ const lines = authorOutput.trim().split('\n').filter(Boolean);
251
+
252
+ // Count commits per email and track name
253
+ const contributorMap = new Map();
254
+ for (const line of lines) {
255
+ const [email, name] = line.split('|');
256
+ if (!email) continue;
257
+
258
+ if (!contributorMap.has(email)) {
259
+ contributorMap.set(email, { email, name: name || email, commits: 0 });
260
+ }
261
+ contributorMap.get(email).commits++;
262
+ }
263
+
264
+ // Convert to array and sort by commits (descending)
265
+ const contributors = Array.from(contributorMap.values()).sort((a, b) => b.commits - a.commits);
266
+
267
+ // Author = most commits
268
+ const author = contributors[0] || null;
269
+
270
+ return {
271
+ author_email: author?.email || null,
272
+ author_name: author?.name || null,
273
+ contributors,
274
+ };
275
+ } catch (error) {
276
+ console.warn(chalk.yellow('Could not extract contributors:'), error.message);
277
+ return { author_email: null, author_name: null, contributors: [] };
278
+ }
279
+ }
280
+
283
281
  /**
284
282
  * Main function to analyze local git changes and prepare review payload
285
283
  * @param {string|null} targetBranch - The branch to compare against. If null, uses git reflog to find fork point.
286
- * @param {string|null} ticketSystem - The ticket system to use (jira or ado), or null to skip ticket extraction
287
284
  * @param {string[]|null} ignorePatterns - Array of glob patterns to ignore files
288
285
  * @returns {Object|null} - The payload object ready for API submission, or null on error
289
286
  */
290
- export async function runLocalReview(
291
- targetBranch = null,
292
- _ticketSystem = null,
293
- ignorePatterns = null
294
- ) {
287
+ export async function runLocalReview(targetBranch = null, ignorePatterns = null) {
295
288
  try {
296
289
  // 1. Get Repo URL, current branch name, and repository root
297
290
  const { stdout: repoUrl } = await execa('git', ['remote', 'get-url', 'origin']);
@@ -486,12 +479,21 @@ export async function runLocalReview(
486
479
  });
487
480
  }
488
481
 
489
- // 5. Assemble the final payload
482
+ // 5. Get contributors from commits
483
+ const { author_email, author_name, contributors } = await getContributors(
484
+ diffRange,
485
+ repoRootPath
486
+ );
487
+
488
+ // 6. Assemble the final payload
490
489
  return {
491
490
  repo_url: normalizeRepoUrl(repoUrl.trim()),
492
491
  commit_messages: commitMessages,
493
492
  changed_files: changedFiles,
494
493
  source_branch: branchName,
494
+ author_email,
495
+ author_name,
496
+ contributors,
495
497
  };
496
498
  } catch (error) {
497
499
  console.error(chalk.red('Failed to run local review analysis:'), error.message);
@@ -6,6 +6,7 @@ import {
6
6
  truncateContent,
7
7
  normalizeRepoUrl,
8
8
  shouldIgnoreFile,
9
+ getContributors,
9
10
  } from './git-logic.js';
10
11
  import { execa } from 'execa';
11
12
 
@@ -82,7 +83,7 @@ describe('runUncommittedReview', () => {
82
83
  throw new Error(`Unmocked command: ${command}`);
83
84
  });
84
85
 
85
- const result = await runUncommittedReview('staged', null);
86
+ const result = await runUncommittedReview('staged');
86
87
 
87
88
  expect(result).toBeDefined();
88
89
  expect(result.repo_url).toBe('https://github.com/user/repo'); // Normalized (no .git)
@@ -119,56 +120,13 @@ describe('runUncommittedReview', () => {
119
120
  throw new Error(`Unmocked command: ${command}`);
120
121
  });
121
122
 
122
- const result = await runUncommittedReview('unstaged', null);
123
+ const result = await runUncommittedReview('unstaged');
123
124
 
124
125
  expect(result).toBeDefined();
125
126
  expect(result.source_branch).toBe('feature-branch');
126
127
  expect(result.changed_files).toHaveLength(1);
127
128
  });
128
129
 
129
- it('should analyze all uncommitted changes', async () => {
130
- vi.mocked(execa).mockImplementation(async (cmd, args) => {
131
- const command = [cmd, ...args].join(' ');
132
-
133
- if (command.includes('remote get-url origin')) {
134
- return { stdout: 'https://github.com/user/repo.git' };
135
- }
136
- if (command.includes('rev-parse --abbrev-ref HEAD')) {
137
- return { stdout: 'feature-branch' };
138
- }
139
- if (command.includes('rev-parse --show-toplevel')) {
140
- return { stdout: '/fake/repo/path' };
141
- }
142
- if (command.includes('diff --cached --name-status')) {
143
- return { stdout: 'M\tstaged.js' };
144
- }
145
- if (command === 'git diff --name-status') {
146
- return { stdout: 'M\tunstaged.js' };
147
- }
148
- if (command.includes('diff --cached -U15 -- staged.js')) {
149
- return { stdout: 'diff staged' };
150
- }
151
- if (command.includes('diff -U15 -- unstaged.js')) {
152
- return { stdout: 'diff unstaged' };
153
- }
154
- if (command.includes('show HEAD:staged.js')) {
155
- return { stdout: 'staged old content' };
156
- }
157
- if (command.includes('show HEAD:unstaged.js')) {
158
- return { stdout: 'unstaged old content' };
159
- }
160
-
161
- throw new Error(`Unmocked command: ${command}`);
162
- });
163
-
164
- const result = await runUncommittedReview('all', null);
165
-
166
- expect(result).toBeDefined();
167
- expect(result.changed_files).toHaveLength(2);
168
- expect(result.changed_files[0].path).toBe('staged.js');
169
- expect(result.changed_files[1].path).toBe('unstaged.js');
170
- });
171
-
172
130
  it('should return null when no changes found', async () => {
173
131
  vi.mocked(execa).mockImplementation(async (cmd, args) => {
174
132
  const command = [cmd, ...args].join(' ');
@@ -192,7 +150,7 @@ describe('runUncommittedReview', () => {
192
150
  throw new Error(`Unmocked command: ${command}`);
193
151
  });
194
152
 
195
- const result = await runUncommittedReview('all', null);
153
+ const result = await runUncommittedReview('staged');
196
154
 
197
155
  expect(result).toBeNull();
198
156
  });
@@ -346,7 +304,7 @@ describe('runLocalReview - fork point detection', () => {
346
304
  throw new Error(`Unmocked command: ${command}`);
347
305
  });
348
306
 
349
- const result = await runLocalReview(null, 'jira');
307
+ const result = await runLocalReview(null);
350
308
 
351
309
  expect(result).toBeDefined();
352
310
  expect(result.source_branch).toBe('feature-branch');
@@ -396,7 +354,7 @@ describe('runLocalReview - fork point detection', () => {
396
354
  throw new Error(`Unmocked command: ${command}`);
397
355
  });
398
356
 
399
- const result = await runLocalReview('main', 'jira');
357
+ const result = await runLocalReview('main');
400
358
 
401
359
  expect(result).toBeDefined();
402
360
 
@@ -571,3 +529,138 @@ describe('shouldIgnoreFile', () => {
571
529
  expect(shouldIgnoreFile('file.js', pattern)).toBe(false);
572
530
  });
573
531
  });
532
+
533
+ describe('getContributors', () => {
534
+ beforeEach(() => {
535
+ vi.mock('execa');
536
+ });
537
+
538
+ afterEach(() => {
539
+ vi.restoreAllMocks();
540
+ });
541
+
542
+ it('should extract single contributor with commit count', async () => {
543
+ vi.mocked(execa).mockImplementation(async (cmd, args) => {
544
+ const command = [cmd, ...args].join(' ');
545
+ if (command.includes('log --format=%ae|%an')) {
546
+ return {
547
+ stdout: 'john@example.com|John Doe\njohn@example.com|John Doe\njohn@example.com|John Doe',
548
+ };
549
+ }
550
+ throw new Error(`Unmocked command: ${command}`);
551
+ });
552
+
553
+ const result = await getContributors('abc123..HEAD', '/path/to/repo');
554
+
555
+ expect(result.author_email).toBe('john@example.com');
556
+ expect(result.author_name).toBe('John Doe');
557
+ expect(result.contributors).toHaveLength(1);
558
+ expect(result.contributors[0]).toEqual({
559
+ email: 'john@example.com',
560
+ name: 'John Doe',
561
+ commits: 3,
562
+ });
563
+ });
564
+
565
+ it('should identify author as contributor with most commits', async () => {
566
+ vi.mocked(execa).mockImplementation(async (cmd, args) => {
567
+ const command = [cmd, ...args].join(' ');
568
+ if (command.includes('log --format=%ae|%an')) {
569
+ return {
570
+ stdout: [
571
+ 'alice@example.com|Alice Smith',
572
+ 'alice@example.com|Alice Smith',
573
+ 'alice@example.com|Alice Smith',
574
+ 'alice@example.com|Alice Smith',
575
+ 'alice@example.com|Alice Smith',
576
+ 'bob@example.com|Bob Jones',
577
+ 'bob@example.com|Bob Jones',
578
+ 'charlie@example.com|Charlie Brown',
579
+ ].join('\n'),
580
+ };
581
+ }
582
+ throw new Error(`Unmocked command: ${command}`);
583
+ });
584
+
585
+ const result = await getContributors('abc123..HEAD', '/path/to/repo');
586
+
587
+ expect(result.author_email).toBe('alice@example.com');
588
+ expect(result.author_name).toBe('Alice Smith');
589
+ expect(result.contributors).toHaveLength(3);
590
+ expect(result.contributors[0].commits).toBe(5); // Alice - most commits
591
+ expect(result.contributors[1].commits).toBe(2); // Bob
592
+ expect(result.contributors[2].commits).toBe(1); // Charlie
593
+ });
594
+
595
+ it('should handle empty commit range', async () => {
596
+ vi.mocked(execa).mockImplementation(async (cmd, args) => {
597
+ const command = [cmd, ...args].join(' ');
598
+ if (command.includes('log --format=%ae|%an')) {
599
+ return { stdout: '' };
600
+ }
601
+ throw new Error(`Unmocked command: ${command}`);
602
+ });
603
+
604
+ const result = await getContributors('abc123..HEAD', '/path/to/repo');
605
+
606
+ expect(result.author_email).toBeNull();
607
+ expect(result.author_name).toBeNull();
608
+ expect(result.contributors).toEqual([]);
609
+ });
610
+
611
+ it('should handle git command errors gracefully', async () => {
612
+ vi.mocked(execa).mockImplementation(async () => {
613
+ throw new Error('Git error');
614
+ });
615
+
616
+ const result = await getContributors('abc123..HEAD', '/path/to/repo');
617
+
618
+ expect(result.author_email).toBeNull();
619
+ expect(result.author_name).toBeNull();
620
+ expect(result.contributors).toEqual([]);
621
+ });
622
+
623
+ it('should handle missing name in git log', async () => {
624
+ vi.mocked(execa).mockImplementation(async (cmd, args) => {
625
+ const command = [cmd, ...args].join(' ');
626
+ if (command.includes('log --format=%ae|%an')) {
627
+ return { stdout: 'user@example.com|' };
628
+ }
629
+ throw new Error(`Unmocked command: ${command}`);
630
+ });
631
+
632
+ const result = await getContributors('abc123..HEAD', '/path/to/repo');
633
+
634
+ expect(result.author_email).toBe('user@example.com');
635
+ expect(result.author_name).toBe('user@example.com'); // Falls back to email
636
+ expect(result.contributors[0].name).toBe('user@example.com');
637
+ });
638
+
639
+ it('should sort contributors by commit count descending', async () => {
640
+ vi.mocked(execa).mockImplementation(async (cmd, args) => {
641
+ const command = [cmd, ...args].join(' ');
642
+ if (command.includes('log --format=%ae|%an')) {
643
+ return {
644
+ stdout: [
645
+ 'c@example.com|C User',
646
+ 'a@example.com|A User',
647
+ 'a@example.com|A User',
648
+ 'a@example.com|A User',
649
+ 'b@example.com|B User',
650
+ 'b@example.com|B User',
651
+ ].join('\n'),
652
+ };
653
+ }
654
+ throw new Error(`Unmocked command: ${command}`);
655
+ });
656
+
657
+ const result = await getContributors('abc123..HEAD', '/path/to/repo');
658
+
659
+ expect(result.contributors[0].email).toBe('a@example.com');
660
+ expect(result.contributors[0].commits).toBe(3);
661
+ expect(result.contributors[1].email).toBe('b@example.com');
662
+ expect(result.contributors[1].commits).toBe(2);
663
+ expect(result.contributors[2].email).toBe('c@example.com');
664
+ expect(result.contributors[2].commits).toBe(1);
665
+ });
666
+ });
package/src/index.js CHANGED
@@ -10,14 +10,7 @@ import { readFileSync } from 'fs';
10
10
  import { join, dirname } from 'path';
11
11
  import { fileURLToPath } from 'url';
12
12
  import { runLocalReview } from './git-logic.js';
13
- import {
14
- getApiKey,
15
- setApiKey,
16
- getApiEndpoint,
17
- setApiEndpoint,
18
- getTicketSystem,
19
- setTicketSystem,
20
- } from './config.js';
13
+ import { getApiKey, setApiKey, getApiEndpoint, setApiEndpoint } from './config.js';
21
14
  import { formatReviewOutput } from './formatter.js';
22
15
 
23
16
  const require = createRequire(import.meta.url);
@@ -99,18 +92,15 @@ Examples:
99
92
  $ kk review main Review changes against main branch
100
93
  $ kk stg --dry-run Preview staged changes review
101
94
  $ kk diff Review unstaged changes
102
- $ kk all Review all uncommitted changes
103
95
  $ kk review main --json Output raw JSON (for CI/CD integration)
104
96
 
105
97
  Common Options:
106
98
  --dry-run Show payload without sending to API
107
99
  --json Output raw API response as JSON
108
- --ticket-system <system> Use specific ticket system (jira or ado)
109
100
 
110
101
  Configuration:
111
102
  $ kk config --key YOUR_KEY
112
- $ kk config --endpoint https://api.korekt.ai/review/local
113
- $ kk config --ticket-system ado
103
+ $ kk config --endpoint https://api.korekt.ai/api/review
114
104
 
115
105
  CI/CD Integration:
116
106
  $ kk get-script github Output GitHub Actions integration script
@@ -126,7 +116,6 @@ program
126
116
  '[target-branch]',
127
117
  'The branch to compare against (e.g., main, develop). If not specified, auto-detects fork point.'
128
118
  )
129
- .option('--ticket-system <system>', 'Ticket system to use (jira or ado)')
130
119
  .option('--dry-run', 'Show payload without sending to API')
131
120
  .option(
132
121
  '--ignore <patterns...>',
@@ -153,30 +142,14 @@ program
153
142
  process.exit(1);
154
143
  }
155
144
 
156
- // Determine ticket system to use (or null if not configured)
157
- const ticketSystem = options.ticketSystem || getTicketSystem() || null;
158
-
159
- // Validate ticket system
160
- if (ticketSystem && !['jira', 'ado'].includes(ticketSystem.toLowerCase())) {
161
- log(chalk.red(`Invalid ticket system: ${ticketSystem}`));
162
- log(chalk.gray('Valid options: jira, ado'));
163
- process.exit(1);
164
- }
165
-
166
145
  // Gather all data using our git logic module
167
- const payload = await runLocalReview(targetBranch, ticketSystem, options.ignore);
146
+ const payload = await runLocalReview(targetBranch, options.ignore);
168
147
 
169
148
  if (!payload) {
170
149
  log(chalk.red('Could not proceed with review due to errors during analysis.'));
171
150
  process.exit(1);
172
151
  }
173
152
 
174
- // Add ticket system to payload if specified
175
- if (ticketSystem) {
176
- payload.ticket_system = ticketSystem;
177
- log(chalk.gray(`Using ticket system: ${ticketSystem}`));
178
- }
179
-
180
153
  // If dry-run, just show the payload and exit
181
154
  if (options.dryRun) {
182
155
  log(chalk.yellow('\nšŸ“‹ Dry Run - Payload that would be sent:\n'));
@@ -200,7 +173,7 @@ program
200
173
  log(` Commits: ${chalk.cyan(payload.commit_messages.length)}`);
201
174
  log(` Files: ${chalk.cyan(payload.changed_files.length)}\n`);
202
175
 
203
- log(chalk.bold(' Files to review:'));
176
+ log(chalk.bold(` ${payload.changed_files.length} files to review:`));
204
177
  payload.changed_files.forEach((file) => {
205
178
  const statusColor =
206
179
  {
@@ -276,7 +249,6 @@ program
276
249
  .command('review-staged')
277
250
  .aliases(['stg', 'staged', 'cached'])
278
251
  .description('Review staged changes (git diff --cached)')
279
- .option('--ticket-system <system>', 'Ticket system to use (jira or ado)')
280
252
  .option('--dry-run', 'Show payload without sending to API')
281
253
  .option('--json', 'Output raw API response as JSON')
282
254
  .action(async (options) => {
@@ -288,28 +260,13 @@ program
288
260
  .command('review-unstaged')
289
261
  .alias('diff')
290
262
  .description('Review unstaged changes (git diff)')
291
- .option('--ticket-system <system>', 'Ticket system to use (jira or ado)')
292
263
  .option('--dry-run', 'Show payload without sending to API')
293
- .option('--untracked', 'Include untracked files in the review')
294
264
  .option('--json', 'Output raw API response as JSON')
295
265
  .action(async (options) => {
296
266
  log(chalk.blue.bold('šŸš€ Reviewing unstaged changes...'));
297
267
  await reviewUncommitted('unstaged', options);
298
268
  });
299
269
 
300
- program
301
- .command('review-all-uncommitted')
302
- .alias('all')
303
- .description('Review all uncommitted changes (staged + unstaged)')
304
- .option('--ticket-system <system>', 'Ticket system to use (jira or ado)')
305
- .option('--dry-run', 'Show payload without sending to API')
306
- .option('--untracked', 'Include untracked files in the review')
307
- .option('--json', 'Output raw API response as JSON')
308
- .action(async (options) => {
309
- log(chalk.blue.bold('šŸš€ Reviewing all uncommitted changes...'));
310
- await reviewUncommitted('all', options);
311
- });
312
-
313
270
  async function reviewUncommitted(mode, options) {
314
271
  const apiKey = getApiKey();
315
272
  if (!apiKey) {
@@ -325,27 +282,14 @@ async function reviewUncommitted(mode, options) {
325
282
  process.exit(1);
326
283
  }
327
284
 
328
- const ticketSystem = options.ticketSystem || getTicketSystem() || null;
329
-
330
- if (ticketSystem && !['jira', 'ado'].includes(ticketSystem.toLowerCase())) {
331
- log(chalk.red(`Invalid ticket system: ${ticketSystem}`));
332
- log(chalk.gray('Valid options: jira, ado'));
333
- process.exit(1);
334
- }
335
-
336
285
  const { runUncommittedReview } = await import('./git-logic.js');
337
- const payload = await runUncommittedReview(mode, ticketSystem, options.untracked);
286
+ const payload = await runUncommittedReview(mode);
338
287
 
339
288
  if (!payload) {
340
289
  log(chalk.red('No changes found or error occurred during analysis.'));
341
290
  process.exit(1);
342
291
  }
343
292
 
344
- if (ticketSystem) {
345
- payload.ticket_system = ticketSystem;
346
- log(chalk.gray(`Using ticket system: ${ticketSystem}`));
347
- }
348
-
349
293
  if (options.dryRun) {
350
294
  log(chalk.yellow('\nšŸ“‹ Dry Run - Payload that would be sent:\n'));
351
295
 
@@ -364,7 +308,7 @@ async function reviewUncommitted(mode, options) {
364
308
  if (!options.json) {
365
309
  log(chalk.yellow('\nšŸ“‹ Ready to submit uncommitted changes for review:\n'));
366
310
  log(chalk.gray(' Comparing against HEAD (last commit)\n'));
367
- log(chalk.bold(' Files to review:'));
311
+ log(chalk.bold(` ${payload.changed_files.length} files to review:`));
368
312
  payload.changed_files.forEach((file) => {
369
313
  const statusColor =
370
314
  {
@@ -440,22 +384,17 @@ program
440
384
  .description('Configure API settings')
441
385
  .option('--key <key>', 'Your API key')
442
386
  .option('--endpoint <endpoint>', 'Your API endpoint URL')
443
- .option('--ticket-system <system>', 'Ticket system (jira, ado)')
444
387
  .option('--show', 'Show current configuration')
445
388
  .action((options) => {
446
389
  // Show current config if --show flag is used
447
390
  if (options.show) {
448
391
  const apiKey = getApiKey();
449
392
  const apiEndpoint = getApiEndpoint();
450
- const ticketSystem = getTicketSystem();
451
393
 
452
394
  console.log(chalk.bold('\nCurrent Configuration:\n'));
453
395
  console.log(` API Key: ${apiKey ? chalk.green('āœ“ Set') : chalk.red('āœ— Not set')}`);
454
396
  console.log(
455
- ` API Endpoint: ${apiEndpoint ? chalk.cyan(apiEndpoint) : chalk.red('āœ— Not set')}`
456
- );
457
- console.log(
458
- ` Ticket System: ${ticketSystem ? chalk.cyan(ticketSystem) : chalk.gray('Not configured')}\n`
397
+ ` API Endpoint: ${apiEndpoint ? chalk.cyan(apiEndpoint) : chalk.red('āœ— Not set')}\n`
459
398
  );
460
399
  return;
461
400
  }
@@ -476,29 +415,11 @@ program
476
415
  setApiEndpoint(options.endpoint);
477
416
  console.log(chalk.green('āœ“ API Endpoint saved successfully!'));
478
417
  }
479
- if (options.ticketSystem !== undefined) {
480
- if (options.ticketSystem === '') {
481
- // Clear ticket system
482
- setTicketSystem(null);
483
- console.log(chalk.green('āœ“ Ticket System cleared!'));
484
- } else {
485
- // Validate ticket system
486
- const validSystems = ['jira', 'ado'];
487
- if (!validSystems.includes(options.ticketSystem.toLowerCase())) {
488
- console.error(chalk.red(`Invalid ticket system: ${options.ticketSystem}`));
489
- console.error(chalk.gray(`Valid options: ${validSystems.join(', ')}`));
490
- return;
491
- }
492
- setTicketSystem(options.ticketSystem);
493
- console.log(chalk.green('āœ“ Ticket System saved successfully!'));
494
- }
495
- }
496
- if (!options.key && !options.endpoint && options.ticketSystem === undefined && !options.show) {
418
+ if (!options.key && !options.endpoint && !options.show) {
497
419
  console.log(chalk.yellow('Please provide at least one configuration option.'));
498
420
  console.log('\nUsage:');
499
421
  console.log(' kk config --key YOUR_API_KEY');
500
- console.log(' kk config --endpoint https://api.korekt.ai/review/local');
501
- console.log(' kk config --ticket-system jira');
422
+ console.log(' kk config --endpoint https://api.korekt.ai/api/review');
502
423
  console.log(' kk config --show (view current configuration)');
503
424
  }
504
425
  });