@rigour-labs/core 6.7.10 → 6.8.1-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (346) hide show
  1. package/dist/brief/briefing.d.ts +59 -0
  2. package/dist/brief/briefing.js +126 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +2 -0
  5. package/dist/inference/cloud-provider.js +12 -2
  6. package/dist/review/backtest-init.d.ts +8 -5
  7. package/dist/review/backtest-init.js +27 -9
  8. package/dist/review/backtest-last.js +3 -1
  9. package/dist/review/reviewer/adapters.d.ts +4 -0
  10. package/dist/review/reviewer/adapters.js +23 -0
  11. package/dist/review/reviewer/inputs.d.ts +4 -1
  12. package/dist/review/reviewer/inputs.js +36 -2
  13. package/dist/review/reviewer/record.d.ts +5 -0
  14. package/dist/review/reviewer/record.js +3 -3
  15. package/dist/review/reviewer/verdict.d.ts +14 -0
  16. package/dist/review/reviewer/verdict.js +46 -6
  17. package/dist/review/reviewer.d.ts +2 -0
  18. package/dist/review/reviewer.js +24 -5
  19. package/dist/review-learning/repo-rules.d.ts +14 -2
  20. package/dist/review-learning/repo-rules.js +59 -9
  21. package/dist/task/thread.d.ts +47 -0
  22. package/dist/task/thread.js +234 -0
  23. package/dist/templates/universal-config.js +4 -0
  24. package/dist/types/index.d.ts +21 -0
  25. package/dist/types/index.js +7 -0
  26. package/package.json +11 -8
  27. package/dist/context/automatic-index-cache.test.d.ts +0 -1
  28. package/dist/context/automatic-index-cache.test.js +0 -30
  29. package/dist/context/automatic-index.test.d.ts +0 -1
  30. package/dist/context/automatic-index.test.js +0 -45
  31. package/dist/context/cache-engine.test.d.ts +0 -1
  32. package/dist/context/cache-engine.test.js +0 -99
  33. package/dist/context/dependency-graph.test.d.ts +0 -1
  34. package/dist/context/dependency-graph.test.js +0 -13
  35. package/dist/context/index-status.test.d.ts +0 -1
  36. package/dist/context/index-status.test.js +0 -26
  37. package/dist/context.test.d.ts +0 -1
  38. package/dist/context.test.js +0 -228
  39. package/dist/deep/agent-review.test.d.ts +0 -1
  40. package/dist/deep/agent-review.test.js +0 -53
  41. package/dist/deep/code-context.test.d.ts +0 -1
  42. package/dist/deep/code-context.test.js +0 -48
  43. package/dist/deep/code-pass.test.d.ts +0 -1
  44. package/dist/deep/code-pass.test.js +0 -112
  45. package/dist/deep/code-review-prompt.test.d.ts +0 -1
  46. package/dist/deep/code-review-prompt.test.js +0 -20
  47. package/dist/deep/code-verifier.test.d.ts +0 -1
  48. package/dist/deep/code-verifier.test.js +0 -54
  49. package/dist/deep/diff-tests/calls.test.d.ts +0 -1
  50. package/dist/deep/diff-tests/calls.test.js +0 -33
  51. package/dist/deep/diff-tests/run.test.d.ts +0 -1
  52. package/dist/deep/diff-tests/run.test.js +0 -58
  53. package/dist/deep/fact-extractor.test.d.ts +0 -1
  54. package/dist/deep/fact-extractor.test.js +0 -581
  55. package/dist/deep/parse-findings.test.d.ts +0 -1
  56. package/dist/deep/parse-findings.test.js +0 -25
  57. package/dist/deep/pr-review.test.d.ts +0 -1
  58. package/dist/deep/pr-review.test.js +0 -80
  59. package/dist/deep/prompts.test.d.ts +0 -1
  60. package/dist/deep/prompts.test.js +0 -235
  61. package/dist/deep/reference-pack.test.d.ts +0 -1
  62. package/dist/deep/reference-pack.test.js +0 -46
  63. package/dist/deep/related-changes.test.d.ts +0 -1
  64. package/dist/deep/related-changes.test.js +0 -33
  65. package/dist/deep/review-context-export.test.d.ts +0 -1
  66. package/dist/deep/review-context-export.test.js +0 -32
  67. package/dist/deep/review-tools.test.d.ts +0 -1
  68. package/dist/deep/review-tools.test.js +0 -39
  69. package/dist/deep/risk.test.d.ts +0 -1
  70. package/dist/deep/risk.test.js +0 -101
  71. package/dist/deep/verifier.test.d.ts +0 -1
  72. package/dist/deep/verifier.test.js +0 -635
  73. package/dist/discovery.test.d.ts +0 -1
  74. package/dist/discovery.test.js +0 -93
  75. package/dist/environment.test.d.ts +0 -1
  76. package/dist/environment.test.js +0 -94
  77. package/dist/firewall/firewall.test.d.ts +0 -1
  78. package/dist/firewall/firewall.test.js +0 -117
  79. package/dist/firewall/trust-boundaries.test.d.ts +0 -1
  80. package/dist/firewall/trust-boundaries.test.js +0 -64
  81. package/dist/firewall/trusted-control.test.d.ts +0 -1
  82. package/dist/firewall/trusted-control.test.js +0 -188
  83. package/dist/gates/agent-team.test.d.ts +0 -1
  84. package/dist/gates/agent-team.test.js +0 -113
  85. package/dist/gates/ast.test.d.ts +0 -1
  86. package/dist/gates/ast.test.js +0 -112
  87. package/dist/gates/checkpoint.test.d.ts +0 -1
  88. package/dist/gates/checkpoint.test.js +0 -105
  89. package/dist/gates/content.test.d.ts +0 -1
  90. package/dist/gates/content.test.js +0 -73
  91. package/dist/gates/coverage.test.d.ts +0 -1
  92. package/dist/gates/coverage.test.js +0 -53
  93. package/dist/gates/dedupe-failures.test.d.ts +0 -1
  94. package/dist/gates/dedupe-failures.test.js +0 -12
  95. package/dist/gates/deep-analysis.test.d.ts +0 -1
  96. package/dist/gates/deep-analysis.test.js +0 -86
  97. package/dist/gates/deep-intent.test.d.ts +0 -1
  98. package/dist/gates/deep-intent.test.js +0 -51
  99. package/dist/gates/deep-timeout.test.d.ts +0 -1
  100. package/dist/gates/deep-timeout.test.js +0 -10
  101. package/dist/gates/deprecated-apis.test.d.ts +0 -1
  102. package/dist/gates/deprecated-apis.test.js +0 -318
  103. package/dist/gates/deprecated-dependencies.test.d.ts +0 -1
  104. package/dist/gates/deprecated-dependencies.test.js +0 -55
  105. package/dist/gates/frontend-secret-exposure.test.d.ts +0 -1
  106. package/dist/gates/frontend-secret-exposure.test.js +0 -148
  107. package/dist/gates/hallucinated-imports/framework-modules-nuxt.test.d.ts +0 -1
  108. package/dist/gates/hallucinated-imports/framework-modules-nuxt.test.js +0 -27
  109. package/dist/gates/hallucinated-imports/js-resolver-types.test.d.ts +0 -1
  110. package/dist/gates/hallucinated-imports/js-resolver-types.test.js +0 -14
  111. package/dist/gates/hallucinated-imports-sveltekit.test.d.ts +0 -1
  112. package/dist/gates/hallucinated-imports-sveltekit.test.js +0 -132
  113. package/dist/gates/hallucinated-imports.test.d.ts +0 -1
  114. package/dist/gates/hallucinated-imports.test.js +0 -1206
  115. package/dist/gates/js-style-context.test.d.ts +0 -1
  116. package/dist/gates/js-style-context.test.js +0 -35
  117. package/dist/gates/logic-drift.test.d.ts +0 -1
  118. package/dist/gates/logic-drift.test.js +0 -52
  119. package/dist/gates/phantom-apis.test.d.ts +0 -1
  120. package/dist/gates/phantom-apis.test.js +0 -396
  121. package/dist/gates/promise-safety.test.d.ts +0 -1
  122. package/dist/gates/promise-safety.test.js +0 -34
  123. package/dist/gates/runner.test.d.ts +0 -1
  124. package/dist/gates/runner.test.js +0 -77
  125. package/dist/gates/scoped-gates.test.d.ts +0 -1
  126. package/dist/gates/scoped-gates.test.js +0 -52
  127. package/dist/gates/security-patterns-owasp.test.d.ts +0 -1
  128. package/dist/gates/security-patterns-owasp.test.js +0 -186
  129. package/dist/gates/security-patterns.test.d.ts +0 -1
  130. package/dist/gates/security-patterns.test.js +0 -194
  131. package/dist/gates/semantic-bugs.test.d.ts +0 -1
  132. package/dist/gates/semantic-bugs.test.js +0 -76
  133. package/dist/gates/side-effect-analysis.test.d.ts +0 -1
  134. package/dist/gates/side-effect-analysis.test.js +0 -162
  135. package/dist/gates/style-drift.test.d.ts +0 -1
  136. package/dist/gates/style-drift.test.js +0 -26
  137. package/dist/gates/test-quality.test.d.ts +0 -1
  138. package/dist/gates/test-quality.test.js +0 -325
  139. package/dist/gates/trusted-reviews.test.d.ts +0 -1
  140. package/dist/gates/trusted-reviews.test.js +0 -16
  141. package/dist/gates/unindexed-reads/queries.test.d.ts +0 -1
  142. package/dist/gates/unindexed-reads/queries.test.js +0 -54
  143. package/dist/gates/unindexed-reads/schema.test.d.ts +0 -1
  144. package/dist/gates/unindexed-reads/schema.test.js +0 -81
  145. package/dist/gates/unindexed-reads/unindexed-reads.test.d.ts +0 -1
  146. package/dist/gates/unindexed-reads/unindexed-reads.test.js +0 -90
  147. package/dist/hooks/checker.test.d.ts +0 -1
  148. package/dist/hooks/checker.test.js +0 -159
  149. package/dist/hooks/dlp-confidence.test.d.ts +0 -1
  150. package/dist/hooks/dlp-confidence.test.js +0 -51
  151. package/dist/hooks/dlp-feedback.test.d.ts +0 -1
  152. package/dist/hooks/dlp-feedback.test.js +0 -131
  153. package/dist/hooks/input-validator.test.d.ts +0 -1
  154. package/dist/hooks/input-validator.test.js +0 -329
  155. package/dist/hooks/templates.test.d.ts +0 -1
  156. package/dist/hooks/templates.test.js +0 -27
  157. package/dist/inference/brain-placeholder.test.d.ts +0 -1
  158. package/dist/inference/brain-placeholder.test.js +0 -28
  159. package/dist/inference/cloud-provider.test.d.ts +0 -1
  160. package/dist/inference/cloud-provider.test.js +0 -139
  161. package/dist/inference/executable.test.d.ts +0 -1
  162. package/dist/inference/executable.test.js +0 -41
  163. package/dist/inference/http-download.test.d.ts +0 -1
  164. package/dist/inference/http-download.test.js +0 -109
  165. package/dist/inference/llama-engine-checksum.test.d.ts +0 -1
  166. package/dist/inference/llama-engine-checksum.test.js +0 -27
  167. package/dist/inference/llama-engine.test.d.ts +0 -1
  168. package/dist/inference/llama-engine.test.js +0 -51
  169. package/dist/inference/llama-process.test.d.ts +0 -1
  170. package/dist/inference/llama-process.test.js +0 -61
  171. package/dist/inference/local-model.test.d.ts +0 -1
  172. package/dist/inference/local-model.test.js +0 -23
  173. package/dist/inference/model-download.test.d.ts +0 -1
  174. package/dist/inference/model-download.test.js +0 -125
  175. package/dist/inference/model-manager.test.d.ts +0 -1
  176. package/dist/inference/model-manager.test.js +0 -24
  177. package/dist/inference/types.test.d.ts +0 -1
  178. package/dist/inference/types.test.js +0 -19
  179. package/dist/memory/recall.test.d.ts +0 -1
  180. package/dist/memory/recall.test.js +0 -36
  181. package/dist/pattern-index/indexer.test.d.ts +0 -6
  182. package/dist/pattern-index/indexer.test.js +0 -197
  183. package/dist/pattern-index/matcher.test.d.ts +0 -6
  184. package/dist/pattern-index/matcher.test.js +0 -238
  185. package/dist/pattern-index/pattern-reuse.test.d.ts +0 -1
  186. package/dist/pattern-index/pattern-reuse.test.js +0 -76
  187. package/dist/pattern-index/semantic-runtime.test.d.ts +0 -1
  188. package/dist/pattern-index/semantic-runtime.test.js +0 -31
  189. package/dist/pattern-index/staleness.test.d.ts +0 -6
  190. package/dist/pattern-index/staleness.test.js +0 -211
  191. package/dist/review/agent-fixes.test.d.ts +0 -1
  192. package/dist/review/agent-fixes.test.js +0 -32
  193. package/dist/review/backtest-init.test.d.ts +0 -1
  194. package/dist/review/backtest-init.test.js +0 -98
  195. package/dist/review/backtest-judges.test.d.ts +0 -1
  196. package/dist/review/backtest-judges.test.js +0 -32
  197. package/dist/review/backtest-last.test.d.ts +0 -1
  198. package/dist/review/backtest-last.test.js +0 -109
  199. package/dist/review/backtest.test.d.ts +0 -1
  200. package/dist/review/backtest.test.js +0 -183
  201. package/dist/review/baseline.test.d.ts +0 -1
  202. package/dist/review/baseline.test.js +0 -22
  203. package/dist/review/branch-checks.test.d.ts +0 -1
  204. package/dist/review/branch-checks.test.js +0 -48
  205. package/dist/review/check-outcomes.test.d.ts +0 -1
  206. package/dist/review/check-outcomes.test.js +0 -56
  207. package/dist/review/code-patterns.test.d.ts +0 -1
  208. package/dist/review/code-patterns.test.js +0 -142
  209. package/dist/review/dead-code.test.d.ts +0 -1
  210. package/dist/review/dead-code.test.js +0 -154
  211. package/dist/review/deep-runs.test.d.ts +0 -1
  212. package/dist/review/deep-runs.test.js +0 -23
  213. package/dist/review/effectiveness.test.d.ts +0 -1
  214. package/dist/review/effectiveness.test.js +0 -42
  215. package/dist/review/fix-scope.test.d.ts +0 -1
  216. package/dist/review/fix-scope.test.js +0 -104
  217. package/dist/review/generated-files.test.d.ts +0 -1
  218. package/dist/review/generated-files.test.js +0 -25
  219. package/dist/review/migration-order.test.d.ts +0 -1
  220. package/dist/review/migration-order.test.js +0 -62
  221. package/dist/review/quiet.test.d.ts +0 -1
  222. package/dist/review/quiet.test.js +0 -38
  223. package/dist/review/receipt.test.d.ts +0 -1
  224. package/dist/review/receipt.test.js +0 -60
  225. package/dist/review/review-task.test.d.ts +0 -1
  226. package/dist/review/review-task.test.js +0 -89
  227. package/dist/review/review.test.d.ts +0 -1
  228. package/dist/review/review.test.js +0 -203
  229. package/dist/review/reviewer/adapters.test.d.ts +0 -1
  230. package/dist/review/reviewer/adapters.test.js +0 -60
  231. package/dist/review/reviewer/api-judge.test.d.ts +0 -1
  232. package/dist/review/reviewer/api-judge.test.js +0 -108
  233. package/dist/review/reviewer/background.test.d.ts +0 -1
  234. package/dist/review/reviewer/background.test.js +0 -118
  235. package/dist/review/reviewer/context.test.d.ts +0 -1
  236. package/dist/review/reviewer/context.test.js +0 -45
  237. package/dist/review/reviewer/exec.test.d.ts +0 -1
  238. package/dist/review/reviewer/exec.test.js +0 -59
  239. package/dist/review/reviewer/inputs.test.d.ts +0 -1
  240. package/dist/review/reviewer/inputs.test.js +0 -26
  241. package/dist/review/reviewer/panel.test.d.ts +0 -1
  242. package/dist/review/reviewer/panel.test.js +0 -110
  243. package/dist/review/reviewer/record.test.d.ts +0 -1
  244. package/dist/review/reviewer/record.test.js +0 -32
  245. package/dist/review/reviewer/rule-writer.test.d.ts +0 -1
  246. package/dist/review/reviewer/rule-writer.test.js +0 -52
  247. package/dist/review/reviewer/settings.test.d.ts +0 -1
  248. package/dist/review/reviewer/settings.test.js +0 -57
  249. package/dist/review/reviewer/usage.test.d.ts +0 -1
  250. package/dist/review/reviewer/usage.test.js +0 -14
  251. package/dist/review/reviewer.test.d.ts +0 -1
  252. package/dist/review/reviewer.test.js +0 -705
  253. package/dist/review/stories.test.d.ts +0 -1
  254. package/dist/review/stories.test.js +0 -58
  255. package/dist/review/toolchain.test.d.ts +0 -1
  256. package/dist/review/toolchain.test.js +0 -108
  257. package/dist/review/typed/redundancy.test.d.ts +0 -1
  258. package/dist/review/typed/redundancy.test.js +0 -315
  259. package/dist/review/typed/schema-nullability.test.d.ts +0 -1
  260. package/dist/review/typed/schema-nullability.test.js +0 -63
  261. package/dist/review-learning/human-edits.test.d.ts +0 -1
  262. package/dist/review-learning/human-edits.test.js +0 -47
  263. package/dist/review-learning/repo-rules.test.d.ts +0 -1
  264. package/dist/review-learning/repo-rules.test.js +0 -55
  265. package/dist/review-learning/review-learning.test.d.ts +0 -1
  266. package/dist/review-learning/review-learning.test.js +0 -249
  267. package/dist/safety.test.d.ts +0 -1
  268. package/dist/safety.test.js +0 -42
  269. package/dist/semantic/benchmark.test.d.ts +0 -1
  270. package/dist/semantic/benchmark.test.js +0 -22
  271. package/dist/semantic/intent/intent.test.d.ts +0 -1
  272. package/dist/semantic/intent/intent.test.js +0 -46
  273. package/dist/semantic/learn/learn.test.d.ts +0 -1
  274. package/dist/semantic/learn/learn.test.js +0 -67
  275. package/dist/semantic/origins.test.d.ts +0 -1
  276. package/dist/semantic/origins.test.js +0 -77
  277. package/dist/semantic/project-facts.test.d.ts +0 -1
  278. package/dist/semantic/project-facts.test.js +0 -45
  279. package/dist/semantic/sites/call-sites.test.d.ts +0 -1
  280. package/dist/semantic/sites/call-sites.test.js +0 -46
  281. package/dist/services/adaptive-thresholds.test.d.ts +0 -1
  282. package/dist/services/adaptive-thresholds.test.js +0 -53
  283. package/dist/services/agent-history.test.d.ts +0 -1
  284. package/dist/services/agent-history.test.js +0 -69
  285. package/dist/services/context-scope-summary.test.d.ts +0 -1
  286. package/dist/services/context-scope-summary.test.js +0 -17
  287. package/dist/services/context-telemetry-service.test.d.ts +0 -1
  288. package/dist/services/context-telemetry-service.test.js +0 -182
  289. package/dist/services/cursor-usage-sync.test.d.ts +0 -1
  290. package/dist/services/cursor-usage-sync.test.js +0 -173
  291. package/dist/services/engineering-knowledge-graph.test.d.ts +0 -1
  292. package/dist/services/engineering-knowledge-graph.test.js +0 -76
  293. package/dist/services/model-pricing.test.d.ts +0 -1
  294. package/dist/services/model-pricing.test.js +0 -44
  295. package/dist/services/observed-savings.test.d.ts +0 -1
  296. package/dist/services/observed-savings.test.js +0 -37
  297. package/dist/services/score-history.test.d.ts +0 -1
  298. package/dist/services/score-history.test.js +0 -61
  299. package/dist/smoke.test.d.ts +0 -1
  300. package/dist/smoke.test.js +0 -17
  301. package/dist/storage/cache-cleanup.test.d.ts +0 -1
  302. package/dist/storage/cache-cleanup.test.js +0 -54
  303. package/dist/storage/context-telemetry.test.d.ts +0 -1
  304. package/dist/storage/context-telemetry.test.js +0 -80
  305. package/dist/storage/db.test.d.ts +0 -1
  306. package/dist/storage/db.test.js +0 -46
  307. package/dist/storage/fix-lessons.test.d.ts +0 -1
  308. package/dist/storage/fix-lessons.test.js +0 -61
  309. package/dist/storage/lessons.test.d.ts +0 -1
  310. package/dist/storage/lessons.test.js +0 -81
  311. package/dist/storage/local-encryption.test.d.ts +0 -1
  312. package/dist/storage/local-encryption.test.js +0 -34
  313. package/dist/storage/local-memory.test.d.ts +0 -1
  314. package/dist/storage/local-memory.test.js +0 -55
  315. package/dist/storage/share-memory.test.d.ts +0 -1
  316. package/dist/storage/share-memory.test.js +0 -34
  317. package/dist/storage/team-diagnostics.test.d.ts +0 -1
  318. package/dist/storage/team-diagnostics.test.js +0 -51
  319. package/dist/storage/team-scope.test.d.ts +0 -1
  320. package/dist/storage/team-scope.test.js +0 -22
  321. package/dist/storage/team-store.test.d.ts +0 -1
  322. package/dist/storage/team-store.test.js +0 -11
  323. package/dist/storage/team-sync-scope.test.d.ts +0 -1
  324. package/dist/storage/team-sync-scope.test.js +0 -100
  325. package/dist/storage/team-vector-store.test.d.ts +0 -1
  326. package/dist/storage/team-vector-store.test.js +0 -56
  327. package/dist/storage/telemetry-scope.test.d.ts +0 -1
  328. package/dist/storage/telemetry-scope.test.js +0 -38
  329. package/dist/telemetry/telemetry.test.d.ts +0 -1
  330. package/dist/telemetry/telemetry.test.js +0 -64
  331. package/dist/types/index.test.d.ts +0 -1
  332. package/dist/types/index.test.js +0 -33
  333. package/dist/utils/command-line.test.d.ts +0 -1
  334. package/dist/utils/command-line.test.js +0 -8
  335. package/dist/utils/diff-removed.test.d.ts +0 -1
  336. package/dist/utils/diff-removed.test.js +0 -29
  337. package/dist/utils/diff.test.d.ts +0 -1
  338. package/dist/utils/diff.test.js +0 -38
  339. package/dist/utils/glob-paths.test.d.ts +0 -1
  340. package/dist/utils/glob-paths.test.js +0 -40
  341. package/dist/utils/profile.test.d.ts +0 -1
  342. package/dist/utils/profile.test.js +0 -68
  343. package/dist/utils/scanner.test.d.ts +0 -1
  344. package/dist/utils/scanner.test.js +0 -48
  345. package/dist/utils/scope.test.d.ts +0 -1
  346. package/dist/utils/scope.test.js +0 -38
