@blockdeepanshu/ai-pr-review-cli 1.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.
@@ -0,0 +1,409 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Batch Review Script
5
+ * Automatically reviews large repositories in chunks to avoid rate limits
6
+ */
7
+
8
+ const { execSync } = require('child_process');
9
+ const fs = require('fs');
10
+
11
+ async function batchReview() {
12
+ const isFrontend = process.argv.includes('--frontend');
13
+ const batchSizeArg = process.argv.indexOf('--batch-size');
14
+ const defaultBatchSize = batchSizeArg > -1 ? parseInt(process.argv[batchSizeArg + 1]) : 8;
15
+
16
+ console.log('šŸ¤– Starting Batch AI Review...\n');
17
+ if (isFrontend) {
18
+ console.log('šŸŽØ Frontend optimization enabled');
19
+ }
20
+ console.log('DEBUG: Current directory:', process.cwd());
21
+ console.log('DEBUG: Default batch size:', defaultBatchSize);
22
+
23
+ try {
24
+ // Get all changed files (both staged and unstaged)
25
+ let changedFiles = [];
26
+ try {
27
+ // Try staged files first
28
+ const staged = execSync('git diff --name-only --cached', { encoding: 'utf-8' })
29
+ .split('\n')
30
+ .filter(f => f.trim().length > 0);
31
+
32
+ // Try unstaged files
33
+ const unstaged = execSync('git diff --name-only', { encoding: 'utf-8' })
34
+ .split('\n')
35
+ .filter(f => f.trim().length > 0);
36
+
37
+ // Combine and deduplicate
38
+ changedFiles = [...new Set([...staged, ...unstaged])];
39
+
40
+ console.log('DEBUG: Staged files:', staged);
41
+ console.log('DEBUG: Unstaged files:', unstaged);
42
+ } catch (error) {
43
+ console.log('DEBUG: Git diff failed:', error.message);
44
+ }
45
+
46
+ if (changedFiles.length === 0) {
47
+ console.log('āŒ No changes detected. Nothing to review.');
48
+ console.log('\nšŸ’” To include files for review:');
49
+ console.log(' git add your-file.js # Stage specific file');
50
+ console.log(' git add . # Stage all changes');
51
+ console.log(' git status # See what can be staged');
52
+ console.log('\nšŸ” The tool reviews:');
53
+ console.log(' • Staged files (git add)');
54
+ console.log(' • Uncommitted changes');
55
+ console.log(' • Commits since base branch');
56
+ return;
57
+ }
58
+
59
+ console.log(`šŸ“ Found ${changedFiles.length} changed files`);
60
+ console.log('Files:', changedFiles.join(', '));
61
+
62
+ // Group files by type/directory for logical batching
63
+ const batches = createBatches(changedFiles, defaultBatchSize, isFrontend);
64
+
65
+ console.log(`\nšŸ”„ Creating ${batches.length} review batches...\n`);
66
+
67
+ for (let i = 0; i < batches.length; i++) {
68
+ const batch = batches[i];
69
+ console.log(`--- Batch ${i + 1}/${batches.length}: ${batch.name} ---`);
70
+ console.log(`Files: ${batch.files.join(', ')}`);
71
+
72
+ // Stage only these files
73
+ execSync('git reset'); // Clear staging area
74
+ batch.files.forEach(file => {
75
+ try {
76
+ execSync(`git add "${file}"`);
77
+ } catch (error) {
78
+ console.log(`āš ļø Could not add ${file}: ${error.message}`);
79
+ }
80
+ });
81
+
82
+ // Review this batch
83
+ try {
84
+ console.log('šŸ” Reviewing...');
85
+
86
+ // Check if we're in dry-run mode
87
+ const isDryRun = process.argv.includes('--dry-run');
88
+
89
+ if (isDryRun) {
90
+ console.log('DRY RUN: Would run: node bin/cli.js review');
91
+ console.log('DRY RUN: Simulating AI review...\n');
92
+ } else {
93
+ // Use the local development version instead of global npm package
94
+ const path = require('path');
95
+ const cliPath = path.join(__dirname, 'bin', 'cli.js');
96
+ execSync(`node "${cliPath}" review`, { stdio: 'inherit' });
97
+ }
98
+ } catch (error) {
99
+ console.log(`āŒ Review failed for batch ${i + 1}: ${error.message}`);
100
+ console.log('ā³ Waiting 30 seconds before continuing...\n');
101
+ await new Promise(resolve => setTimeout(resolve, 30000));
102
+ }
103
+
104
+ console.log('\n');
105
+
106
+ // Wait between batches to respect rate limits
107
+ if (i < batches.length - 1) {
108
+ console.log('ā³ Waiting 10 seconds before next batch...\n');
109
+ await new Promise(resolve => setTimeout(resolve, 10000));
110
+ }
111
+ }
112
+
113
+ // Reset staging area
114
+ execSync('git reset');
115
+ console.log('āœ… Batch review completed!');
116
+ console.log('šŸ’” Remember to stage and commit your changes after addressing feedback.');
117
+
118
+ } catch (error) {
119
+ console.error('āŒ Batch review failed:', error.message);
120
+ }
121
+ }
122
+
123
+ function createBatches(files, batchSize = 8, isFrontend = false) {
124
+ const batches = [];
125
+
126
+ // Filter out files we should skip entirely
127
+ const filteredFiles = files.filter(f => {
128
+ const skipPatterns = [
129
+ /node_modules/,
130
+ /\.git/,
131
+ /dist\//,
132
+ /build\//,
133
+ /coverage\//,
134
+ /\.min\.(js|css)$/,
135
+ /bundle.*\.js$/,
136
+ /chunk.*\.js$/,
137
+ /\.map$/,
138
+ /\.log$/
139
+ ];
140
+ return !skipPatterns.some(pattern => pattern.test(f));
141
+ });
142
+
143
+ // Universal grouping (works for all project types)
144
+ let groups;
145
+
146
+ if (isFrontend) {
147
+ // Frontend-specific grouping
148
+ groups = createFrontendGroups(filteredFiles);
149
+ } else {
150
+ // Universal grouping for all project types
151
+ groups = createUniversalGroups(filteredFiles);
152
+ }
153
+
154
+ // Create batches from groups with dynamic batch sizes
155
+ Object.entries(groups).forEach(([groupName, groupFiles]) => {
156
+ if (groupFiles.length === 0) return;
157
+
158
+ // Adjust batch size based on file type and frontend optimization
159
+ let dynamicBatchSize = batchSize;
160
+
161
+ if (isFrontend) {
162
+ // Frontend-optimized batch sizes
163
+ if (groupName === 'packageLock') {
164
+ dynamicBatchSize = 1; // Package-lock is huge, review alone
165
+ } else if (groupName === 'styles') {
166
+ dynamicBatchSize = 15; // CSS files are usually smaller in frontend
167
+ } else if (groupName === 'components') {
168
+ dynamicBatchSize = 5; // React/Vue components can be complex
169
+ } else if (groupName === 'pages') {
170
+ dynamicBatchSize = 4; // Pages are usually larger/complex
171
+ } else if (groupName === 'hooks') {
172
+ dynamicBatchSize = 8; // Hooks are typically focused
173
+ } else if (groupName === 'utils') {
174
+ dynamicBatchSize = 10; // Utility functions are usually small
175
+ } else if (groupName === 'config') {
176
+ dynamicBatchSize = 12; // Config files are small
177
+ } else if (groupName === 'tests') {
178
+ dynamicBatchSize = 6; // Test files can be substantial
179
+ }
180
+ } else {
181
+ // Universal batch sizes for all project types
182
+ if (groupName === 'lockFiles') {
183
+ dynamicBatchSize = 1; // Lock files are huge, review alone
184
+ } else if (groupName === 'api') {
185
+ dynamicBatchSize = 6; // API endpoints can be complex
186
+ } else if (groupName === 'services') {
187
+ dynamicBatchSize = 5; // Services contain business logic
188
+ } else if (groupName === 'models') {
189
+ dynamicBatchSize = 8; // Models are usually focused
190
+ } else if (groupName === 'frontend') {
191
+ dynamicBatchSize = 6; // Frontend components in full-stack
192
+ } else if (groupName === 'utils') {
193
+ dynamicBatchSize = 10; // Utility functions are usually small
194
+ } else if (groupName === 'tests') {
195
+ dynamicBatchSize = 8; // Test files vary in size
196
+ } else if (groupName === 'config') {
197
+ dynamicBatchSize = 12; // Config files are typically small
198
+ } else if (groupName === 'infrastructure') {
199
+ dynamicBatchSize = 7; // Infrastructure files can be complex
200
+ } else if (groupName === 'scripts') {
201
+ dynamicBatchSize = 10; // Scripts are usually focused
202
+ } else if (groupName === 'docs') {
203
+ dynamicBatchSize = 15; // Documentation files are usually text
204
+ } else if (groupName === 'styles') {
205
+ dynamicBatchSize = 12; // CSS files
206
+ }
207
+ }
208
+
209
+ // Split large groups into smaller batches
210
+ for (let i = 0; i < groupFiles.length; i += dynamicBatchSize) {
211
+ const batchFiles = groupFiles.slice(i, i + dynamicBatchSize);
212
+ const batchNumber = Math.floor(i / dynamicBatchSize) + 1;
213
+ const name = groupFiles.length > dynamicBatchSize
214
+ ? `${groupName} (batch ${batchNumber})`
215
+ : groupName;
216
+
217
+ batches.push({
218
+ name,
219
+ files: batchFiles
220
+ });
221
+ }
222
+ });
223
+
224
+ // Sort batches by priority (most important first)
225
+ const priorityOrder = isFrontend ? [
226
+ 'components', 'pages', 'hooks', 'utils', 'config',
227
+ 'styles', 'tests', 'assets', 'packageLock', 'docs', 'other'
228
+ ] : [
229
+ 'api', 'services', 'models', 'frontend', 'utils', 'config',
230
+ 'tests', 'infrastructure', 'scripts', 'styles', 'lockFiles', 'docs', 'other'
231
+ ];
232
+
233
+ batches.sort((a, b) => {
234
+ const aPriority = priorityOrder.findIndex(p => a.name.includes(p));
235
+ const bPriority = priorityOrder.findIndex(p => b.name.includes(p));
236
+ return aPriority - bPriority;
237
+ });
238
+
239
+ return batches;
240
+ }
241
+
242
+ function createFrontendGroups(filteredFiles) {
243
+ return {
244
+ components: filteredFiles.filter(f =>
245
+ /\.(js|jsx|ts|tsx|vue)$/.test(f) &&
246
+ /\/(components|ui|widgets)\//.test(f)
247
+ ),
248
+ pages: filteredFiles.filter(f =>
249
+ /\.(js|jsx|ts|tsx|vue)$/.test(f) &&
250
+ /\/(pages|views|screens|routes)\//.test(f)
251
+ ),
252
+ hooks: filteredFiles.filter(f =>
253
+ /\.(js|jsx|ts|tsx)$/.test(f) &&
254
+ /\/(hooks|composables|custom)\//.test(f)
255
+ ),
256
+ utils: filteredFiles.filter(f =>
257
+ /\.(js|jsx|ts|tsx)$/.test(f) &&
258
+ /\/(utils|helpers|lib|shared)\//.test(f)
259
+ ),
260
+ styles: filteredFiles.filter(f =>
261
+ /\.(css|scss|sass|less|stylus|module\.css)$/.test(f)
262
+ ),
263
+ config: filteredFiles.filter(f => {
264
+ // Split package.json separately due to size
265
+ if (f.includes('package-lock.json')) return false;
266
+ return /\.(json|yml|yaml|toml|js|ts)$/.test(f) &&
267
+ /(config|settings|env|webpack|vite|rollup|babel)/.test(f);
268
+ }),
269
+ packageLock: filteredFiles.filter(f => f.includes('package-lock.json')),
270
+ tests: filteredFiles.filter(f =>
271
+ /\.(test|spec)\.(js|ts|jsx|tsx)$/.test(f) ||
272
+ /\/__tests__\//.test(f)
273
+ ),
274
+ docs: filteredFiles.filter(f => /\.(md|txt|rst)$/.test(f)),
275
+ assets: filteredFiles.filter(f =>
276
+ /\/(assets|static|public)\/.*\.(js|ts|jsx|tsx|css|scss)$/.test(f)
277
+ ),
278
+ other: filteredFiles.filter(f => {
279
+ const assigned = [
280
+ /(components|ui|widgets|pages|views|screens|routes|hooks|composables|custom|utils|helpers|lib|shared)\//,
281
+ /\.(css|scss|sass|less|stylus|module\.css)$/,
282
+ /(config|settings|env|webpack|vite|rollup|babel)/,
283
+ /package-lock\.json/,
284
+ /\.(test|spec)\.(js|ts|jsx|tsx)$/,
285
+ /\/__tests__\//,
286
+ /\.(md|txt|rst)$/,
287
+ /\/(assets|static|public)\/.*\.(js|ts|jsx|tsx|css|scss)$/
288
+ ];
289
+ return !assigned.some(pattern => pattern.test(f));
290
+ })
291
+ };
292
+
293
+ return groups;
294
+ }
295
+
296
+ function createUniversalGroups(filteredFiles) {
297
+ return {
298
+ // Backend/API files
299
+ api: filteredFiles.filter(f =>
300
+ /\.(js|ts|py|java|go|rb|php|cs)$/.test(f) &&
301
+ /\/(api|routes|controllers|endpoints|handlers)\//.test(f)
302
+ ),
303
+
304
+ // Services and business logic
305
+ services: filteredFiles.filter(f =>
306
+ /\.(js|ts|py|java|go|rb|php|cs)$/.test(f) &&
307
+ /\/(services|business|logic|core|domain)\//.test(f)
308
+ ),
309
+
310
+ // Models and database
311
+ models: filteredFiles.filter(f =>
312
+ /\.(js|ts|py|java|go|rb|php|cs|sql)$/.test(f) &&
313
+ /\/(models|entities|schemas|database|db|migrations)\//.test(f)
314
+ ),
315
+
316
+ // Frontend components (for full-stack projects)
317
+ frontend: filteredFiles.filter(f =>
318
+ /\.(js|jsx|ts|tsx|vue)$/.test(f) &&
319
+ /\/(components|pages|views|screens|ui)\//.test(f)
320
+ ),
321
+
322
+ // Utilities and helpers
323
+ utils: filteredFiles.filter(f =>
324
+ /\.(js|ts|py|java|go|rb|php|cs)$/.test(f) &&
325
+ /\/(utils|helpers|lib|shared|common)\//.test(f)
326
+ ),
327
+
328
+ // Tests
329
+ tests: filteredFiles.filter(f =>
330
+ /\.(test|spec)\.(js|ts|py|java|go|rb|php|cs)$/.test(f) ||
331
+ /\/(tests?|__tests__|spec)\//.test(f) ||
332
+ /test_.*\.py$/.test(f)
333
+ ),
334
+
335
+ // Configuration files
336
+ config: filteredFiles.filter(f => {
337
+ if (f.includes('package-lock.json') || f.includes('yarn.lock') || f.includes('poetry.lock')) return false;
338
+ return /\.(json|yml|yaml|toml|ini|cfg|conf|properties|xml)$/.test(f) ||
339
+ /(config|settings|env)/.test(f);
340
+ }),
341
+
342
+ // Lock files (handled separately due to size)
343
+ lockFiles: filteredFiles.filter(f =>
344
+ /\.(lock|lock\.json)$/.test(f) ||
345
+ f.includes('package-lock.json') ||
346
+ f.includes('yarn.lock') ||
347
+ f.includes('poetry.lock') ||
348
+ f.includes('Gemfile.lock')
349
+ ),
350
+
351
+ // Infrastructure and DevOps
352
+ infrastructure: filteredFiles.filter(f =>
353
+ /\.(tf|yml|yaml)$/.test(f) && /(terraform|ansible|kubernetes|docker|k8s)/.test(f) ||
354
+ /Dockerfile|docker-compose|\.tf$/.test(f)
355
+ ),
356
+
357
+ // Scripts and automation
358
+ scripts: filteredFiles.filter(f =>
359
+ /\.(sh|bash|ps1|py|rb|js)$/.test(f) &&
360
+ /\/(scripts|bin|tools|automation)\//.test(f) ||
361
+ /Makefile|Rakefile/.test(f)
362
+ ),
363
+
364
+ // Documentation
365
+ docs: filteredFiles.filter(f =>
366
+ /\.(md|txt|rst|adoc|tex)$/.test(f) ||
367
+ /\/(docs|documentation)\//.test(f) ||
368
+ /README|CHANGELOG|LICENSE/.test(f)
369
+ ),
370
+
371
+ // Styles (for any project with styling)
372
+ styles: filteredFiles.filter(f =>
373
+ /\.(css|scss|sass|less|stylus)$/.test(f)
374
+ ),
375
+
376
+ // Other files
377
+ other: filteredFiles.filter(f => {
378
+ const assigned = [
379
+ /\/(api|routes|controllers|endpoints|handlers)\//,
380
+ /\/(services|business|logic|core|domain)\//,
381
+ /\/(models|entities|schemas|database|db|migrations)\//,
382
+ /\/(components|pages|views|screens|ui)\//,
383
+ /\/(utils|helpers|lib|shared|common)\//,
384
+ /\.(test|spec)\.(js|ts|py|java|go|rb|php|cs)$/,
385
+ /\/(tests?|__tests__|spec)\//,
386
+ /test_.*\.py$/,
387
+ /\.(json|yml|yaml|toml|ini|cfg|conf|properties|xml)$/,
388
+ /\.(lock|lock\.json)$/,
389
+ /package-lock\.json|yarn\.lock|poetry\.lock|Gemfile\.lock/,
390
+ /\.(tf|yml|yaml).*terraform|ansible|kubernetes|docker|k8s/,
391
+ /Dockerfile|docker-compose|\.tf$/,
392
+ /\.(sh|bash|ps1|py|rb|js).*\/(scripts|bin|tools|automation)\//,
393
+ /Makefile|Rakefile/,
394
+ /\.(md|txt|rst|adoc|tex)$/,
395
+ /\/(docs|documentation)\//,
396
+ /README|CHANGELOG|LICENSE/,
397
+ /\.(css|scss|sass|less|stylus)$/
398
+ ];
399
+ return !assigned.some(pattern => pattern.test(f));
400
+ })
401
+ };
402
+
403
+ return groups;
404
+ }
405
+
406
+ // Run the batch review
407
+ batchReview().catch(console.error);
408
+
409
+
package/bin/cli.js ADDED
@@ -0,0 +1,113 @@
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
+ program
60
+ .command("batch")
61
+ .description("šŸ”„ Review large repositories in batches to avoid rate limits")
62
+ .option("--dry-run", "Show what would be reviewed without actually running AI review")
63
+ .option("--frontend", "Optimize batching for frontend projects (React, Vue, Angular)")
64
+ .option("--batch-size <number>", "Number of files per batch (default: 8)", "8")
65
+ .action(async (options) => {
66
+ const { default: chalk } = await import("chalk");
67
+ console.log(chalk.blue.bold("\nšŸ”„ Batch AI Review"));
68
+ console.log(chalk.gray("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"));
69
+
70
+ try {
71
+ const { spawn } = require('child_process');
72
+ const path = require('path');
73
+ const batchScriptPath = path.join(__dirname, '..', 'batch-review.js');
74
+
75
+ const args = [batchScriptPath];
76
+ if (options.dryRun) {
77
+ args.push('--dry-run');
78
+ }
79
+ if (options.frontend) {
80
+ args.push('--frontend');
81
+ }
82
+ if (options.batchSize) {
83
+ args.push('--batch-size', options.batchSize);
84
+ }
85
+
86
+ const child = spawn('node', args, { stdio: 'inherit' });
87
+ child.on('close', (code) => {
88
+ if (code !== 0) {
89
+ console.error(chalk.red(`\nāŒ Batch review exited with code ${code}`));
90
+ }
91
+ });
92
+ } catch (error) {
93
+ console.error(chalk.red(`āŒ Failed to run batch review: ${error.message}`));
94
+ }
95
+ });
96
+
97
+ // Also support direct usage without subcommand
98
+ program
99
+ .option("-b, --base <branch>", "Base branch to compare against")
100
+ .option("-m, --model <model>", "AI model to use")
101
+ .option("--provider <provider>", "AI provider")
102
+ .option("-v, --verbose", "Show detailed output")
103
+ .action(async (options) => {
104
+ // If no specific command was run, default to review
105
+ if (process.argv.length > 2 && !process.argv.includes('review')) {
106
+ const { default: chalk } = await import("chalk");
107
+ console.log(chalk.blue.bold("\nšŸ¤– AI PR Review"));
108
+ console.log(chalk.gray("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"));
109
+ await review(options);
110
+ }
111
+ });
112
+
113
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@blockdeepanshu/ai-pr-review-cli",
3
+ "version": "1.2.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
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "type": "commonjs",
14
+ "engines": {
15
+ "node": ">=16.0.0"
16
+ },
17
+ "scripts": {
18
+ "start": "node bin/cli.js",
19
+ "batch-review": "node batch-review.js",
20
+ "test": "echo \"Error: no test specified\" && exit 1",
21
+ "prepublishOnly": "echo 'Ready to publish!'"
22
+ },
23
+ "keywords": [
24
+ "ai",
25
+ "artificial-intelligence",
26
+ "pr-review",
27
+ "pull-request",
28
+ "code-review",
29
+ "cli",
30
+ "git",
31
+ "github",
32
+ "openai",
33
+ "gpt",
34
+ "developer-tools",
35
+ "automation"
36
+ ],
37
+ "author": {
38
+ "name": "Deepanshu Chauhan",
39
+ "email": "chauhandeepanshu336@gmail.com"
40
+ },
41
+ "license": "MIT",
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "https://github.com/blockDeepanshu/ai-pr-review.git"
45
+ },
46
+ "bugs": {
47
+ "url": "https://github.com/blockDeepanshu/ai-pr-review/issues"
48
+ },
49
+ "homepage": "https://github.com/blockDeepanshu/ai-pr-review#readme",
50
+ "files": [
51
+ "bin/",
52
+ "src/",
53
+ "batch-review.js",
54
+ "README.md",
55
+ "CHANGELOG.md",
56
+ "LICENSE"
57
+ ],
58
+ "preferGlobal": true,
59
+ "dependencies": {
60
+ "axios": "^1.6.0",
61
+ "chalk": "^5.3.0",
62
+ "commander": "^11.0.0",
63
+ "dotenv": "^17.3.1",
64
+ "ora": "^6.3.1",
65
+ "simple-git": "^3.19.1"
66
+ },
67
+ "devDependencies": {},
68
+ "funding": {
69
+ "type": "individual",
70
+ "url": "https://github.com/blockDeepanshu"
71
+ }
72
+ }
package/src/ai.js ADDED
@@ -0,0 +1,38 @@
1
+ const axios = require("axios");
2
+ require("dotenv").config();
3
+ async function openai(prompt, model) {
4
+ try {
5
+ const res = await axios.post(
6
+ "https://api.openai.com/v1/chat/completions",
7
+ {
8
+ model,
9
+ messages: [{ role: "user", content: prompt }],
10
+ temperature: 0.2,
11
+ },
12
+ {
13
+ headers: {
14
+ Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
15
+ },
16
+ },
17
+ );
18
+
19
+ return res.data.choices[0].message.content;
20
+ } catch (error) {
21
+ if (error.response?.status === 429) {
22
+ const resetTime = error.response.headers['x-ratelimit-reset-requests'] || 'unknown';
23
+ throw new Error(`Rate limit exceeded. Please wait and try again. Reset time: ${resetTime}. Consider upgrading your OpenAI plan for higher limits.`);
24
+ } else if (error.response?.status === 401) {
25
+ throw new Error('Invalid OpenAI API key. Please check your OPENAI_API_KEY environment variable.');
26
+ } else if (error.response?.status === 403) {
27
+ throw new Error('OpenAI API access denied. Check your API key permissions.');
28
+ } else {
29
+ throw new Error(`OpenAI API error: ${error.response?.data?.error?.message || error.message}`);
30
+ }
31
+ }
32
+ }
33
+
34
+ async function runAI(prompt, config) {
35
+ return openai(prompt, config.model);
36
+ }
37
+
38
+ 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 };