korekt-cli 0.2.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 +130 -0
- package/package.json +50 -0
- package/src/config.js +75 -0
- package/src/formatter.js +143 -0
- package/src/git-logic.js +440 -0
- package/src/git-logic.test.js +542 -0
- package/src/index.js +398 -0
package/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Korekt CLI
|
|
2
|
+
|
|
3
|
+
AI-powered code review CLI - Keep your kode korekt โจ
|
|
4
|
+
|
|
5
|
+
`kk` integrates seamlessly with your local Git workflow to provide intelligent code reviews powered by AI.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
* **AI-Powered Analysis**: Get instant, intelligent code reviews with severity levels, categories, and actionable suggestions
|
|
10
|
+
* **Local Git Integration**: Works with committed changes, staged changes, and unstaged modifications
|
|
11
|
+
* **Ticket System Integration**: Automatically extracts ticket IDs from branch names and commit messages (Jira & Azure DevOps)
|
|
12
|
+
* **Beautiful Output**: Color-coded issues with severity indicators, file locations, and suggested fixes
|
|
13
|
+
* **Ultra-Fast**: Short command syntax (`kk`) for maximum developer efficiency
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g korekt-cli
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
Configure the CLI with your API credentials:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
kk config --key YOUR_API_KEY
|
|
27
|
+
kk config --endpoint https://api.korekt.ai/review/local
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Run your first review:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Review committed changes against a target branch
|
|
34
|
+
kk review main
|
|
35
|
+
|
|
36
|
+
# Review only staged changes
|
|
37
|
+
kk stg
|
|
38
|
+
|
|
39
|
+
# Review only unstaged changes
|
|
40
|
+
kk diff
|
|
41
|
+
|
|
42
|
+
# Review all uncommitted changes (staged + unstaged)
|
|
43
|
+
kk all
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
### Configuration
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Set API key
|
|
52
|
+
kk config --key YOUR_API_KEY
|
|
53
|
+
|
|
54
|
+
# Set API endpoint
|
|
55
|
+
kk config --endpoint https://api.korekt.ai/review/local
|
|
56
|
+
|
|
57
|
+
# Set default ticket system (jira or ado)
|
|
58
|
+
kk config --ticket-system jira
|
|
59
|
+
|
|
60
|
+
# Show current configuration
|
|
61
|
+
kk config --show
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Review Commands
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Review committed changes (auto-detect base branch)
|
|
68
|
+
kk review
|
|
69
|
+
|
|
70
|
+
# Review against specific branch
|
|
71
|
+
kk review main
|
|
72
|
+
|
|
73
|
+
# Review with ticket system override
|
|
74
|
+
kk review main --ticket-system ado
|
|
75
|
+
|
|
76
|
+
# Review with ignored files
|
|
77
|
+
kk review main --ignore "*.lock" "dist/*"
|
|
78
|
+
|
|
79
|
+
# Dry run (preview payload without sending)
|
|
80
|
+
kk review main --dry-run
|
|
81
|
+
|
|
82
|
+
# Review staged changes only
|
|
83
|
+
kk stg
|
|
84
|
+
# Aliases: kk staged, kk cached
|
|
85
|
+
|
|
86
|
+
# Review unstaged changes only
|
|
87
|
+
kk diff
|
|
88
|
+
|
|
89
|
+
# Review all uncommitted changes
|
|
90
|
+
kk all
|
|
91
|
+
|
|
92
|
+
# Include untracked files
|
|
93
|
+
kk all --untracked
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Alternative Command
|
|
97
|
+
|
|
98
|
+
Both `kk` and `korekt` commands are available:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
korekt review main # Same as: kk review main
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Environment Variables
|
|
105
|
+
|
|
106
|
+
You can also configure using environment variables:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
export KOREKT_API_KEY="your-api-key"
|
|
110
|
+
export KOREKT_API_ENDPOINT="https://api.korekt.ai/review/local"
|
|
111
|
+
export KOREKT_TICKET_SYSTEM="jira"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Note: Config file takes precedence over environment variables.
|
|
115
|
+
|
|
116
|
+
## Help
|
|
117
|
+
|
|
118
|
+
For more options and detailed help:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
kk --help
|
|
122
|
+
kk review --help
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Development
|
|
126
|
+
|
|
127
|
+
To run tests:
|
|
128
|
+
```bash
|
|
129
|
+
npm test
|
|
130
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "korekt-cli",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "AI-powered code review CLI - Keep your kode korekt",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"kk": "src/index.js",
|
|
8
|
+
"korekt": "src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "vitest",
|
|
13
|
+
"test:watch": "vitest --watch",
|
|
14
|
+
"test:coverage": "vitest --coverage"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"code-review",
|
|
18
|
+
"ai",
|
|
19
|
+
"cli",
|
|
20
|
+
"git",
|
|
21
|
+
"korekt",
|
|
22
|
+
"code-quality",
|
|
23
|
+
"static-analysis"
|
|
24
|
+
],
|
|
25
|
+
"author": "Vladan Djokic",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/vladan-me/korekt-cli.git"
|
|
29
|
+
},
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/vladan-me/korekt-cli/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://korekt.ai",
|
|
34
|
+
"files": [
|
|
35
|
+
"src"
|
|
36
|
+
],
|
|
37
|
+
"license": "UNLICENSED",
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"axios": "^1.12.2",
|
|
40
|
+
"chalk": "^5.6.2",
|
|
41
|
+
"commander": "^14.0.1",
|
|
42
|
+
"conf": "^15.0.2",
|
|
43
|
+
"dotenv": "^17.2.3",
|
|
44
|
+
"execa": "^9.6.0",
|
|
45
|
+
"ora": "^9.0.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"vitest": "^3.2.4"
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/config.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
import dotenv from 'dotenv';
|
|
3
|
+
|
|
4
|
+
// Load .env file if it exists (quietly)
|
|
5
|
+
dotenv.config({ quiet: true });
|
|
6
|
+
|
|
7
|
+
// Initialize config
|
|
8
|
+
const config = new Conf({
|
|
9
|
+
projectName: 'korekt-cli',
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get the API key from config or environment
|
|
14
|
+
* Priority: 1) config store, 2) .env file
|
|
15
|
+
*/
|
|
16
|
+
export function getApiKey() {
|
|
17
|
+
const configKey = config.get('apiKey');
|
|
18
|
+
if (configKey) return configKey;
|
|
19
|
+
|
|
20
|
+
return process.env.KOREKT_API_KEY || null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Set the API key in config store
|
|
25
|
+
*/
|
|
26
|
+
export function setApiKey(key) {
|
|
27
|
+
config.set('apiKey', key);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get the API endpoint URL from config or environment
|
|
32
|
+
* Priority: 1) config store, 2) .env file
|
|
33
|
+
*/
|
|
34
|
+
export function getApiEndpoint() {
|
|
35
|
+
const configEndpoint = config.get('apiEndpoint');
|
|
36
|
+
if (configEndpoint) return configEndpoint;
|
|
37
|
+
|
|
38
|
+
return process.env.KOREKT_API_ENDPOINT || null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Set the API endpoint in config store
|
|
43
|
+
*/
|
|
44
|
+
export function setApiEndpoint(endpoint) {
|
|
45
|
+
config.set('apiEndpoint', endpoint);
|
|
46
|
+
}
|
|
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
|
+
/**
|
|
67
|
+
* Get all configuration
|
|
68
|
+
*/
|
|
69
|
+
export function getConfig() {
|
|
70
|
+
return {
|
|
71
|
+
apiKey: getApiKey(),
|
|
72
|
+
apiEndpoint: getApiEndpoint(),
|
|
73
|
+
ticketSystem: getTicketSystem(),
|
|
74
|
+
};
|
|
75
|
+
}
|
package/src/formatter.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { execaSync } from 'execa';
|
|
4
|
+
|
|
5
|
+
// Emojis and colors inspired by the provided ADO script
|
|
6
|
+
const SEVERITY_ICONS = {
|
|
7
|
+
critical: '๐ฃ',
|
|
8
|
+
high: '๐ด',
|
|
9
|
+
medium: '๐ ',
|
|
10
|
+
low: '๐ก',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const SEVERITY_COLORS = {
|
|
14
|
+
critical: chalk.magenta.bold,
|
|
15
|
+
high: chalk.red.bold,
|
|
16
|
+
medium: chalk.yellow.bold,
|
|
17
|
+
low: chalk.gray.bold,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const CATEGORY_ICONS = {
|
|
21
|
+
bug: '๐',
|
|
22
|
+
security: '๐ก๏ธ',
|
|
23
|
+
best_practice: 'โจ',
|
|
24
|
+
dependency: '๐ฆ',
|
|
25
|
+
performance: '๐',
|
|
26
|
+
rbac: '๐',
|
|
27
|
+
syntax: '๐',
|
|
28
|
+
clean_code: '๐งผ',
|
|
29
|
+
documentation: '๐',
|
|
30
|
+
test_coverage: '๐งช',
|
|
31
|
+
readability: '๐',
|
|
32
|
+
default: 'โ๏ธ', // Default icon
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Capitalize and replace underscores for category display.
|
|
37
|
+
* @param {string} category
|
|
38
|
+
* @returns {string}
|
|
39
|
+
*/
|
|
40
|
+
function formatCategory(category) {
|
|
41
|
+
if (!category) return '';
|
|
42
|
+
return category
|
|
43
|
+
.replace(/_/g, ' ')
|
|
44
|
+
.replace(/\b\w/g, char => char.toUpperCase());
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get the git repository root directory.
|
|
49
|
+
* @returns {string} - Absolute path to the git repository root
|
|
50
|
+
*/
|
|
51
|
+
function getGitRoot() {
|
|
52
|
+
try {
|
|
53
|
+
const { stdout } = execaSync('git', ['rev-parse', '--show-toplevel']);
|
|
54
|
+
return stdout.trim();
|
|
55
|
+
} catch (error) {
|
|
56
|
+
// Fallback to current working directory if not in a git repo
|
|
57
|
+
return process.cwd();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Convert a relative file path to an absolute path for better IDE integration.
|
|
63
|
+
* @param {string} filePath - The file path from the API response
|
|
64
|
+
* @returns {string} - Absolute file path
|
|
65
|
+
*/
|
|
66
|
+
function toAbsolutePath(filePath) {
|
|
67
|
+
if (path.isAbsolute(filePath)) {
|
|
68
|
+
return filePath;
|
|
69
|
+
}
|
|
70
|
+
const gitRoot = getGitRoot();
|
|
71
|
+
return path.join(gitRoot, filePath);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Format and display the API response in the new, detailed style.
|
|
76
|
+
* @param {Object} data - The API response data
|
|
77
|
+
*/
|
|
78
|
+
export function formatReviewOutput(data) {
|
|
79
|
+
const { review, summary } = data.analysis;
|
|
80
|
+
|
|
81
|
+
console.log(chalk.bold.blue('๐ค Automated Code Review Results\n'));
|
|
82
|
+
|
|
83
|
+
// --- Praises Section ---
|
|
84
|
+
if (review && review.praises && review.praises.length > 0) {
|
|
85
|
+
console.log(chalk.bold.magenta(`โจ Praises (${summary.total_praises})`));
|
|
86
|
+
review.praises.forEach(praise => {
|
|
87
|
+
const categoryIcon = CATEGORY_ICONS[praise.category] || CATEGORY_ICONS.default;
|
|
88
|
+
const formattedCategory = formatCategory(praise.category);
|
|
89
|
+
const absolutePath = toAbsolutePath(praise.file_path);
|
|
90
|
+
console.log(` โ
${chalk.green.bold(formattedCategory)} in ${absolutePath}:${praise.line_number}`);
|
|
91
|
+
console.log(` ${praise.message}\n`);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// --- Issues Section ---
|
|
96
|
+
if (review && review.issues && review.issues.length > 0) {
|
|
97
|
+
console.log(chalk.bold.red(`โ ๏ธ Issues Found (${summary.total_issues})`));
|
|
98
|
+
|
|
99
|
+
// Severity Summary Table
|
|
100
|
+
console.log(chalk.underline('Severity Count:'));
|
|
101
|
+
const severities = ['critical', 'high', 'medium', 'low'];
|
|
102
|
+
severities.forEach(severity => {
|
|
103
|
+
const count = summary[severity] || 0;
|
|
104
|
+
if (count > 0) {
|
|
105
|
+
const icon = SEVERITY_ICONS[severity];
|
|
106
|
+
const color = SEVERITY_COLORS[severity];
|
|
107
|
+
const label = severity.charAt(0).toUpperCase() + severity.slice(1);
|
|
108
|
+
console.log(`${icon} ${color(label)}: ${count}`);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
console.log(''); // Newline for spacing
|
|
112
|
+
|
|
113
|
+
// Issues Details
|
|
114
|
+
review.issues.forEach((issue, index) => {
|
|
115
|
+
const severityIcon = SEVERITY_ICONS[issue.severity] || 'โ';
|
|
116
|
+
const severityColor = SEVERITY_COLORS[issue.severity] || chalk.white;
|
|
117
|
+
const categoryIcon = CATEGORY_ICONS[issue.category] || CATEGORY_ICONS.default;
|
|
118
|
+
const formattedCategory = formatCategory(issue.category);
|
|
119
|
+
const absolutePath = toAbsolutePath(issue.file_path);
|
|
120
|
+
|
|
121
|
+
console.log(
|
|
122
|
+
`${severityIcon} ${severityColor(
|
|
123
|
+
issue.severity.toUpperCase()
|
|
124
|
+
)} in ${absolutePath}:${issue.line_number} (${categoryIcon} ${formattedCategory})`
|
|
125
|
+
);
|
|
126
|
+
console.log(` ${issue.message}`);
|
|
127
|
+
|
|
128
|
+
if (issue.suggested_fix) {
|
|
129
|
+
console.log(chalk.bold('\n๐ก Suggested Fix:'));
|
|
130
|
+
// Indent the suggested fix for readability
|
|
131
|
+
const indentedFix = issue.suggested_fix.split('\n').map(line => ` ${line}`).join('\n');
|
|
132
|
+
console.log(chalk.green(indentedFix));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Add separator between issues (but not after the last one)
|
|
136
|
+
if (index < review.issues.length - 1) {
|
|
137
|
+
const terminalWidth = process.stdout.columns || 80;
|
|
138
|
+
console.log(chalk.gray('โ'.repeat(terminalWidth)));
|
|
139
|
+
}
|
|
140
|
+
console.log(); // Add a blank line for spacing
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|