@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,329 +0,0 @@
1
- /**
2
- * Tests for Input Validation Gate — AI Agent DLP (Data Loss Prevention)
3
- *
4
- */
5
- import { describe, it, expect } from 'vitest';
6
- import { scanInputForCredentials, formatDLPAlert, createDLPAuditEntry, } from './input-validator.js';
7
- // ── Cloud Provider Keys ──────────────────────────────────────────
8
- describe('scanInputForCredentials — AWS', () => {
9
- it('detects AWS Access Key IDs', () => {
10
- const result = scanInputForCredentials('Here is my key: AKIAZ9Y8X7W6V5U4T3Q2');
11
- expect(result.status).toBe('blocked');
12
- expect(result.detections).toHaveLength(1);
13
- expect(result.detections[0].type).toBe('aws_access_key');
14
- expect(result.detections[0].severity).toBe('critical');
15
- expect(result.detections[0].decision).toBe('block');
16
- expect(result.detections[0].confidence).toBeGreaterThanOrEqual(80);
17
- });
18
- it('detects AWS Secret Key assignments', () => {
19
- const result = scanInputForCredentials('aws_secret_access_key = "Z9Y8X7W6V5U4T3S2R1Q0P9O8N7M6B5C4A3D2E1F0"');
20
- expect(result.status).toBe('blocked');
21
- expect(result.detections.some(d => d.type === 'aws_secret_key')).toBe(true);
22
- });
23
- });
24
- describe('scanInputForCredentials — GCP', () => {
25
- it('detects GCP service account JSON', () => {
26
- const input = '{"type": "service_account", "project_id": "my-proj", "private_key": "-----BEGIN RSA PRIVATE KEY-----"}';
27
- const result = scanInputForCredentials(input);
28
- expect(result.status).toBe('blocked');
29
- // May detect as gcp_service_account and/or private_key
30
- expect(result.detections.length).toBeGreaterThanOrEqual(1);
31
- });
32
- });
33
- describe('scanInputForCredentials — Azure', () => {
34
- it('detects Azure storage key', () => {
35
- const result = scanInputForCredentials('AccountKey=dGhpcyBpcyBhIGJhc2U2NCBzdHJpbmcgdGhhdCBpcyBsb25nIGVub3VnaCB0byBtYXRjaA==');
36
- expect(result.status).toBe('blocked');
37
- expect(result.detections.some(d => d.type === 'azure_key')).toBe(true);
38
- });
39
- });
40
- // ── API Keys (Provider-Specific) ─────────────────────────────────
41
- describe('scanInputForCredentials — API keys', () => {
42
- it('detects OpenAI key', () => {
43
- const result = scanInputForCredentials('sk-proj-Z9Y8X7W6V5U4T3S2R1Q0P9O8N7M6');
44
- expect(result.status).toBe('blocked');
45
- expect(result.detections[0].type).toBe('openai_key');
46
- });
47
- it('detects Anthropic key', () => {
48
- const result = scanInputForCredentials('sk-ant-api03-Z9Y8X7W6V5U4T3S2R1Q0');
49
- expect(result.status).toBe('blocked');
50
- expect(result.detections[0].type).toBe('anthropic_key');
51
- });
52
- it('detects GitHub PAT', () => {
53
- const result = scanInputForCredentials('ghp_Z9Y8X7W6V5U4T3S2R1Q0P9O8N7M6B5C4A3D2');
54
- expect(result.status).toBe('blocked');
55
- expect(result.detections[0].type).toBe('github_token');
56
- });
57
- it('detects Stripe live key', () => {
58
- const result = scanInputForCredentials('sk_live_Z9Y8X7W6V5U4T3S2R1Q0P9O8');
59
- expect(result.status).toBe('blocked');
60
- expect(result.detections[0].type).toBe('stripe_key');
61
- });
62
- it('detects SendGrid key', () => {
63
- const result = scanInputForCredentials('SG.Z9Y8X7W6V5U4T3S2R1Q0P9.Z9Y8X7W6V5U4T3S2R1Q0P9O8N7M6B5C4A3D2E1F0GH7');
64
- expect(result.status).toBe('blocked');
65
- expect(result.detections[0].type).toBe('sendgrid_key');
66
- });
67
- });
68
- // ── Private Keys ─────────────────────────────────────────────────
69
- describe('scanInputForCredentials — Private keys', () => {
70
- it('detects RSA private key header', () => {
71
- const result = scanInputForCredentials('-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQ...');
72
- expect(result.status).toBe('blocked');
73
- expect(result.detections.some(d => d.type === 'private_key')).toBe(true);
74
- });
75
- it('detects EC private key header', () => {
76
- const result = scanInputForCredentials('-----BEGIN EC PRIVATE KEY-----');
77
- expect(result.status).toBe('blocked');
78
- });
79
- it('detects OPENSSH private key header', () => {
80
- const result = scanInputForCredentials('-----BEGIN OPENSSH PRIVATE KEY-----');
81
- expect(result.status).toBe('blocked');
82
- });
83
- });
84
- // ── Database Connection Strings ──────────────────────────────────
85
- describe('scanInputForCredentials — Database URLs', () => {
86
- it('detects PostgreSQL connection string', () => {
87
- const result = scanInputForCredentials('postgresql://user:password@prod-server:5432/mydb');
88
- expect(result.status).toBe('blocked');
89
- const dbDetection = result.detections.find(d => d.type === 'database_url');
90
- expect(dbDetection).toBeDefined();
91
- });
92
- it('detects MongoDB connection string', () => {
93
- const result = scanInputForCredentials('mongodb+srv://admin:s3cret@cluster0.prod.mongodb.net/production');
94
- expect(result.status).toBe('blocked');
95
- });
96
- it('detects Redis connection string', () => {
97
- const result = scanInputForCredentials('redis://default:mypassword@redis-host:6379');
98
- expect(result.status).toBe('blocked');
99
- });
100
- });
101
- // ── Bearer Tokens & JWTs ─────────────────────────────────────────
102
- describe('scanInputForCredentials — Tokens', () => {
103
- it('detects JWT token', () => {
104
- const jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
105
- const result = scanInputForCredentials(jwt);
106
- expect(result.status).toBe('blocked');
107
- expect(result.detections.some(d => d.type === 'jwt_token')).toBe(true);
108
- });
109
- });
110
- // ── Generic Patterns ─────────────────────────────────────────────
111
- describe('scanInputForCredentials — Generic patterns', () => {
112
- it('allows ambiguous short password assignment without high entropy', () => {
113
- const result = scanInputForCredentials("password = 'WinterAccess987!'");
114
- expect(result.status).toBe('clean');
115
- });
116
- it('allows placeholder api_key assignment', () => {
117
- const result = scanInputForCredentials('api_key: "abcdefghijklmnopqrstuvwxyz"');
118
- expect(result.status).toBe('clean');
119
- expect(result.detections).toHaveLength(0);
120
- expect(result.allowed_detections?.[0].reason_codes).toContain('safe_example_value');
121
- });
122
- it('warns on high-entropy api_key assignment', () => {
123
- const result = scanInputForCredentials('api_key: "Z9y8X7w6V5u4T3s2R1q0P9o8N7m6B5c4"');
124
- expect(result.status).toBe('warning');
125
- expect(result.detections[0].decision).toBe('warn');
126
- expect(result.detections[0].reason_codes).toContain('high_entropy');
127
- });
128
- it('warns on high-entropy .env format values', () => {
129
- const result = scanInputForCredentials('DATABASE_PASSWORD=Z9y8X7w6V5u4T3s2R1q0P9o8N7m6B5c4Xy');
130
- expect(result.status).toBe('warning');
131
- expect(result.detections.some(d => d.type === 'env_variable')).toBe(true);
132
- });
133
- it('allows placeholder URL with embedded credentials', () => {
134
- const result = scanInputForCredentials('http://admin:password123@internal-api.company.com/v1');
135
- expect(result.status).toBe('clean');
136
- });
137
- });
138
- // ── Clean Input ──────────────────────────────────────────────────
139
- describe('scanInputForCredentials — Clean input', () => {
140
- it('returns clean for normal code', () => {
141
- const result = scanInputForCredentials('function hello() { return "world"; }');
142
- expect(result.status).toBe('clean');
143
- expect(result.detections).toHaveLength(0);
144
- });
145
- it('returns clean for env var references (not values)', () => {
146
- const result = scanInputForCredentials('const key = process.env.API_KEY;');
147
- expect(result.status).toBe('clean');
148
- });
149
- it('returns clean for placeholder values', () => {
150
- const result = scanInputForCredentials('password = "xxx"');
151
- expect(result.status).toBe('clean'); // too short
152
- });
153
- it('returns clean for SSH host aliases without sensitive options', () => {
154
- const result = scanInputForCredentials('ssh deploy@staging');
155
- expect(result.status).toBe('clean');
156
- });
157
- it('returns clean for public Stripe keys', () => {
158
- const result = scanInputForCredentials('STRIPE_PUBLIC_KEY=pk_test_123456789012345678901234');
159
- expect(result.status).toBe('clean');
160
- expect(result.allowed_detections?.[0].reason_codes).toContain('public_or_test_key');
161
- });
162
- it('returns clean for fake provider samples in docs', () => {
163
- const result = scanInputForCredentials('// example: AKIAIOSFODNN7EXAMPLE');
164
- expect(result.status).toBe('clean');
165
- });
166
- it('returns clean for checksum lines', () => {
167
- const result = scanInputForCredentials('sha256:abcdefabcdefabcdefabcdefabcdefabcdef');
168
- expect(result.status).toBe('clean');
169
- });
170
- it('returns clean for env variable references', () => {
171
- const result = scanInputForCredentials('NPM_TOKEN=${NPM_TOKEN}');
172
- expect(result.status).toBe('clean');
173
- });
174
- });
175
- // ── Config Options ───────────────────────────────────────────────
176
- describe('scanInputForCredentials — Config', () => {
177
- it('respects enabled: false', () => {
178
- const result = scanInputForCredentials('AKIAIOSFODNN7EXAMPLE', { enabled: false });
179
- expect(result.status).toBe('clean');
180
- });
181
- it('returns warning instead of blocked when block_on_detection is false', () => {
182
- const result = scanInputForCredentials('AKIAZ9Y8X7W6V5U4T3Q2', { block_on_detection: false });
183
- expect(result.status).toBe('warning');
184
- expect(result.detections).toHaveLength(1);
185
- });
186
- it('respects custom min_secret_length', () => {
187
- const result = scanInputForCredentials('password = "short"', { min_secret_length: 20 });
188
- // "short" is only 5 chars, below default 8, so it would be skipped anyway
189
- expect(result.status).toBe('clean');
190
- });
191
- it('applies custom patterns', () => {
192
- const result = scanInputForCredentials('internal-token-XYZ123456', {
193
- custom_patterns: ['internal-token-[A-Z0-9]+'],
194
- });
195
- expect(result.status).toBe('warning');
196
- expect(result.detections[0].type).toBe('custom_pattern');
197
- });
198
- it('respects ignore patterns', () => {
199
- const result = scanInputForCredentials('AKIAIOSFODNN7EXAMPLE', {
200
- ignore_patterns: ['AKIAIOSFODNN7EXAMPLE'],
201
- });
202
- expect(result.status).toBe('clean');
203
- });
204
- });
205
- // ── Performance ──────────────────────────────────────────────────
206
- describe('scanInputForCredentials — Performance', () => {
207
- it('completes in under 50ms for typical input', () => {
208
- const input = 'A'.repeat(10000); // 10KB of text
209
- const result = scanInputForCredentials(input);
210
- expect(result.duration_ms).toBeLessThan(50);
211
- });
212
- it('tracks scanned_length correctly', () => {
213
- const input = 'test input here';
214
- const result = scanInputForCredentials(input);
215
- expect(result.scanned_length).toBe(input.length);
216
- });
217
- });
218
- // ── Deduplication ────────────────────────────────────────────────
219
- describe('scanInputForCredentials — Deduplication', () => {
220
- it('deduplicates overlapping detections and keeps higher severity', () => {
221
- // Input that might trigger both generic password_assignment and a more specific pattern
222
- const input = 'api_key = "sk-proj-Z9Y8X7W6V5U4T3S2R1Q0P9O8N7M6"';
223
- const result = scanInputForCredentials(input);
224
- // Should not have duplicate detections for the same match region
225
- const positions = result.detections.map(d => d.position?.start);
226
- const uniquePositions = new Set(positions);
227
- expect(uniquePositions.size).toBe(positions.length);
228
- });
229
- });
230
- // ── Redaction ────────────────────────────────────────────────────
231
- describe('scanInputForCredentials — Redaction', () => {
232
- it('redacts matched credentials', () => {
233
- const result = scanInputForCredentials('AKIAZ9Y8X7W6V5U4T3Q2');
234
- expect(result.detections[0].redacted).toContain('****');
235
- expect(result.detections[0].redacted).not.toBe('AKIAZ9Y8X7W6V5U4T3Q2');
236
- });
237
- });
238
- // ── Compliance ───────────────────────────────────────────────────
239
- describe('scanInputForCredentials — Compliance', () => {
240
- it('includes compliance tags for AWS keys', () => {
241
- const result = scanInputForCredentials('AKIAZ9Y8X7W6V5U4T3Q2');
242
- expect(result.detections[0].compliance).toContain('SOC2-CC6.1');
243
- expect(result.detections[0].compliance).toContain('HIPAA-164.312');
244
- expect(result.detections[0].compliance).toContain('PCI-DSS-3.4');
245
- });
246
- });
247
- // ── formatDLPAlert ───────────────────────────────────────────────
248
- describe('formatDLPAlert', () => {
249
- it('returns clean message for clean input', () => {
250
- const result = scanInputForCredentials('just normal code');
251
- const alert = formatDLPAlert(result);
252
- expect(alert).toContain('clean');
253
- });
254
- it('shows BLOCKED header when credentials found', () => {
255
- const result = scanInputForCredentials('AKIAZ9Y8X7W6V5U4T3Q2');
256
- const alert = formatDLPAlert(result);
257
- expect(alert).toContain('BLOCKED');
258
- expect(alert).toContain('likely live secret');
259
- });
260
- it('shows WARNING header when block_on_detection is false', () => {
261
- const result = scanInputForCredentials('AKIAZ9Y8X7W6V5U4T3Q2', { block_on_detection: false });
262
- const alert = formatDLPAlert(result);
263
- expect(alert).toContain('WARNING');
264
- });
265
- it('includes severity, redacted value, and recommendation', () => {
266
- const result = scanInputForCredentials('AKIAZ9Y8X7W6V5U4T3Q2');
267
- const alert = formatDLPAlert(result);
268
- expect(alert).toContain('CRITICAL');
269
- expect(alert).toContain('****');
270
- expect(alert).toContain('Confidence');
271
- expect(alert).toContain('Why');
272
- expect(alert).toContain('process.env');
273
- });
274
- });
275
- // ── createDLPAuditEntry ──────────────────────────────────────────
276
- describe('createDLPAuditEntry', () => {
277
- it('creates structured audit entry', () => {
278
- const result = scanInputForCredentials('AKIAZ9Y8X7W6V5U4T3Q2');
279
- const entry = createDLPAuditEntry(result, { agent: 'claude', userId: 'test-user' });
280
- expect(entry.type).toBe('dlp_event');
281
- expect(entry.agent).toBe('claude');
282
- expect(entry.userId).toBe('test-user');
283
- expect(entry.status).toBe('blocked');
284
- expect(entry.timestamp).toBeDefined();
285
- expect(Array.isArray(entry.detections)).toBe(true);
286
- });
287
- it('uses provided timestamp if given', () => {
288
- const result = scanInputForCredentials('just code');
289
- const ts = '2025-01-01T00:00:00.000Z';
290
- const entry = createDLPAuditEntry(result, { agent: 'cursor', timestamp: ts });
291
- expect(entry.timestamp).toBe(ts);
292
- });
293
- it('redacts credentials in audit log (no raw match)', () => {
294
- const result = scanInputForCredentials('AKIAZ9Y8X7W6V5U4T3Q2');
295
- const entry = createDLPAuditEntry(result, { agent: 'claude' });
296
- const detections = entry.detections;
297
- // Audit entry should have redacted field but NOT the raw match
298
- expect(detections[0].redacted).toBeDefined();
299
- expect(detections[0].match).toBeUndefined();
300
- expect(detections[0].confidence).toBeDefined();
301
- expect(detections[0].decision).toBe('block');
302
- });
303
- });
304
- describe('false positive regression', () => {
305
- it('allows engineering prompts about API key storage', () => {
306
- const result = scanInputForCredentials('Should Cursor Admin API keys be stored in ~/.rigour/settings.json under cursor.apiKey?');
307
- expect(result.status).toBe('clean');
308
- });
309
- it('allows credential-less localhost database URLs', () => {
310
- expect(scanInputForCredentials('redis://localhost:6379').status).toBe('clean');
311
- expect(scanInputForCredentials('mongodb://localhost:27017/test').status).toBe('clean');
312
- });
313
- it('allows placeholder env assignments', () => {
314
- expect(scanInputForCredentials('API_KEY=your-key-here').status).toBe('clean');
315
- expect(scanInputForCredentials('api_key = "replace-me"').status).toBe('clean');
316
- });
317
- it('allows bare SK-like token without Twilio context', () => {
318
- expect(scanInputForCredentials('SK-test-fixture-not-a-real-twilio-key').status).toBe('clean');
319
- });
320
- it('allows sk-proj examples in comments', () => {
321
- expect(scanInputForCredentials('// sk-proj-abcdefghijklmnopqrstuvwxyz').status).toBe('clean');
322
- });
323
- it('still blocks live production database URLs', () => {
324
- expect(scanInputForCredentials('postgresql://user:password@prod-server:5432/mydb').status).toBe('blocked');
325
- });
326
- it('still blocks live AWS keys in prose', () => {
327
- expect(scanInputForCredentials('Here is my key: AKIAZ9Y8X7W6V5U4T3Q2').status).toBe('blocked');
328
- });
329
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,27 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { execFileSync } from 'child_process';
3
- import { generateHookFiles, pushCommandFor, pushGateShell, stopCommandFor } from './templates.js';
4
- const CHECKER = 'npx @rigour-labs/cli@6.5.0 hooks check';
5
- describe('hook templates', () => {
6
- it('reviews before the agent stops when the checker is the CLI, like `rigour hooks init`', () => {
7
- const claude = JSON.parse(generateHookFiles('claude', CHECKER)[0].content);
8
- expect(claude.hooks.Stop[0].hooks[0]).toEqual({ type: 'command', command: 'npx @rigour-labs/cli@6.5.0 hooks stop --tool claude', timeout: 120 });
9
- const cursor = JSON.parse(generateHookFiles('cursor', CHECKER).find(f => f.path.endsWith('hooks.json')).content);
10
- expect(cursor.hooks.stop[0]).toEqual({ command: 'npx @rigour-labs/cli@6.5.0 hooks stop --tool cursor', loop_limit: 3 });
11
- });
12
- it("gates the agent's git push when the checker is the CLI", () => {
13
- const claude = JSON.parse(generateHookFiles('claude', CHECKER)[0].content);
14
- expect(claude.hooks.PreToolUse[0]).toEqual({ matcher: 'Bash', hooks: [{ type: 'command', command: expect.stringContaining('npx @rigour-labs/cli@6.5.0 hooks push --stdin'), timeout: 1800 }] });
15
- expect(pushCommandFor('node ./my-checker.js')).toBeUndefined();
16
- });
17
- it.skipIf(process.platform === 'win32')('starts the push gate only for a command that mentions git push', () => {
18
- const run = (command) => execFileSync('sh', ['-c', pushGateShell('cat > /dev/null; echo gated; exit 2').replace(/^sh -c /, '').slice(1, -1)], { input: JSON.stringify({ tool_input: { command } }), encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
19
- expect(run('npm test')).toBe('');
20
- expect(() => run('git push -u origin main')).toThrow(); // the gate's exit 2 reaches the agent
21
- });
22
- it('adds no stop hook for a checker that is not the CLI', () => {
23
- expect(stopCommandFor('node ./my-checker.js', 'claude')).toBeUndefined();
24
- const claude = JSON.parse(generateHookFiles('claude', 'node ./my-checker.js')[0].content);
25
- expect(claude.hooks.Stop).toBeUndefined();
26
- });
27
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,28 +0,0 @@
1
- import { spawnSync } from 'child_process';
2
- import fs from 'fs';
3
- import os from 'os';
4
- import path from 'path';
5
- import { describe, expect, it } from 'vitest';
6
- const PACKAGES = path.resolve(__dirname, '../../..');
7
- const scripts = fs.readdirSync(PACKAGES)
8
- .filter(name => name.startsWith('brain-') && !name.includes('win'))
9
- .map(name => path.join(PACKAGES, name, 'bin', 'rigour-brain'));
10
- describe('rigour-brain placeholder', () => {
11
- it.runIf(process.platform !== 'win32')('prints its message without running the command it names', () => {
12
- const bin = fs.mkdtempSync(path.join(os.tmpdir(), 'brain-'));
13
- const marker = path.join(bin, 'ran');
14
- fs.writeFileSync(path.join(bin, 'rigour'), `#!/bin/sh\necho "$@" > "${marker}"\n`, { mode: 0o755 });
15
- try {
16
- expect(scripts.length).toBeGreaterThan(0);
17
- for (const script of scripts) {
18
- const result = spawnSync('sh', [script, '--version'], { env: { PATH: `${bin}:/usr/bin:/bin` }, encoding: 'utf8' });
19
- expect(result.status).toBe(1);
20
- expect(result.stderr).toContain('rigour deep pull');
21
- expect(fs.existsSync(marker)).toBe(false);
22
- }
23
- }
24
- finally {
25
- fs.rmSync(bin, { recursive: true, force: true });
26
- }
27
- });
28
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,139 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from 'vitest';
2
- const openaiCreate = vi.fn();
3
- const anthropicCreate = vi.fn();
4
- vi.mock('openai', () => ({
5
- default: class {
6
- chat = { completions: { create: openaiCreate } };
7
- },
8
- }));
9
- vi.mock('@anthropic-ai/sdk', () => ({
10
- default: class {
11
- options;
12
- constructor(options) {
13
- this.options = options;
14
- }
15
- messages = { create: anthropicCreate };
16
- },
17
- }));
18
- const { CloudProvider } = await import('./cloud-provider.js');
19
- const { priceTokens } = await import('./pricing.js');
20
- beforeEach(() => {
21
- openaiCreate.mockReset();
22
- anthropicCreate.mockReset();
23
- });
24
- describe('CloudProvider', () => {
25
- it('names no model it cannot know: a provider without a default needs one', () => {
26
- expect(() => new CloudProvider('somecloud', 'k')).toThrow(/pass --model-name/);
27
- expect(() => new CloudProvider('somecloud', 'k', { modelName: 'x' })).not.toThrow();
28
- });
29
- it('sends Claude calls to a base URL when one is given', async () => {
30
- anthropicCreate.mockResolvedValue({ content: [{ type: 'text', text: 'ok' }], usage: { input_tokens: 1, output_tokens: 1 } });
31
- const provider = new CloudProvider('claude', 'k', { modelName: 'claude-sonnet-5-5', baseUrl: 'https://gateway.example/anthropic' });
32
- await provider.setup();
33
- expect(provider.client.options).toMatchObject({ baseURL: 'https://gateway.example/anthropic' });
34
- });
35
- it('sends OpenRouter calls to OpenRouter, asking it to report their cost', async () => {
36
- openaiCreate.mockResolvedValue({ choices: [{ message: { content: 'ok' } }], usage: { prompt_tokens: 1, completion_tokens: 1 } });
37
- const provider = new CloudProvider('openrouter', 'k', { modelName: 'anthropic/claude-sonnet-5.5' });
38
- await provider.setup();
39
- await provider.analyze('prompt');
40
- expect(openaiCreate.mock.calls[0][0]).toMatchObject({ usage: { include: true } });
41
- });
42
- it('passes the per-call timeout to the SDK request', async () => {
43
- anthropicCreate.mockResolvedValue({ content: [{ type: 'text', text: 'ok' }], usage: { input_tokens: 10, output_tokens: 2 } });
44
- const provider = new CloudProvider('claude', 'k', { modelName: 'claude-sonnet-5-5' });
45
- await provider.setup();
46
- await provider.analyze('p', { timeout: 30_000 });
47
- expect(anthropicCreate.mock.calls[0][1]).toEqual({ timeout: 30_000 });
48
- });
49
- it('prices tokens at list price when the provider reports no cost', async () => {
50
- anthropicCreate.mockResolvedValue({ content: [{ type: 'text', text: 'ok' }], usage: { input_tokens: 1_000_000, output_tokens: 100_000 } });
51
- const provider = new CloudProvider('claude', 'k', { modelName: 'claude-sonnet-5-5' });
52
- await provider.setup();
53
- await provider.analyze('p');
54
- expect(provider.usage()).toEqual({ inputTokens: 1_000_000, outputTokens: 100_000, costUsd: 3 });
55
- });
56
- it('asks OpenRouter for its own cost, and uses it', async () => {
57
- openaiCreate.mockResolvedValue({ choices: [{ message: { content: 'ok' } }], usage: { prompt_tokens: 500, completion_tokens: 50, cost: 0.0123 } });
58
- const provider = new CloudProvider('openrouter', 'k', { baseUrl: 'https://openrouter.ai/api/v1', modelName: 'anthropic/claude-sonnet-5.5' });
59
- await provider.setup();
60
- await provider.analyze('p');
61
- expect(openaiCreate.mock.calls[0][0].usage).toEqual({ include: true });
62
- expect(provider.usage()).toEqual({ inputTokens: 500, outputTokens: 50, costUsd: 0.0123 });
63
- });
64
- it('sends no OpenRouter-only field to other endpoints, and reports no cost for an unpriced model', async () => {
65
- openaiCreate.mockResolvedValue({ choices: [{ message: { content: 'ok' } }], usage: { prompt_tokens: 500, completion_tokens: 50 } });
66
- const provider = new CloudProvider('openai', 'k', { modelName: 'some-new-model' });
67
- await provider.setup();
68
- await provider.analyze('p');
69
- expect(openaiCreate.mock.calls[0][0]).not.toHaveProperty('usage');
70
- expect(provider.usage()).toEqual({ inputTokens: 500, outputTokens: 50 });
71
- });
72
- });
73
- describe('CloudProvider.chat', () => {
74
- const history = [
75
- { role: 'user', content: 'review' },
76
- { role: 'assistant', content: '', toolCalls: [{ id: 'a', name: 'read_file', arguments: { path: 'x.ts' } }, { id: 'b', name: 'grep', arguments: { pattern: 'y' } }] },
77
- { role: 'tool', toolCallId: 'a', content: 'x' },
78
- { role: 'tool', toolCallId: 'b', content: 'y' },
79
- ];
80
- const tools = [{ name: 'read_file', description: 'd', parameters: { type: 'object' } }];
81
- it('speaks Anthropic tool use: grouped tool results, tool_use blocks back', async () => {
82
- anthropicCreate.mockResolvedValue({ content: [{ type: 'text', text: 'checking' }, { type: 'tool_use', id: 'c', name: 'read_file', input: { path: 'z.ts' } }] });
83
- const provider = new CloudProvider('claude', 'k', { modelName: 'claude-sonnet-5-5' });
84
- await provider.setup();
85
- const reply = await provider.chat(history, tools);
86
- const sent = anthropicCreate.mock.calls[0][0];
87
- expect(sent.messages).toHaveLength(3);
88
- expect(sent.messages[2].content.map((b) => b.tool_use_id)).toEqual(['a', 'b']);
89
- expect(sent.tools[0]).toEqual({ name: 'read_file', description: 'd', input_schema: { type: 'object' } });
90
- expect(reply).toEqual({ text: 'checking', toolCalls: [{ id: 'c', name: 'read_file', arguments: { path: 'z.ts' } }] });
91
- expect(sent).not.toHaveProperty('tool_choice');
92
- });
93
- it('keeps tools defined but forbids calls when asked for a final answer', async () => {
94
- anthropicCreate.mockResolvedValue({ content: [{ type: 'text', text: '{}' }] });
95
- openaiCreate.mockResolvedValue({ choices: [{ message: { content: '{}' } }] });
96
- for (const name of ['claude', 'openai']) {
97
- const provider = new CloudProvider(name, 'k', { modelName: 'm' });
98
- await provider.setup();
99
- await provider.chat(history, tools, { toolChoice: 'none' });
100
- }
101
- expect(anthropicCreate.mock.calls[0][0]).toMatchObject({ tools: [expect.anything()], tool_choice: { type: 'none' } });
102
- expect(openaiCreate.mock.calls[0][0]).toMatchObject({ tools: [expect.anything()], tool_choice: 'none' });
103
- });
104
- it('speaks OpenAI function calling, and survives malformed arguments', async () => {
105
- openaiCreate.mockResolvedValue({ choices: [{ message: { content: null, tool_calls: [{ id: 'c', function: { name: 'grep', arguments: '{not json' } }] } }] });
106
- const provider = new CloudProvider('openai', 'k', { modelName: 'm' });
107
- await provider.setup();
108
- const reply = await provider.chat(history, tools);
109
- const sent = openaiCreate.mock.calls[0][0];
110
- expect(sent.messages[1].tool_calls[0].function).toEqual({ name: 'read_file', arguments: '{"path":"x.ts"}' });
111
- expect(sent.messages[2]).toEqual({ role: 'tool', tool_call_id: 'a', content: 'x' });
112
- expect(sent.tools[0].type).toBe('function');
113
- expect(reply).toEqual({ text: '', toolCalls: [{ id: 'c', name: 'grep', arguments: {} }] });
114
- });
115
- });
116
- describe('prompt caching', () => {
117
- it('caches the first prompt for Claude, directly and through OpenRouter, and nowhere else', async () => {
118
- anthropicCreate.mockResolvedValue({ content: [{ type: 'text', text: '{}' }] });
119
- openaiCreate.mockResolvedValue({ choices: [{ message: { content: '{}' } }] });
120
- const first = [{ role: 'user', content: 'big diff' }];
121
- const claude = new CloudProvider('claude', 'k', { modelName: 'claude-sonnet-5-5' });
122
- const viaOpenRouter = new CloudProvider('openrouter', 'k', { baseUrl: 'https://openrouter.ai/api/v1', modelName: 'anthropic/claude-sonnet-5.5' });
123
- const other = new CloudProvider('openrouter', 'k', { baseUrl: 'https://openrouter.ai/api/v1', modelName: 'openai/gpt-5' });
124
- for (const p of [claude, viaOpenRouter, other]) {
125
- await p.setup();
126
- await p.chat(first, []);
127
- }
128
- const cached = { role: 'user', content: [{ type: 'text', text: 'big diff', cache_control: { type: 'ephemeral' } }] };
129
- expect(anthropicCreate.mock.calls[0][0].messages[0]).toEqual(cached);
130
- expect(openaiCreate.mock.calls[0][0].messages[0]).toEqual(cached);
131
- expect(openaiCreate.mock.calls[1][0].messages[0]).toEqual({ role: 'user', content: 'big diff' });
132
- });
133
- });
134
- describe('priceTokens', () => {
135
- it('prices known models and refuses to guess for the rest', () => {
136
- expect(priceTokens('anthropic/claude-opus-5.5', 1_000_000, 1_000_000)).toBe(24);
137
- expect(priceTokens('mystery-7b', 1, 1)).toBeUndefined();
138
- });
139
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,41 +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 { ensureExecutableBinary, isExecutableBinary } from './executable.js';
6
- describe('executable helpers', () => {
7
- let testDir;
8
- beforeEach(() => {
9
- testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rigour-exec-test-'));
10
- });
11
- afterEach(() => {
12
- fs.rmSync(testDir, { recursive: true, force: true });
13
- });
14
- it('detects executable files', () => {
15
- const binaryPath = path.join(testDir, 'rigour-brain');
16
- fs.writeFileSync(binaryPath, '#!/usr/bin/env sh\necho ok\n', 'utf-8');
17
- fs.chmodSync(binaryPath, 0o755);
18
- expect(isExecutableBinary(binaryPath)).toBe(true);
19
- });
20
- it('repairs executable bit when missing', () => {
21
- const binaryPath = path.join(testDir, 'rigour-brain');
22
- fs.writeFileSync(binaryPath, '#!/usr/bin/env sh\necho ok\n', 'utf-8');
23
- fs.chmodSync(binaryPath, 0o644);
24
- const result = ensureExecutableBinary(binaryPath);
25
- if (process.platform === 'win32') {
26
- // Windows does not use POSIX executable bits.
27
- expect(result.ok).toBe(true);
28
- expect(result.fixed).toBe(false);
29
- expect(isExecutableBinary(binaryPath)).toBe(true);
30
- return;
31
- }
32
- // Some CI/filesystem setups can deny chmod transitions; in that case
33
- // the helper should fail gracefully without throwing.
34
- if (!result.ok) {
35
- expect(result.fixed).toBe(false);
36
- return;
37
- }
38
- expect(result.fixed).toBe(true);
39
- expect(isExecutableBinary(binaryPath)).toBe(true);
40
- });
41
- });
@@ -1 +0,0 @@
1
- export {};