korekt-cli 0.6.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.6.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)
@@ -332,15 +281,10 @@ export async function getContributors(diffRange, repoRootPath) {
332
281
  /**
333
282
  * Main function to analyze local git changes and prepare review payload
334
283
  * @param {string|null} targetBranch - The branch to compare against. If null, uses git reflog to find fork point.
335
- * @param {string|null} ticketSystem - The ticket system to use (jira or ado), or null to skip ticket extraction
336
284
  * @param {string[]|null} ignorePatterns - Array of glob patterns to ignore files
337
285
  * @returns {Object|null} - The payload object ready for API submission, or null on error
338
286
  */
339
- export async function runLocalReview(
340
- targetBranch = null,
341
- _ticketSystem = null,
342
- ignorePatterns = null
343
- ) {
287
+ export async function runLocalReview(targetBranch = null, ignorePatterns = null) {
344
288
  try {
345
289
  // 1. Get Repo URL, current branch name, and repository root
346
290
  const { stdout: repoUrl } = await execa('git', ['remote', 'get-url', 'origin']);
@@ -83,7 +83,7 @@ describe('runUncommittedReview', () => {
83
83
  throw new Error(`Unmocked command: ${command}`);
84
84
  });
85
85
 
86
- const result = await runUncommittedReview('staged', null);
86
+ const result = await runUncommittedReview('staged');
87
87
 
88
88
  expect(result).toBeDefined();
89
89
  expect(result.repo_url).toBe('https://github.com/user/repo'); // Normalized (no .git)
@@ -120,56 +120,13 @@ describe('runUncommittedReview', () => {
120
120
  throw new Error(`Unmocked command: ${command}`);
121
121
  });
122
122
 
123
- const result = await runUncommittedReview('unstaged', null);
123
+ const result = await runUncommittedReview('unstaged');
124
124
 
125
125
  expect(result).toBeDefined();
126
126
  expect(result.source_branch).toBe('feature-branch');
127
127
  expect(result.changed_files).toHaveLength(1);
128
128
  });
129
129
 
130
- it('should analyze all uncommitted changes', async () => {
131
- vi.mocked(execa).mockImplementation(async (cmd, args) => {
132
- const command = [cmd, ...args].join(' ');
133
-
134
- if (command.includes('remote get-url origin')) {
135
- return { stdout: 'https://github.com/user/repo.git' };
136
- }
137
- if (command.includes('rev-parse --abbrev-ref HEAD')) {
138
- return { stdout: 'feature-branch' };
139
- }
140
- if (command.includes('rev-parse --show-toplevel')) {
141
- return { stdout: '/fake/repo/path' };
142
- }
143
- if (command.includes('diff --cached --name-status')) {
144
- return { stdout: 'M\tstaged.js' };
145
- }
146
- if (command === 'git diff --name-status') {
147
- return { stdout: 'M\tunstaged.js' };
148
- }
149
- if (command.includes('diff --cached -U15 -- staged.js')) {
150
- return { stdout: 'diff staged' };
151
- }
152
- if (command.includes('diff -U15 -- unstaged.js')) {
153
- return { stdout: 'diff unstaged' };
154
- }
155
- if (command.includes('show HEAD:staged.js')) {
156
- return { stdout: 'staged old content' };
157
- }
158
- if (command.includes('show HEAD:unstaged.js')) {
159
- return { stdout: 'unstaged old content' };
160
- }
161
-
162
- throw new Error(`Unmocked command: ${command}`);
163
- });
164
-
165
- const result = await runUncommittedReview('all', null);
166
-
167
- expect(result).toBeDefined();
168
- expect(result.changed_files).toHaveLength(2);
169
- expect(result.changed_files[0].path).toBe('staged.js');
170
- expect(result.changed_files[1].path).toBe('unstaged.js');
171
- });
172
-
173
130
  it('should return null when no changes found', async () => {
174
131
  vi.mocked(execa).mockImplementation(async (cmd, args) => {
175
132
  const command = [cmd, ...args].join(' ');
@@ -193,7 +150,7 @@ describe('runUncommittedReview', () => {
193
150
  throw new Error(`Unmocked command: ${command}`);
194
151
  });
195
152
 
196
- const result = await runUncommittedReview('all', null);
153
+ const result = await runUncommittedReview('staged');
197
154
 
198
155
  expect(result).toBeNull();
199
156
  });
@@ -347,7 +304,7 @@ describe('runLocalReview - fork point detection', () => {
347
304
  throw new Error(`Unmocked command: ${command}`);
348
305
  });
349
306
 
350
- const result = await runLocalReview(null, 'jira');
307
+ const result = await runLocalReview(null);
351
308
 
352
309
  expect(result).toBeDefined();
353
310
  expect(result.source_branch).toBe('feature-branch');
@@ -397,7 +354,7 @@ describe('runLocalReview - fork point detection', () => {
397
354
  throw new Error(`Unmocked command: ${command}`);
398
355
  });
399
356
 
400
- const result = await runLocalReview('main', 'jira');
357
+ const result = await runLocalReview('main');
401
358
 
402
359
  expect(result).toBeDefined();
403
360
 
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'));
@@ -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
 
@@ -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
  });