git-diff-ai-reviewer 1.1.3 → 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.
- package/README.md +114 -8
- package/bin/review.js +116 -10
- package/package.json +1 -1
- package/src/claude.js +65 -15
- package/src/config.js +1 -0
- package/src/context.js +567 -0
- package/src/gemini.js +66 -12
- package/src/hooks.js +186 -0
- package/src/index.js +32 -1
- package/src/prompts.js +68 -4
- package/src/provider.js +2 -0
- package/src/rules.js +6 -0
package/README.md
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
# git-diff-ai-reviewer
|
|
2
2
|
|
|
3
|
-
AI-powered code review tool using **Claude** or **Gemini** APIs. Reviews git branch diffs
|
|
3
|
+
AI-powered code review tool using **Claude** or **Gemini** APIs. Reviews git branch diffs with full code context, iterative AI follow-ups, and structured severity-tagged feedback.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔍 **Context-aware review** — sends not just the diff, but the full functions, imports, and callers of changed code
|
|
8
|
+
- 🔄 **Iterative context gathering** — if the AI needs more context, it asks for it automatically (configurable rounds)
|
|
9
|
+
- 🚀 **Pre-push hook** — automatically review code before every `git push`
|
|
10
|
+
- 🎯 **Severity levels** — CRITICAL, WARNING, SUGGESTION with CI-friendly exit codes
|
|
11
|
+
- 🔧 **Fix prompt generation** — auto-generate prompts for AI agents to fix issues
|
|
12
|
+
- ⚙️ **Configurable rules** — built-in presets or custom review rules
|
|
4
13
|
|
|
5
14
|
## Installation
|
|
6
15
|
|
|
@@ -59,7 +68,7 @@ git-diff-ai-reviewer review --provider gemini
|
|
|
59
68
|
# Review against a specific base branch
|
|
60
69
|
git-diff-ai-reviewer review --base develop
|
|
61
70
|
|
|
62
|
-
# Preview diff without calling API
|
|
71
|
+
# Preview diff and context without calling API
|
|
63
72
|
git-diff-ai-reviewer review --dry-run
|
|
64
73
|
|
|
65
74
|
# Generate fix prompt from latest review
|
|
@@ -79,6 +88,71 @@ Add to your `package.json`:
|
|
|
79
88
|
}
|
|
80
89
|
```
|
|
81
90
|
|
|
91
|
+
## Pre-Push Hook (Review on Push)
|
|
92
|
+
|
|
93
|
+
Automatically run an AI code review before every `git push`. If CRITICAL issues are found, the push is blocked.
|
|
94
|
+
|
|
95
|
+
### Install the hook
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
git-diff-ai-reviewer hook install
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
This creates a `.git/hooks/pre-push` script that runs the review before each push.
|
|
102
|
+
|
|
103
|
+
### Skip a review
|
|
104
|
+
|
|
105
|
+
Sometimes you need to push without waiting for a review:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# Option 1: Environment variable
|
|
109
|
+
GIT_SKIP_REVIEW=1 git push
|
|
110
|
+
|
|
111
|
+
# Option 2: Git's built-in flag (skips all hooks)
|
|
112
|
+
git push --no-verify
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Uninstall the hook
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
git-diff-ai-reviewer hook uninstall
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Check hook status
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
git-diff-ai-reviewer hook status
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### How it works
|
|
128
|
+
|
|
129
|
+
| Review result | Push behavior |
|
|
130
|
+
|---------------|---------------|
|
|
131
|
+
| ✅ No critical issues | Push proceeds |
|
|
132
|
+
| ❌ CRITICAL issues found | Push blocked (exit code 1) |
|
|
133
|
+
| ⚠️ Review error (API down, etc.) | Push proceeds (fails open) |
|
|
134
|
+
|
|
135
|
+
## Context-Aware Review
|
|
136
|
+
|
|
137
|
+
Unlike simple diff-only review tools, `git-diff-ai-reviewer` automatically gathers context for each change:
|
|
138
|
+
|
|
139
|
+
1. **Function context** — extracts the full body of every function/class that was modified
|
|
140
|
+
2. **Imports** — includes the import/require statements from each changed file
|
|
141
|
+
3. **Callers** — finds where modified functions are used across the project (via `git grep`)
|
|
142
|
+
|
|
143
|
+
This context is sent alongside the diff, giving the AI a much deeper understanding of the changes.
|
|
144
|
+
|
|
145
|
+
### Iterative Context Requests
|
|
146
|
+
|
|
147
|
+
If the AI determines it needs more context to give a thorough review, it can request:
|
|
148
|
+
|
|
149
|
+
- Specific file ranges (`FILE: path/to/file.js LINES: 10-50`)
|
|
150
|
+
- Function definitions (`FILE: path/to/file.js FUNCTION: helperName`)
|
|
151
|
+
- Caller/usage information (`CALLERS: functionName`)
|
|
152
|
+
- Full file contents (`FILE: path/to/config.json`)
|
|
153
|
+
|
|
154
|
+
The tool automatically fulfills these requests and continues the conversation, up to `maxContextRounds` times (default: 3).
|
|
155
|
+
|
|
82
156
|
## Providers
|
|
83
157
|
|
|
84
158
|
| Provider | Env Variable | Default Model |
|
|
@@ -135,8 +209,12 @@ Create `.ai-review.config.json` in your project root:
|
|
|
135
209
|
"baseBranch": "main",
|
|
136
210
|
"model": "gemini-2.0-flash",
|
|
137
211
|
"maxTokens": 4096,
|
|
212
|
+
"maxContextRounds": 3,
|
|
138
213
|
"outputDir": "./reviews",
|
|
139
|
-
"reviewRules":
|
|
214
|
+
"reviewRules": {
|
|
215
|
+
"preset": "standard",
|
|
216
|
+
"extend": []
|
|
217
|
+
}
|
|
140
218
|
}
|
|
141
219
|
```
|
|
142
220
|
|
|
@@ -148,23 +226,39 @@ Create `.ai-review.config.json` in your project root:
|
|
|
148
226
|
| `baseBranch` | string | `"main"` | Branch to compare against |
|
|
149
227
|
| `model` | string | per-provider | Override the AI model |
|
|
150
228
|
| `maxTokens` | number | `4096` | Max response tokens |
|
|
229
|
+
| `maxContextRounds` | number | `3` | Max iterative context follow-up rounds |
|
|
151
230
|
| `outputDir` | string | `"./reviews"` | Where to write output files |
|
|
152
231
|
| `reviewRules` | string\|array | `"standard"` | Preset name or custom rules array |
|
|
153
232
|
|
|
154
233
|
### Custom Rules Example
|
|
155
234
|
|
|
235
|
+
You can completely replace the rules by providing an array:
|
|
236
|
+
|
|
156
237
|
```json
|
|
157
238
|
{
|
|
158
239
|
"provider": "gemini",
|
|
159
240
|
"reviewRules": [
|
|
160
241
|
"Use camelCase for variable names",
|
|
161
|
-
"All functions must have JSDoc comments"
|
|
162
|
-
"No inline styles in React components",
|
|
163
|
-
"Database queries must use parameterized statements"
|
|
242
|
+
"All functions must have JSDoc comments"
|
|
164
243
|
]
|
|
165
244
|
}
|
|
166
245
|
```
|
|
167
246
|
|
|
247
|
+
Or **extend** an existing preset with your own project-specific rules:
|
|
248
|
+
|
|
249
|
+
```json
|
|
250
|
+
{
|
|
251
|
+
"provider": "gemini",
|
|
252
|
+
"reviewRules": {
|
|
253
|
+
"preset": "standard",
|
|
254
|
+
"extend": [
|
|
255
|
+
"No inline styles in React components",
|
|
256
|
+
"Database queries must use parameterized statements"
|
|
257
|
+
]
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
168
262
|
## Programmatic API
|
|
169
263
|
|
|
170
264
|
```javascript
|
|
@@ -173,18 +267,30 @@ const {
|
|
|
173
267
|
getDiff,
|
|
174
268
|
getChangedFiles,
|
|
175
269
|
detectProvider,
|
|
270
|
+
buildInitialContext,
|
|
271
|
+
formatContextForPrompt,
|
|
272
|
+
installHook,
|
|
176
273
|
STANDARD_RULES,
|
|
177
274
|
} = require('git-diff-ai-reviewer');
|
|
178
275
|
|
|
276
|
+
// Review with context
|
|
179
277
|
const diff = getDiff('main');
|
|
180
278
|
const files = getChangedFiles('main');
|
|
181
|
-
const
|
|
279
|
+
const context = buildInitialContext(diff, files);
|
|
280
|
+
const formattedContext = formatContextForPrompt(context);
|
|
281
|
+
|
|
182
282
|
const review = await reviewCode(diff, {
|
|
183
|
-
provider,
|
|
283
|
+
provider: 'gemini',
|
|
184
284
|
changedFiles: files,
|
|
185
285
|
branchName: 'feature/xyz',
|
|
286
|
+
formattedContext,
|
|
287
|
+
maxContextRounds: 3,
|
|
186
288
|
reviewRules: STANDARD_RULES,
|
|
187
289
|
});
|
|
290
|
+
|
|
291
|
+
// Install pre-push hook programmatically
|
|
292
|
+
const result = installHook();
|
|
293
|
+
console.log(result.message);
|
|
188
294
|
```
|
|
189
295
|
|
|
190
296
|
## Output Files
|
package/bin/review.js
CHANGED
|
@@ -8,6 +8,8 @@ const { reviewCode, parseSeverityCounts, detectProvider, PROVIDERS, DEFAULT_MODE
|
|
|
8
8
|
const { buildFixPrompt } = require('../src/prompts');
|
|
9
9
|
const { loadConfig } = require('../src/config');
|
|
10
10
|
const { writeReviewOutput, writeFixPrompt, findLatestReview } = require('../src/output');
|
|
11
|
+
const { buildInitialContext, formatContextForPrompt } = require('../src/context');
|
|
12
|
+
const { installHook, uninstallHook, hookStatus } = require('../src/hooks');
|
|
11
13
|
|
|
12
14
|
// ─── Argument Parsing ────────────────────────────────────────────────
|
|
13
15
|
|
|
@@ -45,10 +47,15 @@ function parseArgs(argv) {
|
|
|
45
47
|
case '-h':
|
|
46
48
|
flags.help = true;
|
|
47
49
|
break;
|
|
50
|
+
case '--skip-review':
|
|
51
|
+
flags.skipReview = true;
|
|
52
|
+
break;
|
|
48
53
|
default:
|
|
49
54
|
if (!args[i].startsWith('-') && !command) {
|
|
50
55
|
command = args[i];
|
|
51
|
-
} else {
|
|
56
|
+
} else if (!args[i].startsWith('-') && command === 'hook') {
|
|
57
|
+
// Hook subcommands are parsed separately
|
|
58
|
+
} else if (args[i].startsWith('-')) {
|
|
52
59
|
console.warn(`Unknown option: ${args[i]}`);
|
|
53
60
|
}
|
|
54
61
|
}
|
|
@@ -65,11 +72,14 @@ function showHelp() {
|
|
|
65
72
|
║ 🤖 AI Code Review CLI ║
|
|
66
73
|
╚══════════════════════════════════════════════════════╝
|
|
67
74
|
|
|
68
|
-
Usage: ai-
|
|
75
|
+
Usage: git-diff-ai-reviewer <command> [options]
|
|
69
76
|
|
|
70
77
|
Commands:
|
|
71
|
-
review
|
|
72
|
-
fix
|
|
78
|
+
review Review code changes using AI (default)
|
|
79
|
+
fix Generate a fix prompt from the latest review
|
|
80
|
+
hook install Install pre-push git hook (review before push)
|
|
81
|
+
hook uninstall Remove the pre-push git hook
|
|
82
|
+
hook status Check if the pre-push hook is installed
|
|
73
83
|
|
|
74
84
|
Options:
|
|
75
85
|
-b, --base <branch> Base branch to compare against (default: main)
|
|
@@ -94,12 +104,19 @@ Review Rule Presets:
|
|
|
94
104
|
standard Balanced everyday review (default)
|
|
95
105
|
comprehensive Full audit for critical code
|
|
96
106
|
|
|
107
|
+
Pre-Push Hook:
|
|
108
|
+
Install: git-diff-ai-reviewer hook install
|
|
109
|
+
Uninstall: git-diff-ai-reviewer hook uninstall
|
|
110
|
+
Skip once: GIT_SKIP_REVIEW=1 git push
|
|
111
|
+
Skip once: git push --no-verify
|
|
112
|
+
|
|
97
113
|
Examples:
|
|
98
|
-
ai-
|
|
99
|
-
ai-
|
|
100
|
-
ai-
|
|
101
|
-
ai-
|
|
102
|
-
ai-
|
|
114
|
+
git-diff-ai-reviewer review Review with auto-detected provider
|
|
115
|
+
git-diff-ai-reviewer review --provider gemini Review using Gemini
|
|
116
|
+
git-diff-ai-reviewer review --base develop Review against develop branch
|
|
117
|
+
git-diff-ai-reviewer review --dry-run Preview without calling API
|
|
118
|
+
git-diff-ai-reviewer fix Generate fix prompt from latest review
|
|
119
|
+
git-diff-ai-reviewer hook install Enable review-on-push
|
|
103
120
|
|
|
104
121
|
Config File (.ai-review.config.json):
|
|
105
122
|
{
|
|
@@ -107,6 +124,7 @@ Config File (.ai-review.config.json):
|
|
|
107
124
|
"baseBranch": "main",
|
|
108
125
|
"model": "",
|
|
109
126
|
"maxTokens": 4096,
|
|
127
|
+
"maxContextRounds": 3,
|
|
110
128
|
"outputDir": "./reviews",
|
|
111
129
|
"reviewRules": "standard"
|
|
112
130
|
}
|
|
@@ -150,14 +168,30 @@ async function commandReview(config, flags) {
|
|
|
150
168
|
console.log(` Diff size: ${diff.length} characters`);
|
|
151
169
|
console.log('─'.repeat(40));
|
|
152
170
|
|
|
171
|
+
// Gather code context
|
|
172
|
+
console.log('\n📦 Gathering code context...');
|
|
173
|
+
const context = buildInitialContext(diff, changedFiles);
|
|
174
|
+
const formattedContext = formatContextForPrompt(context);
|
|
175
|
+
|
|
176
|
+
const contextFunctions = context.files.reduce((sum, f) => sum + f.functions.length, 0);
|
|
177
|
+
const contextCallers = context.files.reduce((sum, f) => sum + f.callers.length, 0);
|
|
178
|
+
console.log(` ${contextFunctions} function(s) extracted, ${contextCallers} caller group(s) found`);
|
|
179
|
+
if (formattedContext) {
|
|
180
|
+
console.log(` Context size: ${formattedContext.length} characters`);
|
|
181
|
+
}
|
|
182
|
+
|
|
153
183
|
// Dry run mode — show what would be sent
|
|
154
184
|
if (flags.dryRun) {
|
|
155
185
|
console.log('\n📋 DRY RUN — Diff extracted, API not called.\n');
|
|
156
186
|
console.log(`Provider: ${provider} | Model: ${model}`);
|
|
157
187
|
console.log(`Rules: ${config.reviewRules.length} active`);
|
|
188
|
+
console.log(`Context: ${contextFunctions} function(s), ${contextCallers} caller group(s)`);
|
|
158
189
|
console.log('Changed files:');
|
|
159
190
|
changedFiles.forEach(f => console.log(` - ${f}`));
|
|
160
191
|
console.log(`\nDiff preview (first 500 chars):\n${diff.slice(0, 500)}...`);
|
|
192
|
+
if (formattedContext) {
|
|
193
|
+
console.log(`\nContext preview (first 500 chars):\n${formattedContext.slice(0, 500)}...`);
|
|
194
|
+
}
|
|
161
195
|
console.log('\n💡 Remove --dry-run to send to AI for review.');
|
|
162
196
|
process.exit(0);
|
|
163
197
|
}
|
|
@@ -173,6 +207,8 @@ async function commandReview(config, flags) {
|
|
|
173
207
|
model,
|
|
174
208
|
maxTokens: config.maxTokens,
|
|
175
209
|
reviewRules: config.reviewRules,
|
|
210
|
+
formattedContext,
|
|
211
|
+
maxContextRounds: config.maxContextRounds,
|
|
176
212
|
});
|
|
177
213
|
|
|
178
214
|
// Parse severity counts
|
|
@@ -245,6 +281,67 @@ async function commandFix(config, flags) {
|
|
|
245
281
|
console.log('');
|
|
246
282
|
}
|
|
247
283
|
|
|
284
|
+
// ─── Hook Command ───────────────────────────────────────────────────
|
|
285
|
+
|
|
286
|
+
function commandHook(subcommand) {
|
|
287
|
+
switch (subcommand) {
|
|
288
|
+
case 'install': {
|
|
289
|
+
const result = installHook();
|
|
290
|
+
if (result.success) {
|
|
291
|
+
console.log('');
|
|
292
|
+
console.log('✅ ' + result.message);
|
|
293
|
+
console.log(` 📄 ${result.path}`);
|
|
294
|
+
console.log('');
|
|
295
|
+
console.log(' The AI review will run automatically before each push.');
|
|
296
|
+
console.log(' To skip a review:');
|
|
297
|
+
console.log(' GIT_SKIP_REVIEW=1 git push');
|
|
298
|
+
console.log(' git push --no-verify');
|
|
299
|
+
console.log('');
|
|
300
|
+
} else {
|
|
301
|
+
console.error('');
|
|
302
|
+
console.error('❌ ' + result.message);
|
|
303
|
+
console.error('');
|
|
304
|
+
process.exit(1);
|
|
305
|
+
}
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
case 'uninstall': {
|
|
309
|
+
const result = uninstallHook();
|
|
310
|
+
if (result.success) {
|
|
311
|
+
console.log('');
|
|
312
|
+
console.log('✅ ' + result.message);
|
|
313
|
+
console.log('');
|
|
314
|
+
} else {
|
|
315
|
+
console.error('');
|
|
316
|
+
console.error('❌ ' + result.message);
|
|
317
|
+
console.error('');
|
|
318
|
+
process.exit(1);
|
|
319
|
+
}
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
case 'status': {
|
|
323
|
+
const result = hookStatus();
|
|
324
|
+
console.log('');
|
|
325
|
+
if (!result.installed) {
|
|
326
|
+
console.log('📋 Pre-push hook: not installed');
|
|
327
|
+
console.log(' Run "git-diff-ai-reviewer hook install" to enable.');
|
|
328
|
+
} else if (result.ours) {
|
|
329
|
+
console.log('📋 Pre-push hook: ✅ installed (by git-diff-ai-reviewer)');
|
|
330
|
+
console.log(` 📄 ${result.path}`);
|
|
331
|
+
} else {
|
|
332
|
+
console.log('📋 Pre-push hook: ⚠️ installed (by another tool)');
|
|
333
|
+
console.log(` 📄 ${result.path}`);
|
|
334
|
+
}
|
|
335
|
+
console.log('');
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
default:
|
|
339
|
+
console.error('❌ Unknown hook subcommand: ' + (subcommand || '(none)'));
|
|
340
|
+
console.error(' Usage: git-diff-ai-reviewer hook <install|uninstall|status>');
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
248
345
|
// ─── Main ───────────────────────────────────────────────────────────
|
|
249
346
|
|
|
250
347
|
async function main() {
|
|
@@ -264,6 +361,15 @@ async function main() {
|
|
|
264
361
|
// Load config
|
|
265
362
|
const config = loadConfig(flags.configPath);
|
|
266
363
|
|
|
364
|
+
// Handle 'hook' command — parse subcommand from argv
|
|
365
|
+
if (command === 'hook') {
|
|
366
|
+
const args = process.argv.slice(2);
|
|
367
|
+
const hookIdx = args.indexOf('hook');
|
|
368
|
+
const subcommand = args[hookIdx + 1];
|
|
369
|
+
commandHook(subcommand);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
267
373
|
switch (command) {
|
|
268
374
|
case 'review':
|
|
269
375
|
await commandReview(config, flags);
|
|
@@ -273,7 +379,7 @@ async function main() {
|
|
|
273
379
|
break;
|
|
274
380
|
default:
|
|
275
381
|
console.error(`❌ Unknown command: ${command}`);
|
|
276
|
-
console.error(' Run "ai-
|
|
382
|
+
console.error(' Run "git-diff-ai-reviewer --help" for usage info.');
|
|
277
383
|
process.exit(1);
|
|
278
384
|
}
|
|
279
385
|
}
|
package/package.json
CHANGED
package/src/claude.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
const { buildReviewSystemPrompt, buildReviewUserPrompt } = require('./prompts');
|
|
1
|
+
const { buildReviewSystemPrompt, buildReviewUserPromptWithContext, buildReviewUserPrompt, buildFollowUpContextMessage } = require('./prompts');
|
|
2
|
+
const { fulfillContextRequest, formatFollowUpContext } = require('./context');
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Dynamically load the Anthropic SDK.
|
|
@@ -32,7 +33,7 @@ function createClient() {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
|
-
* Send code diff to Claude for review.
|
|
36
|
+
* Send code diff to Claude for review with iterative context gathering.
|
|
36
37
|
* @param {string} diff - The git diff content
|
|
37
38
|
* @param {Object} options
|
|
38
39
|
* @param {string[]} options.changedFiles - List of changed file paths
|
|
@@ -40,6 +41,8 @@ function createClient() {
|
|
|
40
41
|
* @param {string} [options.model] - Claude model to use
|
|
41
42
|
* @param {number} [options.maxTokens] - Max response tokens
|
|
42
43
|
* @param {string[]} [options.reviewRules] - Custom review rules
|
|
44
|
+
* @param {string} [options.formattedContext] - Pre-formatted context string
|
|
45
|
+
* @param {number} [options.maxContextRounds] - Max follow-up context rounds
|
|
43
46
|
* @returns {Promise<string>} The review text
|
|
44
47
|
*/
|
|
45
48
|
async function reviewCode(diff, options = {}) {
|
|
@@ -49,28 +52,74 @@ async function reviewCode(diff, options = {}) {
|
|
|
49
52
|
model = 'claude-sonnet-4-20250514',
|
|
50
53
|
maxTokens = 4096,
|
|
51
54
|
reviewRules = [],
|
|
55
|
+
formattedContext = '',
|
|
56
|
+
maxContextRounds = 3,
|
|
52
57
|
} = options;
|
|
53
58
|
|
|
54
59
|
const client = createClient();
|
|
55
60
|
|
|
56
61
|
const systemPrompt = buildReviewSystemPrompt({ reviewRules });
|
|
57
|
-
|
|
62
|
+
|
|
63
|
+
// Build initial message — with or without context
|
|
64
|
+
const userMessage = formattedContext
|
|
65
|
+
? buildReviewUserPromptWithContext(diff, changedFiles, branchName, formattedContext)
|
|
66
|
+
: buildReviewUserPrompt(diff, changedFiles, branchName);
|
|
58
67
|
|
|
59
68
|
console.log(`🤖 Sending ${diff.length} chars of diff to Claude (${model})...`);
|
|
69
|
+
if (formattedContext) {
|
|
70
|
+
console.log(`📦 Including code context (${formattedContext.length} chars)`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Conversation history for multi-turn
|
|
74
|
+
const messages = [
|
|
75
|
+
{ role: 'user', content: userMessage },
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
let reviewText = '';
|
|
79
|
+
let round = 0;
|
|
80
|
+
|
|
81
|
+
while (round <= maxContextRounds) {
|
|
82
|
+
const response = await client.messages.create({
|
|
83
|
+
model,
|
|
84
|
+
max_tokens: maxTokens,
|
|
85
|
+
system: systemPrompt,
|
|
86
|
+
messages,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const responseText = response.content
|
|
90
|
+
.filter(block => block.type === 'text')
|
|
91
|
+
.map(block => block.text)
|
|
92
|
+
.join('\n');
|
|
60
93
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
max_tokens: maxTokens,
|
|
64
|
-
system: systemPrompt,
|
|
65
|
-
messages: [
|
|
66
|
-
{ role: 'user', content: userMessage },
|
|
67
|
-
],
|
|
68
|
-
});
|
|
94
|
+
// Check if AI is requesting more context
|
|
95
|
+
const { hasRequest, contextData, cleanResponse } = fulfillContextRequest(responseText);
|
|
69
96
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
97
|
+
if (hasRequest && round < maxContextRounds) {
|
|
98
|
+
round++;
|
|
99
|
+
console.log(`🔄 AI requested more context (round ${round}/${maxContextRounds})...`);
|
|
100
|
+
console.log(` Fulfilling ${contextData.length} context request(s)...`);
|
|
101
|
+
|
|
102
|
+
// Add assistant response to history
|
|
103
|
+
messages.push({ role: 'assistant', content: responseText });
|
|
104
|
+
|
|
105
|
+
// Fulfill the request and add as user message
|
|
106
|
+
const followUpText = formatFollowUpContext(contextData);
|
|
107
|
+
const followUpMessage = buildFollowUpContextMessage(followUpText);
|
|
108
|
+
messages.push({ role: 'user', content: followUpMessage });
|
|
109
|
+
|
|
110
|
+
// If there was a partial review in the response, keep it
|
|
111
|
+
if (cleanResponse.trim()) {
|
|
112
|
+
reviewText = cleanResponse + '\n\n';
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
// No more context requests — this is the final review
|
|
116
|
+
reviewText += hasRequest ? cleanResponse : responseText;
|
|
117
|
+
if (round > 0) {
|
|
118
|
+
console.log(`✅ Context gathering complete after ${round} round(s).`);
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
74
123
|
|
|
75
124
|
return reviewText;
|
|
76
125
|
}
|
|
@@ -93,3 +142,4 @@ module.exports = {
|
|
|
93
142
|
reviewCode,
|
|
94
143
|
parseSeverityCounts,
|
|
95
144
|
};
|
|
145
|
+
|
package/src/config.js
CHANGED
|
@@ -11,6 +11,7 @@ const DEFAULT_CONFIG = {
|
|
|
11
11
|
maxTokens: 4096,
|
|
12
12
|
outputDir: './reviews',
|
|
13
13
|
reviewRules: 'standard', // preset name ('basic', 'standard', 'comprehensive') or custom array
|
|
14
|
+
maxContextRounds: 3, // max follow-up context rounds for iterative review
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
/**
|