openairev 0.3.10 → 0.3.12
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/package.json +1 -1
- package/src/agents/claude-code.js +1 -1
- package/src/agents/codex.js +1 -1
- package/src/cli/review.js +12 -9
- package/src/cli/wait.js +3 -0
- package/src/mcp/mcp-server.js +13 -19
- package/src/orchestrator/orchestrator.js +9 -20
- package/src/review/review-runner.js +20 -6
- package/src/tools/git-tools.js +32 -0
- package/src/review/input-stager.js +0 -35
- package/src/review/input-stager.test.js +0 -53
package/package.json
CHANGED
|
@@ -25,7 +25,7 @@ export class ClaudeCodeAdapter {
|
|
|
25
25
|
stream = false,
|
|
26
26
|
signal,
|
|
27
27
|
} = {}) {
|
|
28
|
-
const args = ['-p', prompt];
|
|
28
|
+
const args = ['-p', prompt, '--max-budget-usd', '5'];
|
|
29
29
|
|
|
30
30
|
if (stream) {
|
|
31
31
|
args.push('--output-format', 'stream-json', '--verbose', '--include-partial-messages');
|
package/src/agents/codex.js
CHANGED
package/src/cli/review.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { readFileSync } from 'fs';
|
|
3
2
|
import { loadConfig, getReviewer, getMaxIterations } from '../config/config-loader.js';
|
|
4
|
-
import {
|
|
3
|
+
import { hasDiff, buildDiffCmd } from '../tools/git-tools.js';
|
|
5
4
|
import { runReview } from '../review/review-runner.js';
|
|
6
5
|
import { runWorkflow } from '../orchestrator/orchestrator.js';
|
|
7
6
|
import { createSession, saveSession } from '../session/session-manager.js';
|
|
@@ -37,13 +36,17 @@ export async function reviewCommand(options) {
|
|
|
37
36
|
console.log(` Mode: ${chalk.cyan('implement → review loop')}`);
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
let
|
|
39
|
+
let diffCmd = '';
|
|
40
|
+
let hasChanges = false;
|
|
41
41
|
if (options.file) {
|
|
42
42
|
console.log(` Source: ${chalk.cyan(options.file)}`);
|
|
43
|
-
|
|
43
|
+
diffCmd = `cat ${JSON.stringify(options.file)}`;
|
|
44
|
+
hasChanges = true;
|
|
44
45
|
} else {
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
const ref = options.diff || '';
|
|
47
|
+
try { hasChanges = hasDiff(ref); } catch { /* no diff yet is ok for workflow mode */ }
|
|
48
|
+
diffCmd = buildDiffCmd(ref);
|
|
49
|
+
if (hasChanges) console.log(` Diff cmd: ${chalk.cyan(diffCmd)}`);
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
if (options.specRef) console.log(` Spec: ${chalk.cyan(options.specRef)}`);
|
|
@@ -56,13 +59,13 @@ export async function reviewCommand(options) {
|
|
|
56
59
|
console.log('');
|
|
57
60
|
|
|
58
61
|
if (options.once) {
|
|
59
|
-
if (!
|
|
62
|
+
if (!hasChanges) {
|
|
60
63
|
console.log(chalk.yellow('No changes found. Stage some changes or specify --diff <ref>.'));
|
|
61
64
|
process.exit(0);
|
|
62
65
|
}
|
|
63
66
|
try {
|
|
64
67
|
console.log(chalk.dim('Starting review...\n'));
|
|
65
|
-
const review = await runReview(
|
|
68
|
+
const review = await runReview(diffCmd, { config, reviewerName, cwd, stream: true });
|
|
66
69
|
|
|
67
70
|
const session = createSession({ executor, reviewer: reviewerName, diff_ref: options.diff || 'auto' });
|
|
68
71
|
session.iterations.push({ round: 1, review, timestamp: new Date().toISOString() });
|
|
@@ -83,7 +86,7 @@ export async function reviewCommand(options) {
|
|
|
83
86
|
} else {
|
|
84
87
|
try {
|
|
85
88
|
const result = await runWorkflow({
|
|
86
|
-
config, executor, reviewerName, maxRounds,
|
|
89
|
+
config, executor, reviewerName, maxRounds, diffRef: options.diff,
|
|
87
90
|
taskDescription: options.task, specRef: options.specRef, tools: config.tools,
|
|
88
91
|
cwd, skipAnalyze, skipPlan,
|
|
89
92
|
onStageChange: (stage) => console.log(chalk.bold(`\n[${stageLabel(stage)}]\n`)),
|
package/src/cli/wait.js
CHANGED
package/src/mcp/mcp-server.js
CHANGED
|
@@ -6,7 +6,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
6
6
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
import { loadConfig, getReviewer } from '../config/config-loader.js';
|
|
9
|
-
import { getDiff } from '../tools/git-tools.js';
|
|
9
|
+
import { getDiff, hasDiff, buildDiffCmd } from '../tools/git-tools.js';
|
|
10
10
|
import { runToolGates } from '../tools/tool-runner.js';
|
|
11
11
|
import { runReview } from '../review/review-runner.js';
|
|
12
12
|
import { createSession, saveSession } from '../session/session-manager.js';
|
|
@@ -29,28 +29,19 @@ server.tool(
|
|
|
29
29
|
'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 starts in the background and returns immediately. After calling this, run `openairev wait` via Bash to stream progress and get the verdict — one blocking call, no polling needed.',
|
|
30
30
|
{
|
|
31
31
|
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".'),
|
|
32
|
-
|
|
33
|
-
diff_cmd: z.string().optional().describe('The git command used to get the diff, e.g. "git diff HEAD -- src/auth.ts src/routes.ts". If provided instead of diff, the server will run this command to get the diff.'),
|
|
32
|
+
diff_cmd: z.string().optional().describe('The git diff command for the reviewer to run, e.g. "git diff HEAD -- src/auth.ts src/routes.ts". The reviewer runs in the same repo and executes this itself. If omitted, auto-detects staged or unstaged changes.'),
|
|
34
33
|
task_description: z.string().optional().describe('What the code is supposed to do. Used for requirement checking.'),
|
|
35
34
|
},
|
|
36
|
-
async ({ executor,
|
|
35
|
+
async ({ executor, diff_cmd, task_description }) => {
|
|
37
36
|
const execAgent = executor || Object.keys(config.agents || {}).find(a => config.agents[a].available);
|
|
38
37
|
const reviewerName = getReviewer(config, execAgent);
|
|
39
38
|
if (!reviewerName) {
|
|
40
39
|
return { content: [{ type: 'text', text: `No reviewer configured for executor "${execAgent}"` }] };
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const { execSync } = await import('child_process');
|
|
47
|
-
diffContent = execSync(diff_cmd, { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, cwd });
|
|
48
|
-
} catch (e) {
|
|
49
|
-
return { content: [{ type: 'text', text: `diff_cmd failed: ${e.message}` }] };
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
if (!diffContent) diffContent = getDiff();
|
|
53
|
-
if (!diffContent?.trim()) {
|
|
42
|
+
const reviewDiffCmd = diff_cmd || buildDiffCmd();
|
|
43
|
+
|
|
44
|
+
if (!hasDiff()) {
|
|
54
45
|
return { content: [{ type: 'text', text: 'No changes found to review.' }] };
|
|
55
46
|
}
|
|
56
47
|
|
|
@@ -62,7 +53,7 @@ server.tool(
|
|
|
62
53
|
};
|
|
63
54
|
|
|
64
55
|
activeAbort = new AbortController();
|
|
65
|
-
activeReview = runReview(
|
|
56
|
+
activeReview = runReview(reviewDiffCmd, {
|
|
66
57
|
config,
|
|
67
58
|
reviewerName,
|
|
68
59
|
taskDescription: task_description,
|
|
@@ -76,13 +67,16 @@ server.tool(
|
|
|
76
67
|
session.status = 'completed';
|
|
77
68
|
saveSession(session, cwd);
|
|
78
69
|
|
|
79
|
-
|
|
80
|
-
status: 'completed',
|
|
70
|
+
const progressData = {
|
|
71
|
+
status: review.error ? 'error' : 'completed',
|
|
81
72
|
reviewer: reviewerName,
|
|
82
73
|
progress: review.progress || [],
|
|
83
74
|
verdict: review.verdict,
|
|
84
75
|
executor_feedback: review.executor_feedback,
|
|
85
|
-
}
|
|
76
|
+
};
|
|
77
|
+
if (review.error) progressData.error = review.error;
|
|
78
|
+
if (review.partial_notice) progressData.partial_notice = review.partial_notice;
|
|
79
|
+
writeProgress(progressData);
|
|
86
80
|
activeReview = null;
|
|
87
81
|
activeAbort = null;
|
|
88
82
|
return review;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createAdapter } from '../agents/registry.js';
|
|
2
2
|
import { runReview } from '../review/review-runner.js';
|
|
3
3
|
import { loadPromptFile } from '../review/prompt-loader.js';
|
|
4
|
-
import {
|
|
4
|
+
import { hasDiff, buildDiffCmd } from '../tools/git-tools.js';
|
|
5
5
|
import { runToolGates } from '../tools/tool-runner.js';
|
|
6
6
|
import {
|
|
7
7
|
createChain, transitionTo, addRound, setArtifact,
|
|
@@ -44,7 +44,7 @@ export async function runWorkflow({
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
let
|
|
47
|
+
let hasChanges = !!initialDiff;
|
|
48
48
|
let codeReviewCount = 0;
|
|
49
49
|
let planReviewCount = 0;
|
|
50
50
|
|
|
@@ -140,13 +140,7 @@ export async function runWorkflow({
|
|
|
140
140
|
const prompt = buildImplementationPrompt(chain, specRef);
|
|
141
141
|
await runExecutor(executor, config, prompt, chain, cwd);
|
|
142
142
|
|
|
143
|
-
|
|
144
|
-
currentDiff = getDiff(diffRef);
|
|
145
|
-
} catch {
|
|
146
|
-
currentDiff = '';
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (currentDiff?.trim()) {
|
|
143
|
+
if (hasDiff(diffRef)) {
|
|
150
144
|
setArtifact(chain, 'current_diff_ref', diffRef || 'auto', cwd);
|
|
151
145
|
transitionTo(chain, 'code_review', cwd);
|
|
152
146
|
} else {
|
|
@@ -168,7 +162,8 @@ export async function runWorkflow({
|
|
|
168
162
|
toolResults = runToolGates(Object.keys(tools), cwd, tools);
|
|
169
163
|
}
|
|
170
164
|
|
|
171
|
-
const
|
|
165
|
+
const reviewDiffCmd = diffRef ? `git diff ${diffRef}` : 'git diff --staged || git diff';
|
|
166
|
+
const review = await runReviewRound(reviewerName, config, reviewDiffCmd, {
|
|
172
167
|
kind: 'code_review', chain, specRef, cwd,
|
|
173
168
|
});
|
|
174
169
|
|
|
@@ -205,14 +200,7 @@ export async function runWorkflow({
|
|
|
205
200
|
const feedback = buildFeedback(lastVerdict, cwd);
|
|
206
201
|
await runExecutor(executor, config, feedback, chain, cwd);
|
|
207
202
|
|
|
208
|
-
|
|
209
|
-
currentDiff = getDiff(diffRef);
|
|
210
|
-
} catch (e) {
|
|
211
|
-
closeChain(chain, 'error', cwd);
|
|
212
|
-
return { chain, status: 'error', message: `Failed to get diff: ${e.message}` };
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (!currentDiff?.trim()) {
|
|
203
|
+
if (!hasDiff(diffRef)) {
|
|
216
204
|
closeChain(chain, 'error', cwd);
|
|
217
205
|
return { chain, status: 'error', message: 'No changes after fix attempt' };
|
|
218
206
|
}
|
|
@@ -255,15 +243,16 @@ async function runExecutor(executor, config, prompt, chain, cwd) {
|
|
|
255
243
|
return { output: result?.result || result?.raw || null, session_id: sessionId };
|
|
256
244
|
}
|
|
257
245
|
|
|
258
|
-
async function runReviewRound(reviewerName, config,
|
|
246
|
+
async function runReviewRound(reviewerName, config, input, { kind, chain, specRef, cwd }) {
|
|
259
247
|
const promptFile = kind === 'plan_review' ? 'plan-reviewer.md' : 'reviewer.md';
|
|
260
248
|
const sessionId = getReviewerSession(chain, kind);
|
|
261
249
|
|
|
262
|
-
return runReview(
|
|
250
|
+
return runReview(input, {
|
|
263
251
|
config, reviewerName, promptFile,
|
|
264
252
|
taskDescription: chain.task?.user_request,
|
|
265
253
|
specRef: specRef || chain.task?.spec_ref,
|
|
266
254
|
cwd, sessionId, stream: true,
|
|
255
|
+
inputMode: kind === 'plan_review' ? 'inline' : 'diff_cmd',
|
|
267
256
|
});
|
|
268
257
|
}
|
|
269
258
|
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { mkdirSync, writeFileSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { createAdapter } from '../agents/registry.js';
|
|
4
|
-
import { stageInput, buildInputReference } from './input-stager.js';
|
|
5
4
|
import { loadPromptFile } from './prompt-loader.js';
|
|
6
5
|
|
|
7
|
-
export async function runReview(
|
|
6
|
+
export async function runReview(input, {
|
|
8
7
|
config,
|
|
9
8
|
reviewerName,
|
|
10
9
|
promptFile = 'reviewer.md',
|
|
@@ -14,6 +13,7 @@ export async function runReview(content, {
|
|
|
14
13
|
sessionId = null,
|
|
15
14
|
stream = false,
|
|
16
15
|
signal,
|
|
16
|
+
inputMode = 'diff_cmd',
|
|
17
17
|
}) {
|
|
18
18
|
const adapter = createAdapter(reviewerName, config, { cwd });
|
|
19
19
|
|
|
@@ -22,8 +22,6 @@ export async function runReview(content, {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const reviewerPrompt = loadPromptFile(promptFile, cwd);
|
|
25
|
-
const staged = stageInput(content, { cwd });
|
|
26
|
-
const inputRef = buildInputReference(staged);
|
|
27
25
|
|
|
28
26
|
let prompt = reviewerPrompt;
|
|
29
27
|
if (taskDescription) {
|
|
@@ -32,7 +30,12 @@ export async function runReview(content, {
|
|
|
32
30
|
if (specRef) {
|
|
33
31
|
prompt += `\n\nSpec reference: ${specRef}\nRead the spec file for requirements and acceptance criteria.`;
|
|
34
32
|
}
|
|
35
|
-
|
|
33
|
+
|
|
34
|
+
if (inputMode === 'diff_cmd') {
|
|
35
|
+
prompt += `\n\n--- CHANGES TO REVIEW ---\nRun this command to get the diff:\n\`${input}\`\n\nReview the changed files. You are in the same repo as the executor — run the diff command yourself, read the files, and produce your verdict.`;
|
|
36
|
+
} else {
|
|
37
|
+
prompt += `\n\n--- CONTENT TO REVIEW ---\n${input}`;
|
|
38
|
+
}
|
|
36
39
|
|
|
37
40
|
const schemaFile = promptFile === 'plan-reviewer.md' ? 'plan-verdict-schema.json' : 'verdict-schema.json';
|
|
38
41
|
|
|
@@ -51,7 +54,7 @@ export async function runReview(content, {
|
|
|
51
54
|
|
|
52
55
|
logReviewerOutput(rawOutput, reviewerName, cwd);
|
|
53
56
|
|
|
54
|
-
|
|
57
|
+
const reviewResult = {
|
|
55
58
|
reviewer: reviewerName,
|
|
56
59
|
verdict,
|
|
57
60
|
executor_feedback: executorFeedback,
|
|
@@ -59,6 +62,17 @@ export async function runReview(content, {
|
|
|
59
62
|
progress: result?.progress || [],
|
|
60
63
|
session_id: adapter.sessionName || adapter.sessionId,
|
|
61
64
|
};
|
|
65
|
+
|
|
66
|
+
if (!verdict) {
|
|
67
|
+
const explicitError = result?.error;
|
|
68
|
+
if (explicitError) {
|
|
69
|
+
reviewResult.error = `Reviewer (${reviewerName}) failed: ${explicitError}`;
|
|
70
|
+
} else {
|
|
71
|
+
reviewResult.error = `Reviewer (${reviewerName}) produced no verdict. Possible causes: context budget exceeded, auth failure, or schema mismatch. Check .openairev/logs/ for details.`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return reviewResult;
|
|
62
76
|
}
|
|
63
77
|
|
|
64
78
|
function logReviewerOutput(rawOutput, reviewerName, cwd) {
|
package/src/tools/git-tools.js
CHANGED
|
@@ -34,6 +34,38 @@ export function getDiff(ref, { context = 1, excludes = EXCLUDE_PATTERNS } = {})
|
|
|
34
34
|
return '';
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Check if there are changes without reading the diff content.
|
|
39
|
+
* Uses git diff --quiet (exit code only, no output).
|
|
40
|
+
*/
|
|
41
|
+
export function hasDiff(ref) {
|
|
42
|
+
try {
|
|
43
|
+
if (ref) {
|
|
44
|
+
execFileSync('git', ['diff', '--quiet', ref]);
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
// Try staged first
|
|
48
|
+
try {
|
|
49
|
+
execFileSync('git', ['diff', '--quiet', '--cached']);
|
|
50
|
+
// exit 0 = no staged changes, try unstaged
|
|
51
|
+
execFileSync('git', ['diff', '--quiet']);
|
|
52
|
+
return false; // no changes at all
|
|
53
|
+
} catch {
|
|
54
|
+
return true; // either staged or unstaged has changes
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
return true; // exit 1 = has changes
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Build a diff command string for the reviewer to run.
|
|
63
|
+
*/
|
|
64
|
+
export function buildDiffCmd(ref) {
|
|
65
|
+
if (ref) return `git diff ${ref}`;
|
|
66
|
+
return 'git diff --staged || git diff';
|
|
67
|
+
}
|
|
68
|
+
|
|
37
69
|
function gitExec(args) {
|
|
38
70
|
try {
|
|
39
71
|
return execFileSync('git', args, { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 });
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { writeFileSync, mkdirSync } from 'fs';
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
|
|
4
|
-
const INLINE_THRESHOLD = 8_000; // characters — inline if under this
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Stage review input. For small content, returns it inline.
|
|
8
|
-
* For large content, writes to .openairev/tmp/ and returns a file reference.
|
|
9
|
-
*/
|
|
10
|
-
export function stageInput(content, { cwd = process.cwd(), label = 'review-input' } = {}) {
|
|
11
|
-
if (content.length <= INLINE_THRESHOLD) {
|
|
12
|
-
return { mode: 'inline', content };
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const tmpDir = join(cwd, '.openairev', 'tmp');
|
|
16
|
-
mkdirSync(tmpDir, { recursive: true });
|
|
17
|
-
|
|
18
|
-
const filename = `${label}-${Date.now()}.diff`;
|
|
19
|
-
const filePath = join(tmpDir, filename);
|
|
20
|
-
const relativePath = `.openairev/tmp/${filename}`;
|
|
21
|
-
|
|
22
|
-
writeFileSync(filePath, content);
|
|
23
|
-
|
|
24
|
-
return { mode: 'file', filePath, relativePath };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Build the prompt prefix for the first pass based on staging result.
|
|
29
|
-
*/
|
|
30
|
-
export function buildInputReference(staged) {
|
|
31
|
-
if (staged.mode === 'inline') {
|
|
32
|
-
return `\n\n--- DIFF ---\n${staged.content}`;
|
|
33
|
-
}
|
|
34
|
-
return `\n\nThe diff to review is stored at: ${staged.relativePath}\nRead that file to see the full changes. It is too large to include inline.`;
|
|
35
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { describe, it, beforeEach, afterEach } from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { mkdirSync, rmSync, existsSync, readFileSync } from 'fs';
|
|
4
|
-
import { join } from 'path';
|
|
5
|
-
import { stageInput, buildInputReference } from './input-stager.js';
|
|
6
|
-
|
|
7
|
-
const TMP = join(process.cwd(), '.test-tmp-stager');
|
|
8
|
-
|
|
9
|
-
describe('input-stager', () => {
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
mkdirSync(TMP, { recursive: true });
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
afterEach(() => {
|
|
15
|
-
rmSync(TMP, { recursive: true, force: true });
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('inlines small content', () => {
|
|
19
|
-
const result = stageInput('small diff', { cwd: TMP });
|
|
20
|
-
assert.equal(result.mode, 'inline');
|
|
21
|
-
assert.equal(result.content, 'small diff');
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('writes large content to file', () => {
|
|
25
|
-
const large = 'x'.repeat(10_000);
|
|
26
|
-
const result = stageInput(large, { cwd: TMP });
|
|
27
|
-
assert.equal(result.mode, 'file');
|
|
28
|
-
assert.ok(result.filePath);
|
|
29
|
-
assert.ok(result.relativePath.startsWith('.openairev/tmp/'));
|
|
30
|
-
assert.ok(existsSync(result.filePath));
|
|
31
|
-
assert.equal(readFileSync(result.filePath, 'utf-8'), large);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('buildInputReference returns inline content for small input', () => {
|
|
35
|
-
const staged = { mode: 'inline', content: 'diff here' };
|
|
36
|
-
const ref = buildInputReference(staged);
|
|
37
|
-
assert.ok(ref.includes('diff here'));
|
|
38
|
-
assert.ok(ref.includes('--- DIFF ---'));
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('buildInputReference returns file path for large input', () => {
|
|
42
|
-
const staged = { mode: 'file', relativePath: '.openairev/tmp/test.diff' };
|
|
43
|
-
const ref = buildInputReference(staged);
|
|
44
|
-
assert.ok(ref.includes('.openairev/tmp/test.diff'));
|
|
45
|
-
assert.ok(ref.includes('Read that file'));
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('uses custom label for filename', () => {
|
|
49
|
-
const large = 'y'.repeat(10_000);
|
|
50
|
-
const result = stageInput(large, { cwd: TMP, label: 'my-review' });
|
|
51
|
-
assert.ok(result.relativePath.includes('my-review'));
|
|
52
|
-
});
|
|
53
|
-
});
|