ai-pr-review-cli 1.0.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/LICENSE +21 -0
- package/README.md +212 -0
- package/bin/cli.js +75 -0
- package/package.json +66 -0
- package/src/ai.js +25 -0
- package/src/config.js +52 -0
- package/src/git.js +148 -0
- package/src/prompt.js +22 -0
- package/src/reviewer.js +151 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AI PR Review CLI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# š¤ AI PR Review CLI
|
|
2
|
+
|
|
3
|
+
An intelligent command-line tool that uses AI to review your code changes and provide instant feedback on pull requests, issues, typos, and improvements.
|
|
4
|
+
|
|
5
|
+
## ⨠Features
|
|
6
|
+
|
|
7
|
+
- š **Intelligent Code Analysis** - Uses GPT to analyze your code changes
|
|
8
|
+
- šÆ **Smart Branch Detection** - Automatically detects common base branches
|
|
9
|
+
- šØ **Beautiful CLI Interface** - Clean, colorful output with emojis
|
|
10
|
+
- ā” **Fast & Easy** - One command to get comprehensive feedback
|
|
11
|
+
- š§ **Flexible Configuration** - Multiple ways to configure base branch and AI model
|
|
12
|
+
- š **Categorized Feedback** - Issues, typos, and improvements clearly separated
|
|
13
|
+
|
|
14
|
+
## š Quick Start
|
|
15
|
+
|
|
16
|
+
### Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Install globally for use anywhere
|
|
20
|
+
npm install -g ai-pr-review-cli
|
|
21
|
+
|
|
22
|
+
# Or use with npx (no installation needed)
|
|
23
|
+
npx ai-pr-review-cli review
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Setup
|
|
27
|
+
|
|
28
|
+
1. **Get an OpenAI API key** from [OpenAI](https://platform.openai.com/api-keys)
|
|
29
|
+
|
|
30
|
+
2. **Set your API key**:
|
|
31
|
+
```bash
|
|
32
|
+
# Option 1: Environment variable (recommended)
|
|
33
|
+
export OPENAI_API_KEY="your-api-key-here"
|
|
34
|
+
|
|
35
|
+
# Option 2: Create .env file in your project
|
|
36
|
+
echo "OPENAI_API_KEY=your-api-key-here" > .env
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
3. **Run a review**:
|
|
40
|
+
```bash
|
|
41
|
+
# In your git repository
|
|
42
|
+
ai-pr-review review
|
|
43
|
+
|
|
44
|
+
# Or use the short alias
|
|
45
|
+
aipr review
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## š Usage
|
|
49
|
+
|
|
50
|
+
### Basic Usage
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Review current branch against main
|
|
54
|
+
ai-pr-review review
|
|
55
|
+
|
|
56
|
+
# Review against specific branch
|
|
57
|
+
ai-pr-review review --base origin/develop
|
|
58
|
+
|
|
59
|
+
# Use different AI model
|
|
60
|
+
ai-pr-review review --model gpt-4o
|
|
61
|
+
|
|
62
|
+
# Show detailed output
|
|
63
|
+
ai-pr-review review --verbose
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Command Options
|
|
67
|
+
|
|
68
|
+
| Option | Short | Description | Example |
|
|
69
|
+
|--------|-------|-------------|---------|
|
|
70
|
+
| `--base <branch>` | `-b` | Base branch to compare against | `-b origin/main` |
|
|
71
|
+
| `--model <model>` | `-m` | AI model to use | `-m gpt-4o` |
|
|
72
|
+
| `--provider <provider>` | | AI provider (currently only openai) | `--provider openai` |
|
|
73
|
+
| `--verbose` | `-v` | Show detailed output | `-v` |
|
|
74
|
+
|
|
75
|
+
### Examples
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Review feature branch against main
|
|
79
|
+
git checkout feature/new-feature
|
|
80
|
+
ai-pr-review review --base main
|
|
81
|
+
|
|
82
|
+
# Review with GPT-4 and verbose output
|
|
83
|
+
ai-pr-review review --model gpt-4o --verbose
|
|
84
|
+
|
|
85
|
+
# Review staged changes only
|
|
86
|
+
git add .
|
|
87
|
+
ai-pr-review review
|
|
88
|
+
|
|
89
|
+
# Review uncommitted changes
|
|
90
|
+
ai-pr-review review # Automatically includes working directory changes
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## āļø Configuration
|
|
94
|
+
|
|
95
|
+
### Method 1: Command Line Arguments (Recommended)
|
|
96
|
+
```bash
|
|
97
|
+
ai-pr-review review --base origin/main --model gpt-4o-mini
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Method 2: Project Configuration File
|
|
101
|
+
Create `.aiprconfig.json` in your project root:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"provider": "openai",
|
|
106
|
+
"model": "gpt-4o-mini",
|
|
107
|
+
"baseBranch": "origin/main"
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Method 3: Auto-Detection
|
|
112
|
+
The tool automatically detects common base branches in this order:
|
|
113
|
+
1. `origin/main`
|
|
114
|
+
2. `origin/master`
|
|
115
|
+
3. `origin/develop`
|
|
116
|
+
4. `main`
|
|
117
|
+
5. `master`
|
|
118
|
+
6. `develop`
|
|
119
|
+
7. `HEAD~1` (fallback)
|
|
120
|
+
|
|
121
|
+
## šÆ What Gets Reviewed
|
|
122
|
+
|
|
123
|
+
The tool analyzes:
|
|
124
|
+
- ā
**Committed changes** between branches
|
|
125
|
+
- ā
**Staged changes** (files added with `git add`)
|
|
126
|
+
- ā
**Working directory changes** (uncommitted modifications)
|
|
127
|
+
- ā
**File contents** for context
|
|
128
|
+
|
|
129
|
+
## š Output Categories
|
|
130
|
+
|
|
131
|
+
### šØ Issues
|
|
132
|
+
- Critical bugs and logic errors
|
|
133
|
+
- Security vulnerabilities
|
|
134
|
+
- Performance problems
|
|
135
|
+
- Breaking changes
|
|
136
|
+
|
|
137
|
+
### āļø Typos
|
|
138
|
+
- Spelling mistakes in comments
|
|
139
|
+
- Grammar errors in strings
|
|
140
|
+
- Documentation typos
|
|
141
|
+
|
|
142
|
+
### š” Improvements
|
|
143
|
+
- Code quality suggestions
|
|
144
|
+
- Best practice recommendations
|
|
145
|
+
- Performance optimizations
|
|
146
|
+
- Refactoring opportunities
|
|
147
|
+
|
|
148
|
+
## š§ Supported AI Models
|
|
149
|
+
|
|
150
|
+
| Model | Speed | Quality | Cost |
|
|
151
|
+
|-------|-------|---------|------|
|
|
152
|
+
| `gpt-4o-mini` | ā”ā”ā” | āāā | š° |
|
|
153
|
+
| `gpt-4o` | ā”ā” | āāāāā | š°š°š° |
|
|
154
|
+
| `gpt-3.5-turbo` | ā”ā”ā” | āā | š° |
|
|
155
|
+
|
|
156
|
+
## š ļø Troubleshooting
|
|
157
|
+
|
|
158
|
+
### "No changes found to review"
|
|
159
|
+
- Make sure you're on a feature branch (not main/master)
|
|
160
|
+
- Check if you have uncommitted changes: `git status`
|
|
161
|
+
- Try specifying a different base branch: `--base main`
|
|
162
|
+
- Update remote branches: `git fetch origin`
|
|
163
|
+
|
|
164
|
+
### "API key not found"
|
|
165
|
+
```bash
|
|
166
|
+
# Set your OpenAI API key
|
|
167
|
+
export OPENAI_API_KEY="your-key-here"
|
|
168
|
+
|
|
169
|
+
# Or create .env file
|
|
170
|
+
echo "OPENAI_API_KEY=your-key-here" > .env
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### "Branch not found"
|
|
174
|
+
```bash
|
|
175
|
+
# List all available branches
|
|
176
|
+
git branch -a
|
|
177
|
+
|
|
178
|
+
# Use a branch that exists
|
|
179
|
+
ai-pr-review review --base origin/master
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## š Security & Privacy
|
|
183
|
+
|
|
184
|
+
- Your code is sent to OpenAI's API for analysis
|
|
185
|
+
- API keys are read from environment variables or .env files
|
|
186
|
+
- No code is stored permanently by the tool
|
|
187
|
+
- Consider using this on non-sensitive codebases
|
|
188
|
+
- Review OpenAI's [data usage policies](https://openai.com/policies/api-data-usage-policies)
|
|
189
|
+
|
|
190
|
+
## š¤ Contributing
|
|
191
|
+
|
|
192
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
193
|
+
|
|
194
|
+
## š License
|
|
195
|
+
|
|
196
|
+
MIT License - see [LICENSE](LICENSE) file for details.
|
|
197
|
+
|
|
198
|
+
## š” Tips
|
|
199
|
+
|
|
200
|
+
- **Use on feature branches** for best results
|
|
201
|
+
- **Commit changes first** for more accurate reviews
|
|
202
|
+
- **Use `--verbose`** to see what's being analyzed
|
|
203
|
+
- **Try different models** for varying levels of detail
|
|
204
|
+
- **Set up aliases** in your shell for quick access:
|
|
205
|
+
```bash
|
|
206
|
+
alias review="ai-pr-review review"
|
|
207
|
+
alias aipr="ai-pr-review review"
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
Made with ā¤ļø for developers who want better code reviews!
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { Command } = require("commander");
|
|
4
|
+
const review = require("../src/reviewer");
|
|
5
|
+
const { createGlobalConfig } = require("../src/config");
|
|
6
|
+
|
|
7
|
+
const program = new Command();
|
|
8
|
+
|
|
9
|
+
program
|
|
10
|
+
.name("ai-pr-review")
|
|
11
|
+
.description("š¤ AI-powered Pull Request reviewer")
|
|
12
|
+
.version("1.0.0");
|
|
13
|
+
|
|
14
|
+
program
|
|
15
|
+
.command("review")
|
|
16
|
+
.description("š Review current branch changes with AI")
|
|
17
|
+
.option("-b, --base <branch>", "Base branch to compare against (e.g., main, origin/main, develop)")
|
|
18
|
+
.option("-m, --model <model>", "AI model to use (default: gpt-4o-mini)")
|
|
19
|
+
.option("--provider <provider>", "AI provider (default: openai)")
|
|
20
|
+
.option("-v, --verbose", "Show detailed output")
|
|
21
|
+
.action(async (options) => {
|
|
22
|
+
// Show a nice header
|
|
23
|
+
const { default: chalk } = await import("chalk");
|
|
24
|
+
console.log(chalk.blue.bold("\nš¤ AI PR Review"));
|
|
25
|
+
console.log(chalk.gray("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n"));
|
|
26
|
+
|
|
27
|
+
await review(options);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
program
|
|
31
|
+
.command("config")
|
|
32
|
+
.description("āļø Set up global configuration")
|
|
33
|
+
.option("-m, --model <model>", "Default AI model to use")
|
|
34
|
+
.option("-b, --base <branch>", "Default base branch")
|
|
35
|
+
.option("--provider <provider>", "Default AI provider")
|
|
36
|
+
.action(async (options) => {
|
|
37
|
+
const { default: chalk } = await import("chalk");
|
|
38
|
+
|
|
39
|
+
if (Object.keys(options).length === 0) {
|
|
40
|
+
console.log(chalk.yellow("Please specify configuration options:"));
|
|
41
|
+
console.log(chalk.gray(" ai-pr-review config --model gpt-4o-mini --base main"));
|
|
42
|
+
console.log(chalk.gray(" ai-pr-review config --provider openai"));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const configPath = createGlobalConfig(options);
|
|
48
|
+
console.log(chalk.green("ā
Global configuration saved!"));
|
|
49
|
+
console.log(chalk.gray(` Config file: ${configPath}`));
|
|
50
|
+
|
|
51
|
+
if (options.model) console.log(chalk.blue(` Default model: ${options.model}`));
|
|
52
|
+
if (options.provider) console.log(chalk.blue(` Default provider: ${options.provider}`));
|
|
53
|
+
if (options.base) console.log(chalk.blue(` Default base branch: ${options.base}`));
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error(chalk.red(`ā Failed to save configuration: ${error.message}`));
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Also support direct usage without subcommand
|
|
60
|
+
program
|
|
61
|
+
.option("-b, --base <branch>", "Base branch to compare against")
|
|
62
|
+
.option("-m, --model <model>", "AI model to use")
|
|
63
|
+
.option("--provider <provider>", "AI provider")
|
|
64
|
+
.option("-v, --verbose", "Show detailed output")
|
|
65
|
+
.action(async (options) => {
|
|
66
|
+
// If no specific command was run, default to review
|
|
67
|
+
if (process.argv.length > 2 && !process.argv.includes('review')) {
|
|
68
|
+
const { default: chalk } = await import("chalk");
|
|
69
|
+
console.log(chalk.blue.bold("\nš¤ AI PR Review"));
|
|
70
|
+
console.log(chalk.gray("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n"));
|
|
71
|
+
await review(options);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ai-pr-review-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "š¤ AI-powered Pull Request reviewer - Get instant feedback on your code changes using GPT",
|
|
5
|
+
"main": "src/reviewer.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ai-pr-review": "./bin/cli.js",
|
|
8
|
+
"aipr": "./bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"type": "commonjs",
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=16.0.0"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"start": "node bin/cli.js",
|
|
16
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
17
|
+
"prepublishOnly": "echo 'Ready to publish!'"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"ai",
|
|
21
|
+
"artificial-intelligence",
|
|
22
|
+
"pr-review",
|
|
23
|
+
"pull-request",
|
|
24
|
+
"code-review",
|
|
25
|
+
"cli",
|
|
26
|
+
"git",
|
|
27
|
+
"github",
|
|
28
|
+
"openai",
|
|
29
|
+
"gpt",
|
|
30
|
+
"developer-tools",
|
|
31
|
+
"automation"
|
|
32
|
+
],
|
|
33
|
+
"author": {
|
|
34
|
+
"name": "Deepanshu Chauhan",
|
|
35
|
+
"email": "chauhandeepanshu336@gmail.com"
|
|
36
|
+
},
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "https://github.com/yourusername/ai-pr-review-cli.git"
|
|
41
|
+
},
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/yourusername/ai-pr-review-cli/issues"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://github.com/yourusername/ai-pr-review-cli#readme",
|
|
46
|
+
"files": [
|
|
47
|
+
"bin/",
|
|
48
|
+
"src/",
|
|
49
|
+
"README.md",
|
|
50
|
+
"LICENSE"
|
|
51
|
+
],
|
|
52
|
+
"preferGlobal": true,
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"axios": "^1.6.0",
|
|
55
|
+
"chalk": "^5.3.0",
|
|
56
|
+
"commander": "^11.0.0",
|
|
57
|
+
"dotenv": "^17.3.1",
|
|
58
|
+
"ora": "^6.3.1",
|
|
59
|
+
"simple-git": "^3.19.1"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {},
|
|
62
|
+
"funding": {
|
|
63
|
+
"type": "individual",
|
|
64
|
+
"url": "https://github.com/blockDeepanshu"
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/ai.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const axios = require("axios");
|
|
2
|
+
require("dotenv").config();
|
|
3
|
+
async function openai(prompt, model) {
|
|
4
|
+
const res = await axios.post(
|
|
5
|
+
"https://api.openai.com/v1/chat/completions",
|
|
6
|
+
{
|
|
7
|
+
model,
|
|
8
|
+
messages: [{ role: "user", content: prompt }],
|
|
9
|
+
temperature: 0.2,
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
headers: {
|
|
13
|
+
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
return res.data.choices[0].message.content;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function runAI(prompt, config) {
|
|
22
|
+
return openai(prompt, config.model);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = { runAI };
|
package/src/config.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
|
|
5
|
+
function getConfig() {
|
|
6
|
+
// Default configuration
|
|
7
|
+
const defaultConfig = {
|
|
8
|
+
provider: "openai",
|
|
9
|
+
model: "gpt-4o-mini",
|
|
10
|
+
baseBranch: null, // Will be auto-detected
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Try to load global config from user's home directory
|
|
14
|
+
const globalConfigPath = path.join(os.homedir(), ".aiprconfig.json");
|
|
15
|
+
let config = { ...defaultConfig };
|
|
16
|
+
|
|
17
|
+
if (fs.existsSync(globalConfigPath)) {
|
|
18
|
+
try {
|
|
19
|
+
const globalConfig = JSON.parse(fs.readFileSync(globalConfigPath, "utf-8"));
|
|
20
|
+
config = { ...config, ...globalConfig };
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.warn("Warning: Could not parse global config file:", globalConfigPath);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Try to load local project config (overrides global)
|
|
27
|
+
const localConfigPath = path.join(process.cwd(), ".aiprconfig.json");
|
|
28
|
+
if (fs.existsSync(localConfigPath)) {
|
|
29
|
+
try {
|
|
30
|
+
const localConfig = JSON.parse(fs.readFileSync(localConfigPath, "utf-8"));
|
|
31
|
+
config = { ...config, ...localConfig };
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.warn("Warning: Could not parse local config file:", localConfigPath);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return config;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function createGlobalConfig(options) {
|
|
41
|
+
const globalConfigPath = path.join(os.homedir(), ".aiprconfig.json");
|
|
42
|
+
const config = {
|
|
43
|
+
provider: options.provider || "openai",
|
|
44
|
+
model: options.model || "gpt-4o-mini",
|
|
45
|
+
...(options.baseBranch && { baseBranch: options.baseBranch })
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
fs.writeFileSync(globalConfigPath, JSON.stringify(config, null, 2));
|
|
49
|
+
return globalConfigPath;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = { getConfig, createGlobalConfig };
|
package/src/git.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
const simpleGit = require("simple-git");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
|
|
4
|
+
const git = simpleGit();
|
|
5
|
+
|
|
6
|
+
async function getCurrentBranch() {
|
|
7
|
+
try {
|
|
8
|
+
const status = await git.status();
|
|
9
|
+
return status.current;
|
|
10
|
+
} catch (error) {
|
|
11
|
+
return 'unknown';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function checkBranchExists(branchName) {
|
|
16
|
+
try {
|
|
17
|
+
await git.raw(['rev-parse', '--verify', branchName]);
|
|
18
|
+
return true;
|
|
19
|
+
} catch (error) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function detectBaseBranch() {
|
|
25
|
+
// List of common base branch names in order of preference
|
|
26
|
+
const commonBranches = [
|
|
27
|
+
'origin/main',
|
|
28
|
+
'origin/master',
|
|
29
|
+
'origin/develop',
|
|
30
|
+
'main',
|
|
31
|
+
'master',
|
|
32
|
+
'develop'
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
for (const branch of commonBranches) {
|
|
36
|
+
if (await checkBranchExists(branch)) {
|
|
37
|
+
return branch;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Fallback to HEAD~1 if no common branch found
|
|
42
|
+
return 'HEAD~1';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function getAllBranches() {
|
|
46
|
+
try {
|
|
47
|
+
const result = await git.branch(['-a']);
|
|
48
|
+
return result.all;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function getDiff(baseBranch) {
|
|
55
|
+
try {
|
|
56
|
+
// First try the original approach
|
|
57
|
+
const committedDiff = await git.diff([`${baseBranch}...HEAD`]);
|
|
58
|
+
|
|
59
|
+
// Also check for working directory changes
|
|
60
|
+
const workingDiff = await git.diff();
|
|
61
|
+
const stagedDiff = await git.diff(['--cached']);
|
|
62
|
+
|
|
63
|
+
// Combine all diffs
|
|
64
|
+
const totalDiff = [committedDiff, workingDiff, stagedDiff].filter(d => d.length > 0).join('\n\n--- NEXT DIFF SECTION ---\n\n');
|
|
65
|
+
|
|
66
|
+
return totalDiff;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
// Silently try alternative approaches
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
// Try diffing against the previous commit
|
|
72
|
+
return await git.diff(['HEAD~1']);
|
|
73
|
+
} catch (error2) {
|
|
74
|
+
try {
|
|
75
|
+
// Fallback: show changes in working directory and staged changes
|
|
76
|
+
const workingDiff = await git.diff();
|
|
77
|
+
const stagedDiff = await git.diff(['--cached']);
|
|
78
|
+
return workingDiff + '\n' + stagedDiff;
|
|
79
|
+
} catch (error3) {
|
|
80
|
+
return '';
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function getChangedFiles(baseBranch) {
|
|
87
|
+
try {
|
|
88
|
+
// Get committed changes
|
|
89
|
+
const committedSummary = await git.diffSummary([`${baseBranch}...HEAD`]);
|
|
90
|
+
const committedFiles = committedSummary.files.map((f) => f.file);
|
|
91
|
+
|
|
92
|
+
// Also check for working directory changes
|
|
93
|
+
const workingSummary = await git.diffSummary();
|
|
94
|
+
const workingFiles = workingSummary.files.map((f) => f.file);
|
|
95
|
+
|
|
96
|
+
// Also check for staged changes
|
|
97
|
+
const stagedSummary = await git.diffSummary(['--cached']);
|
|
98
|
+
const stagedFiles = stagedSummary.files.map((f) => f.file);
|
|
99
|
+
|
|
100
|
+
// Combine all changed files (remove duplicates)
|
|
101
|
+
const allFiles = [...new Set([...committedFiles, ...workingFiles, ...stagedFiles])];
|
|
102
|
+
|
|
103
|
+
return allFiles;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
// Try alternatives silently
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
// Try diffing against the previous commit
|
|
109
|
+
const summary = await git.diffSummary(['HEAD~1']);
|
|
110
|
+
return summary.files.map((f) => f.file);
|
|
111
|
+
} catch (error2) {
|
|
112
|
+
try {
|
|
113
|
+
// Fallback: show changes in working directory
|
|
114
|
+
const summary = await git.diffSummary();
|
|
115
|
+
return summary.files.map((f) => f.file);
|
|
116
|
+
} catch (error3) {
|
|
117
|
+
// Return all JavaScript/TypeScript files in src directory
|
|
118
|
+
const files = [];
|
|
119
|
+
if (fs.existsSync('src')) {
|
|
120
|
+
const srcFiles = fs.readdirSync('src', { recursive: true });
|
|
121
|
+
files.push(...srcFiles
|
|
122
|
+
.filter(f => typeof f === 'string' && (f.endsWith('.js') || f.endsWith('.ts') || f.endsWith('.jsx') || f.endsWith('.tsx')))
|
|
123
|
+
.map(f => `src/${f}`)
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
// Also check root directory for common files
|
|
127
|
+
const rootFiles = ['package.json', 'README.md', 'index.js', 'app.js', 'server.js'];
|
|
128
|
+
rootFiles.forEach(file => {
|
|
129
|
+
if (fs.existsSync(file)) {
|
|
130
|
+
files.push(file);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
return files;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function readFiles(files) {
|
|
140
|
+
return files
|
|
141
|
+
.filter((f) => fs.existsSync(f))
|
|
142
|
+
.map((file) => ({
|
|
143
|
+
name: file,
|
|
144
|
+
content: fs.readFileSync(file, "utf-8").slice(0, 5000),
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports = { getDiff, getChangedFiles, readFiles, getCurrentBranch, checkBranchExists, detectBaseBranch, getAllBranches };
|
package/src/prompt.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
function buildPrompt(diff, files) {
|
|
2
|
+
return `You are a strict code reviewer. Find ALL problems in these code changes. Check for:
|
|
3
|
+
|
|
4
|
+
ISSUES: Syntax errors, logic bugs, invalid JSON/config, security flaws, broken imports, undefined variables, type errors
|
|
5
|
+
TYPOS: Spelling/grammar mistakes in comments, strings, documentation
|
|
6
|
+
IMPROVEMENTS: Code quality, performance, best practices, refactoring opportunities
|
|
7
|
+
|
|
8
|
+
DIFF:
|
|
9
|
+
${diff}
|
|
10
|
+
|
|
11
|
+
FILES:
|
|
12
|
+
${files.map((f) => `${f.name}:\n${f.content}`).join("\n\n")}
|
|
13
|
+
|
|
14
|
+
Be thorough and critical. Return ONLY this JSON (no other text):
|
|
15
|
+
{
|
|
16
|
+
"issues": ["list specific problems found"],
|
|
17
|
+
"typos": ["list spelling/grammar errors"],
|
|
18
|
+
"improvements": ["list enhancement suggestions"]
|
|
19
|
+
}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
module.exports = { buildPrompt };
|
package/src/reviewer.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
const { getDiff, getChangedFiles, readFiles, getCurrentBranch, checkBranchExists, detectBaseBranch } = require("./git");
|
|
2
|
+
const { buildPrompt } = require("./prompt");
|
|
3
|
+
const { runAI } = require("./ai");
|
|
4
|
+
const { getConfig } = require("./config");
|
|
5
|
+
|
|
6
|
+
async function review(options = {}) {
|
|
7
|
+
const { default: ora } = await import("ora");
|
|
8
|
+
const { default: chalk } = await import("chalk");
|
|
9
|
+
|
|
10
|
+
const spinner = ora("š¤ AI is analyzing your code changes...").start();
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
// Merge config with CLI options
|
|
14
|
+
const baseConfig = getConfig();
|
|
15
|
+
const config = {
|
|
16
|
+
...baseConfig,
|
|
17
|
+
...(options.provider && { provider: options.provider }),
|
|
18
|
+
...(options.model && { model: options.model })
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Determine base branch: CLI option > config file > auto-detection
|
|
22
|
+
let baseBranch = options.base || config.baseBranch;
|
|
23
|
+
|
|
24
|
+
if (!baseBranch) {
|
|
25
|
+
if (options.verbose) console.log(chalk.gray("š Auto-detecting base branch..."));
|
|
26
|
+
baseBranch = await detectBaseBranch();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Get current git status
|
|
30
|
+
const currentBranch = await getCurrentBranch();
|
|
31
|
+
const baseBranchExists = await checkBranchExists(baseBranch);
|
|
32
|
+
|
|
33
|
+
console.log(chalk.blue(`\nš Analyzing changes on branch: ${chalk.bold(currentBranch)}`));
|
|
34
|
+
if (options.verbose || !baseBranchExists) {
|
|
35
|
+
console.log(chalk.gray(` Comparing against: ${baseBranch} ${baseBranchExists ? 'ā' : 'ā'}`));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const diff = await getDiff(baseBranch);
|
|
39
|
+
const changedFiles = await getChangedFiles(baseBranch);
|
|
40
|
+
const files = readFiles(changedFiles);
|
|
41
|
+
|
|
42
|
+
if (files.length === 0 && diff.length === 0) {
|
|
43
|
+
spinner.info(chalk.yellow('No changes found to review'));
|
|
44
|
+
|
|
45
|
+
console.log(chalk.yellow('\nš” Possible reasons:'));
|
|
46
|
+
if (currentBranch === baseBranch.replace('origin/', '')) {
|
|
47
|
+
console.log(chalk.gray(' ⢠You are currently on the base branch. Create a feature branch first.'));
|
|
48
|
+
}
|
|
49
|
+
if (!baseBranchExists) {
|
|
50
|
+
console.log(chalk.gray(' ⢠The base branch does not exist. Try "main", "master", or "origin/master".'));
|
|
51
|
+
}
|
|
52
|
+
console.log(chalk.gray(' ⢠No commits made yet on this branch.'));
|
|
53
|
+
console.log(chalk.gray(' ⢠Try: git fetch origin (to update remote branches)'));
|
|
54
|
+
console.log(chalk.gray(' ⢠Try: git status (to see uncommitted changes)'));
|
|
55
|
+
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log(chalk.green(`š Found ${changedFiles.length} changed file${changedFiles.length === 1 ? '' : 's'}: ${chalk.bold(changedFiles.join(', '))}`));
|
|
60
|
+
|
|
61
|
+
const prompt = buildPrompt(diff, files);
|
|
62
|
+
const result = await runAI(prompt, config);
|
|
63
|
+
|
|
64
|
+
spinner.succeed(chalk.green('Review completed!'));
|
|
65
|
+
|
|
66
|
+
let parsed;
|
|
67
|
+
try {
|
|
68
|
+
parsed = JSON.parse(result);
|
|
69
|
+
} catch (jsonError) {
|
|
70
|
+
// Try multiple strategies to extract JSON
|
|
71
|
+
let jsonText = null;
|
|
72
|
+
|
|
73
|
+
// Strategy 1: Find JSON object with proper structure
|
|
74
|
+
const jsonMatch = result.match(/\{[\s\S]*?"issues"[\s\S]*?"typos"[\s\S]*?"improvements"[\s\S]*?\}/);
|
|
75
|
+
if (jsonMatch) {
|
|
76
|
+
jsonText = jsonMatch[0];
|
|
77
|
+
} else {
|
|
78
|
+
// Strategy 2: Find any JSON object
|
|
79
|
+
const anyJsonMatch = result.match(/\{[\s\S]*?\}/);
|
|
80
|
+
if (anyJsonMatch) {
|
|
81
|
+
jsonText = anyJsonMatch[0];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (jsonText) {
|
|
86
|
+
try {
|
|
87
|
+
parsed = JSON.parse(jsonText);
|
|
88
|
+
// Ensure required structure
|
|
89
|
+
if (!parsed.issues) parsed.issues = [];
|
|
90
|
+
if (!parsed.typos) parsed.typos = [];
|
|
91
|
+
if (!parsed.improvements) parsed.improvements = [];
|
|
92
|
+
} catch (extractError) {
|
|
93
|
+
// Create fallback response
|
|
94
|
+
parsed = {
|
|
95
|
+
issues: [`JSON parsing failed. Raw AI response: ${result.substring(0, 200)}...`],
|
|
96
|
+
typos: [],
|
|
97
|
+
improvements: []
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
// No JSON found at all - create fallback response
|
|
102
|
+
parsed = {
|
|
103
|
+
issues: [`AI response format error. Response: ${result.substring(0, 200)}...`],
|
|
104
|
+
typos: [],
|
|
105
|
+
improvements: []
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Display results with better formatting
|
|
111
|
+
console.log(chalk.red.bold("\nšØ Issues"));
|
|
112
|
+
if (parsed.issues.length === 0) {
|
|
113
|
+
console.log(chalk.gray(" No critical issues found"));
|
|
114
|
+
} else {
|
|
115
|
+
parsed.issues.forEach((i, index) =>
|
|
116
|
+
console.log(chalk.red(` ${index + 1}. ${i}`))
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
console.log(chalk.yellow.bold("\nāļø Typos"));
|
|
121
|
+
if (parsed.typos.length === 0) {
|
|
122
|
+
console.log(chalk.gray(" No typos found"));
|
|
123
|
+
} else {
|
|
124
|
+
parsed.typos.forEach((t, index) =>
|
|
125
|
+
console.log(chalk.yellow(` ${index + 1}. ${t}`))
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.log(chalk.cyan.bold("\nš” Improvements"));
|
|
130
|
+
if (parsed.improvements.length === 0) {
|
|
131
|
+
console.log(chalk.gray(" No improvement suggestions"));
|
|
132
|
+
} else {
|
|
133
|
+
parsed.improvements.forEach((i, index) =>
|
|
134
|
+
console.log(chalk.cyan(` ${index + 1}. ${i}`))
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Summary
|
|
139
|
+
const totalCount = parsed.issues.length + parsed.typos.length + parsed.improvements.length;
|
|
140
|
+
if (totalCount === 0) {
|
|
141
|
+
console.log(chalk.green.bold("\n⨠Great job! Your code looks clean and well-written."));
|
|
142
|
+
} else {
|
|
143
|
+
console.log(chalk.blue(`\nš Review Summary: ${parsed.issues.length} issues, ${parsed.typos.length} typos, ${parsed.improvements.length} improvements`));
|
|
144
|
+
}
|
|
145
|
+
} catch (e) {
|
|
146
|
+
spinner.fail(chalk.red("Review failed"));
|
|
147
|
+
console.error(chalk.red(`ā Error: ${e.message}`));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
module.exports = review;
|