@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.
Files changed (287) hide show
  1. package/.opencode/agent/describe/pr-describer.md +221 -0
  2. package/.opencode/agent/review/documentation.md +56 -0
  3. package/.opencode/agent/review/performance.md +32 -130
  4. package/.opencode/agent/review/quality.md +36 -104
  5. package/.opencode/agent/review/security.md +32 -94
  6. package/.opencode/agent/review/style.md +26 -10
  7. package/.opencode/agent/review/unified-reviewer.md +74 -0
  8. package/.opencode/opencode.jsonc +4 -41
  9. package/.opencode/tool/write_json_output.ts +24 -0
  10. package/README.md +215 -82
  11. package/dist/ci/runner.d.ts.map +1 -1
  12. package/dist/ci/runner.js +4 -4
  13. package/dist/ci/runner.js.map +1 -1
  14. package/dist/cli/describe-mr.d.ts +11 -0
  15. package/dist/cli/describe-mr.d.ts.map +1 -0
  16. package/dist/cli/describe-mr.js +104 -0
  17. package/dist/cli/describe-mr.js.map +1 -0
  18. package/dist/cli/describe-pr.d.ts +12 -0
  19. package/dist/cli/describe-pr.d.ts.map +1 -0
  20. package/dist/cli/describe-pr.js +105 -0
  21. package/dist/cli/describe-pr.js.map +1 -0
  22. package/dist/cli/index.js +234 -20
  23. package/dist/cli/index.js.map +1 -1
  24. package/dist/cli/init.d.ts +1 -1
  25. package/dist/cli/init.d.ts.map +1 -1
  26. package/dist/cli/init.js +337 -120
  27. package/dist/cli/init.js.map +1 -1
  28. package/dist/cli/post-comments.d.ts +15 -0
  29. package/dist/cli/post-comments.d.ts.map +1 -0
  30. package/dist/cli/post-comments.js +216 -0
  31. package/dist/cli/post-comments.js.map +1 -0
  32. package/dist/cli/review-local.d.ts +3 -0
  33. package/dist/cli/review-local.d.ts.map +1 -1
  34. package/dist/cli/review-local.js +46 -63
  35. package/dist/cli/review-local.js.map +1 -1
  36. package/dist/cli/review-mr.d.ts +7 -0
  37. package/dist/cli/review-mr.d.ts.map +1 -1
  38. package/dist/cli/review-mr.js +88 -117
  39. package/dist/cli/review-mr.js.map +1 -1
  40. package/dist/cli/review-pr.d.ts +6 -0
  41. package/dist/cli/review-pr.d.ts.map +1 -1
  42. package/dist/cli/review-pr.js +81 -114
  43. package/dist/cli/review-pr.js.map +1 -1
  44. package/dist/cli/show-changes.d.ts +15 -0
  45. package/dist/cli/show-changes.d.ts.map +1 -0
  46. package/dist/cli/show-changes.js +184 -0
  47. package/dist/cli/show-changes.js.map +1 -0
  48. package/dist/github/client.d.ts +199 -4
  49. package/dist/github/client.d.ts.map +1 -1
  50. package/dist/github/client.js +37 -2
  51. package/dist/github/client.js.map +1 -1
  52. package/dist/github/client.test.d.ts +2 -0
  53. package/dist/github/client.test.d.ts.map +1 -0
  54. package/dist/github/client.test.js +206 -0
  55. package/dist/github/client.test.js.map +1 -0
  56. package/dist/github/platform-adapter.d.ts +31 -0
  57. package/dist/github/platform-adapter.d.ts.map +1 -0
  58. package/dist/github/platform-adapter.js +129 -0
  59. package/dist/github/platform-adapter.js.map +1 -0
  60. package/dist/github/platform-adapter.test.d.ts +2 -0
  61. package/dist/github/platform-adapter.test.d.ts.map +1 -0
  62. package/dist/github/platform-adapter.test.js +40 -0
  63. package/dist/github/platform-adapter.test.js.map +1 -0
  64. package/dist/gitlab/client.d.ts +12 -0
  65. package/dist/gitlab/client.d.ts.map +1 -1
  66. package/dist/gitlab/client.js +19 -1
  67. package/dist/gitlab/client.js.map +1 -1
  68. package/dist/gitlab/diff-parser.test.d.ts +2 -0
  69. package/dist/gitlab/diff-parser.test.d.ts.map +1 -0
  70. package/dist/gitlab/diff-parser.test.js +315 -0
  71. package/dist/gitlab/diff-parser.test.js.map +1 -0
  72. package/dist/gitlab/platform-adapter.d.ts +27 -0
  73. package/dist/gitlab/platform-adapter.d.ts.map +1 -0
  74. package/dist/gitlab/platform-adapter.js +121 -0
  75. package/dist/gitlab/platform-adapter.js.map +1 -0
  76. package/dist/gitlab/platform-adapter.test.d.ts +2 -0
  77. package/dist/gitlab/platform-adapter.test.d.ts.map +1 -0
  78. package/dist/gitlab/platform-adapter.test.js +21 -0
  79. package/dist/gitlab/platform-adapter.test.js.map +1 -0
  80. package/dist/index.test.d.ts +2 -0
  81. package/dist/index.test.d.ts.map +1 -0
  82. package/dist/index.test.js +7 -0
  83. package/dist/index.test.js.map +1 -0
  84. package/dist/lib/change-summary.d.ts +8 -0
  85. package/dist/lib/change-summary.d.ts.map +1 -0
  86. package/dist/lib/change-summary.js +2 -0
  87. package/dist/lib/change-summary.js.map +1 -0
  88. package/dist/lib/code-quality-report.d.ts +44 -0
  89. package/dist/lib/code-quality-report.d.ts.map +1 -0
  90. package/dist/lib/code-quality-report.js +62 -0
  91. package/dist/lib/code-quality-report.js.map +1 -0
  92. package/dist/lib/code-quality-report.test.d.ts +2 -0
  93. package/dist/lib/code-quality-report.test.d.ts.map +1 -0
  94. package/dist/lib/code-quality-report.test.js +327 -0
  95. package/dist/lib/code-quality-report.test.js.map +1 -0
  96. package/dist/{gitlab → lib}/comment-formatter.d.ts +6 -3
  97. package/dist/lib/comment-formatter.d.ts.map +1 -0
  98. package/dist/{gitlab → lib}/comment-formatter.js +63 -16
  99. package/dist/lib/comment-formatter.js.map +1 -0
  100. package/dist/lib/comment-formatter.test.d.ts +2 -0
  101. package/dist/lib/comment-formatter.test.d.ts.map +1 -0
  102. package/dist/lib/comment-formatter.test.js +607 -0
  103. package/dist/lib/comment-formatter.test.js.map +1 -0
  104. package/dist/lib/comment-manager.d.ts +61 -0
  105. package/dist/lib/comment-manager.d.ts.map +1 -0
  106. package/dist/lib/comment-manager.js +91 -0
  107. package/dist/lib/comment-manager.js.map +1 -0
  108. package/dist/lib/comment-manager.test.d.ts +2 -0
  109. package/dist/lib/comment-manager.test.d.ts.map +1 -0
  110. package/dist/lib/comment-manager.test.js +618 -0
  111. package/dist/lib/comment-manager.test.js.map +1 -0
  112. package/dist/lib/comment-poster.d.ts +21 -0
  113. package/dist/lib/comment-poster.d.ts.map +1 -0
  114. package/dist/lib/comment-poster.js +96 -0
  115. package/dist/lib/comment-poster.js.map +1 -0
  116. package/dist/lib/comment-poster.test.d.ts +5 -0
  117. package/dist/lib/comment-poster.test.d.ts.map +1 -0
  118. package/dist/lib/comment-poster.test.js +215 -0
  119. package/dist/lib/comment-poster.test.js.map +1 -0
  120. package/dist/lib/config-model-overrides.test.d.ts +12 -0
  121. package/dist/lib/config-model-overrides.test.d.ts.map +1 -0
  122. package/dist/lib/config-model-overrides.test.js +254 -0
  123. package/dist/lib/config-model-overrides.test.js.map +1 -0
  124. package/dist/lib/config.d.ts +93 -8
  125. package/dist/lib/config.d.ts.map +1 -1
  126. package/dist/lib/config.js +178 -25
  127. package/dist/lib/config.js.map +1 -1
  128. package/dist/lib/config.test.d.ts +2 -0
  129. package/dist/lib/config.test.d.ts.map +1 -0
  130. package/dist/lib/config.test.js +36 -0
  131. package/dist/lib/config.test.js.map +1 -0
  132. package/dist/lib/context-compression.d.ts +19 -0
  133. package/dist/lib/context-compression.d.ts.map +1 -0
  134. package/dist/lib/context-compression.js +170 -0
  135. package/dist/lib/context-compression.js.map +1 -0
  136. package/dist/lib/context-compression.test.d.ts +2 -0
  137. package/dist/lib/context-compression.test.d.ts.map +1 -0
  138. package/dist/lib/context-compression.test.js +33 -0
  139. package/dist/lib/context-compression.test.js.map +1 -0
  140. package/dist/lib/context-loader.d.ts +29 -0
  141. package/dist/lib/context-loader.d.ts.map +1 -0
  142. package/dist/lib/context-loader.js +75 -0
  143. package/dist/lib/context-loader.js.map +1 -0
  144. package/dist/lib/context-loader.test.d.ts +2 -0
  145. package/dist/lib/context-loader.test.d.ts.map +1 -0
  146. package/dist/lib/context-loader.test.js +207 -0
  147. package/dist/lib/context-loader.test.js.map +1 -0
  148. package/dist/lib/describe-core.d.ts +9 -0
  149. package/dist/lib/describe-core.d.ts.map +1 -0
  150. package/dist/lib/describe-core.js +71 -0
  151. package/dist/lib/describe-core.js.map +1 -0
  152. package/dist/lib/describe-core.test.d.ts +2 -0
  153. package/dist/lib/describe-core.test.d.ts.map +1 -0
  154. package/dist/lib/describe-core.test.js +208 -0
  155. package/dist/lib/describe-core.test.js.map +1 -0
  156. package/dist/lib/describe-output-path.test.d.ts +2 -0
  157. package/dist/lib/describe-output-path.test.d.ts.map +1 -0
  158. package/dist/lib/describe-output-path.test.js +51 -0
  159. package/dist/lib/describe-output-path.test.js.map +1 -0
  160. package/dist/lib/describe-parser.d.ts +3 -0
  161. package/dist/lib/describe-parser.d.ts.map +1 -0
  162. package/dist/lib/describe-parser.js +163 -0
  163. package/dist/lib/describe-parser.js.map +1 -0
  164. package/dist/lib/describe-parser.test.d.ts +2 -0
  165. package/dist/lib/describe-parser.test.d.ts.map +1 -0
  166. package/dist/lib/describe-parser.test.js +282 -0
  167. package/dist/lib/describe-parser.test.js.map +1 -0
  168. package/dist/lib/description-executor.d.ts +22 -0
  169. package/dist/lib/description-executor.d.ts.map +1 -0
  170. package/dist/lib/description-executor.js +72 -0
  171. package/dist/lib/description-executor.js.map +1 -0
  172. package/dist/lib/description-formatter.d.ts +37 -0
  173. package/dist/lib/description-formatter.d.ts.map +1 -0
  174. package/dist/lib/description-formatter.js +219 -0
  175. package/dist/lib/description-formatter.js.map +1 -0
  176. package/dist/{gitlab → lib}/diff-parser.d.ts +11 -0
  177. package/dist/lib/diff-parser.d.ts.map +1 -0
  178. package/dist/{gitlab → lib}/diff-parser.js +40 -3
  179. package/dist/lib/diff-parser.js.map +1 -0
  180. package/dist/lib/issue-parser.d.ts +29 -0
  181. package/dist/lib/issue-parser.d.ts.map +1 -0
  182. package/dist/lib/issue-parser.js +153 -0
  183. package/dist/lib/issue-parser.js.map +1 -0
  184. package/dist/lib/issue-parser.test.d.ts +2 -0
  185. package/dist/lib/issue-parser.test.d.ts.map +1 -0
  186. package/dist/lib/issue-parser.test.js +281 -0
  187. package/dist/lib/issue-parser.test.js.map +1 -0
  188. package/dist/lib/json-output-schema.d.ts +207 -0
  189. package/dist/lib/json-output-schema.d.ts.map +1 -0
  190. package/dist/lib/json-output-schema.js +124 -0
  191. package/dist/lib/json-output-schema.js.map +1 -0
  192. package/dist/lib/json-output-schema.test.d.ts +2 -0
  193. package/dist/lib/json-output-schema.test.d.ts.map +1 -0
  194. package/dist/lib/json-output-schema.test.js +92 -0
  195. package/dist/lib/json-output-schema.test.js.map +1 -0
  196. package/dist/lib/json-output.d.ts +43 -0
  197. package/dist/lib/json-output.d.ts.map +1 -0
  198. package/dist/lib/json-output.js +34 -0
  199. package/dist/lib/json-output.js.map +1 -0
  200. package/dist/lib/output-paths.d.ts +6 -0
  201. package/dist/lib/output-paths.d.ts.map +1 -0
  202. package/dist/lib/output-paths.js +5 -0
  203. package/dist/lib/output-paths.js.map +1 -0
  204. package/dist/lib/platform-client.d.ts +130 -0
  205. package/dist/lib/platform-client.d.ts.map +1 -0
  206. package/dist/lib/platform-client.js +8 -0
  207. package/dist/lib/platform-client.js.map +1 -0
  208. package/dist/lib/position-validator.d.ts +36 -0
  209. package/dist/lib/position-validator.d.ts.map +1 -0
  210. package/dist/lib/position-validator.js +43 -0
  211. package/dist/lib/position-validator.js.map +1 -0
  212. package/dist/lib/repository-validator.d.ts +52 -0
  213. package/dist/lib/repository-validator.d.ts.map +1 -0
  214. package/dist/lib/repository-validator.js +219 -0
  215. package/dist/lib/repository-validator.js.map +1 -0
  216. package/dist/lib/repository-validator.test.d.ts +5 -0
  217. package/dist/lib/repository-validator.test.d.ts.map +1 -0
  218. package/dist/lib/repository-validator.test.js +341 -0
  219. package/dist/lib/repository-validator.test.js.map +1 -0
  220. package/dist/lib/review-core.d.ts +66 -0
  221. package/dist/lib/review-core.d.ts.map +1 -0
  222. package/dist/lib/review-core.js +449 -0
  223. package/dist/lib/review-core.js.map +1 -0
  224. package/dist/lib/review-core.test.d.ts +2 -0
  225. package/dist/lib/review-core.test.d.ts.map +1 -0
  226. package/dist/lib/review-core.test.js +552 -0
  227. package/dist/lib/review-core.test.js.map +1 -0
  228. package/dist/lib/review-orchestrator.d.ts +77 -0
  229. package/dist/lib/review-orchestrator.d.ts.map +1 -0
  230. package/dist/lib/review-orchestrator.js +124 -0
  231. package/dist/lib/review-orchestrator.js.map +1 -0
  232. package/dist/lib/review-orchestrator.test.d.ts +2 -0
  233. package/dist/lib/review-orchestrator.test.d.ts.map +1 -0
  234. package/dist/lib/review-orchestrator.test.js +413 -0
  235. package/dist/lib/review-orchestrator.test.js.map +1 -0
  236. package/dist/lib/review-output-path.test.d.ts +2 -0
  237. package/dist/lib/review-output-path.test.d.ts.map +1 -0
  238. package/dist/lib/review-output-path.test.js +83 -0
  239. package/dist/lib/review-output-path.test.js.map +1 -0
  240. package/dist/lib/review-parser.d.ts +2 -0
  241. package/dist/lib/review-parser.d.ts.map +1 -0
  242. package/dist/lib/review-parser.js +100 -0
  243. package/dist/lib/review-parser.js.map +1 -0
  244. package/dist/lib/unified-review-executor.d.ts +49 -0
  245. package/dist/lib/unified-review-executor.d.ts.map +1 -0
  246. package/dist/lib/unified-review-executor.js +158 -0
  247. package/dist/lib/unified-review-executor.js.map +1 -0
  248. package/dist/lib/unified-review-executor.test.d.ts +5 -0
  249. package/dist/lib/unified-review-executor.test.d.ts.map +1 -0
  250. package/dist/lib/unified-review-executor.test.js +344 -0
  251. package/dist/lib/unified-review-executor.test.js.map +1 -0
  252. package/dist/lib/write-json-output.d.ts +13 -0
  253. package/dist/lib/write-json-output.d.ts.map +1 -0
  254. package/dist/lib/write-json-output.js +37 -0
  255. package/dist/lib/write-json-output.js.map +1 -0
  256. package/dist/opencode/agent-loader.d.ts +3 -4
  257. package/dist/opencode/agent-loader.d.ts.map +1 -1
  258. package/dist/opencode/agent-loader.js +51 -42
  259. package/dist/opencode/agent-loader.js.map +1 -1
  260. package/dist/opencode/agent-skill-overlay.d.ts +11 -0
  261. package/dist/opencode/agent-skill-overlay.d.ts.map +1 -0
  262. package/dist/opencode/agent-skill-overlay.js +164 -0
  263. package/dist/opencode/agent-skill-overlay.js.map +1 -0
  264. package/dist/opencode/client.d.ts +14 -5
  265. package/dist/opencode/client.d.ts.map +1 -1
  266. package/dist/opencode/client.js +311 -32
  267. package/dist/opencode/client.js.map +1 -1
  268. package/dist/opencode/client.test.d.ts +2 -0
  269. package/dist/opencode/client.test.d.ts.map +1 -0
  270. package/dist/opencode/client.test.js +317 -0
  271. package/dist/opencode/client.test.js.map +1 -0
  272. package/dist/opencode/opencode-paths.d.ts +2 -0
  273. package/dist/opencode/opencode-paths.d.ts.map +1 -0
  274. package/dist/opencode/opencode-paths.js +7 -0
  275. package/dist/opencode/opencode-paths.js.map +1 -0
  276. package/dist/opencode/skill-loader.d.ts +6 -0
  277. package/dist/opencode/skill-loader.d.ts.map +1 -0
  278. package/dist/opencode/skill-loader.js +36 -0
  279. package/dist/opencode/skill-loader.js.map +1 -0
  280. package/package.json +29 -20
  281. package/.opencode/agent/github-reviewer.md +0 -62
  282. package/.opencode/agent/gitlab-reviewer.md +0 -62
  283. package/.opencode/agent/local-reviewer.md +0 -71
  284. package/dist/gitlab/comment-formatter.d.ts.map +0 -1
  285. package/dist/gitlab/comment-formatter.js.map +0 -1
  286. package/dist/gitlab/diff-parser.d.ts.map +0 -1
  287. 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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=review-orchestrator.test.d.ts.map
@@ -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