openairev 0.3.0 → 0.3.1

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/bin/openairev.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { Command } from 'commander';
4
+ import { VERSION } from '../src/version.js';
4
5
  import { initCommand } from '../src/cli/init.js';
5
6
  import { reviewCommand } from '../src/cli/review.js';
6
7
  import { resumeCommand } from '../src/cli/resume.js';
@@ -12,7 +13,7 @@ const program = new Command();
12
13
  program
13
14
  .name('openairev')
14
15
  .description('OpenAIRev — cross-model AI code reviewer')
15
- .version('0.3.0');
16
+ .version(VERSION);
16
17
 
17
18
  program
18
19
  .command('init')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openairev",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Cross-model AI code reviewer — independent review for AI-assisted coding workflows",
5
5
  "type": "module",
6
6
  "bin": {
@@ -31,8 +31,11 @@ export class CodexAdapter {
31
31
  args.push('--output-schema', schemaPath);
32
32
  }
33
33
 
34
- const onData = stream ? createCodexSummarizer({ reviewerName: typeof stream === 'string' ? stream : 'codex' }) : undefined;
35
- const result = await exec(this.cmd, args, { onData });
34
+ const summarizer = stream ? createCodexSummarizer({
35
+ reviewerName: stream.reviewerName || 'codex',
36
+ tty: stream.tty !== false,
37
+ }) : undefined;
38
+ const result = await exec(this.cmd, args, { onData: summarizer });
36
39
 
37
40
  try {
38
41
  const lines = result.stdout.trim().split('\n');
@@ -62,15 +65,17 @@ export class CodexAdapter {
62
65
  this.sessionId = sessionId;
63
66
  }
64
67
 
68
+ const progress = summarizer?.getProgress() || [];
69
+
65
70
  if (agentMessage) {
66
71
  try {
67
- return { result: JSON.parse(agentMessage), raw_output: result.stdout, session_id: this.sessionId };
72
+ return { result: JSON.parse(agentMessage), raw_output: result.stdout, progress, session_id: this.sessionId };
68
73
  } catch {
69
- return { result: agentMessage, raw_output: result.stdout, session_id: this.sessionId };
74
+ return { result: agentMessage, raw_output: result.stdout, progress, session_id: this.sessionId };
70
75
  }
71
76
  }
72
77
 
73
- return { raw: result.stdout, raw_output: result.stdout, session_id: this.sessionId };
78
+ return { raw: result.stdout, raw_output: result.stdout, progress, session_id: this.sessionId };
74
79
  } catch {
75
80
  return { raw: result.stdout, raw_output: result.stdout, error: 'Failed to parse output' };
76
81
  }
@@ -2,51 +2,70 @@ import chalk from 'chalk';
2
2
 
3
3
  /**
4
4
  * Creates a summarizer callback for Codex NDJSON event streams.
5
- * Prints concise progress lines to stderr instead of raw JSON.
5
+ * tty=true: prints colored progress to stderr.
6
+ * tty=false: collects plain-text progress lines silently.
7
+ * Both modes always collect lines for the final summary.
6
8
  */
7
- export function createCodexSummarizer({ reviewerName } = {}) {
9
+ export function createCodexSummarizer({ reviewerName, tty = true } = {}) {
8
10
  const seenFiles = new Set();
11
+ const progressLines = [];
9
12
  let buffer = '';
10
13
  let started = false;
11
14
 
12
- return (chunk) => {
15
+ const summarizer = (chunk) => {
13
16
  if (!started) {
14
17
  started = true;
15
- if (reviewerName) log(chalk.cyan(` reviewer: ${reviewerName}`));
18
+ if (reviewerName) emit(`reviewer: ${reviewerName}`, 'cyan');
16
19
  }
17
20
  buffer += chunk;
18
21
  const lines = buffer.split('\n');
19
- buffer = lines.pop(); // keep incomplete last line
22
+ buffer = lines.pop();
20
23
 
21
24
  for (const line of lines) {
22
25
  if (!line.trim()) continue;
23
26
  try {
24
27
  const event = JSON.parse(line);
25
- summarizeCodexEvent(event, seenFiles);
28
+ summarizeCodexEvent(event, seenFiles, { emit });
26
29
  } catch {
27
30
  // skip non-JSON
28
31
  }
29
32
  }
30
33
  };
34
+
35
+ summarizer.getProgress = () => progressLines;
36
+
37
+ function emit(msg, color) {
38
+ if (progressLines.length < 200) progressLines.push(msg);
39
+ if (tty) {
40
+ const colorFn = color === 'cyan' ? chalk.cyan
41
+ : color === 'green' ? chalk.green
42
+ : color === 'yellow' ? chalk.yellow
43
+ : color === 'red' ? chalk.red
44
+ : chalk.dim;
45
+ process.stderr.write(` ${colorFn(msg)}\n`);
46
+ }
47
+ }
48
+
49
+ return summarizer;
31
50
  }
32
51
 
33
- function summarizeCodexEvent(event, seenFiles) {
52
+ function summarizeCodexEvent(event, seenFiles, { emit }) {
34
53
  const type = event.type;
35
54
 
36
55
  if (type === 'thread.started') {
37
- log(chalk.dim(` session: ${event.thread_id}`));
56
+ emit(`session: ${event.thread_id}`);
38
57
  }
39
58
 
40
59
  if (type === 'item.started' && event.item?.type === 'todo_list') {
41
60
  const items = event.item.items?.map(i => i.text) || [];
42
- log(chalk.cyan(' plan:'));
43
- for (const item of items) log(chalk.dim(` • ${item}`));
61
+ emit('plan:', 'cyan');
62
+ for (const item of items) emit(` • ${item}`);
44
63
  }
45
64
 
46
65
  if (type === 'item.completed' && event.item?.type === 'todo_list') {
47
66
  const items = event.item.items || [];
48
67
  const done = items.filter(i => i.completed).length;
49
- log(chalk.dim(` progress: ${done}/${items.length} tasks done`));
68
+ emit(`progress: ${done}/${items.length} tasks done`);
50
69
  }
51
70
 
52
71
  if (type === 'item.started' && event.item?.type === 'command_execution') {
@@ -55,10 +74,10 @@ function summarizeCodexEvent(event, seenFiles) {
55
74
  const file = extractFileFromCmd(cmd);
56
75
  if (file && !seenFiles.has(file)) {
57
76
  seenFiles.add(file);
58
- log(chalk.dim(` reading: ${file}`));
77
+ emit(`reading: ${file}`);
59
78
  } else if (!file) {
60
79
  const short = summarizeCmd(cmd);
61
- if (short) log(chalk.dim(` running: ${short}`));
80
+ if (short) emit(`running: ${short}`);
62
81
  }
63
82
  }
64
83
 
@@ -66,27 +85,27 @@ function summarizeCodexEvent(event, seenFiles) {
66
85
  const exit = event.item.exit_code;
67
86
  if (exit !== null && exit !== 0) {
68
87
  const cmd = summarizeCmd(event.item.command || '');
69
- log(chalk.yellow(` command failed (exit ${exit}): ${cmd}`));
88
+ emit(`command failed (exit ${exit}): ${cmd}`, 'yellow');
70
89
  }
71
90
  }
72
91
 
73
92
  if (type === 'item.started' && event.item?.type === 'agent_message') {
74
- log(chalk.cyan(' generating verdict...'));
93
+ emit('generating verdict...', 'cyan');
75
94
  }
76
95
 
77
96
  if (type === 'item.completed' && event.item?.type === 'agent_message') {
78
- log(chalk.green(' verdict ready'));
97
+ emit('verdict ready', 'green');
79
98
  }
80
99
 
81
100
  if (type === 'turn.completed' && event.usage) {
82
101
  const { input_tokens, output_tokens } = event.usage;
83
102
  const total = input_tokens + output_tokens;
84
- log(chalk.dim(` tokens: ${fmt(total)} total (${fmt(input_tokens)} in / ${fmt(output_tokens)} out)`));
103
+ emit(`tokens: ${fmt(total)} total (${fmt(input_tokens)} in / ${fmt(output_tokens)} out)`);
85
104
  }
86
105
 
87
106
  if (type === 'error' || type === 'turn.failed') {
88
107
  const msg = event.message || event.error?.message || 'unknown error';
89
- log(chalk.red(` error: ${msg}`));
108
+ emit(`error: ${msg}`, 'red');
90
109
  }
91
110
  }
92
111
 
@@ -118,7 +137,3 @@ function fmt(n) {
118
137
  if (n >= 1_000) return (n / 1_000).toFixed(1) + 'k';
119
138
  return String(n);
120
139
  }
121
-
122
- function log(msg) {
123
- process.stderr.write(msg + '\n');
124
- }
@@ -8,13 +8,14 @@ import { getDiff } from '../tools/git-tools.js';
8
8
  import { runToolGates } from '../tools/tool-runner.js';
9
9
  import { runReview } from '../review/review-runner.js';
10
10
  import { createSession, saveSession } from '../session/session-manager.js';
11
+ import { VERSION } from '../version.js';
11
12
 
12
13
  const cwd = process.cwd();
13
14
  const config = loadConfig(cwd);
14
15
 
15
16
  const server = new McpServer({
16
17
  name: 'openairev',
17
- version: '0.3.0',
18
+ version: VERSION,
18
19
  });
19
20
 
20
21
  server.tool(
@@ -52,6 +53,7 @@ server.tool(
52
53
  reviewerName,
53
54
  taskDescription: task_description,
54
55
  cwd,
56
+ stream: 'silent',
55
57
  });
56
58
 
57
59
  const session = createSession({ executor: execAgent, reviewer: reviewerName });
@@ -60,8 +62,16 @@ server.tool(
60
62
  session.status = 'completed';
61
63
  saveSession(session, cwd);
62
64
 
63
- const text = review.executor_feedback || JSON.stringify(review.verdict || review, null, 2);
64
- return { content: [{ type: 'text', text }] };
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 };
65
75
  }
66
76
  );
67
77
 
@@ -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 : false,
43
+ stream: stream ? { reviewerName, tty: stream === true } : false,
44
44
  });
45
45
 
46
46
  const verdict = extractVerdict(result);
@@ -54,6 +54,7 @@ export async function runReview(content, {
54
54
  verdict,
55
55
  executor_feedback: executorFeedback,
56
56
  reviewer_output: rawOutput,
57
+ progress: result?.progress || [],
57
58
  session_id: adapter.sessionName || adapter.sessionId,
58
59
  };
59
60
  }
package/src/version.js ADDED
@@ -0,0 +1,8 @@
1
+ import { readFileSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
7
+
8
+ export const VERSION = pkg.version;