@diff-review-system/drs 1.0.0 → 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 -130
- package/.opencode/agent/review/quality.md +36 -104
- package/.opencode/agent/review/security.md +32 -94
- package/.opencode/agent/review/style.md +26 -10
- package/.opencode/agent/review/unified-reviewer.md +74 -0
- package/.opencode/opencode.jsonc +4 -41
- package/.opencode/tool/write_json_output.ts +24 -0
- package/README.md +215 -82
- package/dist/ci/runner.d.ts.map +1 -1
- package/dist/ci/runner.js +4 -4
- 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 +234 -20
- 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 +337 -120
- 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 +46 -63
- package/dist/cli/review-local.js.map +1 -1
- package/dist/cli/review-mr.d.ts +7 -0
- package/dist/cli/review-mr.d.ts.map +1 -1
- package/dist/cli/review-mr.js +88 -117
- 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 +81 -114
- 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/client.d.ts +199 -4
- package/dist/github/client.d.ts.map +1 -1
- package/dist/github/client.js +37 -2
- package/dist/github/client.js.map +1 -1
- package/dist/github/client.test.d.ts +2 -0
- package/dist/github/client.test.d.ts.map +1 -0
- package/dist/github/client.test.js +206 -0
- package/dist/github/client.test.js.map +1 -0
- package/dist/github/platform-adapter.d.ts +31 -0
- package/dist/github/platform-adapter.d.ts.map +1 -0
- package/dist/github/platform-adapter.js +129 -0
- package/dist/github/platform-adapter.js.map +1 -0
- package/dist/github/platform-adapter.test.d.ts +2 -0
- package/dist/github/platform-adapter.test.d.ts.map +1 -0
- package/dist/github/platform-adapter.test.js +40 -0
- package/dist/github/platform-adapter.test.js.map +1 -0
- package/dist/gitlab/client.d.ts +12 -0
- package/dist/gitlab/client.d.ts.map +1 -1
- package/dist/gitlab/client.js +19 -1
- package/dist/gitlab/client.js.map +1 -1
- package/dist/gitlab/diff-parser.test.d.ts +2 -0
- package/dist/gitlab/diff-parser.test.d.ts.map +1 -0
- package/dist/gitlab/diff-parser.test.js +315 -0
- package/dist/gitlab/diff-parser.test.js.map +1 -0
- package/dist/gitlab/platform-adapter.d.ts +27 -0
- package/dist/gitlab/platform-adapter.d.ts.map +1 -0
- package/dist/gitlab/platform-adapter.js +121 -0
- package/dist/gitlab/platform-adapter.js.map +1 -0
- package/dist/gitlab/platform-adapter.test.d.ts +2 -0
- package/dist/gitlab/platform-adapter.test.d.ts.map +1 -0
- package/dist/gitlab/platform-adapter.test.js +21 -0
- package/dist/gitlab/platform-adapter.test.js.map +1 -0
- package/dist/index.test.d.ts +2 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/index.test.js +7 -0
- package/dist/index.test.js.map +1 -0
- 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/code-quality-report.d.ts +44 -0
- package/dist/lib/code-quality-report.d.ts.map +1 -0
- package/dist/lib/code-quality-report.js +62 -0
- package/dist/lib/code-quality-report.js.map +1 -0
- package/dist/lib/code-quality-report.test.d.ts +2 -0
- package/dist/lib/code-quality-report.test.d.ts.map +1 -0
- package/dist/lib/code-quality-report.test.js +327 -0
- package/dist/lib/code-quality-report.test.js.map +1 -0
- package/dist/{gitlab → lib}/comment-formatter.d.ts +6 -3
- package/dist/lib/comment-formatter.d.ts.map +1 -0
- package/dist/{gitlab → lib}/comment-formatter.js +63 -16
- package/dist/lib/comment-formatter.js.map +1 -0
- 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.d.ts +61 -0
- package/dist/lib/comment-manager.d.ts.map +1 -0
- package/dist/lib/comment-manager.js +91 -0
- package/dist/lib/comment-manager.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.d.ts +12 -0
- package/dist/lib/config-model-overrides.test.d.ts.map +1 -0
- package/dist/lib/config-model-overrides.test.js +254 -0
- package/dist/lib/config-model-overrides.test.js.map +1 -0
- package/dist/lib/config.d.ts +93 -8
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +178 -25
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/config.test.d.ts +2 -0
- package/dist/lib/config.test.d.ts.map +1 -0
- package/dist/lib/config.test.js +36 -0
- package/dist/lib/config.test.js.map +1 -0
- 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 +29 -0
- package/dist/lib/context-loader.d.ts.map +1 -0
- package/dist/lib/context-loader.js +75 -0
- package/dist/lib/context-loader.js.map +1 -0
- 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/{gitlab → lib}/diff-parser.d.ts +11 -0
- package/dist/lib/diff-parser.d.ts.map +1 -0
- package/dist/{gitlab → lib}/diff-parser.js +40 -3
- package/dist/lib/diff-parser.js.map +1 -0
- package/dist/lib/issue-parser.d.ts +29 -0
- package/dist/lib/issue-parser.d.ts.map +1 -0
- package/dist/lib/issue-parser.js +153 -0
- package/dist/lib/issue-parser.js.map +1 -0
- package/dist/lib/issue-parser.test.d.ts +2 -0
- package/dist/lib/issue-parser.test.d.ts.map +1 -0
- package/dist/lib/issue-parser.test.js +281 -0
- package/dist/lib/issue-parser.test.js.map +1 -0
- 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 +130 -0
- package/dist/lib/platform-client.d.ts.map +1 -0
- package/dist/lib/platform-client.js +8 -0
- package/dist/lib/platform-client.js.map +1 -0
- package/dist/lib/position-validator.d.ts +36 -0
- package/dist/lib/position-validator.d.ts.map +1 -0
- package/dist/lib/position-validator.js +43 -0
- package/dist/lib/position-validator.js.map +1 -0
- 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 +77 -0
- package/dist/lib/review-orchestrator.d.ts.map +1 -0
- package/dist/lib/review-orchestrator.js +124 -0
- package/dist/lib/review-orchestrator.js.map +1 -0
- 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 +49 -0
- package/dist/lib/unified-review-executor.d.ts.map +1 -0
- package/dist/lib/unified-review-executor.js +158 -0
- package/dist/lib/unified-review-executor.js.map +1 -0
- 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 +51 -42
- 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 +14 -5
- package/dist/opencode/client.d.ts.map +1 -1
- package/dist/opencode/client.js +311 -32
- 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 +29 -20
- package/.opencode/agent/github-reviewer.md +0 -62
- package/.opencode/agent/gitlab-reviewer.md +0 -62
- package/.opencode/agent/local-reviewer.md +0 -71
- package/dist/gitlab/comment-formatter.d.ts.map +0 -1
- package/dist/gitlab/comment-formatter.js.map +0 -1
- package/dist/gitlab/diff-parser.d.ts.map +0 -1
- package/dist/gitlab/diff-parser.js.map +0 -1
|
@@ -0,0 +1,77 @@
|
|
|
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
|
+
*/
|
|
7
|
+
import type { DRSConfig } from './config.js';
|
|
8
|
+
import { type ModelOverrides } from './config.js';
|
|
9
|
+
import { type OpencodeClient } from '../opencode/client.js';
|
|
10
|
+
import { calculateSummary, type ReviewIssue } from './comment-formatter.js';
|
|
11
|
+
import { displayReviewSummary as displaySummary, hasBlockingIssues as checkBlockingIssues } from './review-core.js';
|
|
12
|
+
/**
|
|
13
|
+
* Source information for a review (platform-agnostic)
|
|
14
|
+
*/
|
|
15
|
+
export interface ReviewSource {
|
|
16
|
+
/** Human-readable name for logging (e.g., "PR #123", "MR !456", "Local diff") */
|
|
17
|
+
name: string;
|
|
18
|
+
/** List of changed file paths */
|
|
19
|
+
files: string[];
|
|
20
|
+
/** Optional: files with their diff patches (if available, passed directly to agents) */
|
|
21
|
+
filesWithDiffs?: Array<{
|
|
22
|
+
filename: string;
|
|
23
|
+
patch: string;
|
|
24
|
+
}>;
|
|
25
|
+
/** Additional context to pass to review agents */
|
|
26
|
+
context: Record<string, unknown>;
|
|
27
|
+
/** Working directory for the review (defaults to process.cwd()) */
|
|
28
|
+
workingDir?: string;
|
|
29
|
+
/** Debug mode - print OpenCode configuration */
|
|
30
|
+
debug?: boolean;
|
|
31
|
+
/** Whether this is a staged diff (affects git diff command) */
|
|
32
|
+
staged?: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Result of a review execution
|
|
36
|
+
*/
|
|
37
|
+
export interface ReviewResult {
|
|
38
|
+
/** All issues found by review agents */
|
|
39
|
+
issues: ReviewIssue[];
|
|
40
|
+
/** Calculated summary statistics */
|
|
41
|
+
summary: ReturnType<typeof calculateSummary>;
|
|
42
|
+
/** Diff-based change summary when available */
|
|
43
|
+
changeSummary?: import('./change-summary.js').ChangeSummary;
|
|
44
|
+
/** Number of files actually reviewed (after filtering) */
|
|
45
|
+
filesReviewed: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Filter files based on ignore patterns in config
|
|
49
|
+
*/
|
|
50
|
+
export declare function filterIgnoredFiles(files: string[], config: DRSConfig): string[];
|
|
51
|
+
export interface ConnectOptions {
|
|
52
|
+
debug?: boolean;
|
|
53
|
+
modelOverrides?: ModelOverrides;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Connect to OpenCode server (or start in-process)
|
|
57
|
+
*/
|
|
58
|
+
export declare function connectToOpenCode(config: DRSConfig, workingDir?: string, options?: ConnectOptions): Promise<OpencodeClient>;
|
|
59
|
+
/**
|
|
60
|
+
* Execute a code review using OpenCode agents
|
|
61
|
+
*
|
|
62
|
+
* This is the core review orchestrator that handles:
|
|
63
|
+
* - File filtering (ignore patterns)
|
|
64
|
+
* - OpenCode connection
|
|
65
|
+
* - Agent execution and streaming
|
|
66
|
+
* - Issue parsing and collection
|
|
67
|
+
* - Summary calculation
|
|
68
|
+
*
|
|
69
|
+
* Platform-specific logic (GitHub/GitLab/local) should:
|
|
70
|
+
* 1. Fetch changed files from their source
|
|
71
|
+
* 2. Call this function with a ReviewSource
|
|
72
|
+
* 3. Handle posting results to their platform
|
|
73
|
+
*/
|
|
74
|
+
export declare function executeReview(config: DRSConfig, source: ReviewSource): Promise<ReviewResult>;
|
|
75
|
+
export declare const displayReviewSummary: typeof displaySummary;
|
|
76
|
+
export declare const hasBlockingIssues: typeof checkBlockingIssues;
|
|
77
|
+
//# sourceMappingURL=review-orchestrator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,124 @@
|
|
|
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
|
+
*/
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { shouldIgnoreFile, getModelOverrides, getUnifiedModelOverride, } from './config.js';
|
|
9
|
+
import { createOpencodeClientInstance } from '../opencode/client.js';
|
|
10
|
+
import { calculateSummary } from './comment-formatter.js';
|
|
11
|
+
import { buildBaseInstructions, runReviewPipeline, displayReviewSummary as displaySummary, hasBlockingIssues as checkBlockingIssues, } from './review-core.js';
|
|
12
|
+
import { compressFilesWithDiffs, formatCompressionSummary } from './context-compression.js';
|
|
13
|
+
/**
|
|
14
|
+
* Filter files based on ignore patterns in config
|
|
15
|
+
*/
|
|
16
|
+
export function filterIgnoredFiles(files, config) {
|
|
17
|
+
return files.filter((file) => !shouldIgnoreFile(file, config));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Connect to OpenCode server (or start in-process)
|
|
21
|
+
*/
|
|
22
|
+
export async function connectToOpenCode(config, workingDir, options) {
|
|
23
|
+
console.log(chalk.gray('Connecting to OpenCode server...\n'));
|
|
24
|
+
try {
|
|
25
|
+
// Get model overrides from DRS config
|
|
26
|
+
const modelOverrides = options?.modelOverrides ?? {
|
|
27
|
+
...getModelOverrides(config),
|
|
28
|
+
...getUnifiedModelOverride(config),
|
|
29
|
+
};
|
|
30
|
+
return await createOpencodeClientInstance({
|
|
31
|
+
baseUrl: config.opencode.serverUrl || undefined,
|
|
32
|
+
directory: workingDir || process.cwd(),
|
|
33
|
+
modelOverrides,
|
|
34
|
+
provider: config.opencode.provider,
|
|
35
|
+
config,
|
|
36
|
+
debug: options?.debug,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
console.error(chalk.red('✗ Failed to connect to OpenCode server'));
|
|
41
|
+
console.error(chalk.red(`Error: ${error instanceof Error ? error.message : String(error)}\n`));
|
|
42
|
+
console.log(chalk.yellow('Please ensure OpenCode server is running or check your configuration.\n'));
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Execute a code review using OpenCode agents
|
|
48
|
+
*
|
|
49
|
+
* This is the core review orchestrator that handles:
|
|
50
|
+
* - File filtering (ignore patterns)
|
|
51
|
+
* - OpenCode connection
|
|
52
|
+
* - Agent execution and streaming
|
|
53
|
+
* - Issue parsing and collection
|
|
54
|
+
* - Summary calculation
|
|
55
|
+
*
|
|
56
|
+
* Platform-specific logic (GitHub/GitLab/local) should:
|
|
57
|
+
* 1. Fetch changed files from their source
|
|
58
|
+
* 2. Call this function with a ReviewSource
|
|
59
|
+
* 3. Handle posting results to their platform
|
|
60
|
+
*/
|
|
61
|
+
export async function executeReview(config, source) {
|
|
62
|
+
console.log(chalk.gray(`Found ${source.files.length} changed file(s)\n`));
|
|
63
|
+
// Filter files based on ignore patterns
|
|
64
|
+
const filteredFiles = filterIgnoredFiles(source.files, config);
|
|
65
|
+
const ignoredCount = source.files.length - filteredFiles.length;
|
|
66
|
+
if (ignoredCount > 0) {
|
|
67
|
+
console.log(chalk.gray(`Ignoring ${ignoredCount} file(s) based on patterns\n`));
|
|
68
|
+
}
|
|
69
|
+
if (filteredFiles.length === 0) {
|
|
70
|
+
console.log(chalk.yellow('✓ No files to review after filtering\n'));
|
|
71
|
+
return {
|
|
72
|
+
issues: [],
|
|
73
|
+
summary: calculateSummary(0, []),
|
|
74
|
+
filesReviewed: 0,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
console.log(chalk.gray(`Reviewing ${filteredFiles.length} file(s)\n`));
|
|
78
|
+
// Connect to OpenCode
|
|
79
|
+
const opencode = await connectToOpenCode(config, source.workingDir, { debug: source.debug });
|
|
80
|
+
try {
|
|
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));
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
// No diffs provided - agents will need to run git diff
|
|
91
|
+
filesForInstructions = filteredFiles.map((f) => ({ filename: f }));
|
|
92
|
+
}
|
|
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);
|
|
101
|
+
return {
|
|
102
|
+
issues: result.issues,
|
|
103
|
+
summary: result.summary,
|
|
104
|
+
changeSummary: result.changeSummary,
|
|
105
|
+
filesReviewed: result.filesReviewed,
|
|
106
|
+
};
|
|
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
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
// Always shut down OpenCode client
|
|
118
|
+
await opencode.shutdown();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Re-export display functions from core for backward compatibility
|
|
122
|
+
export const displayReviewSummary = displaySummary;
|
|
123
|
+
export const hasBlockingIssues = checkBlockingIssues;
|
|
124
|
+
//# sourceMappingURL=review-orchestrator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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
|