@diff-review-system/drs 1.1.2 → 2.0.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/.opencode/agent/describe/pr-describer.md +221 -0
- package/.opencode/agent/review/documentation.md +56 -0
- package/.opencode/agent/review/performance.md +32 -139
- package/.opencode/agent/review/quality.md +36 -113
- package/.opencode/agent/review/security.md +32 -97
- package/.opencode/agent/review/style.md +4 -0
- package/.opencode/agent/review/unified-reviewer.md +74 -0
- package/.opencode/opencode.jsonc +4 -29
- package/.opencode/tool/write_json_output.ts +24 -0
- package/README.md +48 -21
- package/dist/ci/runner.d.ts.map +1 -1
- package/dist/ci/runner.js +2 -0
- package/dist/ci/runner.js.map +1 -1
- package/dist/cli/describe-mr.d.ts +11 -0
- package/dist/cli/describe-mr.d.ts.map +1 -0
- package/dist/cli/describe-mr.js +104 -0
- package/dist/cli/describe-mr.js.map +1 -0
- package/dist/cli/describe-pr.d.ts +12 -0
- package/dist/cli/describe-pr.d.ts.map +1 -0
- package/dist/cli/describe-pr.js +105 -0
- package/dist/cli/describe-pr.js.map +1 -0
- package/dist/cli/index.js +220 -16
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.d.ts +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +273 -145
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/post-comments.d.ts +15 -0
- package/dist/cli/post-comments.d.ts.map +1 -0
- package/dist/cli/post-comments.js +216 -0
- package/dist/cli/post-comments.js.map +1 -0
- package/dist/cli/review-local.d.ts +3 -0
- package/dist/cli/review-local.d.ts.map +1 -1
- package/dist/cli/review-local.js +44 -18
- package/dist/cli/review-local.js.map +1 -1
- package/dist/cli/review-mr.d.ts +6 -0
- package/dist/cli/review-mr.d.ts.map +1 -1
- package/dist/cli/review-mr.js +63 -7
- package/dist/cli/review-mr.js.map +1 -1
- package/dist/cli/review-pr.d.ts +6 -0
- package/dist/cli/review-pr.d.ts.map +1 -1
- package/dist/cli/review-pr.js +8 -1
- package/dist/cli/review-pr.js.map +1 -1
- package/dist/cli/show-changes.d.ts +15 -0
- package/dist/cli/show-changes.d.ts.map +1 -0
- package/dist/cli/show-changes.js +184 -0
- package/dist/cli/show-changes.js.map +1 -0
- package/dist/github/platform-adapter.d.ts.map +1 -1
- package/dist/github/platform-adapter.js +4 -2
- package/dist/github/platform-adapter.js.map +1 -1
- package/dist/gitlab/client.d.ts.map +1 -1
- package/dist/gitlab/client.js +1 -1
- package/dist/gitlab/client.js.map +1 -1
- package/dist/gitlab/platform-adapter.d.ts.map +1 -1
- package/dist/gitlab/platform-adapter.js +6 -5
- package/dist/gitlab/platform-adapter.js.map +1 -1
- package/dist/lib/change-summary.d.ts +8 -0
- package/dist/lib/change-summary.d.ts.map +1 -0
- package/dist/lib/change-summary.js +2 -0
- package/dist/lib/change-summary.js.map +1 -0
- package/dist/lib/comment-formatter.d.ts +3 -2
- package/dist/lib/comment-formatter.d.ts.map +1 -1
- package/dist/lib/comment-formatter.js +16 -2
- package/dist/lib/comment-formatter.js.map +1 -1
- package/dist/lib/comment-formatter.test.d.ts +2 -0
- package/dist/lib/comment-formatter.test.d.ts.map +1 -0
- package/dist/lib/comment-formatter.test.js +607 -0
- package/dist/lib/comment-formatter.test.js.map +1 -0
- package/dist/lib/comment-manager.test.d.ts +2 -0
- package/dist/lib/comment-manager.test.d.ts.map +1 -0
- package/dist/lib/comment-manager.test.js +618 -0
- package/dist/lib/comment-manager.test.js.map +1 -0
- package/dist/lib/comment-poster.d.ts +21 -0
- package/dist/lib/comment-poster.d.ts.map +1 -0
- package/dist/lib/comment-poster.js +96 -0
- package/dist/lib/comment-poster.js.map +1 -0
- package/dist/lib/comment-poster.test.d.ts +5 -0
- package/dist/lib/comment-poster.test.d.ts.map +1 -0
- package/dist/lib/comment-poster.test.js +215 -0
- package/dist/lib/comment-poster.test.js.map +1 -0
- package/dist/lib/config-model-overrides.test.js +71 -41
- package/dist/lib/config-model-overrides.test.js.map +1 -1
- package/dist/lib/config.d.ts +63 -7
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +116 -22
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/config.test.js +10 -2
- package/dist/lib/config.test.js.map +1 -1
- package/dist/lib/context-compression.d.ts +19 -0
- package/dist/lib/context-compression.d.ts.map +1 -0
- package/dist/lib/context-compression.js +170 -0
- package/dist/lib/context-compression.js.map +1 -0
- package/dist/lib/context-compression.test.d.ts +2 -0
- package/dist/lib/context-compression.test.d.ts.map +1 -0
- package/dist/lib/context-compression.test.js +33 -0
- package/dist/lib/context-compression.test.js.map +1 -0
- package/dist/lib/context-loader.d.ts.map +1 -1
- package/dist/lib/context-loader.js +8 -1
- package/dist/lib/context-loader.js.map +1 -1
- package/dist/lib/context-loader.test.d.ts +2 -0
- package/dist/lib/context-loader.test.d.ts.map +1 -0
- package/dist/lib/context-loader.test.js +207 -0
- package/dist/lib/context-loader.test.js.map +1 -0
- package/dist/lib/describe-core.d.ts +9 -0
- package/dist/lib/describe-core.d.ts.map +1 -0
- package/dist/lib/describe-core.js +71 -0
- package/dist/lib/describe-core.js.map +1 -0
- package/dist/lib/describe-core.test.d.ts +2 -0
- package/dist/lib/describe-core.test.d.ts.map +1 -0
- package/dist/lib/describe-core.test.js +208 -0
- package/dist/lib/describe-core.test.js.map +1 -0
- package/dist/lib/describe-output-path.test.d.ts +2 -0
- package/dist/lib/describe-output-path.test.d.ts.map +1 -0
- package/dist/lib/describe-output-path.test.js +51 -0
- package/dist/lib/describe-output-path.test.js.map +1 -0
- package/dist/lib/describe-parser.d.ts +3 -0
- package/dist/lib/describe-parser.d.ts.map +1 -0
- package/dist/lib/describe-parser.js +163 -0
- package/dist/lib/describe-parser.js.map +1 -0
- package/dist/lib/describe-parser.test.d.ts +2 -0
- package/dist/lib/describe-parser.test.d.ts.map +1 -0
- package/dist/lib/describe-parser.test.js +282 -0
- package/dist/lib/describe-parser.test.js.map +1 -0
- package/dist/lib/description-executor.d.ts +22 -0
- package/dist/lib/description-executor.d.ts.map +1 -0
- package/dist/lib/description-executor.js +72 -0
- package/dist/lib/description-executor.js.map +1 -0
- package/dist/lib/description-formatter.d.ts +37 -0
- package/dist/lib/description-formatter.d.ts.map +1 -0
- package/dist/lib/description-formatter.js +219 -0
- package/dist/lib/description-formatter.js.map +1 -0
- package/dist/lib/diff-parser.d.ts +11 -0
- package/dist/lib/diff-parser.d.ts.map +1 -1
- package/dist/lib/diff-parser.js +37 -0
- package/dist/lib/diff-parser.js.map +1 -1
- package/dist/lib/issue-parser.d.ts +1 -1
- package/dist/lib/issue-parser.d.ts.map +1 -1
- package/dist/lib/issue-parser.js +16 -14
- package/dist/lib/issue-parser.js.map +1 -1
- package/dist/lib/json-output-schema.d.ts +207 -0
- package/dist/lib/json-output-schema.d.ts.map +1 -0
- package/dist/lib/json-output-schema.js +124 -0
- package/dist/lib/json-output-schema.js.map +1 -0
- package/dist/lib/json-output-schema.test.d.ts +2 -0
- package/dist/lib/json-output-schema.test.d.ts.map +1 -0
- package/dist/lib/json-output-schema.test.js +92 -0
- package/dist/lib/json-output-schema.test.js.map +1 -0
- package/dist/lib/json-output.d.ts +43 -0
- package/dist/lib/json-output.d.ts.map +1 -0
- package/dist/lib/json-output.js +34 -0
- package/dist/lib/json-output.js.map +1 -0
- package/dist/lib/output-paths.d.ts +6 -0
- package/dist/lib/output-paths.d.ts.map +1 -0
- package/dist/lib/output-paths.js +5 -0
- package/dist/lib/output-paths.js.map +1 -0
- package/dist/lib/platform-client.d.ts +1 -1
- package/dist/lib/platform-client.d.ts.map +1 -1
- package/dist/lib/repository-validator.d.ts +52 -0
- package/dist/lib/repository-validator.d.ts.map +1 -0
- package/dist/lib/repository-validator.js +219 -0
- package/dist/lib/repository-validator.js.map +1 -0
- package/dist/lib/repository-validator.test.d.ts +5 -0
- package/dist/lib/repository-validator.test.d.ts.map +1 -0
- package/dist/lib/repository-validator.test.js +341 -0
- package/dist/lib/repository-validator.test.js.map +1 -0
- package/dist/lib/review-core.d.ts +66 -0
- package/dist/lib/review-core.d.ts.map +1 -0
- package/dist/lib/review-core.js +449 -0
- package/dist/lib/review-core.js.map +1 -0
- package/dist/lib/review-core.test.d.ts +2 -0
- package/dist/lib/review-core.test.d.ts.map +1 -0
- package/dist/lib/review-core.test.js +552 -0
- package/dist/lib/review-core.test.js.map +1 -0
- package/dist/lib/review-orchestrator.d.ts +27 -10
- package/dist/lib/review-orchestrator.d.ts.map +1 -1
- package/dist/lib/review-orchestrator.js +51 -110
- package/dist/lib/review-orchestrator.js.map +1 -1
- package/dist/lib/review-orchestrator.test.d.ts +2 -0
- package/dist/lib/review-orchestrator.test.d.ts.map +1 -0
- package/dist/lib/review-orchestrator.test.js +413 -0
- package/dist/lib/review-orchestrator.test.js.map +1 -0
- package/dist/lib/review-output-path.test.d.ts +2 -0
- package/dist/lib/review-output-path.test.d.ts.map +1 -0
- package/dist/lib/review-output-path.test.js +83 -0
- package/dist/lib/review-output-path.test.js.map +1 -0
- package/dist/lib/review-parser.d.ts +2 -0
- package/dist/lib/review-parser.d.ts.map +1 -0
- package/dist/lib/review-parser.js +100 -0
- package/dist/lib/review-parser.js.map +1 -0
- package/dist/lib/unified-review-executor.d.ts +21 -4
- package/dist/lib/unified-review-executor.d.ts.map +1 -1
- package/dist/lib/unified-review-executor.js +81 -151
- package/dist/lib/unified-review-executor.js.map +1 -1
- package/dist/lib/unified-review-executor.test.d.ts +5 -0
- package/dist/lib/unified-review-executor.test.d.ts.map +1 -0
- package/dist/lib/unified-review-executor.test.js +344 -0
- package/dist/lib/unified-review-executor.test.js.map +1 -0
- package/dist/lib/write-json-output.d.ts +13 -0
- package/dist/lib/write-json-output.d.ts.map +1 -0
- package/dist/lib/write-json-output.js +37 -0
- package/dist/lib/write-json-output.js.map +1 -0
- package/dist/opencode/agent-loader.d.ts +3 -4
- package/dist/opencode/agent-loader.d.ts.map +1 -1
- package/dist/opencode/agent-loader.js +48 -34
- package/dist/opencode/agent-loader.js.map +1 -1
- package/dist/opencode/agent-skill-overlay.d.ts +11 -0
- package/dist/opencode/agent-skill-overlay.d.ts.map +1 -0
- package/dist/opencode/agent-skill-overlay.js +164 -0
- package/dist/opencode/agent-skill-overlay.js.map +1 -0
- package/dist/opencode/client.d.ts +11 -3
- package/dist/opencode/client.d.ts.map +1 -1
- package/dist/opencode/client.js +237 -71
- package/dist/opencode/client.js.map +1 -1
- package/dist/opencode/client.test.d.ts +2 -0
- package/dist/opencode/client.test.d.ts.map +1 -0
- package/dist/opencode/client.test.js +317 -0
- package/dist/opencode/client.test.js.map +1 -0
- package/dist/opencode/opencode-paths.d.ts +2 -0
- package/dist/opencode/opencode-paths.d.ts.map +1 -0
- package/dist/opencode/opencode-paths.js +7 -0
- package/dist/opencode/opencode-paths.js.map +1 -0
- package/dist/opencode/skill-loader.d.ts +6 -0
- package/dist/opencode/skill-loader.d.ts.map +1 -0
- package/dist/opencode/skill-loader.js +36 -0
- package/dist/opencode/skill-loader.js.map +1 -0
- package/package.json +7 -7
- package/.opencode/agent/github-reviewer.md +0 -77
- package/.opencode/agent/gitlab-reviewer.md +0 -77
- package/.opencode/agent/local-reviewer.md +0 -63
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review-orchestrator.d.ts","sourceRoot":"","sources":["../../src/lib/review-orchestrator.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"review-orchestrator.d.ts","sourceRoot":"","sources":["../../src/lib/review-orchestrator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAgC,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC1F,OAAO,EAAE,gBAAgB,EAAE,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC5E,OAAO,EAGL,oBAAoB,IAAI,cAAc,EACtC,iBAAiB,IAAI,mBAAmB,EAEzC,MAAM,kBAAkB,CAAC;AAG1B;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,wFAAwF;IACxF,cAAc,CAAC,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5D,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gDAAgD;IAChD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,+DAA+D;IAC/D,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,wCAAwC;IACxC,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,oCAAoC;IACpC,OAAO,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;IAC7C,+CAA+C;IAC/C,aAAa,CAAC,EAAE,OAAO,qBAAqB,EAAE,aAAa,CAAC;IAC5D,0DAA0D;IAC1D,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,SAAS,GAAG,MAAM,EAAE,CAE/E;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,SAAS,EACjB,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,cAAc,CAAC,CA0BzB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,YAAY,CAAC,CAoFvB;AAGD,eAAO,MAAM,oBAAoB,uBAAiB,CAAC;AACnD,eAAO,MAAM,iBAAiB,4BAAsB,CAAC"}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review orchestrator for local diff reviews
|
|
3
|
+
*
|
|
4
|
+
* This module handles local git diff reviews (pre-push analysis).
|
|
5
|
+
* It uses the shared core logic from review-core.ts.
|
|
6
|
+
*/
|
|
1
7
|
import chalk from 'chalk';
|
|
2
|
-
import { shouldIgnoreFile, getModelOverrides,
|
|
8
|
+
import { shouldIgnoreFile, getModelOverrides, getUnifiedModelOverride, } from './config.js';
|
|
3
9
|
import { createOpencodeClientInstance } from '../opencode/client.js';
|
|
4
|
-
import { parseReviewIssues } from './issue-parser.js';
|
|
5
10
|
import { calculateSummary } from './comment-formatter.js';
|
|
6
|
-
import {
|
|
11
|
+
import { buildBaseInstructions, runReviewPipeline, displayReviewSummary as displaySummary, hasBlockingIssues as checkBlockingIssues, } from './review-core.js';
|
|
12
|
+
import { compressFilesWithDiffs, formatCompressionSummary } from './context-compression.js';
|
|
7
13
|
/**
|
|
8
14
|
* Filter files based on ignore patterns in config
|
|
9
15
|
*/
|
|
@@ -13,15 +19,21 @@ export function filterIgnoredFiles(files, config) {
|
|
|
13
19
|
/**
|
|
14
20
|
* Connect to OpenCode server (or start in-process)
|
|
15
21
|
*/
|
|
16
|
-
export async function connectToOpenCode(config, workingDir) {
|
|
22
|
+
export async function connectToOpenCode(config, workingDir, options) {
|
|
17
23
|
console.log(chalk.gray('Connecting to OpenCode server...\n'));
|
|
18
24
|
try {
|
|
19
25
|
// Get model overrides from DRS config
|
|
20
|
-
const modelOverrides =
|
|
26
|
+
const modelOverrides = options?.modelOverrides ?? {
|
|
27
|
+
...getModelOverrides(config),
|
|
28
|
+
...getUnifiedModelOverride(config),
|
|
29
|
+
};
|
|
21
30
|
return await createOpencodeClientInstance({
|
|
22
31
|
baseUrl: config.opencode.serverUrl || undefined,
|
|
23
32
|
directory: workingDir || process.cwd(),
|
|
24
33
|
modelOverrides,
|
|
34
|
+
provider: config.opencode.provider,
|
|
35
|
+
config,
|
|
36
|
+
debug: options?.debug,
|
|
25
37
|
});
|
|
26
38
|
}
|
|
27
39
|
catch (error) {
|
|
@@ -64,120 +76,49 @@ export async function executeReview(config, source) {
|
|
|
64
76
|
}
|
|
65
77
|
console.log(chalk.gray(`Reviewing ${filteredFiles.length} file(s)\n`));
|
|
66
78
|
// Connect to OpenCode
|
|
67
|
-
const opencode = await connectToOpenCode(config, source.workingDir);
|
|
79
|
+
const opencode = await connectToOpenCode(config, source.workingDir, { debug: source.debug });
|
|
68
80
|
try {
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
**Instructions:**
|
|
77
|
-
1. Use the Read tool to examine each changed file
|
|
78
|
-
2. Analyze the code for issues in your specialty area
|
|
79
|
-
3. Output your findings in this JSON format:
|
|
80
|
-
|
|
81
|
-
\`\`\`json
|
|
82
|
-
{
|
|
83
|
-
"issues": [
|
|
84
|
-
{
|
|
85
|
-
"category": "SECURITY" | "QUALITY" | "STYLE" | "PERFORMANCE",
|
|
86
|
-
"severity": "CRITICAL" | "HIGH" | "MEDIUM" | "LOW",
|
|
87
|
-
"title": "Brief title",
|
|
88
|
-
"file": "path/to/file.ts",
|
|
89
|
-
"line": 42,
|
|
90
|
-
"problem": "Description of the problem",
|
|
91
|
-
"solution": "How to fix it",
|
|
92
|
-
"agent": "security" | "quality" | "style" | "performance"
|
|
93
|
-
}
|
|
94
|
-
]
|
|
95
|
-
}
|
|
96
|
-
\`\`\`
|
|
97
|
-
|
|
98
|
-
Be thorough and identify all issues. Include line numbers when possible.`;
|
|
99
|
-
const agentNames = getAgentNames(config);
|
|
100
|
-
const agentPromises = agentNames.map(async (agentType) => {
|
|
101
|
-
const agentName = `review/${agentType}`;
|
|
102
|
-
console.log(chalk.gray(`Running ${agentType} review...\n`));
|
|
103
|
-
try {
|
|
104
|
-
const reviewPrompt = buildReviewPrompt(agentType, baseInstructions, source.name, filteredFiles);
|
|
105
|
-
const session = await opencode.createSession({
|
|
106
|
-
agent: agentName,
|
|
107
|
-
message: reviewPrompt,
|
|
108
|
-
context: {
|
|
109
|
-
...source.context,
|
|
110
|
-
files: filteredFiles,
|
|
111
|
-
},
|
|
112
|
-
});
|
|
113
|
-
const agentIssues = [];
|
|
114
|
-
for await (const message of opencode.streamMessages(session.id)) {
|
|
115
|
-
if (message.role === 'assistant') {
|
|
116
|
-
const parsedIssues = parseReviewIssues(message.content);
|
|
117
|
-
if (parsedIssues.length > 0) {
|
|
118
|
-
agentIssues.push(...parsedIssues);
|
|
119
|
-
console.log(chalk.green(`✓ [${agentType}] Found ${parsedIssues.length} issue(s)`));
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
await opencode.closeSession(session.id);
|
|
124
|
-
return { agentType, success: true, issues: agentIssues };
|
|
125
|
-
}
|
|
126
|
-
catch (error) {
|
|
127
|
-
console.error(chalk.red(`✗ ${agentType} agent failed: ${error}`));
|
|
128
|
-
return { agentType, success: false, issues: [] };
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
const agentResults = await Promise.all(agentPromises);
|
|
132
|
-
const successfulAgents = agentResults.filter((r) => r.success);
|
|
133
|
-
const failedAgents = agentResults.filter((r) => !r.success);
|
|
134
|
-
if (successfulAgents.length === 0) {
|
|
135
|
-
console.error(chalk.red('\n✗ All review agents failed!\n'));
|
|
136
|
-
console.error(chalk.yellow('This usually means:\n' +
|
|
137
|
-
' 1. Model configuration is incorrect or missing\n' +
|
|
138
|
-
' 2. API credentials are invalid or missing\n' +
|
|
139
|
-
' 3. Models are not accessible or timed out\n' +
|
|
140
|
-
' 4. Agents cannot find files to review\n'));
|
|
141
|
-
await opencode.shutdown();
|
|
142
|
-
process.exit(1);
|
|
81
|
+
// Build instructions - use provided diffs if available, otherwise fall back to git command
|
|
82
|
+
const diffCommand = source.staged ? 'git diff --cached -- <file>' : 'git diff -- <file>';
|
|
83
|
+
// Use provided diffs if available (filtered to match filteredFiles)
|
|
84
|
+
let filesForInstructions;
|
|
85
|
+
if (source.filesWithDiffs && source.filesWithDiffs.length > 0) {
|
|
86
|
+
// Filter to only include files that passed ignore patterns
|
|
87
|
+
filesForInstructions = source.filesWithDiffs.filter((f) => filteredFiles.includes(f.filename));
|
|
143
88
|
}
|
|
144
|
-
|
|
145
|
-
|
|
89
|
+
else {
|
|
90
|
+
// No diffs provided - agents will need to run git diff
|
|
91
|
+
filesForInstructions = filteredFiles.map((f) => ({ filename: f }));
|
|
146
92
|
}
|
|
147
|
-
|
|
148
|
-
const
|
|
93
|
+
const compression = compressFilesWithDiffs(filesForInstructions, config.contextCompression);
|
|
94
|
+
const compressionSummary = formatCompressionSummary(compression);
|
|
95
|
+
if (compressionSummary) {
|
|
96
|
+
console.log(chalk.yellow('⚠ Diff content trimmed to fit token budget.\n'));
|
|
97
|
+
}
|
|
98
|
+
const baseInstructions = buildBaseInstructions(source.name, compression.files, diffCommand, compressionSummary);
|
|
99
|
+
// Run agents using shared core logic
|
|
100
|
+
const result = await runReviewPipeline(opencode, config, baseInstructions, source.name, filteredFiles, source.context, source.workingDir || process.cwd(), source.debug || false);
|
|
149
101
|
return {
|
|
150
|
-
issues,
|
|
151
|
-
summary,
|
|
152
|
-
|
|
102
|
+
issues: result.issues,
|
|
103
|
+
summary: result.summary,
|
|
104
|
+
changeSummary: result.changeSummary,
|
|
105
|
+
filesReviewed: result.filesReviewed,
|
|
153
106
|
};
|
|
154
107
|
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
// Handle "all agents failed" error
|
|
110
|
+
if (error instanceof Error && error.message === 'All review agents failed') {
|
|
111
|
+
await opencode.shutdown();
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
155
116
|
finally {
|
|
156
117
|
// Always shut down OpenCode client
|
|
157
118
|
await opencode.shutdown();
|
|
158
119
|
}
|
|
159
120
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
export function displayReviewSummary(result) {
|
|
164
|
-
console.log(chalk.bold('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
165
|
-
console.log(chalk.bold('📊 Review Summary'));
|
|
166
|
-
console.log(chalk.bold('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
|
|
167
|
-
console.log(` Files reviewed: ${chalk.cyan(result.summary.filesReviewed)}`);
|
|
168
|
-
console.log(` Issues found: ${chalk.yellow(result.summary.issuesFound)}`);
|
|
169
|
-
if (result.summary.issuesFound > 0) {
|
|
170
|
-
console.log(` 🔴 Critical: ${chalk.red(result.summary.bySeverity.CRITICAL)}`);
|
|
171
|
-
console.log(` 🟡 High: ${chalk.yellow(result.summary.bySeverity.HIGH)}`);
|
|
172
|
-
console.log(` 🟠 Medium: ${chalk.hex('#FFA500')(result.summary.bySeverity.MEDIUM)}`);
|
|
173
|
-
console.log(` ⚪ Low: ${chalk.gray(result.summary.bySeverity.LOW)}`);
|
|
174
|
-
}
|
|
175
|
-
console.log('');
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Check if review has blocking issues (CRITICAL or HIGH)
|
|
179
|
-
*/
|
|
180
|
-
export function hasBlockingIssues(result) {
|
|
181
|
-
return result.summary.bySeverity.CRITICAL > 0 || result.summary.bySeverity.HIGH > 0;
|
|
182
|
-
}
|
|
121
|
+
// Re-export display functions from core for backward compatibility
|
|
122
|
+
export const displayReviewSummary = displaySummary;
|
|
123
|
+
export const hasBlockingIssues = checkBlockingIssues;
|
|
183
124
|
//# sourceMappingURL=review-orchestrator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"review-orchestrator.js","sourceRoot":"","sources":["../../src/lib/review-orchestrator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,
|
|
1
|
+
{"version":3,"file":"review-orchestrator.js","sourceRoot":"","sources":["../../src/lib/review-orchestrator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-orchestrator.test.d.ts","sourceRoot":"","sources":["../../src/lib/review-orchestrator.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { filterIgnoredFiles, connectToOpenCode, executeReview, } from './review-orchestrator.js';
|
|
3
|
+
// Mock dependencies
|
|
4
|
+
vi.mock('./config.js', async () => {
|
|
5
|
+
const actual = await vi.importActual('./config.js');
|
|
6
|
+
return {
|
|
7
|
+
...actual,
|
|
8
|
+
shouldIgnoreFile: vi.fn((file, config) => {
|
|
9
|
+
const patterns = config.review.ignorePatterns || [];
|
|
10
|
+
return patterns.some((pattern) => {
|
|
11
|
+
if (pattern.endsWith('/*')) {
|
|
12
|
+
const dir = pattern.slice(0, -2);
|
|
13
|
+
return file.startsWith(dir + '/');
|
|
14
|
+
}
|
|
15
|
+
if (pattern.includes('*')) {
|
|
16
|
+
const regex = new RegExp('^' + pattern.replace(/\*/g, '.*').replace(/\?/g, '.') + '$');
|
|
17
|
+
return regex.test(file);
|
|
18
|
+
}
|
|
19
|
+
return file === pattern;
|
|
20
|
+
});
|
|
21
|
+
}),
|
|
22
|
+
getModelOverrides: vi.fn(() => ({})),
|
|
23
|
+
getUnifiedModelOverride: vi.fn(() => ({})),
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
// Store mock client instance for verification
|
|
27
|
+
let mockOpencodeClient;
|
|
28
|
+
vi.mock('../opencode/client.js', () => ({
|
|
29
|
+
createOpencodeClientInstance: vi.fn(async () => {
|
|
30
|
+
mockOpencodeClient = {
|
|
31
|
+
createSession: vi.fn(async () => ({ id: 'session-1' })),
|
|
32
|
+
streamMessages: vi.fn(async function* () {
|
|
33
|
+
yield {
|
|
34
|
+
role: 'assistant',
|
|
35
|
+
content: JSON.stringify({
|
|
36
|
+
issues: [
|
|
37
|
+
{
|
|
38
|
+
category: 'QUALITY',
|
|
39
|
+
severity: 'MEDIUM',
|
|
40
|
+
title: 'Test issue',
|
|
41
|
+
file: 'src/app.ts',
|
|
42
|
+
line: 10,
|
|
43
|
+
problem: 'Test problem',
|
|
44
|
+
solution: 'Test solution',
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
}),
|
|
48
|
+
};
|
|
49
|
+
}),
|
|
50
|
+
closeSession: vi.fn(async () => { }),
|
|
51
|
+
shutdown: vi.fn(async () => { }),
|
|
52
|
+
};
|
|
53
|
+
return mockOpencodeClient;
|
|
54
|
+
}),
|
|
55
|
+
}));
|
|
56
|
+
vi.mock('./review-core.js', () => ({
|
|
57
|
+
buildBaseInstructions: vi.fn((label) => `Instructions for ${label}`),
|
|
58
|
+
runReviewPipeline: vi.fn(async (_opencode, _config, _instructions, _label, files) => ({
|
|
59
|
+
issues: [
|
|
60
|
+
{
|
|
61
|
+
category: 'QUALITY',
|
|
62
|
+
severity: 'MEDIUM',
|
|
63
|
+
title: 'Test issue',
|
|
64
|
+
file: files[0] || 'src/app.ts',
|
|
65
|
+
line: 10,
|
|
66
|
+
problem: 'Test problem',
|
|
67
|
+
solution: 'Test solution',
|
|
68
|
+
agent: 'quality',
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
summary: {
|
|
72
|
+
filesReviewed: files.length,
|
|
73
|
+
issuesFound: 1,
|
|
74
|
+
bySeverity: { CRITICAL: 0, HIGH: 0, MEDIUM: 1, LOW: 0 },
|
|
75
|
+
byCategory: { SECURITY: 0, QUALITY: 1, STYLE: 0, PERFORMANCE: 0, DOCUMENTATION: 0 },
|
|
76
|
+
},
|
|
77
|
+
filesReviewed: files.length,
|
|
78
|
+
agentResults: [],
|
|
79
|
+
})),
|
|
80
|
+
displayReviewSummary: vi.fn(),
|
|
81
|
+
hasBlockingIssues: vi.fn(() => false),
|
|
82
|
+
}));
|
|
83
|
+
vi.mock('./comment-formatter.js', () => ({
|
|
84
|
+
calculateSummary: vi.fn((filesReviewed, issues) => ({
|
|
85
|
+
filesReviewed,
|
|
86
|
+
issuesFound: issues.length,
|
|
87
|
+
bySeverity: {
|
|
88
|
+
CRITICAL: issues.filter((i) => i.severity === 'CRITICAL').length,
|
|
89
|
+
HIGH: issues.filter((i) => i.severity === 'HIGH').length,
|
|
90
|
+
MEDIUM: issues.filter((i) => i.severity === 'MEDIUM').length,
|
|
91
|
+
LOW: issues.filter((i) => i.severity === 'LOW').length,
|
|
92
|
+
},
|
|
93
|
+
byCategory: {
|
|
94
|
+
SECURITY: issues.filter((i) => i.category === 'SECURITY').length,
|
|
95
|
+
QUALITY: issues.filter((i) => i.category === 'QUALITY').length,
|
|
96
|
+
STYLE: issues.filter((i) => i.category === 'STYLE').length,
|
|
97
|
+
PERFORMANCE: issues.filter((i) => i.category === 'PERFORMANCE').length,
|
|
98
|
+
DOCUMENTATION: issues.filter((i) => i.category === 'DOCUMENTATION').length,
|
|
99
|
+
},
|
|
100
|
+
})),
|
|
101
|
+
}));
|
|
102
|
+
vi.mock('./context-compression.js', () => ({
|
|
103
|
+
compressFilesWithDiffs: vi.fn((files) => ({
|
|
104
|
+
files,
|
|
105
|
+
removedFiles: [],
|
|
106
|
+
removedHunks: 0,
|
|
107
|
+
originalTokens: 1000,
|
|
108
|
+
compressedTokens: 1000,
|
|
109
|
+
})),
|
|
110
|
+
formatCompressionSummary: vi.fn(() => null),
|
|
111
|
+
}));
|
|
112
|
+
describe('review-orchestrator', () => {
|
|
113
|
+
beforeEach(() => {
|
|
114
|
+
vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
115
|
+
vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
116
|
+
});
|
|
117
|
+
describe('filterIgnoredFiles', () => {
|
|
118
|
+
it('should filter files based on ignore patterns', () => {
|
|
119
|
+
const config = {
|
|
120
|
+
review: {
|
|
121
|
+
ignorePatterns: ['*.test.ts', 'dist/*', 'node_modules/*'],
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
const files = [
|
|
125
|
+
'src/app.ts',
|
|
126
|
+
'src/app.test.ts',
|
|
127
|
+
'dist/bundle.js',
|
|
128
|
+
'node_modules/package/index.js',
|
|
129
|
+
'src/utils.ts',
|
|
130
|
+
];
|
|
131
|
+
const result = filterIgnoredFiles(files, config);
|
|
132
|
+
expect(result).toEqual(['src/app.ts', 'src/utils.ts']);
|
|
133
|
+
});
|
|
134
|
+
it('should return all files when no ignore patterns', () => {
|
|
135
|
+
const config = {
|
|
136
|
+
opencode: {},
|
|
137
|
+
gitlab: { url: '', token: '' },
|
|
138
|
+
github: { token: '' },
|
|
139
|
+
review: {
|
|
140
|
+
ignorePatterns: [],
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
const files = ['src/app.ts', 'src/utils.ts', 'README.md'];
|
|
144
|
+
const result = filterIgnoredFiles(files, config);
|
|
145
|
+
expect(result).toEqual(files);
|
|
146
|
+
});
|
|
147
|
+
it('should handle glob patterns correctly', () => {
|
|
148
|
+
const config = {
|
|
149
|
+
review: {
|
|
150
|
+
ignorePatterns: ['**/*.spec.ts', 'test/**'],
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
const files = ['src/app.ts', 'src/app.spec.ts', 'test/integration.ts', 'lib/utils.ts'];
|
|
154
|
+
const result = filterIgnoredFiles(files, config);
|
|
155
|
+
expect(result).toContain('src/app.ts');
|
|
156
|
+
expect(result).toContain('lib/utils.ts');
|
|
157
|
+
});
|
|
158
|
+
it('should handle empty file list', () => {
|
|
159
|
+
const config = {
|
|
160
|
+
review: {
|
|
161
|
+
ignorePatterns: ['*.test.ts'],
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
const result = filterIgnoredFiles([], config);
|
|
165
|
+
expect(result).toEqual([]);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
describe('connectToOpenCode', () => {
|
|
169
|
+
it('should connect to OpenCode server successfully', async () => {
|
|
170
|
+
const config = {
|
|
171
|
+
opencode: {
|
|
172
|
+
serverUrl: 'http://localhost:3000',
|
|
173
|
+
},
|
|
174
|
+
review: {},
|
|
175
|
+
};
|
|
176
|
+
const client = await connectToOpenCode(config, '/test/dir');
|
|
177
|
+
expect(client).toBeDefined();
|
|
178
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
179
|
+
expect(client.createSession).toBeDefined();
|
|
180
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
181
|
+
expect(client.shutdown).toBeDefined();
|
|
182
|
+
});
|
|
183
|
+
it('should handle connection failure', async () => {
|
|
184
|
+
const { createOpencodeClientInstance } = await import('../opencode/client.js');
|
|
185
|
+
vi.mocked(createOpencodeClientInstance).mockRejectedValueOnce(new Error('Connection failed'));
|
|
186
|
+
const config = {
|
|
187
|
+
opencode: {},
|
|
188
|
+
review: {},
|
|
189
|
+
};
|
|
190
|
+
await expect(connectToOpenCode(config)).rejects.toThrow('Connection failed');
|
|
191
|
+
expect(console.error).toHaveBeenCalledWith(expect.stringContaining('Failed to connect to OpenCode server'));
|
|
192
|
+
});
|
|
193
|
+
it('should pass model overrides to OpenCode client', async () => {
|
|
194
|
+
const { createOpencodeClientInstance } = await import('../opencode/client.js');
|
|
195
|
+
const { getModelOverrides, getUnifiedModelOverride } = await import('./config.js');
|
|
196
|
+
vi.mocked(getModelOverrides).mockReturnValue({
|
|
197
|
+
'review/security': 'claude-opus-4',
|
|
198
|
+
});
|
|
199
|
+
vi.mocked(getUnifiedModelOverride).mockReturnValue({
|
|
200
|
+
'review/unified-reviewer': 'claude-sonnet-4',
|
|
201
|
+
});
|
|
202
|
+
const config = {
|
|
203
|
+
opencode: {},
|
|
204
|
+
review: {},
|
|
205
|
+
};
|
|
206
|
+
await connectToOpenCode(config, '/test/dir', { debug: true });
|
|
207
|
+
expect(createOpencodeClientInstance).toHaveBeenCalledWith({
|
|
208
|
+
baseUrl: undefined,
|
|
209
|
+
config,
|
|
210
|
+
directory: '/test/dir',
|
|
211
|
+
modelOverrides: {
|
|
212
|
+
'review/security': 'claude-opus-4',
|
|
213
|
+
'review/unified-reviewer': 'claude-sonnet-4',
|
|
214
|
+
},
|
|
215
|
+
provider: undefined,
|
|
216
|
+
debug: true,
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
it('should use process.cwd() when no working directory provided', async () => {
|
|
220
|
+
const { createOpencodeClientInstance } = await import('../opencode/client.js');
|
|
221
|
+
const config = {
|
|
222
|
+
opencode: {},
|
|
223
|
+
review: {},
|
|
224
|
+
};
|
|
225
|
+
await connectToOpenCode(config);
|
|
226
|
+
expect(createOpencodeClientInstance).toHaveBeenCalledWith(expect.objectContaining({
|
|
227
|
+
config,
|
|
228
|
+
directory: process.cwd(),
|
|
229
|
+
}));
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
describe('executeReview', () => {
|
|
233
|
+
let mockConfig;
|
|
234
|
+
beforeEach(() => {
|
|
235
|
+
mockConfig = {
|
|
236
|
+
opencode: {},
|
|
237
|
+
review: {
|
|
238
|
+
agents: ['security', 'quality'],
|
|
239
|
+
ignorePatterns: ['*.test.ts'],
|
|
240
|
+
},
|
|
241
|
+
contextCompression: {
|
|
242
|
+
enabled: false,
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
});
|
|
246
|
+
it('should execute review successfully with files', async () => {
|
|
247
|
+
const source = {
|
|
248
|
+
name: 'Local diff',
|
|
249
|
+
files: ['src/app.ts', 'src/utils.ts'],
|
|
250
|
+
context: {},
|
|
251
|
+
workingDir: '/test/dir',
|
|
252
|
+
};
|
|
253
|
+
const result = await executeReview(mockConfig, source);
|
|
254
|
+
expect(result.issues).toBeDefined();
|
|
255
|
+
expect(result.summary).toBeDefined();
|
|
256
|
+
expect(result.filesReviewed).toBe(2);
|
|
257
|
+
expect(result.issues.length).toBeGreaterThan(0);
|
|
258
|
+
});
|
|
259
|
+
it('should filter ignored files', async () => {
|
|
260
|
+
const source = {
|
|
261
|
+
name: 'Local diff',
|
|
262
|
+
files: ['src/app.ts', 'src/app.test.ts', 'src/utils.ts'],
|
|
263
|
+
context: {},
|
|
264
|
+
};
|
|
265
|
+
const result = await executeReview(mockConfig, source);
|
|
266
|
+
// Should have filtered out .test.ts file
|
|
267
|
+
expect(result.filesReviewed).toBe(2);
|
|
268
|
+
});
|
|
269
|
+
it('should return empty result when all files are ignored', async () => {
|
|
270
|
+
const source = {
|
|
271
|
+
name: 'Local diff',
|
|
272
|
+
files: ['test1.test.ts', 'test2.test.ts'],
|
|
273
|
+
context: {},
|
|
274
|
+
};
|
|
275
|
+
const result = await executeReview(mockConfig, source);
|
|
276
|
+
expect(result.issues).toEqual([]);
|
|
277
|
+
expect(result.filesReviewed).toBe(0);
|
|
278
|
+
expect(result.summary.issuesFound).toBe(0);
|
|
279
|
+
});
|
|
280
|
+
it('should handle staged diff command', async () => {
|
|
281
|
+
const { buildBaseInstructions } = await import('./review-core.js');
|
|
282
|
+
const source = {
|
|
283
|
+
name: 'Staged changes',
|
|
284
|
+
files: ['src/app.ts'],
|
|
285
|
+
context: {},
|
|
286
|
+
staged: true,
|
|
287
|
+
};
|
|
288
|
+
await executeReview(mockConfig, source);
|
|
289
|
+
expect(buildBaseInstructions).toHaveBeenCalledWith('Staged changes', expect.anything(), 'git diff --cached -- <file>', null);
|
|
290
|
+
});
|
|
291
|
+
it('should handle unstaged diff command', async () => {
|
|
292
|
+
const { buildBaseInstructions } = await import('./review-core.js');
|
|
293
|
+
const source = {
|
|
294
|
+
name: 'Unstaged changes',
|
|
295
|
+
files: ['src/app.ts'],
|
|
296
|
+
context: {},
|
|
297
|
+
staged: false,
|
|
298
|
+
};
|
|
299
|
+
await executeReview(mockConfig, source);
|
|
300
|
+
expect(buildBaseInstructions).toHaveBeenCalledWith('Unstaged changes', expect.anything(), 'git diff -- <file>', null);
|
|
301
|
+
});
|
|
302
|
+
it('should use provided diffs when available', async () => {
|
|
303
|
+
const source = {
|
|
304
|
+
name: 'PR #123',
|
|
305
|
+
files: ['src/app.ts', 'src/utils.ts'],
|
|
306
|
+
filesWithDiffs: [
|
|
307
|
+
{ filename: 'src/app.ts', patch: '+ new code' },
|
|
308
|
+
{ filename: 'src/utils.ts', patch: '- old code\n+ new code' },
|
|
309
|
+
],
|
|
310
|
+
context: {},
|
|
311
|
+
};
|
|
312
|
+
const result = await executeReview(mockConfig, source);
|
|
313
|
+
expect(result.filesReviewed).toBe(2);
|
|
314
|
+
});
|
|
315
|
+
it('should filter filesWithDiffs to match filtered files', async () => {
|
|
316
|
+
const source = {
|
|
317
|
+
name: 'PR #123',
|
|
318
|
+
files: ['src/app.ts', 'src/app.test.ts', 'src/utils.ts'],
|
|
319
|
+
filesWithDiffs: [
|
|
320
|
+
{ filename: 'src/app.ts', patch: '+ new code' },
|
|
321
|
+
{ filename: 'src/app.test.ts', patch: '+ test code' },
|
|
322
|
+
{ filename: 'src/utils.ts', patch: '+ util code' },
|
|
323
|
+
],
|
|
324
|
+
context: {},
|
|
325
|
+
};
|
|
326
|
+
await executeReview(mockConfig, source);
|
|
327
|
+
// Should filter out .test.ts file from diffs as well
|
|
328
|
+
const { buildBaseInstructions } = await import('./review-core.js');
|
|
329
|
+
expect(buildBaseInstructions).toHaveBeenCalled();
|
|
330
|
+
});
|
|
331
|
+
it('should handle empty file list', async () => {
|
|
332
|
+
const source = {
|
|
333
|
+
name: 'Empty review',
|
|
334
|
+
files: [],
|
|
335
|
+
context: {},
|
|
336
|
+
};
|
|
337
|
+
const result = await executeReview(mockConfig, source);
|
|
338
|
+
expect(result.issues).toEqual([]);
|
|
339
|
+
expect(result.filesReviewed).toBe(0);
|
|
340
|
+
});
|
|
341
|
+
it('should pass debug flag to agents', async () => {
|
|
342
|
+
const source = {
|
|
343
|
+
name: 'Debug review',
|
|
344
|
+
files: ['src/app.ts'],
|
|
345
|
+
context: {},
|
|
346
|
+
debug: true,
|
|
347
|
+
};
|
|
348
|
+
await executeReview(mockConfig, source);
|
|
349
|
+
const { runReviewPipeline } = await import('./review-core.js');
|
|
350
|
+
expect(runReviewPipeline).toHaveBeenCalledWith(expect.anything(), mockConfig, expect.anything(), 'Debug review', ['src/app.ts'], {}, process.cwd(), true);
|
|
351
|
+
});
|
|
352
|
+
it('should pass additional context to agents', async () => {
|
|
353
|
+
const source = {
|
|
354
|
+
name: 'PR #123',
|
|
355
|
+
files: ['src/app.ts'],
|
|
356
|
+
context: {
|
|
357
|
+
prNumber: 123,
|
|
358
|
+
author: 'test-user',
|
|
359
|
+
},
|
|
360
|
+
};
|
|
361
|
+
await executeReview(mockConfig, source);
|
|
362
|
+
const { runReviewPipeline } = await import('./review-core.js');
|
|
363
|
+
expect(runReviewPipeline).toHaveBeenCalledWith(expect.anything(), mockConfig, expect.anything(), 'PR #123', ['src/app.ts'], { prNumber: 123, author: 'test-user' }, process.cwd(), false);
|
|
364
|
+
});
|
|
365
|
+
it('should shutdown OpenCode client after review', async () => {
|
|
366
|
+
const source = {
|
|
367
|
+
name: 'Test review',
|
|
368
|
+
files: ['src/app.ts'],
|
|
369
|
+
context: {},
|
|
370
|
+
};
|
|
371
|
+
await executeReview(mockConfig, source);
|
|
372
|
+
// Verify shutdown was called on the mock client
|
|
373
|
+
expect(mockOpencodeClient.shutdown).toHaveBeenCalled();
|
|
374
|
+
});
|
|
375
|
+
it('should shutdown OpenCode client even on error', async () => {
|
|
376
|
+
const { runReviewPipeline } = await import('./review-core.js');
|
|
377
|
+
vi.mocked(runReviewPipeline).mockRejectedValueOnce(new Error('Review failed'));
|
|
378
|
+
const source = {
|
|
379
|
+
name: 'Test review',
|
|
380
|
+
files: ['src/app.ts'],
|
|
381
|
+
context: {},
|
|
382
|
+
};
|
|
383
|
+
await expect(executeReview(mockConfig, source)).rejects.toThrow('Review failed');
|
|
384
|
+
// Verify shutdown was called even on error
|
|
385
|
+
expect(mockOpencodeClient.shutdown).toHaveBeenCalled();
|
|
386
|
+
});
|
|
387
|
+
it('should handle "All review agents failed" error specially', async () => {
|
|
388
|
+
const { runReviewPipeline } = await import('./review-core.js');
|
|
389
|
+
vi.mocked(runReviewPipeline).mockRejectedValueOnce(new Error('All review agents failed'));
|
|
390
|
+
const mockExit = vi.spyOn(process, 'exit').mockImplementation((() => { }));
|
|
391
|
+
const source = {
|
|
392
|
+
name: 'Test review',
|
|
393
|
+
files: ['src/app.ts'],
|
|
394
|
+
context: {},
|
|
395
|
+
};
|
|
396
|
+
await expect(executeReview(mockConfig, source)).rejects.toThrow('All review agents failed');
|
|
397
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
398
|
+
mockExit.mockRestore();
|
|
399
|
+
});
|
|
400
|
+
it('should log compression warning when context is compressed', async () => {
|
|
401
|
+
const { formatCompressionSummary } = await import('./context-compression.js');
|
|
402
|
+
vi.mocked(formatCompressionSummary).mockReturnValueOnce('⚠️ Removed 5 files to fit token budget');
|
|
403
|
+
const source = {
|
|
404
|
+
name: 'Large PR',
|
|
405
|
+
files: ['src/app.ts'],
|
|
406
|
+
context: {},
|
|
407
|
+
};
|
|
408
|
+
await executeReview(mockConfig, source);
|
|
409
|
+
expect(console.log).toHaveBeenCalledWith(expect.stringContaining('Diff content trimmed to fit token budget'));
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
//# sourceMappingURL=review-orchestrator.test.js.map
|