@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,211 +0,0 @@
1
- /**
2
- * Staleness Detector Tests
3
- *
4
- * Tests for the staleness detection system.
5
- */
6
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
7
- import * as fs from 'fs/promises';
8
- import * as path from 'path';
9
- import * as os from 'os';
10
- import { StalenessDetector, checkCodeStaleness } from './staleness.js';
11
- describe('StalenessDetector', () => {
12
- let testDir;
13
- beforeEach(async () => {
14
- testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'rigour-staleness-'));
15
- });
16
- afterEach(async () => {
17
- await fs.rm(testDir, { recursive: true, force: true });
18
- });
19
- describe('React deprecations', () => {
20
- it('should detect componentWillMount', async () => {
21
- await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: { react: '^18.0.0' } }));
22
- const detector = new StalenessDetector(testDir);
23
- const result = await detector.checkStaleness(`
24
- class MyComponent extends React.Component {
25
- componentWillMount() {
26
- console.log('mounting');
27
- }
28
- }
29
- `);
30
- expect(result.status).toBe('DEPRECATED');
31
- expect(result.issues.some(i => i.pattern.includes('componentWillMount'))).toBe(true);
32
- expect(result.issues[0].replacement).toContain('useEffect');
33
- });
34
- it('should detect ReactDOM.render', async () => {
35
- await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: { react: '^18.0.0', 'react-dom': '^18.0.0' } }));
36
- const detector = new StalenessDetector(testDir);
37
- const result = await detector.checkStaleness(`
38
- ReactDOM.render(<App />, document.getElementById('root'));
39
- `);
40
- expect(result.status).toBe('DEPRECATED');
41
- expect(result.issues.some(i => i.pattern.includes('ReactDOM.render'))).toBe(true);
42
- expect(result.issues[0].replacement).toContain('createRoot');
43
- });
44
- it('should not flag React deprecations if React is not installed', async () => {
45
- await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: { express: '^4.0.0' } }));
46
- const detector = new StalenessDetector(testDir);
47
- const result = await detector.checkStaleness(`
48
- componentWillMount() {}
49
- `);
50
- // Should not flag React-specific patterns if React isn't a dependency
51
- expect(result.issues.filter(i => i.pattern.includes('componentWillMount'))).toHaveLength(0);
52
- });
53
- });
54
- describe('Package deprecations', () => {
55
- it('should detect moment.js usage', async () => {
56
- await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: {} }));
57
- const detector = new StalenessDetector(testDir);
58
- const result = await detector.checkStaleness(`
59
- import moment from 'moment';
60
- const date = moment().format('YYYY-MM-DD');
61
- `);
62
- expect(result.status).toBe('STALE');
63
- expect(result.issues.some(i => i.replacement.includes('date-fns'))).toBe(true);
64
- });
65
- it('should detect request library usage', async () => {
66
- await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: {} }));
67
- const detector = new StalenessDetector(testDir);
68
- const result = await detector.checkStaleness(`
69
- const request = require('request');
70
- request.get('https://example.com');
71
- `);
72
- expect(result.status).toBe('DEPRECATED');
73
- expect(result.issues.some(i => i.replacement.includes('fetch'))).toBe(true);
74
- });
75
- });
76
- describe('JavaScript deprecations', () => {
77
- it('should detect var keyword usage', async () => {
78
- await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: {} }));
79
- const detector = new StalenessDetector(testDir);
80
- const result = await detector.checkStaleness(`
81
- var name = 'test';
82
- var count = 0;
83
- `);
84
- expect(result.issues.some(i => i.replacement.includes('const') || i.replacement.includes('let'))).toBe(true);
85
- });
86
- });
87
- describe('Redux deprecations', () => {
88
- it('should detect createStore usage in modern Redux projects', async () => {
89
- await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: { redux: '^4.2.0' } }));
90
- const detector = new StalenessDetector(testDir);
91
- const result = await detector.checkStaleness(`
92
- import { createStore } from 'redux';
93
- const store = createStore(reducer);
94
- `);
95
- expect(result.status).toBe('STALE');
96
- expect(result.issues.some(i => i.replacement.includes('configureStore'))).toBe(true);
97
- });
98
- });
99
- describe('Node.js deprecations', () => {
100
- it('should detect Buffer constructor', async () => {
101
- await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: {} }));
102
- const detector = new StalenessDetector(testDir);
103
- const result = await detector.checkStaleness(`
104
- const buf = new Buffer('hello');
105
- `);
106
- expect(result.status).toBe('DEPRECATED');
107
- expect(result.issues.some(i => i.replacement.includes('Buffer.from'))).toBe(true);
108
- });
109
- });
110
- describe('Next.js deprecations', () => {
111
- it('should detect getInitialProps in Next 13+ projects', async () => {
112
- await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: { next: '^13.0.0' } }));
113
- const detector = new StalenessDetector(testDir);
114
- const result = await detector.checkStaleness(`
115
- MyPage.getInitialProps = async (ctx) => {
116
- return { data: {} };
117
- };
118
- `);
119
- expect(result.issues.some(i => i.pattern.includes('getInitialProps'))).toBe(true);
120
- });
121
- });
122
- describe('TypeScript best practices', () => {
123
- it('should warn about enum usage', async () => {
124
- await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: {} }));
125
- const detector = new StalenessDetector(testDir);
126
- const result = await detector.checkStaleness(`
127
- enum Status {
128
- Active = 'active',
129
- Inactive = 'inactive'
130
- }
131
- `);
132
- expect(result.issues.some(i => i.severity === 'info' && i.pattern.includes('enum'))).toBe(true);
133
- });
134
- });
135
- describe('Project context', () => {
136
- it('should return project versions in context', async () => {
137
- await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({
138
- dependencies: {
139
- react: '^18.2.0',
140
- next: '^14.0.0'
141
- },
142
- devDependencies: {
143
- typescript: '^5.0.0'
144
- }
145
- }));
146
- const detector = new StalenessDetector(testDir);
147
- const result = await detector.checkStaleness('const x = 1;');
148
- expect(result.projectContext.react).toBeDefined();
149
- expect(result.projectContext.next).toBeDefined();
150
- expect(result.projectContext.typescript).toBeDefined();
151
- });
152
- });
153
- describe('Custom deprecations', () => {
154
- it('should allow adding custom deprecation rules', async () => {
155
- await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: {} }));
156
- const detector = new StalenessDetector(testDir);
157
- detector.addDeprecation({
158
- pattern: 'legacyFunction',
159
- deprecatedIn: '1.0.0',
160
- replacement: 'newFunction()',
161
- severity: 'error',
162
- reason: 'Custom deprecation'
163
- });
164
- const result = await detector.checkStaleness(`
165
- legacyFunction();
166
- `);
167
- expect(result.issues.some(i => i.replacement === 'newFunction()')).toBe(true);
168
- });
169
- });
170
- describe('Overall status', () => {
171
- it('should return FRESH when no issues found', async () => {
172
- await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: {} }));
173
- const detector = new StalenessDetector(testDir);
174
- const result = await detector.checkStaleness(`
175
- const name = 'test';
176
- const greet = () => console.log('Hello');
177
- `);
178
- expect(result.status).toBe('FRESH');
179
- });
180
- it('should return STALE for warnings', async () => {
181
- await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: { redux: '^4.2.0' } }));
182
- const detector = new StalenessDetector(testDir);
183
- const result = await detector.checkStaleness(`
184
- createStore(reducer);
185
- `);
186
- expect(result.status).toBe('STALE');
187
- });
188
- it('should return DEPRECATED for errors', async () => {
189
- await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: {} }));
190
- const detector = new StalenessDetector(testDir);
191
- const result = await detector.checkStaleness(`
192
- new Buffer('hello');
193
- `);
194
- expect(result.status).toBe('DEPRECATED');
195
- });
196
- });
197
- });
198
- describe('checkCodeStaleness', () => {
199
- let testDir;
200
- beforeEach(async () => {
201
- testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'rigour-staleness-'));
202
- await fs.writeFile(path.join(testDir, 'package.json'), JSON.stringify({ dependencies: {} }));
203
- });
204
- afterEach(async () => {
205
- await fs.rm(testDir, { recursive: true, force: true });
206
- });
207
- it('should be a quick helper for staleness checking', async () => {
208
- const result = await checkCodeStaleness(testDir, 'const x = 1;');
209
- expect(result.status).toBe('FRESH');
210
- });
211
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,32 +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 { listResolvedFixes, openFindingCount, recordReviewOutcome } from './agent-fixes.js';
6
- const finding = (file) => ({ id: 'semantic-bugs', title: 't', details: 'd', files: [file], line: 2 });
7
- describe('recordReviewOutcome', () => {
8
- let cwd;
9
- beforeEach(() => {
10
- cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-fixes-'));
11
- fs.writeFileSync(path.join(cwd, 'a.ts'), 'before\n');
12
- });
13
- afterEach(() => { fs.rmSync(cwd, { recursive: true, force: true }); });
14
- it('stores the before and after once a reviewed file no longer has the finding', () => {
15
- expect(recordReviewOutcome(cwd, [finding('a.ts')], ['a.ts'])).toMatchObject({ opened: 1, resolved: 0 });
16
- fs.writeFileSync(path.join(cwd, 'a.ts'), 'after\n');
17
- expect(recordReviewOutcome(cwd, [], ['a.ts'])).toMatchObject({ opened: 0, resolved: 1 });
18
- const [fix] = listResolvedFixes(cwd);
19
- expect(fix).toMatchObject({ file: 'a.ts', rule: 'semantic-bugs', before: 'before\n', after: 'after\n' });
20
- expect(openFindingCount(cwd)).toBe(0);
21
- });
22
- it('keeps a finding open when its file was not part of the review (committed, not fixed)', () => {
23
- recordReviewOutcome(cwd, [finding('a.ts')], ['a.ts']);
24
- expect(recordReviewOutcome(cwd, [], ['other.ts'])).toMatchObject({ opened: 0, resolved: 0 });
25
- expect(openFindingCount(cwd)).toBe(1);
26
- });
27
- it('does not count a finding that vanished without an edit as a fix', () => {
28
- recordReviewOutcome(cwd, [finding('a.ts')], ['a.ts']);
29
- expect(recordReviewOutcome(cwd, [], ['a.ts'])).toMatchObject({ opened: 0, resolved: 0 });
30
- expect(listResolvedFixes(cwd)).toEqual([]);
31
- });
32
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,98 +0,0 @@
1
- import { execFileSync } from 'child_process';
2
- import fs from 'fs';
3
- import os from 'os';
4
- import path from 'path';
5
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
6
- import { ConfigSchema } from '../types/index.js';
7
- import { ledgerProblems, loadLedger, LEDGER_PATH, LedgerSchema } from './backtest.js';
8
- import { scaffoldLedger } from './backtest-init.js';
9
- let repo;
10
- const git = (...args) => execFileSync('git', ['-C', repo, ...args], { encoding: 'utf8' }).trim();
11
- const config = ConfigSchema.parse({ version: 1 });
12
- const REVIEWS = [
13
- { id: 1, user: { login: 'ci-bot', type: 'Bot' }, body: 'automated', state: 'COMMENTED', submitted_at: '2026-04-12T10:00:00Z', commit_id: 'c1c1c1c1c1' },
14
- { id: 2, user: { login: 'senior', type: 'User' }, body: '## Review\n\n- A retry posts the invoice twice.\n- **Quadratic** scan in `latestPayment`.\nlooks good otherwise', state: 'CHANGES_REQUESTED', submitted_at: '2026-04-12T12:00:00Z', commit_id: 'c2c2c2c2c2' },
15
- ];
16
- const INLINE = [
17
- { path: 'src/lib/server/invoices.ts', line: 40, body: 'The export reads every line item for ids.\n\nNarrow it.', created_at: '2026-04-12T12:00:00Z' },
18
- { path: 'src/checkout/total.ts', line: null, original_line: null, body: 'Drops the currency', created_at: '2026-04-12T12:00:00Z' },
19
- ];
20
- function fakeExec(calls) {
21
- return async (command, args, options) => {
22
- calls.push([command, ...args]);
23
- if (command === 'git') {
24
- try {
25
- return { exitCode: 0, stdout: execFileSync('git', args, { cwd: options.cwd, encoding: 'utf8', stdio: 'pipe' }), stderr: '' };
26
- }
27
- catch (error) {
28
- return { exitCode: error.status ?? 1, stdout: '', stderr: String(error.stderr ?? '') };
29
- }
30
- }
31
- if (args[0] === 'auth')
32
- return { exitCode: 0, stdout: 'token\n', stderr: '' };
33
- if (args[0] === 'pr')
34
- return { exitCode: 0, stdout: 'author\n', stderr: '' };
35
- if (args[1].endsWith('/reviews'))
36
- return { exitCode: 0, stdout: JSON.stringify(REVIEWS), stderr: '' };
37
- return { exitCode: 0, stdout: JSON.stringify(INLINE), stderr: '' };
38
- };
39
- }
40
- beforeEach(() => {
41
- repo = fs.mkdtempSync(path.join(os.tmpdir(), 'backtest-init-'));
42
- git('init', '-q', '-b', 'main');
43
- git('config', 'user.email', 't@example.com');
44
- git('config', 'user.name', 't');
45
- git('config', 'commit.gpgsign', 'false');
46
- fs.writeFileSync(path.join(repo, 'a.ts'), 'export const a = 1;\n');
47
- git('add', '-A');
48
- execFileSync('git', ['-C', repo, 'commit', '-qm', 'main as of the review'], { env: { ...process.env, GIT_COMMITTER_DATE: '2026-04-12T09:00:00Z', GIT_AUTHOR_DATE: '2026-04-12T09:00:00Z' } });
49
- });
50
- afterEach(() => { fs.rmSync(repo, { recursive: true, force: true }); });
51
- describe('rigour backtest init', () => {
52
- it('takes the base from where the reviewed commit left main, not from main as of the review', async () => {
53
- const forked = git('rev-parse', 'HEAD');
54
- git('checkout', '-q', '-b', 'feature');
55
- fs.writeFileSync(path.join(repo, 'b.ts'), 'export const b = 1;\n');
56
- git('add', '-A');
57
- execFileSync('git', ['-C', repo, 'commit', '-qm', 'the branch'], { env: { ...process.env, GIT_COMMITTER_DATE: '2026-04-12T09:30:00Z', GIT_AUTHOR_DATE: '2026-04-12T09:30:00Z' } });
58
- const reviewed = git('rev-parse', 'HEAD');
59
- git('checkout', '-q', 'main');
60
- fs.writeFileSync(path.join(repo, 'c.ts'), 'export const c = 1;\n');
61
- git('add', '-A');
62
- execFileSync('git', ['-C', repo, 'commit', '-qm', 'main moves on before the review'], { env: { ...process.env, GIT_COMMITTER_DATE: '2026-04-12T11:00:00Z', GIT_AUTHOR_DATE: '2026-04-12T11:00:00Z' } });
63
- const realCommit = (calls) => async (command, args, options) => {
64
- const answer = await fakeExec(calls)(command, args, options);
65
- return command === 'gh' && args[1]?.endsWith('/reviews') ? { ...answer, stdout: JSON.stringify(REVIEWS.map(r => ({ ...r, commit_id: reviewed }))) } : answer;
66
- };
67
- const { rounds } = await scaffoldLedger(repo, 212, config, realCommit([]));
68
- expect(rounds[0].base).toBe(forked);
69
- });
70
- it('writes one round per human review: inline comments become file-and-window points, body bullets need a pattern', async () => {
71
- const calls = [];
72
- const mainAtReview = git('rev-parse', 'HEAD');
73
- const { rounds, incomplete } = await scaffoldLedger(repo, 212, config, fakeExec(calls));
74
- expect(rounds).toHaveLength(1);
75
- expect(rounds[0]).toMatchObject({ id: 'pr212-r1', commit: 'c2c2c2c2c2', base: mainAtReview, reviewed_at: '2026-04-12T12:00:00Z', pr: 212 });
76
- expect(rounds[0].points).toEqual([
77
- { id: 'R1-1', point: 'The export reads every line item for ids.', file: 'src/lib/server/invoices\\.ts', lines: [30, 50] },
78
- { id: 'R1-2', point: 'Drops the currency', file: 'src/checkout/total\\.ts', needs: 'a line window or a text pattern (the comment has no line)' },
79
- { id: 'R1-B1', point: 'A retry posts the invoice twice.', file: '', needs: 'a file pattern and a text pattern (this point was made in the review body, not on a line)' },
80
- { id: 'R1-B2', point: 'Quadratic scan in latestPayment.', file: '', needs: 'a file pattern and a text pattern (this point was made in the review body, not on a line)' },
81
- ]);
82
- expect(incomplete).toBe(3);
83
- expect(calls.find(c => c[0] === 'gh' && c[1] === 'pr')).toContain('212');
84
- const written = LedgerSchema.parse(JSON.parse(fs.readFileSync(path.join(repo, LEDGER_PATH), 'utf8')));
85
- expect(ledgerProblems(written)).toHaveLength(3);
86
- expect(() => loadLedger(repo)).toThrow('rows a person still has to complete');
87
- });
88
- it('keeps rounds already in the ledger and replaces its own', async () => {
89
- fs.mkdirSync(path.join(repo, '.rigour'));
90
- fs.writeFileSync(path.join(repo, LEDGER_PATH), JSON.stringify({ rounds: [
91
- { id: 'pr212-r1', commit: 'old0000', base: 'main', points: [], must_not_flag: [] },
92
- { id: 'pr7-r1', commit: 'other00', base: 'main', points: [{ id: 'P', point: 'p', file: 'x', text: 'x' }], must_not_flag: [] },
93
- ] }));
94
- await scaffoldLedger(repo, 212, config, fakeExec([]));
95
- const written = LedgerSchema.parse(JSON.parse(fs.readFileSync(path.join(repo, LEDGER_PATH), 'utf8')));
96
- expect(written.rounds.map(r => [r.id, r.commit])).toEqual([['pr7-r1', 'other00'], ['pr212-r1', 'c2c2c2c2c2']]);
97
- });
98
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,32 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { formatJudges, judgedFrom } from './backtest-judges.js';
3
- const base = { outcome: 'findings', items: [], unverified: [], resolved: [], answerInReply: [], notes: [], advisory: [], disputed: [], dropped: [], dismissed: [], reviewers: ['claude', 'codex'], cached: false };
4
- describe('measuring each judge', () => {
5
- it('computes Cohen\'s kappa between two judges: 1 in full agreement, 0 at chance, and says when they share blind spots', () => {
6
- const round = (claude, codex) => ({ judges: { judges: ['claude', 'codex'], caught: { claude, codex }, points: ['p1', 'p2', 'p3', 'p4'], runs: 2 }, points: [] });
7
- expect(formatJudges([round(['p1', 'p3'], ['p1', 'p3'])])).toContain('kappa 1.00 over 4 point(s) (they share blind spots: a second judge adds little)');
8
- expect(formatJudges([round(['p1', 'p2'], ['p1', 'p3'])])).toContain('kappa 0.00 over 4 point(s)');
9
- expect(formatJudges([round(['p1'], ['p2'])])).toMatch(/kappa -0\.\d\d over 4/);
10
- });
11
- it('takes each judge\'s own item from the panel, including what it raised in its own words', () => {
12
- const lock = { id: 'a', kind: 'finding', class: 'correctness', file: 'src/job.ts', line: 2, issue: 'returns before the lock' };
13
- const judged = judgedFrom({ ...base, costUsd: 2, runs: 3, panel: [
14
- { item: lock, judges: ['claude', 'codex'], calls: { claude: 'raised', codex: 'raised' }, status: 'confirmed', members: [{ judge: 'claude', item: lock }, { judge: 'codex', item: { ...lock, id: 'b', line: 3, issue: 'lock taken late' } }] },
15
- ] });
16
- expect(judged).toEqual({ judges: ['claude', 'codex'], costUsd: 2, runs: 3, raised: [
17
- { judge: 'claude', file: 'src/job.ts', line: 2, text: 'returns before the lock' },
18
- { judge: 'codex', file: 'src/job.ts', line: 3, text: 'lock taken late' },
19
- ] });
20
- expect(judgedFrom({ ...base, reviewers: ['claude'] })).toBeUndefined();
21
- });
22
- it('reports each judge, what only it found, each pair\'s agreement and the cost of a caught point', () => {
23
- const round = (caught) => ({ judges: { judges: ['claude', 'codex'], caught, points: ['p1', 'p2', 'p3', 'p4'], runs: 3, costUsd: 3 }, points: ['p1', 'p2', 'p3', 'p4'].map(id => ({ id, caught: Object.values(caught).some(ids => ids.includes(id)) })) });
24
- const report = formatJudges([round({ claude: ['p1', 'p2'], codex: ['p1', 'p3'] }), round({ claude: ['p1'], codex: ['p1'] })]);
25
- expect(report).toContain('Judges (2 round(s), 8 human point(s))');
26
- expect(report).toContain('claude: raised 3/8; 1 that no other judge raised');
27
- expect(report).toContain('codex: raised 3/8; 1 that no other judge raised');
28
- expect(report).toMatch(/claude and codex: kappa 0\.\d\d over 8 point\(s\)/);
29
- expect(report).toContain('6 agent run(s), $6.00 recorded, $1.50 per caught point');
30
- expect(formatJudges([{ points: [] }])).toBe('');
31
- });
32
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,109 +0,0 @@
1
- import { execFileSync } from 'child_process';
2
- import fs from 'fs';
3
- import os from 'os';
4
- import path from 'path';
5
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
6
- import { ConfigSchema } from '../types/index.js';
7
- import { backtestLast, formatLast, scoreLast, LAST_LEDGER_PATH } from './backtest-last.js';
8
- import { roundsForPr } from './backtest-init.js';
9
- const config = ConfigSchema.parse({ version: 1 });
10
- const item = (file, line, blocking = true, gate = 'reviewer:claude') => ({ gate, file, line, text: `${file} scan has no upper bound`, blocking });
11
- const result = (round, items, caught = 0, costUsd) => ({ round, head: 'h', base: 'b', points: Array.from({ length: caught }, (_, i) => ({ id: `p${i}`, point: 'p', caught: true, noted: false })), falseBlocks: [], items, durationMs: 1000, ...(costUsd !== undefined ? { costUsd } : {}) });
12
- describe('the front door report', () => {
13
- const ledger = { rounds: [
14
- { id: 'pr7-r1', commit: 'c', base: 'b', reviewed_at: '2026-04-12T12:00:00Z', pr: 7, points: [{ id: 'R1-1', point: 'drops the currency', file: 'src/b\\.ts', lines: [1, 11] }], must_not_flag: [] },
15
- { id: 'pr7-r2', commit: 'c', base: 'b', reviewed_at: '2026-04-12T13:00:00Z', pr: 7, points: [{ id: 'R2-1', point: 'the export reads every line item', file: 'src/a\\.ts', lines: [1, 11] }], must_not_flag: [] },
16
- { id: 'pr7-r3', commit: 'c', base: 'b', reviewed_at: '2026-04-12T14:00:00Z', pr: 7, points: [{ id: 'R3-1', point: 'still unbounded', file: 'src/a\\.ts', lines: [1, 11] }], must_not_flag: [] },
17
- { id: 'pr7-approved', commit: 'c', base: 'b', reviewed_at: '2026-04-12T15:00:00Z', pr: 7, points: [], must_not_flag: [] },
18
- { id: 'pr8-approved', commit: 'c', base: 'b', reviewed_at: '2026-04-13T15:00:00Z', pr: 8, points: [], must_not_flag: [] },
19
- ] };
20
- it('counts points raised later that an earlier round blocked, with how many rounds earlier, and every block on an approved head', () => {
21
- const results = [result('pr7-r1', [item('src/a.ts', 3)]), result('pr7-r2', [item('src/a.ts', 3)], 1, 1.5), result('pr7-r3', [], 0, 0.5), result('pr7-approved', [item('src/a.ts', 3)]), result('pr8-approved', [item('src/z.ts', 9, false)])];
22
- const r = scoreLast(ledger, results);
23
- expect(r).toMatchObject({ prs: 2, rounds: 5, approvedHeads: 2, pointsLater: 2, sameRound: 1, costUsd: 2 });
24
- expect(r.early).toEqual([
25
- { pr: 7, round: 'pr7-r1', point: 'the export reads every line item', by: 'reviewer:claude src/a.ts:3', roundsEarlier: 1 },
26
- { pr: 7, round: 'pr7-r1', point: 'still unbounded', by: 'reviewer:claude src/a.ts:3', roundsEarlier: 2 }, // the earliest round that blocked it counts
27
- ]);
28
- expect(r.blocksOnApproved).toEqual([{ pr: 7, round: 'pr7-approved', item: item('src/a.ts', 3) }]); // an advisory item on pr8 is not a block
29
- const text = formatLast({ ...r, skipped: ['pr9-r1: commit abcdef012 is not reachable (force-pushed away)'] });
30
- expect(text.split('\n')[0]).toBe('1 blocking item(s) on 2 approved head(s): every one is a block the team would have overridden.'); // the worse number first
31
- expect(text).toContain('2 of 2 point(s) people raised in a later round were already blocked, 1.5 round(s) earlier on average.');
32
- expect(text).toContain('skipped pr9-r1: commit abcdef012');
33
- const clean = formatLast({ ...scoreLast(ledger, results.map(x => (x.round.endsWith('-approved') ? { ...x, items: [] } : x))), skipped: [] });
34
- expect(clean.split('\n')[0]).toContain('point(s) people raised in a later round were already blocked'); // nothing on approved heads: the catches lead
35
- expect(clean).toContain('0 blocking items on 2 approved head(s).');
36
- });
37
- });
38
- describe('rigour backtest --last', () => {
39
- let repo;
40
- const git = (...args) => execFileSync('git', ['-C', repo, ...args], { encoding: 'utf8' }).trim();
41
- const commit = (message, date) => { git('add', '-A'); execFileSync('git', ['-C', repo, 'commit', '-qm', message], { env: { ...process.env, GIT_COMMITTER_DATE: date, GIT_AUTHOR_DATE: date } }); return git('rev-parse', 'HEAD'); };
42
- let reviewed;
43
- const fake = (calls) => async (command, args, options) => {
44
- calls.push([command, ...args]);
45
- if (command === 'git') {
46
- if (args[0] === 'fetch')
47
- return { exitCode: 0, stdout: '', stderr: '' }; // the head is already here
48
- try {
49
- return { exitCode: 0, stdout: execFileSync('git', args, { cwd: options.cwd, encoding: 'utf8', stdio: 'pipe' }), stderr: '' };
50
- }
51
- catch (error) {
52
- return { exitCode: error.status ?? 1, stdout: '', stderr: String(error.stderr ?? '') };
53
- }
54
- }
55
- if (args[0] === 'auth')
56
- return { exitCode: 0, stdout: 'token\n', stderr: '' };
57
- if (args[0] === 'pr' && args[1] === 'list')
58
- return { exitCode: 0, stdout: JSON.stringify([{ number: 7, mergedAt: '2026-04-12T16:00:00Z' }, { number: 6, mergedAt: '2026-04-11T16:00:00Z' }]), stderr: '' };
59
- if (args[0] === 'pr')
60
- return { exitCode: 0, stdout: 'author\n', stderr: '' };
61
- const pr = /pulls\/(\d+)\//.exec(args[1])?.[1];
62
- if (args[1].endsWith('/reviews')) {
63
- if (pr === '6')
64
- return { exitCode: 0, stdout: JSON.stringify([{ id: 60, user: { login: 'author', type: 'User' }, body: 'self note', state: 'COMMENTED', submitted_at: '2026-04-11T12:00:00Z', commit_id: reviewed }]), stderr: '' };
65
- return { exitCode: 0, stdout: JSON.stringify([
66
- { id: 1, user: { login: 'senior', type: 'User' }, body: 'first pass', state: 'CHANGES_REQUESTED', submitted_at: '2026-04-12T12:00:00Z', commit_id: reviewed },
67
- { id: 2, user: { login: 'senior', type: 'User' }, body: 'second pass', state: 'CHANGES_REQUESTED', submitted_at: '2026-04-12T13:00:00Z', commit_id: reviewed },
68
- { id: 3, user: { login: 'senior', type: 'User' }, body: '', state: 'APPROVED', submitted_at: '2026-04-12T14:00:00Z', commit_id: reviewed },
69
- ]), stderr: '' };
70
- }
71
- if (args[1].includes('/reviews/1/'))
72
- return { exitCode: 0, stdout: JSON.stringify([{ path: 'b.ts', line: 1, body: 'Drops the currency', created_at: '2026-04-12T12:00:00Z' }]), stderr: '' };
73
- if (args[1].includes('/reviews/2/'))
74
- return { exitCode: 0, stdout: JSON.stringify([{ path: 'a.ts', line: 1, body: 'The export reads every line item.', created_at: '2026-04-12T13:00:00Z' }]), stderr: '' };
75
- return { exitCode: 0, stdout: '[]', stderr: '' };
76
- };
77
- beforeEach(() => {
78
- repo = fs.mkdtempSync(path.join(os.tmpdir(), 'backtest-last-'));
79
- git('init', '-q', '-b', 'main');
80
- git('config', 'user.email', 't@example.com');
81
- git('config', 'user.name', 't');
82
- git('config', 'commit.gpgsign', 'false');
83
- fs.writeFileSync(path.join(repo, 'a.ts'), 'export const a = 1;\n');
84
- commit('main', '2026-04-12T09:00:00Z');
85
- git('checkout', '-q', '-b', 'feature');
86
- fs.writeFileSync(path.join(repo, 'b.ts'), 'export const b = 1;\n');
87
- reviewed = commit('the branch', '2026-04-12T09:30:00Z');
88
- });
89
- afterEach(() => { fs.rmSync(repo, { recursive: true, force: true }); });
90
- it('builds the ledger from the last merged pull requests, the approved head included, runs it and leads with the two numbers', async () => {
91
- const calls = [];
92
- const report = await backtestLast(repo, config, { last: 2, exec: fake(calls), collect: async () => ({ items: [item('a.ts', 1)], costUsd: 0.25 }) });
93
- expect(report).toMatchObject({ prs: 1, rounds: 3, approvedHeads: 1, pointsLater: 1, sameRound: 1, costUsd: 0.75 });
94
- expect(report.early).toEqual([{ pr: 7, round: 'pr7-r1', point: 'The export reads every line item.', by: 'reviewer:claude a.ts:1', roundsEarlier: 1 }]);
95
- expect(report.blocksOnApproved.map(b => b.round)).toEqual(['pr7-approved']);
96
- expect(report.skipped).toEqual(['pull request 6: pull request 6 has no review by a person yet']); // the author's own note is not a review
97
- expect(calls.filter(c => c[0] === 'git' && c[1] === 'fetch').map(c => c.at(-1))).toEqual(['pull/7/head', 'pull/6/head']);
98
- const written = JSON.parse(fs.readFileSync(path.join(repo, LAST_LEDGER_PATH), 'utf8'));
99
- expect(written.rounds.map(r => [r.id, r.points.length])).toEqual([['pr7-r1', 1], ['pr7-r2', 1], ['pr7-approved', 0]]);
100
- expect(formatLast(report).split('\n')[0]).toContain('1 blocking item(s) on 1 approved head(s)');
101
- });
102
- it('gives the scaffolder the approved head only when asked, with no points', async () => {
103
- const calls = [];
104
- const without = await roundsForPr(repo, 7, config, fake(calls));
105
- expect(without.approved).toBeUndefined();
106
- const withHead = await roundsForPr(repo, 7, config, fake(calls), { approvedHead: true });
107
- expect(withHead.approved).toMatchObject({ id: 'pr7-approved', commit: reviewed, reviewed_at: '2026-04-12T14:00:00Z', points: [] });
108
- });
109
- });
@@ -1 +0,0 @@
1
- export {};