@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
@@ -0,0 +1,59 @@
1
+ import { type LessonMode } from '../review-learning/team-lessons.js';
2
+ /** The most items a briefing gives: past about ten, a briefing is a wall nobody reads. */
3
+ export declare const BRIEFING_MAX_ITEMS = 10;
4
+ /** The most items a briefing for one file gives, the first time an agent edits it: a word in passing, not a wall. */
5
+ export declare const FILE_BRIEFING_MAX_ITEMS = 3;
6
+ export interface BriefingItem {
7
+ kind: 'rule' | 'lesson' | 'settled';
8
+ /** What to do, in the team's words (a rule's text, a lesson's rule). */
9
+ text: string;
10
+ /** Where it came from: the rules file, or the pull requests the lesson was learned and proven on. */
11
+ cite: string;
12
+ /** A rule the team worded as a requirement: a break blocks at review. */
13
+ requirement?: boolean;
14
+ id: string;
15
+ }
16
+ export interface Briefing {
17
+ task?: string;
18
+ goal: string;
19
+ files: string[];
20
+ items: BriefingItem[];
21
+ }
22
+ export interface BriefingInput {
23
+ /** What the task is for: the agent's first prompt, a ticket's summary, the pull request's title. The branch name when absent. */
24
+ goal?: string;
25
+ /** The files the task will touch, when known; otherwise the branch's own changes and the files the goal names. */
26
+ files?: string[];
27
+ lessons?: LessonMode;
28
+ limit?: number;
29
+ }
30
+ /**
31
+ * The briefing for a task in this checkout: requirement rules first (a break of one blocks later), then the team's
32
+ * verified lessons, then the points the team settled against, then guidance rules; `limit` items at most.
33
+ */
34
+ export declare function buildBriefing(cwd: string, input?: BriefingInput): Briefing;
35
+ /** The briefing as an agent reads it: short, numbered, each item with where it came from. Empty when there is nothing to say. */
36
+ export declare function briefingText(briefing: Briefing): string;
37
+ /**
38
+ * The briefing for one file, the first time an agent edits it: the requirement rules that name it (or its folder), the
39
+ * lessons the team learned on it, and the points the team settled against on it; at most three. At session start the
40
+ * task's files are often unknown; the first edit of a file is when they are, and when a briefing can be specific.
41
+ */
42
+ export declare function buildFileBriefing(cwd: string, file: string, input?: {
43
+ lessons?: LessonMode;
44
+ limit?: number;
45
+ }): Briefing;
46
+ /** A file's briefing as an agent reads it, just before it edits that file. Empty when there is nothing to say. */
47
+ export declare function fileBriefingText(briefing: Briefing): string;
48
+ /** Builds a file's briefing and records it on the task's thread, with the file. */
49
+ export declare function briefFile(cwd: string, file: string, input?: {
50
+ lessons?: LessonMode;
51
+ limit?: number;
52
+ session?: string;
53
+ agent?: string;
54
+ }): Briefing;
55
+ /** Builds the briefing and records it on the task's thread (what was briefed, by id, so a later review can be read against it). */
56
+ export declare function briefTask(cwd: string, input: BriefingInput & {
57
+ session?: string;
58
+ agent?: string;
59
+ }): Briefing;
@@ -0,0 +1,126 @@
1
+ /**
2
+ * The briefing: before an agent writes, what a senior on this team would tell it about the task. The repository's own
3
+ * rules and the team's verified lessons for the files the task will likely touch, and the points the team settled
4
+ * against (so the agent does not re-raise or re-do them), at most `limit` items, each cited to where it came from.
5
+ *
6
+ * Nothing here is new knowledge: the same rules and lessons the reviewer checks a change against, selected by the same
7
+ * matchers, before the code exists instead of after. It is deterministic (no model call), local, and it never blocks.
8
+ */
9
+ import { spawnSync } from 'child_process';
10
+ import { rulesForDiff } from '../review-learning/repo-rules.js';
11
+ import { describeLesson, lessonsForDiff, lessonView, rejectedForDiff } from '../review-learning/team-lessons.js';
12
+ import { appendTaskEvent, taskOf } from '../task/thread.js';
13
+ /** The most items a briefing gives: past about ten, a briefing is a wall nobody reads. */
14
+ export const BRIEFING_MAX_ITEMS = 10;
15
+ /** The most items a briefing for one file gives, the first time an agent edits it: a word in passing, not a wall. */
16
+ export const FILE_BRIEFING_MAX_ITEMS = 3;
17
+ /** The most files a briefing reads the task's likely reach from. */
18
+ const LIKELY_FILES = 20;
19
+ /**
20
+ * The briefing for a task in this checkout: requirement rules first (a break of one blocks later), then the team's
21
+ * verified lessons, then the points the team settled against, then guidance rules; `limit` items at most.
22
+ */
23
+ export function buildBriefing(cwd, input = {}) {
24
+ const task = taskOf(cwd);
25
+ const goal = (input.goal?.trim() || (task ? task.branch.replace(/[/_-]+/g, ' ') : '')).slice(0, 2000);
26
+ const files = (input.files?.length ? input.files : likelyFiles(cwd, goal)).slice(0, LIKELY_FILES);
27
+ const limit = Math.max(0, Math.min(input.limit ?? BRIEFING_MAX_ITEMS, BRIEFING_MAX_ITEMS));
28
+ // The rule and lesson matchers read a change: the task's files and the goal's words stand in for the code to come.
29
+ const shape = shapeOf(files, goal);
30
+ // Only rules that name a path or identifier of the task: the reviewer can judge a rule against code; a briefing cannot.
31
+ const rules = rulesForDiff(cwd, shape, true, limit, true);
32
+ const lessons = lessonsForDiff(cwd, shape, input.lessons ?? 'verified', 5, limit, 3).filter(l => l.state === 'verified' || input.lessons === 'all');
33
+ const settled = rejectedForDiff(cwd, shape);
34
+ const cited = (prs) => (prs.length ? `learned in PR ${prs.map(p => `#${p}`).join(', ')}` : 'the team\'s decision');
35
+ const items = [
36
+ ...rules.filter(r => r.requirement).map(r => ({ kind: 'rule', text: r.text, cite: ruleCite(r.source, r.scope), requirement: true, id: `rule:${r.id}` })),
37
+ ...lessons.map(l => {
38
+ const view = lessonView(l);
39
+ return { kind: 'lesson', text: describeLesson({ ...view, prs: [] }), cite: cited(view.prs), id: `lesson:${l.id}` };
40
+ }),
41
+ ...settled.map(l => ({ kind: 'settled', text: `settled against, do not do or raise it: ${lessonView(l).text}`, cite: cited(lessonView(l).prs), id: `settled:${l.id}` })),
42
+ ...rules.filter(r => !r.requirement).map(r => ({ kind: 'rule', text: r.text, cite: ruleCite(r.source, r.scope), id: `rule:${r.id}` })),
43
+ ];
44
+ return { ...(task ? { task: task.key } : {}), goal, files, items: items.slice(0, limit) };
45
+ }
46
+ /** The briefing as an agent reads it: short, numbered, each item with where it came from. Empty when there is nothing to say. */
47
+ export function briefingText(briefing) {
48
+ if (briefing.items.length === 0)
49
+ return '';
50
+ const lines = briefing.items.map((item, i) => `${i + 1}. ${item.requirement ? '[must] ' : item.kind === 'settled' ? '[settled] ' : ''}${item.text} (${item.cite})`);
51
+ return [`Rigour briefing${briefing.task ? ` for ${briefing.task}` : ''}: how this team builds the code this task will likely touch. Follow these; a [must] broken in your change blocks at review.`, ...lines].join('\n');
52
+ }
53
+ /**
54
+ * The briefing for one file, the first time an agent edits it: the requirement rules that name it (or its folder), the
55
+ * lessons the team learned on it, and the points the team settled against on it; at most three. At session start the
56
+ * task's files are often unknown; the first edit of a file is when they are, and when a briefing can be specific.
57
+ */
58
+ export function buildFileBriefing(cwd, file, input = {}) {
59
+ const task = taskOf(cwd);
60
+ const limit = Math.max(0, Math.min(input.limit ?? FILE_BRIEFING_MAX_ITEMS, FILE_BRIEFING_MAX_ITEMS));
61
+ const shape = shapeOf([file], '');
62
+ const mode = input.lessons ?? 'verified';
63
+ const rules = rulesForDiff(cwd, shape, true, BRIEFING_MAX_ITEMS, true).filter(r => r.requirement);
64
+ const lessons = lessonsForDiff(cwd, shape, mode, 0, BRIEFING_MAX_ITEMS, BRIEFING_MAX_ITEMS).filter(l => l.file === file && (l.state === 'verified' || mode === 'all'));
65
+ const settled = rejectedForDiff(cwd, shape).filter(l => l.file === file);
66
+ const cited = (prs) => (prs.length ? `learned in PR ${prs.map(p => `#${p}`).join(', ')}` : 'the team\'s decision');
67
+ const items = [
68
+ ...rules.map(r => ({ kind: 'rule', text: r.text, cite: ruleCite(r.source, r.scope), requirement: true, id: `rule:${r.id}` })),
69
+ ...lessons.map(l => ({ kind: 'lesson', text: lessonView(l).text, cite: cited(lessonView(l).prs), id: `lesson:${l.id}` })),
70
+ ...settled.map(l => ({ kind: 'settled', text: `settled against, do not do or raise it: ${lessonView(l).text}`, cite: cited(lessonView(l).prs), id: `settled:${l.id}` })),
71
+ ];
72
+ return { ...(task ? { task: task.key } : {}), goal: '', files: [file], items: items.slice(0, limit) };
73
+ }
74
+ /** A file's briefing as an agent reads it, just before it edits that file. Empty when there is nothing to say. */
75
+ export function fileBriefingText(briefing) {
76
+ if (briefing.items.length === 0)
77
+ return '';
78
+ const lines = briefing.items.map((item, i) => `${i + 1}. ${item.requirement ? '[must] ' : item.kind === 'settled' ? '[settled] ' : ''}${item.text} (${item.cite})`);
79
+ return [`Rigour, before you edit ${briefing.files[0]}: what this team asks of this file.`, ...lines].join('\n');
80
+ }
81
+ /** Builds a file's briefing and records it on the task's thread, with the file. */
82
+ export function briefFile(cwd, file, input = {}) {
83
+ const briefing = buildFileBriefing(cwd, file, input);
84
+ appendTaskEvent(cwd, { kind: 'brief', file, ...(input.session ? { session: input.session } : {}), ...(input.agent ? { agent: input.agent } : {}), items: briefing.items.length, ids: briefing.items.map(i => i.id), files: [file] });
85
+ return briefing;
86
+ }
87
+ /** Builds the briefing and records it on the task's thread (what was briefed, by id, so a later review can be read against it). */
88
+ export function briefTask(cwd, input) {
89
+ const briefing = buildBriefing(cwd, input);
90
+ appendTaskEvent(cwd, { kind: 'brief', ...(input.session ? { session: input.session } : {}), ...(input.agent ? { agent: input.agent } : {}), items: briefing.items.length, ids: briefing.items.map(i => i.id), files: briefing.files });
91
+ return briefing;
92
+ }
93
+ function ruleCite(source, scope) {
94
+ return scope ? `${source}, for ${scope}` : source;
95
+ }
96
+ /** A stand-in change for the matchers: each file as touched, the goal's words as the added line. */
97
+ function shapeOf(files, goal) {
98
+ const words = goal.replace(/\s+/g, ' ');
99
+ const touched = files.length ? files : ['.'];
100
+ return touched.map(f => `diff --git a/${f} b/${f}\n--- a/${f}\n+++ b/${f}\n@@ -0,0 +1,1 @@\n+${words}\n`).join('');
101
+ }
102
+ /** The files a task will likely touch: what the branch already changed, then tracked files whose path names a word of the goal. */
103
+ function likelyFiles(cwd, goal) {
104
+ const files = [];
105
+ const main = ['origin/main', 'main', 'origin/master', 'master'].find(ref => git(cwd, ['rev-parse', '--verify', '-q', ref]) !== undefined);
106
+ const base = main ? git(cwd, ['merge-base', 'HEAD', main]) : undefined;
107
+ if (base)
108
+ files.push(...(git(cwd, ['diff', '--name-only', `${base}...HEAD`]) ?? '').split('\n').filter(Boolean));
109
+ const words = [...new Set(goal.toLowerCase().match(/[a-z][a-z0-9]{3,}/g) ?? [])].filter(w => !STOP.has(w));
110
+ if (words.length) {
111
+ const tracked = (git(cwd, ['ls-files']) ?? '').split('\n').filter(Boolean);
112
+ for (const file of tracked) {
113
+ if (files.length >= LIKELY_FILES)
114
+ break;
115
+ const name = file.toLowerCase();
116
+ if (!files.includes(file) && words.some(w => name.includes(w)))
117
+ files.push(file);
118
+ }
119
+ }
120
+ return files;
121
+ }
122
+ const STOP = new Set(['this', 'that', 'with', 'from', 'into', 'when', 'then', 'than', 'have', 'make', 'should', 'would', 'could', 'please', 'there', 'their', 'what', 'which', 'about', 'feat', 'test', 'tests', 'code', 'file', 'files', 'change', 'update']);
123
+ function git(cwd, args) {
124
+ const result = spawnSync('git', args, { cwd, encoding: 'utf8', timeout: 5000, maxBuffer: 16 * 1024 * 1024 });
125
+ return result.status === 0 ? result.stdout.trim() : undefined;
126
+ }
package/dist/index.d.ts CHANGED
@@ -119,3 +119,5 @@ export type { SemanticFinding } from './semantic/types.js';
119
119
  export type { ModelUsage } from './storage/index.js';
120
120
  export type { CheckpointMetric } from './storage/index.js';
121
121
  export type { CursorUsageSyncOptions } from './services/cursor-usage-client.js';
122
+ export { appendTaskEvent, readThread, taskOf, threadText, threadsDir, THREADS_DIR, type TaskEvent, type TaskEventKind, type ThreadEvent } from './task/thread.js';
123
+ export { briefFile, briefingText, briefTask, buildBriefing, buildFileBriefing, fileBriefingText, BRIEFING_MAX_ITEMS, FILE_BRIEFING_MAX_ITEMS, type Briefing, type BriefingInput, type BriefingItem } from './brief/briefing.js';
package/dist/index.js CHANGED
@@ -101,3 +101,5 @@ export { learnFromFix, learnFromFileChange } from './semantic/learn/learn.js';
101
101
  export { extractFixTrees } from './semantic/learn/git-trees.js';
102
102
  export { saveLearnedRule, loadLearnedRules, LEARNED_RULES_DIR } from './semantic/learn/store.js';
103
103
  export { recordContextEvent, recordModelUsage, recordCheckpointMetric, getContextEvents, getModelUsages, getCheckpointMetrics, } from './storage/index.js';
104
+ export { appendTaskEvent, readThread, taskOf, threadText, threadsDir, THREADS_DIR } from './task/thread.js';
105
+ export { briefFile, briefingText, briefTask, buildBriefing, buildFileBriefing, fileBriefingText, BRIEFING_MAX_ITEMS, FILE_BRIEFING_MAX_ITEMS } from './brief/briefing.js';
@@ -107,7 +107,7 @@ export class CloudProvider {
107
107
  const response = await this.client.messages.create({
108
108
  model: this.modelName,
109
109
  max_tokens: options?.maxTokens || 4096,
110
- temperature: options?.temperature ?? 0.1,
110
+ ...claudeSampling(this.modelName, options?.temperature ?? 0.1),
111
111
  messages: cacheFirstPrompt(toAnthropicMessages(messages)),
112
112
  ...(tools.length ? { tools: tools.map(t => ({ name: t.name, description: t.description, input_schema: t.parameters })) } : {}),
113
113
  ...(tools.length && options?.toolChoice === 'none' ? { tool_choice: { type: 'none' } } : {}),
@@ -157,7 +157,7 @@ export class CloudProvider {
157
157
  const response = await this.client.messages.create({
158
158
  model: this.modelName,
159
159
  max_tokens: options?.maxTokens || 2048,
160
- temperature: options?.temperature || 0.1,
160
+ ...claudeSampling(this.modelName, options?.temperature ?? 0.1),
161
161
  messages: [
162
162
  { role: 'user', content: prompt }
163
163
  ],
@@ -203,6 +203,16 @@ export class CloudProvider {
203
203
  this.client = null;
204
204
  }
205
205
  }
206
+ /**
207
+ * Claude models that take no sampling parameters: Anthropic's model guide lists temperature, top_p and top_k as removed
208
+ * (a 400) on Fable, Mythos, Opus 5.5, Opus 5, Opus 4.8 and 4.7 and Sonnet 5, and non-default values as a 400 on Sonnet 5.5
209
+ * and Haiku 5.5; OpenRouter's model list marks temperature unsupported on them too. Older Claude models still take it.
210
+ */
211
+ const CLAUDE_WITHOUT_SAMPLING = /claude-(?:fable|mythos|opus-5|opus-4-[78]|sonnet-5|haiku-5)/;
212
+ /** The temperature to send a Claude model: none to one that rejects sampling parameters. */
213
+ function claudeSampling(model, temperature) {
214
+ return CLAUDE_WITHOUT_SAMPLING.test(model) ? {} : { temperature };
215
+ }
206
216
  /** The per-call timeout, honoured by both SDKs (their default is minutes, with retries). */
207
217
  function requestOptions(options) {
208
218
  return options?.timeout ? { timeout: options.timeout } : {};
@@ -11,11 +11,14 @@ export interface PrRounds {
11
11
  export declare function roundsForPr(cwd: string, pr: number, config: Config, exec?: Exec, options?: {
12
12
  approvedHead?: boolean;
13
13
  }): Promise<PrRounds>;
14
- /** The last `n` merged pull requests, newest merge first. */
15
- export declare function mergedPrs(cwd: string, n: number, config: Config, exec?: Exec): Promise<Array<{
16
- number: number;
17
- mergedAt: string;
18
- }>>;
14
+ /** The last `n` merged pull requests, newest merge first; `incomplete` when the listing could not prove it has them all. */
15
+ export declare function mergedPrs(cwd: string, n: number, config: Config, exec?: Exec): Promise<{
16
+ prs: Array<{
17
+ number: number;
18
+ mergedAt: string;
19
+ }>;
20
+ incomplete: boolean;
21
+ }>;
19
22
  export declare function scaffoldLedger(cwd: string, pr: number, config: Config, exec?: Exec): Promise<{
20
23
  file: string;
21
24
  rounds: LedgerRound[];
@@ -46,17 +46,35 @@ export async function roundsForPr(cwd, pr, config, exec = defaultExec, options =
46
46
  throw new Error(`pull request ${pr} has no review by a person yet`);
47
47
  return { rounds, ...(approved ? { approved } : {}) };
48
48
  }
49
- /** The last `n` merged pull requests, newest merge first. */
49
+ /** How many merged pull requests to list per one wanted, first: gh applies its limit before any sort of ours. */
50
+ const MERGED_OVERFETCH = 4;
51
+ /** The most merged pull requests listed per one wanted before Rigour stops and says the list may be incomplete. */
52
+ const MERGED_OVERFETCH_MAX = 32;
53
+ /** The last `n` merged pull requests, newest merge first; `incomplete` when the listing could not prove it has them all. */
50
54
  export async function mergedPrs(cwd, n, config, exec = defaultExec) {
51
55
  const env = await githubEnv(cwd, config.review?.github_account ?? process.env.RIGOUR_GITHUB_ACCOUNT, exec);
52
- const list = await exec('gh', ['pr', 'list', '--state', 'merged', '--limit', String(n), '--json', 'number,mergedAt'], { cwd, timeoutMs: GH_TIMEOUT_MS, env });
53
- if (list.exitCode !== 0)
54
- throw new Error(`could not list merged pull requests: ${list.stderr.trim() || 'is gh signed in?'}`);
55
- try {
56
- return JSON.parse(list.stdout).sort((a, b) => (a.mergedAt < b.mergedAt ? 1 : -1)).slice(0, n);
57
- }
58
- catch {
59
- throw new Error('could not read the list of merged pull requests');
56
+ // Listed by last update and sorted by merge date here. A merged pull request is updated at or after its merge, so once the
57
+ // oldest update listed is no later than the Nth merge kept, nothing unlisted can be among the last N (its merge is no later
58
+ // than its update, which is no later than that). Until then the listing doubles: bots that touch old pull requests after
59
+ // merge (backports, labels, stale comments) can crowd a window.
60
+ for (let limit = n * MERGED_OVERFETCH;; limit *= 2) {
61
+ const list = await exec('gh', ['pr', 'list', '--state', 'merged', '--limit', String(limit), '--search', 'sort:updated-desc', '--json', 'number,mergedAt,updatedAt'], { cwd, timeoutMs: GH_TIMEOUT_MS, env });
62
+ if (list.exitCode !== 0)
63
+ throw new Error(`could not list merged pull requests: ${list.stderr.trim() || 'is gh signed in?'}`);
64
+ let listed;
65
+ try {
66
+ listed = JSON.parse(list.stdout);
67
+ }
68
+ catch {
69
+ throw new Error('could not read the list of merged pull requests');
70
+ }
71
+ const prs = [...listed].sort((a, b) => (a.mergedAt < b.mergedAt ? 1 : -1)).slice(0, n).map(({ number, mergedAt }) => ({ number, mergedAt }));
72
+ const oldestUpdate = listed.reduce((min, p) => (p.updatedAt < min ? p.updatedAt : min), listed[0]?.updatedAt ?? '');
73
+ const complete = listed.length < limit || (prs.length === n && oldestUpdate <= prs[n - 1].mergedAt);
74
+ if (complete)
75
+ return { prs, incomplete: false };
76
+ if (limit >= n * MERGED_OVERFETCH_MAX)
77
+ return { prs, incomplete: true };
60
78
  }
61
79
  }
62
80
  /** Lines either side of an inline comment that a finding for the same point may land on. */
@@ -17,7 +17,9 @@ const GIT_TIMEOUT_MS = 5 * 60_000;
17
17
  export async function backtestLast(cwd, config, options) {
18
18
  const exec = options.exec ?? defaultExec;
19
19
  const progress = options.progress ?? (() => undefined);
20
- const prs = await mergedPrs(cwd, options.last, config, exec);
20
+ const { prs, incomplete } = await mergedPrs(cwd, options.last, config, exec);
21
+ if (incomplete)
22
+ progress(`backtest: warning: the listing could not prove these are the last ${options.last} merged pull requests (many old pull requests were updated after merge); the result may miss recent ones`);
21
23
  const rounds = [];
22
24
  const skipped = [];
23
25
  for (const pr of prs) {
@@ -13,6 +13,10 @@ export interface Adapter {
13
13
  answer(stdout: string): {
14
14
  text: string;
15
15
  } & Spend;
16
+ /** Variables the judge runs with, on top of what it inherits: what keeps a person's own instructions out of it. */
17
+ env?: Record<string, string>;
18
+ /** What this judge still reads from outside the repository on this machine (a person's own config), or undefined: said on the record, never hidden. */
19
+ outsideRepo?(home: string, version: string | undefined): string | undefined;
16
20
  }
17
21
  /** What a run used: dollars when the CLI reports them (Claude Code), tokens otherwise (Codex reports only tokens). */
18
22
  export interface Spend {
@@ -11,6 +11,8 @@ import os from 'os';
11
11
  import path from 'path';
12
12
  import { defaultExec } from './exec.js';
13
13
  const n = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0);
14
+ /** The Claude Code version the memory switches were verified in: below it, a judge may still load a person's CLAUDE.md. */
15
+ const CLAUDE_MEMORY_ISOLATION = '2.1.285';
14
16
  const READ_ONLY_TOOLS = ['Read', 'Grep', 'Glob', 'Bash(git diff:*)', 'Bash(git show:*)', 'Bash(git log:*)', 'Bash(git grep:*)'];
15
17
  export const ADAPTERS = {
16
18
  claude: {
@@ -28,12 +30,26 @@ export const ADAPTERS = {
28
30
  '--allowedTools', ...READ_ONLY_TOOLS,
29
31
  '--disallowedTools', 'Edit', 'Write', 'NotebookEdit', 'Bash(git push:*)', 'Bash(git commit:*)',
30
32
  ],
33
+ // No memory file loads itself: not ~/.claude/CLAUDE.md, not one in a folder above the repository, not the repository's
34
+ // own (the judge reads the repository's rules as files, as Rigour's prompt tells every judge to), and no auto-memory.
35
+ // Claude Code's own safe mode uses the same switch. What the judge knows is the repository and what Rigour gives it.
36
+ env: { CLAUDE_CODE_DISABLE_CLAUDE_MDS: '1', CLAUDE_CODE_DISABLE_AUTO_MEMORY: '1' },
37
+ // An older Claude Code ignores those switches without a word and loads the memory files anyway: below the version
38
+ // they were verified in, or when the version cannot be read, the record says the isolation is unverified.
39
+ outsideRepo: (_home, version) => {
40
+ const found = /(\d+)\.(\d+)\.(\d+)/.exec(version ?? '');
41
+ const at = found ? found.slice(1).map(Number) : undefined;
42
+ const floor = CLAUDE_MEMORY_ISOLATION.split('.').map(Number);
43
+ const below = !at || at[0] !== floor[0] ? !at || at[0] < floor[0] : at[1] !== floor[1] ? at[1] < floor[1] : at[2] < floor[2];
44
+ return below ? `claude ${found?.[0] ?? '(version unknown)'}: memory isolation unverified (needs ${CLAUDE_MEMORY_ISOLATION} or later)` : undefined;
45
+ },
31
46
  answer: stdout => claudeAnswer(stdout),
32
47
  },
33
48
  cursor: {
34
49
  vendor: 'cursor',
35
50
  binary: 'cursor-agent',
36
51
  // Ask mode is read-only and cannot run git: every input the prompt names is a file.
52
+ outsideRepo: () => 'cursor also reads your own Cursor rules and settings',
37
53
  args: (prompt, model) => ['-p', '--print', '--output-format', 'json', '--trust', '--mode', 'ask', '--model', model ?? 'auto', prompt],
38
54
  answer: stdout => {
39
55
  try {
@@ -48,6 +64,13 @@ export const ADAPTERS = {
48
64
  codex: {
49
65
  vendor: 'openai',
50
66
  binary: 'codex',
67
+ // Codex has no switch that skips a person's ~/.codex/config.toml and ~/.codex/AGENTS.md and keeps their login, so the
68
+ // record says when those exist rather than claiming a judge that only knows the repository.
69
+ outsideRepo: home => {
70
+ const dir = process.env.CODEX_HOME?.trim() || path.join(home, '.codex');
71
+ const read = ['config.toml', 'AGENTS.md'].filter(name => fs.existsSync(path.join(dir, name)));
72
+ return read.length ? `codex also reads ${read.map(name => path.join(dir, name)).join(' and ')}` : undefined;
73
+ },
51
74
  args: (prompt, model, options) => ['exec', '--sandbox', 'read-only', '--json', ...(model ? ['--model', model] : []), '-c', `model_reasoning_effort=${options?.reasoning ?? 'high'}`, prompt],
52
75
  // `codex exec --json` streams events; the last text-bearing one carries the answer.
53
76
  // Warnings arrive as `error` items with a `message`, not `text`, so they are never taken for the answer.
@@ -1,11 +1,12 @@
1
1
  import { type Exec } from './exec.js';
2
- import type { Approval } from './verdict.js';
2
+ import type { Approval, LabelledPoint } from './verdict.js';
3
3
  export interface PullRequest {
4
4
  number: number;
5
5
  state: 'open' | 'closed' | 'merged';
6
6
  draft: boolean;
7
7
  author: string;
8
8
  body: string;
9
+ title?: string;
9
10
  }
10
11
  export interface HumanReviews {
11
12
  /** As the reviewer reads them, oldest first, inline comments after; `none` when there are none. */
@@ -15,6 +16,8 @@ export interface HumanReviews {
15
16
  count: number;
16
17
  /** Each person's approval, in time order: every point that person raised before it is settled. */
17
18
  approvals: Approval[];
19
+ /** The points each review put under a severity heading of its own ("Blocking", "Should fix", "Nits"). */
20
+ labels: LabelledPoint[];
18
21
  /** `<login>, <date> (<n> reviews)` of the latest, for the report. */
19
22
  label?: string;
20
23
  }
@@ -13,7 +13,7 @@ import { GH_TIMEOUT_MS, parseJsonArrays } from './exec.js';
13
13
  export function ghFor(cwd, exec, env) {
14
14
  return args => exec('gh', args, { cwd, timeoutMs: GH_TIMEOUT_MS, env });
15
15
  }
16
- const PR_FIELDS = 'number,state,isDraft,author,body';
16
+ const PR_FIELDS = 'number,state,isDraft,author,body,title';
17
17
  /**
18
18
  * The description as it read at `at` (a backtest's review time): GitHub keeps every version in
19
19
  * `userContentEdits`, the first being the text at creation. Undefined when it cannot be read, so a
@@ -67,7 +67,7 @@ async function viewPullRequest(gh, selector) {
67
67
  const parsed = JSON.parse(result.stdout);
68
68
  if (!Number.isInteger(parsed.number))
69
69
  return { error: `gh returned no pull request number for ${selector}` };
70
- return { pr: { number: parsed.number, state: String(parsed.state ?? '').toLowerCase(), draft: !!parsed.isDraft, author: String(parsed.author?.login ?? ''), body: String(parsed.body ?? '') } };
70
+ return { pr: { number: parsed.number, state: String(parsed.state ?? '').toLowerCase(), draft: !!parsed.isDraft, author: String(parsed.author?.login ?? ''), body: String(parsed.body ?? ''), ...(parsed.title ? { title: String(parsed.title) } : {}) } };
71
71
  }
72
72
  catch {
73
73
  return { error: `gh returned something other than a pull request for ${selector}: ${result.stdout.slice(0, 120)}` };
@@ -104,10 +104,44 @@ export async function humanReviews(gh, pr, reviewsBefore) {
104
104
  key: [...rounds.map((r) => `${r.id},${r.submitted_at},${r.commit_id},${r.state}`), ...inline.map((c) => `${c.id},${c.updated_at}`)].join('|'),
105
105
  count,
106
106
  approvals,
107
+ labels: rounds.filter(worded).flatMap((r) => severityLabels(String(r.body), String(r.user.login), String(r.submitted_at))),
107
108
  ...(latest ? { label: `${latest.user.login}, ${latest.submitted_at} (${count} review${count === 1 ? '' : 's'})` } : {}),
108
109
  },
109
110
  };
110
111
  }
112
+ /** The severity a heading names: "Blocking", "Should fix", "Nits" and their usual spellings, alone on the line. */
113
+ function headingSeverity(line) {
114
+ const bare = line.replace(/[#*_:`>]/g, ' ').replace(/\(\d+\)/g, ' ').replace(/\s+/g, ' ').trim().toLowerCase();
115
+ if (!bare || bare.split(' ').length > 3)
116
+ return undefined;
117
+ if (/^(blocking|blockers?|must fix|must-fix)$/.test(bare))
118
+ return 'blocking';
119
+ if (/^(should fix|should-fix|should)$/.test(bare))
120
+ return 'should-fix';
121
+ if (/^(nits?|non blocking|non-blocking|minor|optional)$/.test(bare))
122
+ return 'non-blocking';
123
+ return undefined;
124
+ }
125
+ /** Each bullet or numbered line under a severity heading of the review, with that heading's severity. */
126
+ function severityLabels(body, login, at) {
127
+ const out = [];
128
+ let severity;
129
+ for (const line of body.split('\n')) {
130
+ const heading = headingSeverity(line);
131
+ if (heading) {
132
+ severity = heading;
133
+ continue;
134
+ }
135
+ if (/^\s{0,3}#{1,6}\s/.test(line))
136
+ severity = undefined; // another heading ends the section
137
+ // A bullet, a numbered line, or a numbered line set in bold or underline ("**1. The column is NOT NULL.**"), which is
138
+ // how many reviewers title a point before its evidence.
139
+ const item = /^\s*(?:\*\*|__)?\s*(?:[-*+]|\d+[.)])\s+(.+?)\s*(?:\*\*|__)?\s*$/.exec(line);
140
+ if (severity && item)
141
+ out.push({ login, at, severity, text: item[1].replace(/\*\*|__/g, '').trim() });
142
+ }
143
+ return out;
144
+ }
111
145
  /** The repository's rules as the reviewer must read them. */
112
146
  export function rulesText(cwd) {
113
147
  return ['AGENTS.md', 'CLAUDE.md'].map(name => {
@@ -5,12 +5,14 @@ export interface ReviewRecord {
5
5
  base: string;
6
6
  scope: 'full' | 'delta';
7
7
  at: string;
8
+ /** `outside_repo`: what the judge also read from the machine's own config (a person's instructions); absent when it read only the repository and Rigour's inputs. */
8
9
  judges: Array<{
9
10
  reviewer: string;
10
11
  version?: string;
11
12
  model?: string;
12
13
  cost_usd?: number;
13
14
  turns?: number;
15
+ outside_repo?: string;
14
16
  }>;
15
17
  /** Checked by Rigour against the checkout. */
16
18
  verified: {
@@ -28,10 +30,13 @@ export interface ReviewRecord {
28
30
  served: number;
29
31
  applied: number;
30
32
  };
33
+ /** `labelled`: points that took the review's own severity heading; `relabelled`: of those, the ones the judge had read otherwise. */
31
34
  prior_points: {
32
35
  open: number;
33
36
  resolved: number;
34
37
  answer_in_reply: number;
38
+ labelled?: number;
39
+ relabelled?: number;
35
40
  };
36
41
  unverified: number;
37
42
  notes: number;
@@ -21,7 +21,7 @@ export function buildRecord(input) {
21
21
  should_fix: input.accounted.advisory,
22
22
  rules: { served: rules.length, followed: rules.filter(r => r.status === 'followed').length, broken: rules.filter(r => r.status === 'broken').length, not_applicable: rules.filter(r => r.status === 'not-applicable').length },
23
23
  lessons: { served: input.lessonsServed, applied: lessons.filter(l => l.applies === true).length },
24
- prior_points: { open: input.accounted.open.filter(i => i.kind === 'prior').length, resolved: input.accounted.resolved.length, answer_in_reply: input.accounted.answerInReply.length },
24
+ prior_points: { open: input.accounted.open.filter(i => i.kind === 'prior').length, resolved: input.accounted.resolved.length, answer_in_reply: input.accounted.answerInReply.length, ...(input.accounted.labels?.served ? { labelled: input.accounted.labels.taken, relabelled: input.accounted.labels.disagreed } : {}) },
25
25
  unverified: input.accounted.unverified.length,
26
26
  notes: input.accounted.notes.length,
27
27
  disputed: input.accounted.disputed.length,
@@ -54,7 +54,7 @@ function canonical(value) {
54
54
  export function recordLines(r, shouldFixShown = 5) {
55
55
  const where = (i) => `${i.file ? `\`${i.file}${i.line ? `:${i.line}` : ''}\` ` : ''}${i.issue}${i.locations?.length ? ` (also ${i.locations.map(l => `\`${l.file}${l.line ? `:${l.line}` : ''}\``).join(', ')})` : ''}`;
56
56
  const v = r.verified;
57
- const lines = [`**Review record** · ${v.blocking.length} blocking · ${v.should_fix.length} should-fix · rules ${v.rules.followed} followed, ${v.rules.broken} broken, ${v.rules.not_applicable} not applicable of ${v.rules.served} · lessons ${v.lessons.applied} of ${v.lessons.served} apply · prior points ${v.prior_points.open} open, ${v.prior_points.resolved} resolved`];
57
+ const lines = [`**Review record** · ${v.blocking.length} blocking · ${v.should_fix.length} should-fix · rules ${v.rules.followed} followed, ${v.rules.broken} broken, ${v.rules.not_applicable} not applicable of ${v.rules.served} · lessons ${v.lessons.applied} of ${v.lessons.served} apply · prior points ${v.prior_points.open} open, ${v.prior_points.resolved} resolved${v.prior_points.labelled !== undefined ? `, ${v.prior_points.labelled} by the review's own label (${v.prior_points.relabelled} relabelled)` : ''}`];
58
58
  for (const i of v.blocking)
59
59
  lines.push(`- **Blocking** ${where(i)}`);
60
60
  for (const i of v.should_fix.slice(0, shouldFixShown))
@@ -64,6 +64,6 @@ export function recordLines(r, shouldFixShown = 5) {
64
64
  const folded = [[v.notes, 'working note'], [v.disputed, 'disputed'], [v.unverified, 'unverified'], [r.people.dismissed, 'dismissed']].filter(([n]) => n > 0);
65
65
  if (folded.length)
66
66
  lines.push(`Also seen, never blocking: ${folded.map(([n, w]) => `${n} ${w}${n === 1 || w === 'disputed' || w === 'unverified' || w === 'dismissed' ? '' : 's'}`).join(', ')}.`);
67
- lines.push(`Judged by ${r.judges.map(j => `${j.reviewer}${j.version ? ` ${j.version}` : ''}${j.model ? ` (${j.model})` : ''}${typeof j.cost_usd === 'number' ? ` $${j.cost_usd.toFixed(2)}` : ''}`).join(', ') || 'no judge'} on \`${r.head.slice(0, 9)}\` against \`${r.base.slice(0, 9)}\` (${r.scope}); ${r.reported.human_reviews} human review(s) seen. Integrity \`${r.integrity.slice(0, 16)}\`.`);
67
+ lines.push(`Judged by ${r.judges.map(j => `${j.reviewer}${j.version ? ` ${j.version}` : ''}${j.model ? ` (${j.model})` : ''}${typeof j.cost_usd === 'number' ? ` $${j.cost_usd.toFixed(2)}` : ''}${j.outside_repo ? ` [${j.outside_repo}]` : ''}`).join(', ') || 'no judge'} on \`${r.head.slice(0, 9)}\` against \`${r.base.slice(0, 9)}\` (${r.scope}); ${r.reported.human_reviews} human review(s) seen. Integrity \`${r.integrity.slice(0, 16)}\`.`);
68
68
  return lines;
69
69
  }
@@ -27,6 +27,14 @@ export interface PriorChecks {
27
27
  approvals: Approval[];
28
28
  inCheckout: (text: string) => string | undefined;
29
29
  changed?: ChangedLines;
30
+ labels?: LabelledPoint[];
31
+ }
32
+ /** A point under a severity heading the reviewer wrote ("Blocking", "Should fix", "Nits"): the reviewer's own label. */
33
+ export interface LabelledPoint {
34
+ login: string;
35
+ at: string;
36
+ severity: NonNullable<PriorPoint['severity']>;
37
+ text: string;
30
38
  }
31
39
  /** The lines a change touched, per file: every line added, and the place of every deletion, in the new file's numbering. */
32
40
  export type ChangedLines = Map<string, Set<number>>;
@@ -252,6 +260,12 @@ export interface Accounting {
252
260
  notes: OpenItem[];
253
261
  /** Should-fixes the judge could show (a verified quote): worth a person's time, never a block. */
254
262
  advisory: OpenItem[];
263
+ /** The reviews' own severity labels: how many there were, how many prior points took one, and how many the judge read otherwise. */
264
+ labels?: {
265
+ served: number;
266
+ taken: number;
267
+ disagreed: number;
268
+ };
255
269
  }
256
270
  /** Everything the reviewers reported blocks; in delta mode, previous open items carry unless resolved with evidence. */
257
271
  export declare function account(verdict: Verdict, previousOpen: OpenItem[] | undefined, verify: Verify, prior?: PriorChecks): Accounting;