@@ -1,581 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- // We test the internal extractFileFacts by importing extractFacts and factsToPromptString
3
- // But we need to test extraction logic directly, so we also test via the public API
4
- const mockGlobby = vi.hoisted(() => vi.fn());
5
- const mockReadFile = vi.hoisted(() => vi.fn());
6
- vi.mock('globby', async (importOriginal) => ({
7
- isDynamicPattern: (await importOriginal()).isDynamicPattern,
8
- globby: mockGlobby,
9
- }));
10
- vi.mock('fs-extra', () => ({
11
- default: {
12
- readFile: mockReadFile,
13
- },
14
- }));
15
- import { extractFacts, factsToPromptString, isTestFile } from './fact-extractor.js';
16
- describe('Fact Extractor', () => {
17
- beforeEach(() => {
18
- vi.clearAllMocks();
19
- });
20
- // ── TypeScript extraction ──
21
- describe('TypeScript/JavaScript extraction', () => {
22
- it('should extract classes with methods and dependencies', async () => {
23
- mockGlobby.mockResolvedValue(['src/service.ts']);
24
- mockReadFile.mockResolvedValue(`
25
- import { Logger } from './logger';
26
-
27
- export class UserService {
28
- constructor(
29
- private db: Database,
30
- private logger: Logger
31
- ) {}
32
-
33
- async findById(id: string): Promise<User> {
34
- return this.db.find(id);
35
- }
36
-
37
- async create(data: CreateUserDto): Promise<User> {
38
- this.logger.info('Creating user');
39
- return this.db.create(data);
40
- }
41
-
42
- async update(id: string, data: UpdateUserDto): Promise<User> {
43
- return this.db.update(id, data);
44
- }
45
-
46
- async delete(id: string): Promise<void> {
47
- return this.db.delete(id);
48
- }
49
- }
50
- `);
51
- const facts = await extractFacts('/project');
52
- expect(facts).toHaveLength(1);
53
- expect(facts[0].language).toBe('typescript');
54
- expect(facts[0].classes).toHaveLength(1);
55
- expect(facts[0].classes[0].name).toBe('UserService');
56
- expect(facts[0].classes[0].methodCount).toBeGreaterThanOrEqual(4);
57
- expect(facts[0].classes[0].dependencies).toContain('Database');
58
- expect(facts[0].classes[0].dependencies).toContain('Logger');
59
- });
60
- it('should extract functions with params and async detection', async () => {
61
- mockGlobby.mockResolvedValue(['src/utils.ts']);
62
- mockReadFile.mockResolvedValue(`
63
- export async function processData(input: string, config: Config, options: Options): Promise<Result> {
64
- if (input.length > 0) {
65
- if (config.validate) {
66
- if (options.strict) {
67
- return validate(input);
68
- }
69
- }
70
- }
71
- return { data: input };
72
- }
73
-
74
- export function simpleHelper(x: number): number {
75
- return x * 2;
76
- }
77
- `);
78
- const facts = await extractFacts('/project');
79
- const funcs = facts[0].functions;
80
- expect(funcs.length).toBeGreaterThanOrEqual(1);
81
- const processData = funcs.find(f => f.name === 'processData');
82
- expect(processData).toBeDefined();
83
- expect(processData.isAsync).toBe(true);
84
- expect(processData.isExported).toBe(true);
85
- expect(processData.paramCount).toBe(3);
86
- expect(processData.maxNesting).toBeGreaterThanOrEqual(3);
87
- });
88
- it('should extract imports from ES modules and require', async () => {
89
- mockGlobby.mockResolvedValue(['src/index.ts']);
90
- mockReadFile.mockResolvedValue(`
91
- import { Router } from 'express';
92
- import path from 'path';
93
- const fs = require('fs-extra');
94
- import type { Config } from './types';
95
-
96
- export function init() {
97
- const router = Router();
98
- return router;
99
- }
100
- `);
101
- const facts = await extractFacts('/project');
102
- expect(facts[0].imports).toContain('express');
103
- expect(facts[0].imports).toContain('path');
104
- expect(facts[0].imports).toContain('fs-extra');
105
- expect(facts[0].imports).toContain('./types');
106
- });
107
- it('should extract error handling patterns', async () => {
108
- mockGlobby.mockResolvedValue(['src/api.ts']);
109
- mockReadFile.mockResolvedValue(`
110
- async function fetchData() {
111
- try {
112
- const res = await fetch('/api');
113
- return res.json();
114
- } catch (err) {
115
- console.error(err);
116
- throw err;
117
- }
118
- }
119
-
120
- async function riskyOp() {
121
- try {
122
- await doSomething();
123
- } catch (err) {
124
- }
125
- }
126
-
127
- fetchData().then(data => {
128
- process(data);
129
- }).catch(() => {});
130
- `);
131
- const facts = await extractFacts('/project');
132
- expect(facts[0].errorHandling.length).toBeGreaterThanOrEqual(2);
133
- const tryCatches = facts[0].errorHandling.filter(e => e.type === 'try-catch');
134
- expect(tryCatches.length).toBeGreaterThanOrEqual(2);
135
- // Strategy detection finds console.error first → 'log'
136
- const strategies = tryCatches.map(e => e.strategy);
137
- expect(strategies.length).toBeGreaterThanOrEqual(2);
138
- });
139
- it('should detect test files and count assertions', async () => {
140
- mockGlobby.mockResolvedValue(['src/utils.test.ts']);
141
- mockReadFile.mockResolvedValue(`
142
- import { describe, it, expect } from 'vitest';
143
-
144
- describe('Utils', () => {
145
- it('should add numbers', () => {
146
- expect(add(1, 2)).toBe(3);
147
- expect(add(0, 0)).toBe(0);
148
- });
149
-
150
- it('should handle negatives', () => {
151
- expect(add(-1, 1)).toBe(0);
152
- });
153
- });
154
- `);
155
- const facts = await extractFacts('/project');
156
- expect(facts[0].hasTests).toBe(true);
157
- expect(facts[0].testAssertions).toBeGreaterThanOrEqual(3);
158
- });
159
- it('should extract exports', async () => {
160
- mockGlobby.mockResolvedValue(['src/types.ts']);
161
- mockReadFile.mockResolvedValue(`
162
- export interface User {
163
- id: string;
164
- name: string;
165
- }
166
-
167
- export const DEFAULT_CONFIG = {};
168
-
169
- export function createUser(): User {
170
- return { id: '1', name: 'test' };
171
- }
172
-
173
- export type Severity = 'high' | 'low';
174
- `);
175
- const facts = await extractFacts('/project');
176
- expect(facts[0].exports).toContain('User');
177
- expect(facts[0].exports).toContain('DEFAULT_CONFIG');
178
- expect(facts[0].exports).toContain('createUser');
179
- expect(facts[0].exports).toContain('Severity');
180
- });
181
- });
182
- // ── Python extraction ──
183
- describe('Python extraction', () => {
184
- it('should extract Python classes', async () => {
185
- mockGlobby.mockResolvedValue(['app/service.py']);
186
- mockReadFile.mockResolvedValue(`
187
- class UserService:
188
- def __init__(self, db):
189
- self.db = db
190
-
191
- def find_by_id(self, user_id):
192
- return self.db.find(user_id)
193
-
194
- def create(self, data):
195
- return self.db.create(data)
196
-
197
- def _validate(self, data):
198
- pass
199
- `);
200
- const facts = await extractFacts('/project');
201
- expect(facts[0].language).toBe('python');
202
- expect(facts[0].classes).toHaveLength(1);
203
- expect(facts[0].classes[0].name).toBe('UserService');
204
- expect(facts[0].classes[0].methodCount).toBeGreaterThanOrEqual(3);
205
- });
206
- it('should extract Python imports', async () => {
207
- mockGlobby.mockResolvedValue(['app/main.py']);
208
- mockReadFile.mockResolvedValue(`
209
- import os
210
- from pathlib import Path
211
- import json
212
- from typing import Optional
213
- from app.service import UserService
214
-
215
- def main():
216
- svc = UserService()
217
- return svc
218
- `);
219
- const facts = await extractFacts('/project');
220
- expect(facts[0].imports).toContain('os');
221
- expect(facts[0].imports).toContain('pathlib');
222
- expect(facts[0].imports).toContain('app.service');
223
- });
224
- });
225
- // ── Go extraction ──
226
- describe('Go extraction', () => {
227
- it('should extract Go structs with fields and methods', async () => {
228
- mockGlobby.mockResolvedValue(['pkg/server.go']);
229
- mockReadFile.mockResolvedValue(`package server
230
-
231
- import (
232
- "net/http"
233
- "sync"
234
- )
235
-
236
- type Server struct {
237
- addr string
238
- port int
239
- handler http.Handler
240
- mu sync.Mutex
241
- *Base
242
- }
243
-
244
- func NewServer(addr string, port int) *Server {
245
- return &Server{addr: addr, port: port}
246
- }
247
-
248
- func (s *Server) Start() error {
249
- s.mu.Lock()
250
- defer s.mu.Unlock()
251
- return http.ListenAndServe(s.addr, s.handler)
252
- }
253
-
254
- func (s *Server) Stop() error {
255
- return nil
256
- }
257
-
258
- func (s *Server) Handler() http.Handler {
259
- return s.handler
260
- }
261
- `);
262
- const facts = await extractFacts('/project');
263
- expect(facts[0].language).toBe('go');
264
- expect(facts[0].structs).toBeDefined();
265
- expect(facts[0].structs.length).toBeGreaterThanOrEqual(1);
266
- const server = facts[0].structs[0];
267
- expect(server.name).toBe('Server');
268
- expect(server.fieldCount).toBeGreaterThanOrEqual(4); // addr, port, handler, mu, Base
269
- expect(server.methodCount).toBeGreaterThanOrEqual(3); // Start, Stop, Handler
270
- expect(server.methods).toContain('Start');
271
- expect(server.methods).toContain('Stop');
272
- expect(server.embeds).toContain('Base');
273
- });
274
- it('should extract Go interfaces', async () => {
275
- mockGlobby.mockResolvedValue(['pkg/store.go']);
276
- mockReadFile.mockResolvedValue(`package store
277
-
278
- type Store interface {
279
- Get(key string) (string, error)
280
- Set(key string, value string) error
281
- Delete(key string) error
282
- List(prefix string) ([]string, error)
283
- Close() error
284
- Watch(key string) <-chan Event
285
- }
286
-
287
- type SimpleStore struct {
288
- data map[string]string
289
- }
290
- `);
291
- const facts = await extractFacts('/project');
292
- expect(facts[0].interfaces).toBeDefined();
293
- expect(facts[0].interfaces.length).toBeGreaterThanOrEqual(1);
294
- const store = facts[0].interfaces[0];
295
- expect(store.name).toBe('Store');
296
- expect(store.methodCount).toBe(6);
297
- expect(store.methods).toContain('Get');
298
- expect(store.methods).toContain('Close');
299
- });
300
- it('should extract Go functions with receiver methods', async () => {
301
- mockGlobby.mockResolvedValue(['pkg/handler.go']);
302
- mockReadFile.mockResolvedValue(`package handler
303
-
304
- type Handler struct {
305
- service Service
306
- }
307
-
308
- func NewHandler(svc Service) *Handler {
309
- return &Handler{service: svc}
310
- }
311
-
312
- func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
313
- if r.Method == "GET" {
314
- h.handleGet(w, r)
315
- } else {
316
- h.handlePost(w, r)
317
- }
318
- }
319
-
320
- func (h *Handler) handleGet(w http.ResponseWriter, r *http.Request) {
321
- data, err := h.service.Find(r.URL.Query().Get("id"))
322
- if err != nil {
323
- http.Error(w, err.Error(), 500)
324
- return
325
- }
326
- json.NewEncoder(w).Encode(data)
327
- }
328
- `);
329
- const facts = await extractFacts('/project');
330
- const funcs = facts[0].functions;
331
- // Should have receiver methods named as Receiver.Method
332
- const serveHTTP = funcs.find(f => f.name === 'Handler.ServeHTTP');
333
- expect(serveHTTP).toBeDefined();
334
- const handleGet = funcs.find(f => f.name === 'Handler.handleGet');
335
- expect(handleGet).toBeDefined();
336
- // NewHandler should be a standalone func (no receiver)
337
- const newHandler = funcs.find(f => f.name === 'NewHandler');
338
- expect(newHandler).toBeDefined();
339
- expect(newHandler.isExported).toBe(true);
340
- });
341
- it('should count concurrency metrics', async () => {
342
- mockGlobby.mockResolvedValue(['pkg/worker.go']);
343
- mockReadFile.mockResolvedValue(`package worker
344
-
345
- import "sync"
346
-
347
- func StartWorkers(n int) {
348
- var mu sync.Mutex
349
- var wg sync.WaitGroup
350
- ch := make(chan int, 10)
351
-
352
- for i := 0; i < n; i++ {
353
- wg.Add(1)
354
- go processItem(ch, &mu, &wg)
355
- }
356
-
357
- go monitorWorkers(ch)
358
-
359
- defer close(ch)
360
- }
361
-
362
- func processItem(ch chan int, mu *sync.Mutex, wg *sync.WaitGroup) {
363
- defer wg.Done()
364
- for item := range ch {
365
- mu.Lock()
366
- process(item)
367
- mu.Unlock()
368
- }
369
- }
370
- `);
371
- const facts = await extractFacts('/project');
372
- expect(facts[0].goroutines).toBeGreaterThanOrEqual(2);
373
- expect(facts[0].channels).toBeGreaterThanOrEqual(1);
374
- expect(facts[0].defers).toBeGreaterThanOrEqual(2);
375
- expect(facts[0].mutexes).toBeGreaterThanOrEqual(2);
376
- });
377
- });
378
- // ── Quality metrics ──
379
- describe('Quality metrics', () => {
380
- it('should detect magic numbers', async () => {
381
- mockGlobby.mockResolvedValue(['src/calc.ts']);
382
- mockReadFile.mockResolvedValue(`
383
- export function calculate(input: number): number {
384
- const base = input * 42;
385
- const tax = base * 17;
386
- const fee = 325 + 1250;
387
- const limit = 9999;
388
- return base + tax + fee + limit;
389
- }
390
- `);
391
- const facts = await extractFacts('/project');
392
- expect(facts[0].magicNumbers).toBeGreaterThanOrEqual(3);
393
- });
394
- it('should count TODOs', async () => {
395
- mockGlobby.mockResolvedValue(['src/app.ts']);
396
- mockReadFile.mockResolvedValue(`
397
- // TODO: implement caching
398
- export function getData() {
399
- // FIXME: this is slow
400
- const data = fetch('/api');
401
- // HACK: workaround for bug
402
- return data;
403
- }
404
- `);
405
- const facts = await extractFacts('/project');
406
- expect(facts[0].todoCount).toBe(4); // TODO, FIXME, HACK, WORKAROUND
407
- });
408
- it('should calculate comment ratio', async () => {
409
- mockGlobby.mockResolvedValue(['src/app.ts']);
410
- mockReadFile.mockResolvedValue(`
411
- // This is a comment
412
- // Another comment
413
- export function foo() {
414
- return 1;
415
- }
416
-
417
- function bar() {
418
- return 2;
419
- }
420
- `);
421
- const facts = await extractFacts('/project');
422
- expect(facts[0].commentRatio).toBeGreaterThan(0);
423
- });
424
- it('should skip trivial files (< 3 lines)', async () => {
425
- mockGlobby.mockResolvedValue(['src/empty.ts']);
426
- mockReadFile.mockResolvedValue(`// empty\n`);
427
- const facts = await extractFacts('/project');
428
- expect(facts).toHaveLength(0);
429
- });
430
- });
431
- // ── factsToPromptString ──
432
- describe('factsToPromptString', () => {
433
- it('should serialize file facts to a prompt string', () => {
434
- const facts = [
435
- {
436
- path: 'src/service.ts',
437
- language: 'typescript',
438
- lineCount: 150,
439
- classes: [{
440
- name: 'UserService',
441
- lineStart: 5,
442
- lineEnd: 140,
443
- methodCount: 8,
444
- methods: ['find', 'create', 'update', 'delete', 'validate', 'transform', 'cache', 'notify'],
445
- publicMethods: ['find', 'create', 'update', 'delete'],
446
- lineCount: 135,
447
- dependencies: ['Database', 'Logger'],
448
- }],
449
- functions: [],
450
- imports: ['express', './types', 'lodash'],
451
- exports: ['UserService'],
452
- errorHandling: [
453
- { type: 'try-catch', lineStart: 10, isEmpty: false, strategy: 'throw' },
454
- { type: 'try-catch', lineStart: 30, isEmpty: true, strategy: 'ignore' },
455
- ],
456
- testAssertions: 0,
457
- hasTests: false,
458
- },
459
- ];
460
- const result = factsToPromptString(facts);
461
- expect(result).toContain('FILE: src/service.ts');
462
- expect(result).toContain('CLASS UserService');
463
- expect(result).toContain('135 lines');
464
- expect(result).toContain('8 methods');
465
- expect(result).toContain('deps: Database, Logger');
466
- expect(result).toContain('ERROR_HANDLING');
467
- expect(result).toContain('1 empty catches');
468
- expect(result).toContain('IMPORTS: 3');
469
- });
470
- it('should include Go structs and concurrency info', () => {
471
- const facts = [
472
- {
473
- path: 'pkg/worker.go',
474
- language: 'go',
475
- lineCount: 200,
476
- classes: [],
477
- functions: [{
478
- name: 'StartWorkers',
479
- lineStart: 10,
480
- lineEnd: 60,
481
- lineCount: 50,
482
- paramCount: 2,
483
- params: ['n int', 'config Config'],
484
- maxNesting: 3,
485
- hasReturn: true,
486
- isAsync: true,
487
- isExported: true,
488
- }],
489
- imports: ['sync', 'context'],
490
- exports: [],
491
- errorHandling: [],
492
- testAssertions: 0,
493
- hasTests: false,
494
- structs: [{
495
- name: 'Worker',
496
- lineStart: 5,
497
- lineEnd: 15,
498
- fieldCount: 4,
499
- methodCount: 3,
500
- methods: ['Start', 'Stop', 'Process'],
501
- lineCount: 10,
502
- embeds: [],
503
- }],
504
- goroutines: 5,
505
- channels: 2,
506
- defers: 3,
507
- mutexes: 1,
508
- },
509
- ];
510
- const result = factsToPromptString(facts);
511
- expect(result).toContain('STRUCT Worker');
512
- expect(result).toContain('4 fields');
513
- expect(result).toContain('3 methods');
514
- expect(result).toContain('CONCURRENCY');
515
- expect(result).toContain('goroutines:5');
516
- expect(result).toContain('channels:2');
517
- expect(result).toContain('defers:3');
518
- expect(result).toContain('mutexes:1');
519
- });
520
- it('should respect maxChars budget', () => {
521
- const manyFacts = Array.from({ length: 100 }, (_, i) => ({
522
- path: `src/file${i}.ts`,
523
- language: 'typescript',
524
- lineCount: 100,
525
- classes: [],
526
- functions: [{
527
- name: `func${i}`,
528
- lineStart: 1,
529
- lineEnd: 50,
530
- lineCount: 50,
531
- paramCount: 2,
532
- params: ['a', 'b'],
533
- maxNesting: 2,
534
- hasReturn: true,
535
- isAsync: false,
536
- isExported: true,
537
- }],
538
- imports: ['express', 'lodash', 'react'],
539
- exports: [`func${i}`],
540
- errorHandling: [],
541
- testAssertions: 0,
542
- hasTests: false,
543
- }));
544
- const result = factsToPromptString(manyFacts, 500);
545
- expect(result.length).toBeLessThanOrEqual(600); // Allow some slack
546
- });
547
- });
548
- });
549
- describe('extractFacts scope', () => {
550
- beforeEach(() => { vi.clearAllMocks(); });
551
- it('globs only the scoped patterns and skips non-code files they match', async () => {
552
- mockGlobby.mockResolvedValue(['src/a.ts', 'src/README.md', 'src/data.json']);
553
- mockReadFile.mockResolvedValue('export const a = 1;\nexport const b = 2;\nexport const c = 3;\n');
554
- const facts = await extractFacts('/project', [], ['src/**/*']);
555
- expect(mockGlobby.mock.calls[0][0]).toEqual(['src/**/*']);
556
- expect(facts.map(f => f.path)).toEqual(['src/a.ts']);
557
- });
558
- it('uses the whole-repo pattern when unscoped', async () => {
559
- mockGlobby.mockResolvedValue([]);
560
- await extractFacts('/project');
561
- expect(mockGlobby.mock.calls[0][0]).toEqual(['**/*.{ts,js,tsx,jsx,py,go,rs,cs,java,rb,kt}']);
562
- });
563
- });
564
- describe('isTestFile', () => {
565
- it('recognises test files by path convention', () => {
566
- for (const p of ['src/a.test.ts', 'src/a.spec.tsx', '__tests__/a.ts', 'tests/a.py', 'test/a.js', 'pkg/a_test.go', 'test_a.py']) {
567
- expect(isTestFile(p, ''), p).toBe(true);
568
- }
569
- });
570
- it('recognises test-runner calls at the start of a line', () => {
571
- expect(isTestFile('src/a.ts', "describe('x', () => {});")).toBe(true);
572
- expect(isTestFile('src/a.ts', " it.each([1])('x', () => {});")).toBe(true);
573
- expect(isTestFile('src/a.py', 'def test_x():\n pass')).toBe(true);
574
- });
575
- it('does not mistake ordinary code or paths for tests', () => {
576
- expect(isTestFile('src/route.ts', 'process.exit(1);\nform.submit();\nawait wait(5);')).toBe(false);
577
- expect(isTestFile('src/check.ts', 'if (/^a/.test(value)) return;')).toBe(false);
578
- expect(isTestFile('src/latest/report.ts', 'export {}')).toBe(false);
579
- expect(isTestFile('src/contest/entry.ts', 'export {}')).toBe(false);
580
- });
581
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,25 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { parseFindings } from './parse-findings.js';
3
- const finding = { category: 'correctness', severity: 'high', file: 'a.ts', line: 3, description: 'bug', suggestion: 'fix', confidence: 0.8 };
4
- describe('parseFindings', () => {
5
- it('parses a findings object, a bare array and fenced JSON', () => {
6
- expect(parseFindings(JSON.stringify({ findings: [finding] }))).toHaveLength(1);
7
- expect(parseFindings(JSON.stringify([finding]))).toHaveLength(1);
8
- expect(parseFindings('Here you go:\n```json\n' + JSON.stringify({ findings: [finding] }) + '\n```')).toHaveLength(1);
9
- });
10
- it('recovers complete findings from a response cut off by the token budget', () => {
11
- const cut = `{"findings": [${JSON.stringify(finding)}, {"category": "security", "severity": "hi`;
12
- expect(parseFindings(cut)).toEqual([finding]);
13
- });
14
- it('drops malformed entries and normalises confidence and severity', () => {
15
- const parsed = parseFindings(JSON.stringify({ findings: [
16
- { ...finding, severity: 'urgent', confidence: 7 },
17
- { category: 'x', description: 'no file' },
18
- ] }));
19
- expect(parsed).toEqual([{ ...finding, severity: 'medium', confidence: 0.5 }]);
20
- });
21
- it('returns nothing for empty or non-JSON output', () => {
22
- expect(parseFindings('')).toEqual([]);
23
- expect(parseFindings('I could not find any issues.')).toEqual([]);
24
- });
25
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,80 +0,0 @@
1
- import fs from 'fs';
2
- import os from 'os';
3
- import path from 'path';
4
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
5
- import { verifyCodeFindings } from './code-verifier.js';
6
- import { diffSections } from './pr-diff.js';
7
- import { buildPrPrompt, reviewPullRequest } from './pr-review.js';
8
- const DIFF = [
9
- 'diff --git a/src/invoice.ts b/src/invoice.ts',
10
- '--- a/src/invoice.ts',
11
- '+++ b/src/invoice.ts',
12
- '@@ -10,3 +10,3 @@ export function invoice(row) {',
13
- ' const headers = {};',
14
- "- const cls = row.currency;",
15
- "+ const cls = 'USD';",
16
- ' return deliver(row, cls);',
17
- 'diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml',
18
- '--- a/pnpm-lock.yaml',
19
- '+++ b/pnpm-lock.yaml',
20
- '@@ -1 +1 @@',
21
- '-a',
22
- '+b',
23
- 'diff --git a/src/price.ts b/src/price.ts',
24
- '--- a/src/price.ts',
25
- '+++ b/src/price.ts',
26
- '@@ -1,1 +1,2 @@',
27
- ' export function price(row) {',
28
- '+ return row.currency;',
29
- ].join('\n');
30
- describe('diffSections', () => {
31
- it('numbers new-side lines, marks removals, and leaves lockfiles out', () => {
32
- const sections = diffSections(DIFF);
33
- expect(sections.map(s => s.file)).toEqual(['src/invoice.ts', 'src/price.ts']);
34
- expect(sections[0].text).toContain(" 11 + const cls = 'USD';");
35
- expect(sections[0].text).toContain(" - const cls = row.currency;");
36
- expect(sections[0].text).toContain(' 12 return deliver(row, cls);');
37
- expect(sections[1].addedLines).toBe(1);
38
- });
39
- });
40
- describe('buildPrPrompt', () => {
41
- it('puts the risky files first and points at the riskiest functions', () => {
42
- const { prompt } = buildPrPrompt({ cwd: '.', diff: DIFF, focus: [{ file: 'src/price.ts', function: 'price', start: 1, questions: ['Do callers still get a currency?'] }], prBody: 'Read the currency live.' });
43
- expect(prompt.indexOf('FILE src/price.ts')).toBeLessThan(prompt.indexOf('FILE src/invoice.ts'));
44
- expect(prompt).toContain('- src/price.ts:1 `price`: Do callers still get a currency?');
45
- expect(prompt).toContain('PR DESCRIPTION (what the author intended):\nRead the currency live.');
46
- expect(prompt).not.toContain('pnpm-lock');
47
- });
48
- });
49
- describe('reviewPullRequest', () => {
50
- let repo;
51
- beforeEach(() => {
52
- repo = fs.mkdtempSync(path.join(os.tmpdir(), 'pr-review-'));
53
- fs.mkdirSync(path.join(repo, 'src'));
54
- fs.writeFileSync(path.join(repo, 'src/invoice.ts'), Array.from({ length: 20 }, (_, i) => i === 10 ? " const cls = 'USD';" : `// ${i + 1}`).join('\n'));
55
- fs.writeFileSync(path.join(repo, 'src/price.ts'), 'export function price(row) {\n return row.currency;\n}\n');
56
- });
57
- afterEach(() => { fs.rmSync(repo, { recursive: true, force: true }); });
58
- it('reviews the PR in one conversation and grounds a cross-file finding in the files it names', async () => {
59
- const replies = [
60
- { text: '', toolCalls: [{ id: 'r', name: 'read_file', arguments: { path: 'src/price.ts' } }] },
61
- { text: JSON.stringify({ findings: [
62
- { category: 'consistency', severity: 'high', file: 'src/invoice.ts', line: 11, description: '`cls` is hard-coded while `price` reads `currency` live.', suggestion: 's', confidence: 0.9 },
63
- { category: 'correctness', severity: 'high', file: 'src/invoice.ts', line: 11, description: '`invented_symbol` is never set.', suggestion: 's', confidence: 0.9 },
64
- ] }), toolCalls: [] },
65
- ];
66
- let conversations = 0;
67
- const provider = {
68
- name: 'fake', isAvailable: async () => true, setup: async () => { }, dispose: () => { }, analyze: async () => '',
69
- chat: async (messages) => { if (messages.length === 1)
70
- conversations++; return replies.shift(); },
71
- };
72
- const result = await reviewPullRequest(provider, { cwd: repo, diff: DIFF, focus: [] }, {});
73
- expect(conversations).toBe(1);
74
- expect(result.toolCalls).toBe(1);
75
- const rejected = {};
76
- const kept = verifyCodeFindings(result.findings, result.contexts, rejected);
77
- expect(kept.map(f => f.description)).toEqual(['`cls` is hard-coded while `price` reads `currency` live.']);
78
- expect(rejected).toEqual({ ungrounded_identifier: 1 });
79
- });
80
- });