openairev 0.3.1 → 0.3.2
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 +12 -2
- package/package.json +1 -1
- package/src/agents/codex.js +1 -0
- package/src/agents/stream-summarizer.js +5 -2
- package/src/cli/init.js +12 -8
- package/src/mcp/mcp-server.js +79 -28
- package/src/review/review-runner.js +1 -1
package/README.md
CHANGED
|
@@ -212,12 +212,22 @@ Restart your agent CLI after adding.
|
|
|
212
212
|
|
|
213
213
|
| Tool | Description |
|
|
214
214
|
|------|-------------|
|
|
215
|
-
| `openairev_review` |
|
|
216
|
-
| `openairev_status` | Check
|
|
215
|
+
| `openairev_review` | Start a review in the background. Returns immediately. |
|
|
216
|
+
| `openairev_status` | Check review progress and get the verdict when ready. |
|
|
217
217
|
| `openairev_run_tests` | Run project test suite |
|
|
218
218
|
| `openairev_run_lint` | Run linter |
|
|
219
219
|
| `openairev_get_diff` | Get current git diff |
|
|
220
220
|
|
|
221
|
+
### Async Review Flow
|
|
222
|
+
|
|
223
|
+
The review runs asynchronously so you can see progress:
|
|
224
|
+
|
|
225
|
+
1. Call `openairev_review` → returns *"Review started. Call openairev_status."*
|
|
226
|
+
2. Call `openairev_status` → shows live progress (files read, commands run, tokens)
|
|
227
|
+
3. When done, `openairev_status` returns the full verdict and feedback
|
|
228
|
+
|
|
229
|
+
This lets the executor AI (and user) see what the reviewer is doing instead of waiting blindly.
|
|
230
|
+
|
|
221
231
|
## Config
|
|
222
232
|
|
|
223
233
|
Generated by `openairev init` at `.openairev/config.yaml`:
|
package/package.json
CHANGED
package/src/agents/codex.js
CHANGED
|
@@ -34,6 +34,7 @@ export class CodexAdapter {
|
|
|
34
34
|
const summarizer = stream ? createCodexSummarizer({
|
|
35
35
|
reviewerName: stream.reviewerName || 'codex',
|
|
36
36
|
tty: stream.tty !== false,
|
|
37
|
+
onProgress: stream.onProgress,
|
|
37
38
|
}) : undefined;
|
|
38
39
|
const result = await exec(this.cmd, args, { onData: summarizer });
|
|
39
40
|
|
|
@@ -6,7 +6,7 @@ import chalk from 'chalk';
|
|
|
6
6
|
* tty=false: collects plain-text progress lines silently.
|
|
7
7
|
* Both modes always collect lines for the final summary.
|
|
8
8
|
*/
|
|
9
|
-
export function createCodexSummarizer({ reviewerName, tty = true } = {}) {
|
|
9
|
+
export function createCodexSummarizer({ reviewerName, tty = true, onProgress } = {}) {
|
|
10
10
|
const seenFiles = new Set();
|
|
11
11
|
const progressLines = [];
|
|
12
12
|
let buffer = '';
|
|
@@ -35,7 +35,10 @@ export function createCodexSummarizer({ reviewerName, tty = true } = {}) {
|
|
|
35
35
|
summarizer.getProgress = () => progressLines;
|
|
36
36
|
|
|
37
37
|
function emit(msg, color) {
|
|
38
|
-
if (progressLines.length < 200)
|
|
38
|
+
if (progressLines.length < 200) {
|
|
39
|
+
progressLines.push(msg);
|
|
40
|
+
if (onProgress) onProgress(progressLines);
|
|
41
|
+
}
|
|
39
42
|
if (tty) {
|
|
40
43
|
const colorFn = color === 'cyan' ? chalk.cyan
|
|
41
44
|
: color === 'green' ? chalk.green
|
package/src/cli/init.js
CHANGED
|
@@ -237,11 +237,13 @@ function setupClaudeCode(cwd) {
|
|
|
237
237
|
${marker}
|
|
238
238
|
## OpenAIRev — Cross-Model Code Review
|
|
239
239
|
|
|
240
|
-
This project uses OpenAIRev for independent AI code review. When the user asks to "review", "get a review", or "check my changes"
|
|
240
|
+
This project uses OpenAIRev for independent AI code review. When the user asks to "review", "get a review", or "check my changes":
|
|
241
241
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
242
|
+
1. Call \`openairev_review\` with \`executor: "claude_code"\` and \`diff_cmd\` scoped to the files you changed (e.g. \`"git diff HEAD -- src/auth.ts src/routes.ts"\`). The review starts in the background and returns immediately.
|
|
243
|
+
2. Call \`openairev_status\` to check progress. It shows what the reviewer is reading and doing. If the review is still running, call it again after a few seconds.
|
|
244
|
+
3. When the review is complete, \`openairev_status\` returns the full verdict with feedback.
|
|
245
|
+
|
|
246
|
+
- **IMPORTANT**: Always use \`diff_cmd\` to scope the diff to your changed files. Do NOT let it auto-detect — the full repo diff may be too large.
|
|
245
247
|
- When you receive review feedback, treat it as **peer review** — use your judgment, don't blindly apply every suggestion.
|
|
246
248
|
- The review verdict includes \`critical_issues\`, \`repair_instructions\`, and a \`confidence\` score. Focus on high-confidence critical issues.
|
|
247
249
|
${marker}
|
|
@@ -297,11 +299,13 @@ function setupCodex(cwd) {
|
|
|
297
299
|
${marker}
|
|
298
300
|
## OpenAIRev — Cross-Model Code Review
|
|
299
301
|
|
|
300
|
-
This project uses OpenAIRev for independent AI code review. When the user asks to "review", "get a review", or "check my changes"
|
|
302
|
+
This project uses OpenAIRev for independent AI code review. When the user asks to "review", "get a review", or "check my changes":
|
|
303
|
+
|
|
304
|
+
1. Call \`openairev_review\` with \`executor: "codex"\` and \`diff_cmd\` scoped to the files you changed (e.g. \`"git diff HEAD -- src/auth.ts src/routes.ts"\`). The review starts in the background and returns immediately.
|
|
305
|
+
2. Call \`openairev_status\` to check progress. It shows what the reviewer is reading and doing. If the review is still running, call it again after a few seconds.
|
|
306
|
+
3. When the review is complete, \`openairev_status\` returns the full verdict with feedback.
|
|
301
307
|
|
|
302
|
-
-
|
|
303
|
-
- A different AI model will review your code independently.
|
|
304
|
-
- **IMPORTANT**: Pass only the diff for files you changed using \`diff_cmd\`, e.g. \`"git diff HEAD -- src/auth.ts src/routes.ts"\`. Do NOT let it auto-detect — the full repo diff may be too large.
|
|
308
|
+
- **IMPORTANT**: Always use \`diff_cmd\` to scope the diff to your changed files. Do NOT let it auto-detect — the full repo diff may be too large.
|
|
305
309
|
- When you receive review feedback, treat it as **peer review** — use your judgment, don't blindly apply every suggestion.
|
|
306
310
|
- The review verdict includes \`critical_issues\`, \`repair_instructions\`, and a \`confidence\` score. Focus on high-confidence critical issues.
|
|
307
311
|
${marker}
|
package/src/mcp/mcp-server.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { mkdirSync, writeFileSync, readFileSync, existsSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
3
5
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
6
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
7
|
import { z } from 'zod';
|
|
@@ -12,6 +14,9 @@ import { VERSION } from '../version.js';
|
|
|
12
14
|
|
|
13
15
|
const cwd = process.cwd();
|
|
14
16
|
const config = loadConfig(cwd);
|
|
17
|
+
const PROGRESS_FILE = join(cwd, '.openairev', 'progress.json');
|
|
18
|
+
|
|
19
|
+
let activeReview = null;
|
|
15
20
|
|
|
16
21
|
const server = new McpServer({
|
|
17
22
|
name: 'openairev',
|
|
@@ -20,7 +25,7 @@ const server = new McpServer({
|
|
|
20
25
|
|
|
21
26
|
server.tool(
|
|
22
27
|
'openairev_review',
|
|
23
|
-
'TRIGGER: Use this tool when the user says "review", "review my code", "get a review", "check my changes", "openairev", or asks for independent/cross-model code review. Sends current code changes to a DIFFERENT AI model for independent review.
|
|
28
|
+
'TRIGGER: Use this tool when the user says "review", "review my code", "get a review", "check my changes", "openairev", or asks for independent/cross-model code review. Sends current code changes to a DIFFERENT AI model for independent review. The review runs in the background — call openairev_status to check progress and get the verdict when ready.',
|
|
24
29
|
{
|
|
25
30
|
executor: z.string().optional().describe('Which agent wrote the code (claude_code or codex). If you are Claude Code, set this to "claude_code". If you are Codex, set this to "codex".'),
|
|
26
31
|
diff: z.string().optional().describe('The diff to review. IMPORTANT: Pass only the diff for files YOU changed, not the entire repo. Use `git diff HEAD -- file1 file2` to scope it. If omitted, auto-detects from git which may be too large.'),
|
|
@@ -48,46 +53,78 @@ server.tool(
|
|
|
48
53
|
return { content: [{ type: 'text', text: 'No changes found to review.' }] };
|
|
49
54
|
}
|
|
50
55
|
|
|
51
|
-
|
|
56
|
+
mkdirSync(join(cwd, '.openairev'), { recursive: true });
|
|
57
|
+
writeProgress({ status: 'running', reviewer: reviewerName, started: new Date().toISOString(), progress: [], verdict: null });
|
|
58
|
+
|
|
59
|
+
const onProgress = (lines) => {
|
|
60
|
+
writeProgress({ status: 'running', reviewer: reviewerName, started: new Date().toISOString(), progress: lines, verdict: null });
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
activeReview = runReview(diffContent, {
|
|
52
64
|
config,
|
|
53
65
|
reviewerName,
|
|
54
66
|
taskDescription: task_description,
|
|
55
67
|
cwd,
|
|
56
|
-
stream:
|
|
68
|
+
stream: { onProgress },
|
|
69
|
+
}).then((review) => {
|
|
70
|
+
const session = createSession({ executor: execAgent, reviewer: reviewerName });
|
|
71
|
+
session.iterations.push({ round: 1, review, timestamp: new Date().toISOString() });
|
|
72
|
+
session.final_verdict = review.verdict;
|
|
73
|
+
session.status = 'completed';
|
|
74
|
+
saveSession(session, cwd);
|
|
75
|
+
|
|
76
|
+
writeProgress({
|
|
77
|
+
status: 'completed',
|
|
78
|
+
reviewer: reviewerName,
|
|
79
|
+
progress: review.progress || [],
|
|
80
|
+
verdict: review.verdict,
|
|
81
|
+
executor_feedback: review.executor_feedback,
|
|
82
|
+
});
|
|
83
|
+
activeReview = null;
|
|
84
|
+
return review;
|
|
85
|
+
}).catch((err) => {
|
|
86
|
+
writeProgress({ status: 'error', reviewer: reviewerName, error: err.message, progress: [], verdict: null });
|
|
87
|
+
activeReview = null;
|
|
57
88
|
});
|
|
58
89
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const parts = [];
|
|
66
|
-
|
|
67
|
-
if (review.progress?.length > 0) {
|
|
68
|
-
parts.push({ type: 'text', text: `Review progress:\n${review.progress.map(l => ` ${l}`).join('\n')}` });
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const feedback = review.executor_feedback || JSON.stringify(review.verdict || review, null, 2);
|
|
72
|
-
parts.push({ type: 'text', text: feedback });
|
|
73
|
-
|
|
74
|
-
return { content: parts };
|
|
90
|
+
return {
|
|
91
|
+
content: [{
|
|
92
|
+
type: 'text',
|
|
93
|
+
text: `Review started. Reviewer: ${reviewerName}\n\nCall openairev_status to check progress and get the verdict when ready.`,
|
|
94
|
+
}],
|
|
95
|
+
};
|
|
75
96
|
}
|
|
76
97
|
);
|
|
77
98
|
|
|
78
99
|
server.tool(
|
|
79
100
|
'openairev_status',
|
|
80
|
-
'
|
|
101
|
+
'Check the progress and result of the current or most recent OpenAIRev review. Call this after openairev_review to see what the reviewer is doing and get the verdict when ready.',
|
|
81
102
|
{},
|
|
82
103
|
async () => {
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
return { content: [{ type: 'text', text: 'No review sessions found.' }] };
|
|
104
|
+
const progress = readProgress();
|
|
105
|
+
if (!progress) {
|
|
106
|
+
return { content: [{ type: 'text', text: 'No review in progress. Call openairev_review first.' }] };
|
|
87
107
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
108
|
+
|
|
109
|
+
if (progress.status === 'running') {
|
|
110
|
+
const lines = progress.progress || [];
|
|
111
|
+
const text = lines.length > 0
|
|
112
|
+
? `Review in progress (reviewer: ${progress.reviewer}):\n${lines.map(l => ` ${l}`).join('\n')}\n\nStill running... Call openairev_status again in a few seconds.`
|
|
113
|
+
: `Review in progress (reviewer: ${progress.reviewer}). Started: ${progress.started}\n\nCall openairev_status again in a few seconds.`;
|
|
114
|
+
return { content: [{ type: 'text', text }] };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (progress.status === 'error') {
|
|
118
|
+
return { content: [{ type: 'text', text: `Review failed: ${progress.error}` }] };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const parts = [];
|
|
122
|
+
if (progress.progress?.length > 0) {
|
|
123
|
+
parts.push({ type: 'text', text: `Review complete:\n${progress.progress.map(l => ` ${l}`).join('\n')}` });
|
|
124
|
+
}
|
|
125
|
+
const feedback = progress.executor_feedback || JSON.stringify(progress.verdict || {}, null, 2);
|
|
126
|
+
parts.push({ type: 'text', text: feedback });
|
|
127
|
+
return { content: parts };
|
|
91
128
|
}
|
|
92
129
|
);
|
|
93
130
|
|
|
@@ -125,6 +162,20 @@ server.tool(
|
|
|
125
162
|
}
|
|
126
163
|
);
|
|
127
164
|
|
|
128
|
-
|
|
165
|
+
function writeProgress(data) {
|
|
166
|
+
try {
|
|
167
|
+
writeFileSync(PROGRESS_FILE, JSON.stringify(data, null, 2));
|
|
168
|
+
} catch { /* non-critical */ }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function readProgress() {
|
|
172
|
+
try {
|
|
173
|
+
if (!existsSync(PROGRESS_FILE)) return null;
|
|
174
|
+
return JSON.parse(readFileSync(PROGRESS_FILE, 'utf-8'));
|
|
175
|
+
} catch {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
129
180
|
const transport = new StdioServerTransport();
|
|
130
181
|
await server.connect(transport);
|
|
@@ -40,7 +40,7 @@ export async function runReview(content, {
|
|
|
40
40
|
schemaFile,
|
|
41
41
|
continueSession: !!sessionId,
|
|
42
42
|
sessionName: sessionId ? undefined : `review-${Date.now()}`,
|
|
43
|
-
stream: stream ? { reviewerName, tty: stream === true } : false,
|
|
43
|
+
stream: stream ? { reviewerName, tty: stream === true, onProgress: stream.onProgress } : false,
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
const verdict = extractVerdict(result);
|