@rigour-labs/core 6.8.0 → 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 (326) hide show
  1. package/dist/inference/cloud-provider.js +12 -2
  2. package/package.json +11 -8
  3. package/dist/brief/briefing.test.d.ts +0 -1
  4. package/dist/brief/briefing.test.js +0 -107
  5. package/dist/context/automatic-index-cache.test.d.ts +0 -1
  6. package/dist/context/automatic-index-cache.test.js +0 -30
  7. package/dist/context/automatic-index.test.d.ts +0 -1
  8. package/dist/context/automatic-index.test.js +0 -45
  9. package/dist/context/cache-engine.test.d.ts +0 -1
  10. package/dist/context/cache-engine.test.js +0 -99
  11. package/dist/context/dependency-graph.test.d.ts +0 -1
  12. package/dist/context/dependency-graph.test.js +0 -13
  13. package/dist/context/index-status.test.d.ts +0 -1
  14. package/dist/context/index-status.test.js +0 -26
  15. package/dist/context.test.d.ts +0 -1
  16. package/dist/context.test.js +0 -228
  17. package/dist/deep/agent-review.test.d.ts +0 -1
  18. package/dist/deep/agent-review.test.js +0 -53
  19. package/dist/deep/code-context.test.d.ts +0 -1
  20. package/dist/deep/code-context.test.js +0 -48
  21. package/dist/deep/code-pass.test.d.ts +0 -1
  22. package/dist/deep/code-pass.test.js +0 -112
  23. package/dist/deep/code-review-prompt.test.d.ts +0 -1
  24. package/dist/deep/code-review-prompt.test.js +0 -20
  25. package/dist/deep/code-verifier.test.d.ts +0 -1
  26. package/dist/deep/code-verifier.test.js +0 -54
  27. package/dist/deep/diff-tests/calls.test.d.ts +0 -1
  28. package/dist/deep/diff-tests/calls.test.js +0 -33
  29. package/dist/deep/diff-tests/run.test.d.ts +0 -1
  30. package/dist/deep/diff-tests/run.test.js +0 -58
  31. package/dist/deep/fact-extractor.test.d.ts +0 -1
  32. package/dist/deep/fact-extractor.test.js +0 -581
  33. package/dist/deep/parse-findings.test.d.ts +0 -1
  34. package/dist/deep/parse-findings.test.js +0 -25
  35. package/dist/deep/pr-review.test.d.ts +0 -1
  36. package/dist/deep/pr-review.test.js +0 -80
  37. package/dist/deep/prompts.test.d.ts +0 -1
  38. package/dist/deep/prompts.test.js +0 -235
  39. package/dist/deep/reference-pack.test.d.ts +0 -1
  40. package/dist/deep/reference-pack.test.js +0 -46
  41. package/dist/deep/related-changes.test.d.ts +0 -1
  42. package/dist/deep/related-changes.test.js +0 -33
  43. package/dist/deep/review-context-export.test.d.ts +0 -1
  44. package/dist/deep/review-context-export.test.js +0 -32
  45. package/dist/deep/review-tools.test.d.ts +0 -1
  46. package/dist/deep/review-tools.test.js +0 -39
  47. package/dist/deep/risk.test.d.ts +0 -1
  48. package/dist/deep/risk.test.js +0 -101
  49. package/dist/deep/verifier.test.d.ts +0 -1
  50. package/dist/deep/verifier.test.js +0 -635
  51. package/dist/discovery.test.d.ts +0 -1
  52. package/dist/discovery.test.js +0 -93
  53. package/dist/environment.test.d.ts +0 -1
  54. package/dist/environment.test.js +0 -94
  55. package/dist/firewall/firewall.test.d.ts +0 -1
  56. package/dist/firewall/firewall.test.js +0 -117
  57. package/dist/firewall/trust-boundaries.test.d.ts +0 -1
  58. package/dist/firewall/trust-boundaries.test.js +0 -64
  59. package/dist/firewall/trusted-control.test.d.ts +0 -1
  60. package/dist/firewall/trusted-control.test.js +0 -188
  61. package/dist/gates/agent-team.test.d.ts +0 -1
  62. package/dist/gates/agent-team.test.js +0 -113
  63. package/dist/gates/ast.test.d.ts +0 -1
  64. package/dist/gates/ast.test.js +0 -112
  65. package/dist/gates/checkpoint.test.d.ts +0 -1
  66. package/dist/gates/checkpoint.test.js +0 -105
  67. package/dist/gates/content.test.d.ts +0 -1
  68. package/dist/gates/content.test.js +0 -73
  69. package/dist/gates/coverage.test.d.ts +0 -1
  70. package/dist/gates/coverage.test.js +0 -53
  71. package/dist/gates/dedupe-failures.test.d.ts +0 -1
  72. package/dist/gates/dedupe-failures.test.js +0 -12
  73. package/dist/gates/deep-analysis.test.d.ts +0 -1
  74. package/dist/gates/deep-analysis.test.js +0 -86
  75. package/dist/gates/deep-intent.test.d.ts +0 -1
  76. package/dist/gates/deep-intent.test.js +0 -51
  77. package/dist/gates/deep-timeout.test.d.ts +0 -1
  78. package/dist/gates/deep-timeout.test.js +0 -10
  79. package/dist/gates/deprecated-apis.test.d.ts +0 -1
  80. package/dist/gates/deprecated-apis.test.js +0 -318
  81. package/dist/gates/deprecated-dependencies.test.d.ts +0 -1
  82. package/dist/gates/deprecated-dependencies.test.js +0 -55
  83. package/dist/gates/frontend-secret-exposure.test.d.ts +0 -1
  84. package/dist/gates/frontend-secret-exposure.test.js +0 -148
  85. package/dist/gates/hallucinated-imports/framework-modules-nuxt.test.d.ts +0 -1
  86. package/dist/gates/hallucinated-imports/framework-modules-nuxt.test.js +0 -27
  87. package/dist/gates/hallucinated-imports/js-resolver-types.test.d.ts +0 -1
  88. package/dist/gates/hallucinated-imports/js-resolver-types.test.js +0 -14
  89. package/dist/gates/hallucinated-imports-sveltekit.test.d.ts +0 -1
  90. package/dist/gates/hallucinated-imports-sveltekit.test.js +0 -132
  91. package/dist/gates/hallucinated-imports.test.d.ts +0 -1
  92. package/dist/gates/hallucinated-imports.test.js +0 -1206
  93. package/dist/gates/js-style-context.test.d.ts +0 -1
  94. package/dist/gates/js-style-context.test.js +0 -35
  95. package/dist/gates/logic-drift.test.d.ts +0 -1
  96. package/dist/gates/logic-drift.test.js +0 -52
  97. package/dist/gates/phantom-apis.test.d.ts +0 -1
  98. package/dist/gates/phantom-apis.test.js +0 -396
  99. package/dist/gates/promise-safety.test.d.ts +0 -1
  100. package/dist/gates/promise-safety.test.js +0 -34
  101. package/dist/gates/runner.test.d.ts +0 -1
  102. package/dist/gates/runner.test.js +0 -77
  103. package/dist/gates/scoped-gates.test.d.ts +0 -1
  104. package/dist/gates/scoped-gates.test.js +0 -52
  105. package/dist/gates/security-patterns-owasp.test.d.ts +0 -1
  106. package/dist/gates/security-patterns-owasp.test.js +0 -186
  107. package/dist/gates/security-patterns.test.d.ts +0 -1
  108. package/dist/gates/security-patterns.test.js +0 -194
  109. package/dist/gates/semantic-bugs.test.d.ts +0 -1
  110. package/dist/gates/semantic-bugs.test.js +0 -76
  111. package/dist/gates/side-effect-analysis.test.d.ts +0 -1
  112. package/dist/gates/side-effect-analysis.test.js +0 -162
  113. package/dist/gates/style-drift.test.d.ts +0 -1
  114. package/dist/gates/style-drift.test.js +0 -26
  115. package/dist/gates/test-quality.test.d.ts +0 -1
  116. package/dist/gates/test-quality.test.js +0 -325
  117. package/dist/gates/trusted-reviews.test.d.ts +0 -1
  118. package/dist/gates/trusted-reviews.test.js +0 -16
  119. package/dist/gates/unindexed-reads/queries.test.d.ts +0 -1
  120. package/dist/gates/unindexed-reads/queries.test.js +0 -54
  121. package/dist/gates/unindexed-reads/schema.test.d.ts +0 -1
  122. package/dist/gates/unindexed-reads/schema.test.js +0 -81
  123. package/dist/gates/unindexed-reads/unindexed-reads.test.d.ts +0 -1
  124. package/dist/gates/unindexed-reads/unindexed-reads.test.js +0 -90
  125. package/dist/hooks/checker.test.d.ts +0 -1
  126. package/dist/hooks/checker.test.js +0 -159
  127. package/dist/hooks/dlp-confidence.test.d.ts +0 -1
  128. package/dist/hooks/dlp-confidence.test.js +0 -51
  129. package/dist/hooks/dlp-feedback.test.d.ts +0 -1
  130. package/dist/hooks/dlp-feedback.test.js +0 -131
  131. package/dist/hooks/input-validator.test.d.ts +0 -1
  132. package/dist/hooks/input-validator.test.js +0 -329
  133. package/dist/hooks/templates.test.d.ts +0 -1
  134. package/dist/hooks/templates.test.js +0 -27
  135. package/dist/inference/brain-placeholder.test.d.ts +0 -1
  136. package/dist/inference/brain-placeholder.test.js +0 -28
  137. package/dist/inference/cloud-provider.test.d.ts +0 -1
  138. package/dist/inference/cloud-provider.test.js +0 -139
  139. package/dist/inference/executable.test.d.ts +0 -1
  140. package/dist/inference/executable.test.js +0 -41
  141. package/dist/inference/http-download.test.d.ts +0 -1
  142. package/dist/inference/http-download.test.js +0 -109
  143. package/dist/inference/llama-engine-checksum.test.d.ts +0 -1
  144. package/dist/inference/llama-engine-checksum.test.js +0 -27
  145. package/dist/inference/llama-engine.test.d.ts +0 -1
  146. package/dist/inference/llama-engine.test.js +0 -51
  147. package/dist/inference/llama-process.test.d.ts +0 -1
  148. package/dist/inference/llama-process.test.js +0 -61
  149. package/dist/inference/local-model.test.d.ts +0 -1
  150. package/dist/inference/local-model.test.js +0 -23
  151. package/dist/inference/model-download.test.d.ts +0 -1
  152. package/dist/inference/model-download.test.js +0 -125
  153. package/dist/inference/model-manager.test.d.ts +0 -1
  154. package/dist/inference/model-manager.test.js +0 -24
  155. package/dist/inference/types.test.d.ts +0 -1
  156. package/dist/inference/types.test.js +0 -19
  157. package/dist/memory/recall.test.d.ts +0 -1
  158. package/dist/memory/recall.test.js +0 -36
  159. package/dist/pattern-index/indexer.test.d.ts +0 -6
  160. package/dist/pattern-index/indexer.test.js +0 -197
  161. package/dist/pattern-index/matcher.test.d.ts +0 -6
  162. package/dist/pattern-index/matcher.test.js +0 -238
  163. package/dist/pattern-index/pattern-reuse.test.d.ts +0 -1
  164. package/dist/pattern-index/pattern-reuse.test.js +0 -76
  165. package/dist/pattern-index/semantic-runtime.test.d.ts +0 -1
  166. package/dist/pattern-index/semantic-runtime.test.js +0 -31
  167. package/dist/pattern-index/staleness.test.d.ts +0 -6
  168. package/dist/pattern-index/staleness.test.js +0 -211
  169. package/dist/review/agent-fixes.test.d.ts +0 -1
  170. package/dist/review/agent-fixes.test.js +0 -32
  171. package/dist/review/backtest-init.test.d.ts +0 -1
  172. package/dist/review/backtest-init.test.js +0 -136
  173. package/dist/review/backtest-judges.test.d.ts +0 -1
  174. package/dist/review/backtest-judges.test.js +0 -32
  175. package/dist/review/backtest-last.test.d.ts +0 -1
  176. package/dist/review/backtest-last.test.js +0 -109
  177. package/dist/review/backtest.test.d.ts +0 -1
  178. package/dist/review/backtest.test.js +0 -183
  179. package/dist/review/baseline.test.d.ts +0 -1
  180. package/dist/review/baseline.test.js +0 -22
  181. package/dist/review/branch-checks.test.d.ts +0 -1
  182. package/dist/review/branch-checks.test.js +0 -48
  183. package/dist/review/check-outcomes.test.d.ts +0 -1
  184. package/dist/review/check-outcomes.test.js +0 -56
  185. package/dist/review/code-patterns.test.d.ts +0 -1
  186. package/dist/review/code-patterns.test.js +0 -142
  187. package/dist/review/dead-code.test.d.ts +0 -1
  188. package/dist/review/dead-code.test.js +0 -154
  189. package/dist/review/deep-runs.test.d.ts +0 -1
  190. package/dist/review/deep-runs.test.js +0 -23
  191. package/dist/review/effectiveness.test.d.ts +0 -1
  192. package/dist/review/effectiveness.test.js +0 -42
  193. package/dist/review/fix-scope.test.d.ts +0 -1
  194. package/dist/review/fix-scope.test.js +0 -104
  195. package/dist/review/generated-files.test.d.ts +0 -1
  196. package/dist/review/generated-files.test.js +0 -25
  197. package/dist/review/migration-order.test.d.ts +0 -1
  198. package/dist/review/migration-order.test.js +0 -62
  199. package/dist/review/quiet.test.d.ts +0 -1
  200. package/dist/review/quiet.test.js +0 -38
  201. package/dist/review/receipt.test.d.ts +0 -1
  202. package/dist/review/receipt.test.js +0 -60
  203. package/dist/review/review-task.test.d.ts +0 -1
  204. package/dist/review/review-task.test.js +0 -89
  205. package/dist/review/review.test.d.ts +0 -1
  206. package/dist/review/review.test.js +0 -203
  207. package/dist/review/reviewer/adapters.test.d.ts +0 -1
  208. package/dist/review/reviewer/adapters.test.js +0 -100
  209. package/dist/review/reviewer/api-judge.test.d.ts +0 -1
  210. package/dist/review/reviewer/api-judge.test.js +0 -108
  211. package/dist/review/reviewer/background.test.d.ts +0 -1
  212. package/dist/review/reviewer/background.test.js +0 -118
  213. package/dist/review/reviewer/context.test.d.ts +0 -1
  214. package/dist/review/reviewer/context.test.js +0 -45
  215. package/dist/review/reviewer/exec.test.d.ts +0 -1
  216. package/dist/review/reviewer/exec.test.js +0 -59
  217. package/dist/review/reviewer/inputs.test.d.ts +0 -1
  218. package/dist/review/reviewer/inputs.test.js +0 -52
  219. package/dist/review/reviewer/panel.test.d.ts +0 -1
  220. package/dist/review/reviewer/panel.test.js +0 -110
  221. package/dist/review/reviewer/record.test.d.ts +0 -1
  222. package/dist/review/reviewer/record.test.js +0 -40
  223. package/dist/review/reviewer/rule-writer.test.d.ts +0 -1
  224. package/dist/review/reviewer/rule-writer.test.js +0 -52
  225. package/dist/review/reviewer/settings.test.d.ts +0 -1
  226. package/dist/review/reviewer/settings.test.js +0 -57
  227. package/dist/review/reviewer/usage.test.d.ts +0 -1
  228. package/dist/review/reviewer/usage.test.js +0 -14
  229. package/dist/review/reviewer.test.d.ts +0 -1
  230. package/dist/review/reviewer.test.js +0 -792
  231. package/dist/review/stories.test.d.ts +0 -1
  232. package/dist/review/stories.test.js +0 -58
  233. package/dist/review/toolchain.test.d.ts +0 -1
  234. package/dist/review/toolchain.test.js +0 -108
  235. package/dist/review/typed/redundancy.test.d.ts +0 -1
  236. package/dist/review/typed/redundancy.test.js +0 -315
  237. package/dist/review/typed/schema-nullability.test.d.ts +0 -1
  238. package/dist/review/typed/schema-nullability.test.js +0 -63
  239. package/dist/review-learning/human-edits.test.d.ts +0 -1
  240. package/dist/review-learning/human-edits.test.js +0 -47
  241. package/dist/review-learning/repo-rules.test.d.ts +0 -1
  242. package/dist/review-learning/repo-rules.test.js +0 -93
  243. package/dist/review-learning/review-learning.test.d.ts +0 -1
  244. package/dist/review-learning/review-learning.test.js +0 -249
  245. package/dist/safety.test.d.ts +0 -1
  246. package/dist/safety.test.js +0 -42
  247. package/dist/semantic/benchmark.test.d.ts +0 -1
  248. package/dist/semantic/benchmark.test.js +0 -22
  249. package/dist/semantic/intent/intent.test.d.ts +0 -1
  250. package/dist/semantic/intent/intent.test.js +0 -46
  251. package/dist/semantic/learn/learn.test.d.ts +0 -1
  252. package/dist/semantic/learn/learn.test.js +0 -67
  253. package/dist/semantic/origins.test.d.ts +0 -1
  254. package/dist/semantic/origins.test.js +0 -77
  255. package/dist/semantic/project-facts.test.d.ts +0 -1
  256. package/dist/semantic/project-facts.test.js +0 -45
  257. package/dist/semantic/sites/call-sites.test.d.ts +0 -1
  258. package/dist/semantic/sites/call-sites.test.js +0 -46
  259. package/dist/services/adaptive-thresholds.test.d.ts +0 -1
  260. package/dist/services/adaptive-thresholds.test.js +0 -53
  261. package/dist/services/agent-history.test.d.ts +0 -1
  262. package/dist/services/agent-history.test.js +0 -69
  263. package/dist/services/context-scope-summary.test.d.ts +0 -1
  264. package/dist/services/context-scope-summary.test.js +0 -17
  265. package/dist/services/context-telemetry-service.test.d.ts +0 -1
  266. package/dist/services/context-telemetry-service.test.js +0 -182
  267. package/dist/services/cursor-usage-sync.test.d.ts +0 -1
  268. package/dist/services/cursor-usage-sync.test.js +0 -173
  269. package/dist/services/engineering-knowledge-graph.test.d.ts +0 -1
  270. package/dist/services/engineering-knowledge-graph.test.js +0 -76
  271. package/dist/services/model-pricing.test.d.ts +0 -1
  272. package/dist/services/model-pricing.test.js +0 -44
  273. package/dist/services/observed-savings.test.d.ts +0 -1
  274. package/dist/services/observed-savings.test.js +0 -37
  275. package/dist/services/score-history.test.d.ts +0 -1
  276. package/dist/services/score-history.test.js +0 -61
  277. package/dist/smoke.test.d.ts +0 -1
  278. package/dist/smoke.test.js +0 -17
  279. package/dist/storage/cache-cleanup.test.d.ts +0 -1
  280. package/dist/storage/cache-cleanup.test.js +0 -54
  281. package/dist/storage/context-telemetry.test.d.ts +0 -1
  282. package/dist/storage/context-telemetry.test.js +0 -80
  283. package/dist/storage/db.test.d.ts +0 -1
  284. package/dist/storage/db.test.js +0 -46
  285. package/dist/storage/fix-lessons.test.d.ts +0 -1
  286. package/dist/storage/fix-lessons.test.js +0 -61
  287. package/dist/storage/lessons.test.d.ts +0 -1
  288. package/dist/storage/lessons.test.js +0 -81
  289. package/dist/storage/local-encryption.test.d.ts +0 -1
  290. package/dist/storage/local-encryption.test.js +0 -34
  291. package/dist/storage/local-memory.test.d.ts +0 -1
  292. package/dist/storage/local-memory.test.js +0 -55
  293. package/dist/storage/share-memory.test.d.ts +0 -1
  294. package/dist/storage/share-memory.test.js +0 -34
  295. package/dist/storage/team-diagnostics.test.d.ts +0 -1
  296. package/dist/storage/team-diagnostics.test.js +0 -51
  297. package/dist/storage/team-scope.test.d.ts +0 -1
  298. package/dist/storage/team-scope.test.js +0 -22
  299. package/dist/storage/team-store.test.d.ts +0 -1
  300. package/dist/storage/team-store.test.js +0 -11
  301. package/dist/storage/team-sync-scope.test.d.ts +0 -1
  302. package/dist/storage/team-sync-scope.test.js +0 -100
  303. package/dist/storage/team-vector-store.test.d.ts +0 -1
  304. package/dist/storage/team-vector-store.test.js +0 -56
  305. package/dist/storage/telemetry-scope.test.d.ts +0 -1
  306. package/dist/storage/telemetry-scope.test.js +0 -38
  307. package/dist/task/thread.test.d.ts +0 -1
  308. package/dist/task/thread.test.js +0 -133
  309. package/dist/telemetry/telemetry.test.d.ts +0 -1
  310. package/dist/telemetry/telemetry.test.js +0 -64
  311. package/dist/types/index.test.d.ts +0 -1
  312. package/dist/types/index.test.js +0 -33
  313. package/dist/utils/command-line.test.d.ts +0 -1
  314. package/dist/utils/command-line.test.js +0 -8
  315. package/dist/utils/diff-removed.test.d.ts +0 -1
  316. package/dist/utils/diff-removed.test.js +0 -29
  317. package/dist/utils/diff.test.d.ts +0 -1
  318. package/dist/utils/diff.test.js +0 -38
  319. package/dist/utils/glob-paths.test.d.ts +0 -1
  320. package/dist/utils/glob-paths.test.js +0 -40
  321. package/dist/utils/profile.test.d.ts +0 -1
  322. package/dist/utils/profile.test.js +0 -68
  323. package/dist/utils/scanner.test.d.ts +0 -1
  324. package/dist/utils/scanner.test.js +0 -48
  325. package/dist/utils/scope.test.d.ts +0 -1
  326. 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 {};