@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,45 +0,0 @@
1
- import fs from 'fs';
2
- import os from 'os';
3
- import path from 'path';
4
- import { afterEach, describe, expect, it } from 'vitest';
5
- import { ProjectFacts } from './project-facts.js';
6
- let root;
7
- afterEach(() => { if (root)
8
- fs.rmSync(root, { recursive: true, force: true }); root = undefined; });
9
- function write(files) {
10
- root = fs.mkdtempSync(path.join(os.tmpdir(), 'project-facts-'));
11
- for (const [name, text] of Object.entries(files)) {
12
- fs.mkdirSync(path.dirname(path.join(root, name)), { recursive: true });
13
- fs.writeFileSync(path.join(root, name), text);
14
- }
15
- return root;
16
- }
17
- describe('ProjectFacts', () => {
18
- it('reads the nearest package.json, so each monorepo package is judged by its own', () => {
19
- const dir = write({
20
- 'package.json': JSON.stringify({ name: 'root', devDependencies: { vite: '^5.0.0' } }),
21
- 'packages/ui/package.json': JSON.stringify({ name: '@x/ui', peerDependencies: { 'solid-js': '^1.8.0' } }),
22
- 'packages/ui/src/a.tsx': '',
23
- });
24
- const facts = new ProjectFacts(dir);
25
- expect(facts.declares('packages/ui/src/a.tsx', 'solid-js')).toBe(true);
26
- expect(facts.declares('packages/ui/src/a.tsx', 'vite')).toBe(false);
27
- expect(facts.majorVersion('packages/ui/src/a.tsx', 'solid-js')).toBe(1);
28
- expect(facts.packageName('packages/ui/src/a.tsx')).toBe('@x/ui');
29
- expect(facts.packageDir('packages/ui/src/a.tsx')).toBe(path.join(dir, 'packages/ui'));
30
- });
31
- it('never reads a manifest above cwd', () => {
32
- const dir = write({ 'package.json': JSON.stringify({ dependencies: { react: '^19.0.0' } }), 'app/src/a.tsx': '' });
33
- const facts = new ProjectFacts(path.join(dir, 'app'));
34
- expect(facts.dependencies('src/a.tsx').size).toBe(0);
35
- expect(facts.packageDir('src/a.tsx')).toBeUndefined();
36
- });
37
- it('resolves compiler options through tsconfig extends', () => {
38
- const dir = write({
39
- 'tsconfig.base.json': JSON.stringify({ compilerOptions: { stripInternal: true } }),
40
- 'pkg/tsconfig.json': JSON.stringify({ extends: '../tsconfig.base.json' }),
41
- 'pkg/src/a.ts': '',
42
- });
43
- expect(new ProjectFacts(dir).compilerOptions('pkg/src/a.ts').stripInternal).toBe(true);
44
- });
45
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,46 +0,0 @@
1
- import fs from 'fs';
2
- import os from 'os';
3
- import path from 'path';
4
- import { afterEach, describe, expect, it } from 'vitest';
5
- import { exportCallSites } from './export.js';
6
- let dir;
7
- afterEach(() => { if (dir)
8
- fs.rmSync(dir, { recursive: true, force: true }); dir = undefined; });
9
- function sites(code) {
10
- dir = fs.mkdtempSync(path.join(os.tmpdir(), 'call-sites-'));
11
- fs.writeFileSync(path.join(dir, 'main.ts'), code);
12
- return exportCallSites(dir, ['main.ts']).map(s => [s.function, s.callee, s.ordinal, s.handledBy]);
13
- }
14
- const DECLS = 'declare function a(): Promise<number>;\ndeclare function b(): Promise<number>;\n';
15
- describe('findCallSites', () => {
16
- it('reports awaited calls and says how each handles failure', () => {
17
- expect(sites(`${DECLS}
18
- export async function load() {
19
- const x = await a();
20
- try { await b(); } catch { return 0; }
21
- const y = await a().catch(() => 0);
22
- return x + y;
23
- }`)).toEqual([
24
- ['load', 'a', 0, null],
25
- ['load', 'b', 0, 'try'],
26
- ['load', 'a', 1, 'call'],
27
- ]);
28
- });
29
- it('treats each element of Promise.all as a site, and allSettled as handled', () => {
30
- expect(sites(`${DECLS}
31
- export async function both() {
32
- const [x, y] = await Promise.all([a(), b()]);
33
- const settled = await Promise.allSettled([a()]);
34
- return [x, y, settled];
35
- }`)).toEqual([
36
- ['both', 'a', 0, null],
37
- ['both', 'b', 0, null],
38
- ['both', 'a', 1, 'all-settled'],
39
- ]);
40
- });
41
- it('counts a callee that catches and returns as handled', () => {
42
- expect(sites(`${DECLS}
43
- async function safeA() { try { return await a(); } catch { return 0; } }
44
- export async function use() { return await safeA(); }`)).toContainEqual(['use', 'safeA', 0, 'call']);
45
- });
46
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,53 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { recordGateRun, getQualityTrend, clearAdaptiveHistory, } from './adaptive-thresholds.js';
3
- import * as fs from 'fs';
4
- import * as path from 'path';
5
- import * as os from 'os';
6
- describe('AdaptiveThresholds', () => {
7
- let testDir;
8
- beforeEach(() => {
9
- testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'adaptive-test-'));
10
- });
11
- afterEach(() => {
12
- clearAdaptiveHistory(testDir);
13
- fs.rmSync(testDir, { recursive: true, force: true });
14
- });
15
- describe('historical tracking', () => {
16
- it('should record gate runs', () => {
17
- recordGateRun(testDir, 5, 2, 10);
18
- recordGateRun(testDir, 6, 1, 5);
19
- const historyPath = path.join(testDir, '.rigour', 'adaptive-history.json');
20
- expect(fs.existsSync(historyPath)).toBe(true);
21
- const history = JSON.parse(fs.readFileSync(historyPath, 'utf-8'));
22
- expect(history.runs).toHaveLength(2);
23
- });
24
- it('should return stable trend for new projects', () => {
25
- const trend = getQualityTrend(testDir);
26
- expect(trend).toBe('stable');
27
- });
28
- it('should detect improving trend', () => {
29
- // Baseline: 15 runs with high failures (with variance for valid std dev)
30
- for (let i = 0; i < 15; i++) {
31
- recordGateRun(testDir, 3, 5, 15 + (i % 5) * 3); // 15,18,21,24,27 repeating
32
- }
33
- // Recent: 5 runs with very low failures (clear improvement)
34
- for (let i = 0; i < 5; i++) {
35
- recordGateRun(testDir, 7, 0, 1);
36
- }
37
- const trend = getQualityTrend(testDir);
38
- expect(trend).toBe('improving');
39
- });
40
- it('should detect degrading trend', () => {
41
- // Baseline: 15 runs with low failures (with variance for valid std dev)
42
- for (let i = 0; i < 15; i++) {
43
- recordGateRun(testDir, 7, 1, 1 + (i % 3)); // 1,2,3 repeating
44
- }
45
- // Recent: 5 runs with high failures (clear degradation)
46
- for (let i = 0; i < 5; i++) {
47
- recordGateRun(testDir, 3, 5, 25);
48
- }
49
- const trend = getQualityTrend(testDir);
50
- expect(trend).toBe('degrading');
51
- });
52
- });
53
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,69 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { buildAgentRuns, normalizeAgentEvent, normalizeAgentEvents } from './agent-history.js';
3
- describe('agent history', () => {
4
- it('normalizes incomplete historical records without dropping them', () => {
5
- const event = normalizeAgentEvent({ type: 'tool_error', arguments: { files: ['src/a.ts'] } });
6
- expect(event.agentId).toBe('unknown-agent');
7
- expect(event.outcome).toBe('error');
8
- expect(event.files).toEqual(['src/a.ts']);
9
- });
10
- it('groups events into stable runs and retains outcomes', () => {
11
- const events = [
12
- normalizeAgentEvent({ id: '1', sessionId: 's1', agentId: 'a1', timestamp: '2026-01-01T00:00:00Z', tool: 'read' }),
13
- normalizeAgentEvent({ id: '2', sessionId: 's1', agentId: 'a1', timestamp: '2026-01-01T00:01:00Z', type: 'completed', files: ['src/a.ts'] }),
14
- ];
15
- expect(buildAgentRuns(events)).toMatchObject([{ id: 's1', eventCount: 2, status: 'success', files: ['src/a.ts'] }]);
16
- });
17
- it('normalizes guidance safely and builds an honest impact receipt', () => {
18
- const events = [normalizeAgentEvent({
19
- id: 'response-1', requestId: 'request-1', agentId: 'agent-1', timestamp: '2026-01-01T00:00:00Z',
20
- type: 'tool_response', status: 'success',
21
- guidance: {
22
- kind: 'context-scope',
23
- recommendation: 'Read only the two evidence-backed files.',
24
- patternRefs: [{ id: 'p1', label: 'withTransaction' }, { bad: true }],
25
- lessonRefs: [{ id: 'l1', label: 'Bound retries' }],
26
- selectedFiles: ['src/a.ts', 42],
27
- excludedFileCount: 18,
28
- },
29
- telemetry: {
30
- candidateTokens: 2400,
31
- returnedTokens: 400,
32
- candidateFiles: 20,
33
- returnedFiles: 2,
34
- cacheStatus: 'exact-hit',
35
- classification: 'measured estimate',
36
- },
37
- })];
38
- expect(events[0].guidance?.selectedFiles).toEqual(['src/a.ts']);
39
- expect(buildAgentRuns(events)[0].impact).toMatchObject({
40
- guidanceCount: 1,
41
- knowledgeItems: 2,
42
- avoidedTokens: 2000,
43
- excludedFiles: 18,
44
- cacheHits: 1,
45
- });
46
- });
47
- it('ignores malformed guidance rather than breaking legacy history', () => {
48
- const event = normalizeAgentEvent({ guidance: { kind: 'context-scope', recommendation: 42 }, telemetry: 'bad' });
49
- expect(event.guidance).toBeUndefined();
50
- expect(event.telemetry).toBeUndefined();
51
- });
52
- });
53
- describe('normalizeAgentEvents', () => {
54
- it('carries request identity into the matching response', () => {
55
- const events = normalizeAgentEvents([
56
- {
57
- type: 'tool_call', requestId: 'request-1', tool: 'rigour_checkpoint',
58
- timestamp: '2026-09-10T00:00:00Z',
59
- arguments: { agentId: 'agent-a', taskId: 'task-a', filesChanged: ['src/a.ts'] },
60
- },
61
- {
62
- type: 'tool_response', requestId: 'request-1', tool: 'rigour_checkpoint', status: 'success',
63
- timestamp: '2026-09-10T00:00:01Z',
64
- },
65
- ]);
66
- expect(events[1]).toMatchObject({ agentId: 'agent-a', taskId: 'task-a', files: ['src/a.ts'] });
67
- expect(buildAgentRuns(events)).toHaveLength(1);
68
- });
69
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,17 +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 { getContextScopeSummary } from './context-telemetry-service.js';
6
- let root;
7
- beforeEach(() => { root = fs.mkdtempSync(path.join(os.tmpdir(), 'scope-summary-')); });
8
- afterEach(() => fs.rmSync(root, { recursive: true, force: true }));
9
- describe('getContextScopeSummary', () => {
10
- it('counts rule files and skips a rules directory such as .clinerules/', async () => {
11
- fs.mkdirSync(path.join(root, '.clinerules'));
12
- fs.writeFileSync(path.join(root, '.clinerules', 'rigour.md'), 'rules');
13
- fs.writeFileSync(path.join(root, 'CLAUDE.md'), 'Use small PRs.');
14
- const summary = await getContextScopeSummary(root);
15
- expect(summary.alwaysOnRuleTokens).toBeGreaterThan(0);
16
- });
17
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,182 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
- import path from 'path';
3
- import os from 'os';
4
- import fs from 'fs-extra';
5
- vi.mock('../storage/db.js', () => ({
6
- isSQLiteAvailable: () => false,
7
- openDatabase: async () => null,
8
- DB_PATH: '/tmp/rigour-test.db',
9
- RIGOUR_DIR: '/tmp/.rigour',
10
- }));
11
- import { getTaskContextStats, getTaskCostStats, getCacheStats, importCursorUsageCsv, importCursorUsageJson, getContextScopeSummary, getCheckpointSummary, estimateTokenCount, } from './context-telemetry-service.js';
12
- import { recordContextEvent, recordCheckpointMetric } from '../storage/context-telemetry.js';
13
- describe('context-telemetry-service', () => {
14
- const testCwd = path.join(os.tmpdir(), `rigour-ctx-service-${Date.now()}-${Math.random().toString(36).slice(2)}`);
15
- const taskId = `task-${Date.now()}`;
16
- beforeEach(async () => {
17
- await fs.ensureDir(path.join(testCwd, '.rigour'));
18
- });
19
- afterEach(async () => {
20
- await fs.remove(testCwd);
21
- });
22
- it('returns zeroed stats when no telemetry exists for task', async () => {
23
- const stats = await getTaskContextStats(taskId, testCwd);
24
- expect(stats.retrievals).toBe(0);
25
- expect(stats.candidateTokens).toBe(0);
26
- expect(stats.potentialAvoidedTokens).toBe(0);
27
- expect(stats.isEstimated).toBe(true);
28
- });
29
- it('calculates avoided context from recorded events', async () => {
30
- await recordContextEvent({
31
- taskId,
32
- toolName: 'rigour_check_pattern',
33
- cacheStatus: 'exact-hit',
34
- candidateTokens: 10000,
35
- returnedTokens: 2000,
36
- deduplicatedTokens: 3000,
37
- }, testCwd);
38
- const stats = await getTaskContextStats(taskId, testCwd);
39
- expect(stats.retrievals).toBe(1);
40
- expect(stats.potentialAvoidedTokens).toBe(8000);
41
- expect(stats.repeatedReadsPrevented).toBeGreaterThan(0);
42
- expect(stats.deduplicatedTokens).toBe(3000);
43
- expect(stats.isEstimated).toBe(false);
44
- });
45
- it('aggregates cache performance from events', async () => {
46
- const cacheTaskId = `${taskId}-cache`;
47
- await recordContextEvent({
48
- taskId: cacheTaskId,
49
- toolName: 'rigour_recall',
50
- cacheStatus: 'exact-hit',
51
- candidateTokens: 1000,
52
- returnedTokens: 200,
53
- }, testCwd);
54
- await recordContextEvent({
55
- taskId: cacheTaskId,
56
- toolName: 'rigour_context_explain',
57
- cacheStatus: 'miss',
58
- candidateTokens: 500,
59
- returnedTokens: 500,
60
- }, testCwd);
61
- const events = await import('../storage/context-telemetry.js').then(m => m.getContextEvents(cacheTaskId, testCwd));
62
- const cache = await getCacheStats(testCwd);
63
- const taskHits = events.filter(e => e.cacheStatus === 'exact-hit').length;
64
- const taskMisses = events.filter(e => e.cacheStatus === 'miss').length;
65
- expect(taskHits).toBe(1);
66
- expect(taskMisses).toBe(1);
67
- expect(cache.hitRate).toBe(0.5);
68
- expect(cache.isEstimated).toBe(false);
69
- });
70
- it('imports CSV usage and marks cost stats as observed', async () => {
71
- const csv = `Date,Model,Input Tokens,Output Tokens,Cost
72
- 2026-08-05,claude-3-5-sonnet,510000,62000,4.83`;
73
- const imported = await importCursorUsageCsv(csv, testCwd);
74
- expect(imported).toBe(1);
75
- const cost = await getTaskCostStats('cursor-imported', testCwd);
76
- expect(cost.actual.inputTokens).toBe(510000);
77
- expect(cost.actual.costUsd).toBe(4.83);
78
- expect(cost.actual.isEstimated).toBe(false);
79
- expect(cost.actual.classification).toBe('observed');
80
- });
81
- it('deduplicates CSV rows on re-import', async () => {
82
- const csv = `Date,Model,Input Tokens,Output Tokens,Cost
83
- 2026-08-05,claude-3-5-sonnet,1000,100,1.00`;
84
- expect(await importCursorUsageCsv(csv, testCwd)).toBe(1);
85
- expect(await importCursorUsageCsv(csv, testCwd)).toBe(0);
86
- });
87
- it('imports JSON usage records', async () => {
88
- const jsonTaskId = `${taskId}-json-${Math.random().toString(36).slice(2)}`;
89
- const imported = await importCursorUsageJson([{
90
- taskId: jsonTaskId,
91
- inputTokens: 2500,
92
- outputTokens: 300,
93
- observedCostUsd: 0.42,
94
- model: 'gpt-4o',
95
- }], testCwd);
96
- expect(imported).toBe(1);
97
- const cost = await getTaskCostStats(jsonTaskId, testCwd);
98
- expect(cost.actual.inputTokens).toBe(2500);
99
- });
100
- it('imports Admin API JSON events with chargedCents and dedupes against sync IDs', async () => {
101
- const adminEvent = {
102
- timestamp: '1750979225854',
103
- conversationId: 'conv-dedupe',
104
- model: 'composer-2.5-standard',
105
- chargedCents: 42.5,
106
- tokenUsage: { inputTokens: 100, outputTokens: 50 },
107
- };
108
- expect(await importCursorUsageJson([adminEvent], testCwd)).toBe(1);
109
- expect(await importCursorUsageJson([adminEvent], testCwd)).toBe(0);
110
- const cost = await getTaskCostStats('conv-dedupe', testCwd);
111
- expect(cost.actual.costUsd).toBeCloseTo(0.425, 3);
112
- expect(cost.actual.isEstimated).toBe(false);
113
- });
114
- it('summarizes agent scope overlap from session file', async () => {
115
- await fs.writeJson(path.join(testCwd, '.rigour', 'agent-session.json'), {
116
- agents: [
117
- { agentId: 'agent-a', taskScope: ['src/api/**'] },
118
- { agentId: 'agent-b', taskScope: ['src/api/**', 'tests/**'] },
119
- ],
120
- });
121
- const scope = await getContextScopeSummary(testCwd);
122
- expect(scope.activeAgentCount).toBe(2);
123
- expect(scope.overlappingScopesResolved).toBeGreaterThan(0);
124
- });
125
- it('estimates governance rule token overhead from AGENTS.md', async () => {
126
- await fs.writeFile(path.join(testCwd, 'AGENTS.md'), '# Agent rules\n'.repeat(40));
127
- const scope = await getContextScopeSummary(testCwd);
128
- expect(scope.alwaysOnRuleTokens).toBe(estimateTokenCount('# Agent rules\n'.repeat(40)));
129
- });
130
- it('aggregates checkpoint compression metrics', async () => {
131
- const cpTaskId = `${taskId}-cp`;
132
- await recordCheckpointMetric({
133
- checkpointId: `checkpoint:${cpTaskId}:a1:phase`,
134
- taskId: cpTaskId,
135
- agentId: 'a1',
136
- rawStateTokens: 10000,
137
- checkpointTokens: 500,
138
- replayTokensAvoided: 9500,
139
- }, testCwd);
140
- const summary = await getCheckpointSummary(cpTaskId, testCwd);
141
- expect(summary.checkpointCount).toBe(1);
142
- expect(summary.rawStateTokens).toBe(10000);
143
- expect(summary.compressionRatio).toBe(20);
144
- expect(summary.replayTokensAvoided).toBe(9500);
145
- });
146
- it('marks observed cost from usages and estimates avoided cost by model', async () => {
147
- const jsonTaskId = `${taskId}-composer-${Math.random().toString(36).slice(2)}`;
148
- await importCursorUsageJson([{
149
- id: `json-composer-${jsonTaskId}`,
150
- taskId: jsonTaskId,
151
- inputTokens: 2_000_000,
152
- outputTokens: 0,
153
- observedCostUsd: 1.25,
154
- model: 'composer-2.5-standard',
155
- source: 'cursor-admin-api',
156
- }], testCwd);
157
- await recordContextEvent({
158
- taskId: jsonTaskId,
159
- toolName: 'rigour_context_scope',
160
- cacheStatus: 'miss',
161
- candidateTokens: 3_000_000,
162
- returnedTokens: 1_000_000,
163
- }, testCwd);
164
- const cost = await getTaskCostStats(jsonTaskId, testCwd);
165
- expect(cost.actual.isEstimated).toBe(false);
166
- expect(cost.actual.costUsd).toBe(1.25);
167
- expect(cost.estimated.isEstimated).toBe(true);
168
- expect(cost.estimated.inputPricePerMillionUsd).toBe(0.5);
169
- expect(cost.estimated.estimatedCostAvoidedUsd).toBeGreaterThan(0);
170
- });
171
- it('does not combine retrieval and checkpoint counterfactuals', async () => {
172
- const id = `${taskId}-separate-${Math.random().toString(36).slice(2)}`;
173
- await recordContextEvent({ taskId: id, toolName: 'rigour_context_scope', cacheStatus: 'miss', candidateTokens: 5000, returnedTokens: 1000 }, testCwd);
174
- await recordCheckpointMetric({ checkpointId: `${id}-cp`, taskId: id, agentId: 'a1', rawStateTokens: 9000, checkpointTokens: 1000, replayTokensAvoided: 8000 }, testCwd);
175
- const cost = await getTaskCostStats(id, testCwd);
176
- expect(cost.estimated.retrievalAvoidedTokens).toBe(4000);
177
- expect(cost.estimated.checkpointReplayAvoidedTokens).toBe(8000);
178
- expect(cost.estimated.potentialContextAvoided).toBe(4000);
179
- expect(cost.estimated.categoriesAreAdditive).toBe(false);
180
- expect(cost.estimated.retrievalAvoidedCostRangeUsd?.max).toBeGreaterThan(cost.estimated.retrievalAvoidedCostRangeUsd?.min ?? 0);
181
- });
182
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,173 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
- import path from 'path';
3
- import os from 'os';
4
- import fs from 'fs-extra';
5
- vi.mock('../storage/db.js', () => ({
6
- isSQLiteAvailable: () => false,
7
- openDatabase: async () => null,
8
- DB_PATH: '/tmp/rigour-test.db',
9
- RIGOUR_DIR: '/tmp/.rigour',
10
- }));
11
- import { fetchCursorUsageFromAdminApi, syncCursorUsageFromAdminApi, getTaskCostStats, } from './context-telemetry-service.js';
12
- import { getModelUsages } from '../storage/context-telemetry.js';
13
- import { centsToUsd, normalizeCursorUsageEvent } from './cursor-usage-normalizer.js';
14
- import { CURSOR_FILTERED_USAGE_URL } from './cursor-usage-client.js';
15
- function buildUsageEvent(overrides = {}) {
16
- return {
17
- timestamp: '1750979225854',
18
- userEmail: 'dev@example.com',
19
- conversationId: 'conv-1',
20
- model: 'composer-2.5-standard',
21
- isTokenBasedCall: true,
22
- tokenUsage: {
23
- inputTokens: 100,
24
- outputTokens: 50,
25
- cacheWriteTokens: 10,
26
- cacheReadTokens: 20,
27
- totalCents: 12.5,
28
- },
29
- chargedCents: 15.25,
30
- ...overrides,
31
- };
32
- }
33
- describe('cursor admin usage sync', () => {
34
- const testCwd = path.join(os.tmpdir(), `rigour-cursor-sync-${Date.now()}-${Math.random().toString(36).slice(2)}`);
35
- const apiKey = 'test-cursor-admin-key';
36
- beforeEach(async () => {
37
- await fs.ensureDir(path.join(testCwd, '.rigour'));
38
- });
39
- afterEach(async () => {
40
- await fs.remove(testCwd);
41
- vi.restoreAllMocks();
42
- });
43
- it('imports a single-page response with basic auth header', async () => {
44
- const fetchImpl = vi.fn(async (_url, init) => ({
45
- ok: true,
46
- status: 200,
47
- json: async () => ({
48
- usageEvents: [buildUsageEvent()],
49
- pagination: { hasNextPage: false, currentPage: 1 },
50
- }),
51
- }));
52
- const imported = await fetchCursorUsageFromAdminApi({
53
- cwd: testCwd,
54
- apiKey,
55
- fetchImpl,
56
- });
57
- expect(imported).toBe(1);
58
- expect(fetchImpl).toHaveBeenCalledTimes(1);
59
- const [, init] = fetchImpl.mock.calls[0];
60
- expect(init.method).toBe('POST');
61
- expect(init.headers.Authorization).toBe(`Basic ${Buffer.from(`${apiKey}:`, 'utf8').toString('base64')}`);
62
- expect(init.headers['Content-Type']).toBe('application/json');
63
- const body = JSON.parse(init.body);
64
- expect(body.pageSize).toBeGreaterThan(0);
65
- expect(body.startDate).toBeLessThan(body.endDate);
66
- });
67
- it('paginates until hasNextPage is false', async () => {
68
- let page = 0;
69
- const fetchImpl = vi.fn(async () => {
70
- page += 1;
71
- return {
72
- ok: true,
73
- status: 200,
74
- json: async () => ({
75
- usageEvents: [buildUsageEvent({ conversationId: `conv-${page}` })],
76
- pagination: { hasNextPage: page < 2, currentPage: page },
77
- }),
78
- };
79
- });
80
- const result = await syncCursorUsageFromAdminApi({ cwd: testCwd, apiKey, fetchImpl });
81
- expect(result.importedCount).toBe(2);
82
- expect(result.totalEvents).toBe(2);
83
- expect(fetchImpl).toHaveBeenCalledTimes(2);
84
- });
85
- it('converts cents to USD and stores token fields', async () => {
86
- const normalized = normalizeCursorUsageEvent(buildUsageEvent());
87
- expect(normalized?.usage.observedCostUsd).toBe(centsToUsd(15.25));
88
- expect(normalized?.usage.inputTokens).toBe(100);
89
- expect(normalized?.usage.outputTokens).toBe(50);
90
- expect(normalized?.usage.cachedInputTokens).toBe(20);
91
- expect(normalized?.usage.cacheWriteTokens).toBe(10);
92
- expect(normalized?.usage.source).toBe('cursor-admin-api');
93
- });
94
- it('deduplicates repeated syncs', async () => {
95
- const fetchImpl = vi.fn(async () => ({
96
- ok: true,
97
- status: 200,
98
- json: async () => ({
99
- usageEvents: [buildUsageEvent()],
100
- pagination: { hasNextPage: false },
101
- }),
102
- }));
103
- const first = await fetchCursorUsageFromAdminApi({ cwd: testCwd, apiKey, fetchImpl });
104
- const second = await fetchCursorUsageFromAdminApi({ cwd: testCwd, apiKey, fetchImpl });
105
- expect(first).toBe(1);
106
- expect(second).toBe(0);
107
- const usages = await getModelUsages(undefined, testCwd);
108
- expect(usages.filter(u => u.source === 'cursor-admin-api')).toHaveLength(1);
109
- });
110
- it('surfaces actionable non-2xx errors without exposing the API key', async () => {
111
- const fetchImpl = vi.fn(async () => ({
112
- ok: false,
113
- status: 401,
114
- text: async () => `Unauthorized for key ${apiKey}`,
115
- }));
116
- await expect(fetchCursorUsageFromAdminApi({ cwd: testCwd, apiKey, fetchImpl })).rejects.toThrow(/401/);
117
- await expect(fetchCursorUsageFromAdminApi({ cwd: testCwd, apiKey, fetchImpl })).rejects.not.toThrow(apiKey);
118
- });
119
- it('skips malformed events and handles partial fields', async () => {
120
- const fetchImpl = vi.fn(async () => ({
121
- ok: true,
122
- status: 200,
123
- json: async () => ({
124
- usageEvents: [
125
- null,
126
- { model: 'composer-2.5-fast', chargedCents: 5 },
127
- buildUsageEvent(),
128
- ],
129
- pagination: { hasNextPage: false },
130
- }),
131
- }));
132
- const imported = await fetchCursorUsageFromAdminApi({ cwd: testCwd, apiKey, fetchImpl });
133
- expect(imported).toBe(2);
134
- });
135
- it('marks observed cost stats from imported admin API events', async () => {
136
- const fetchImpl = vi.fn(async () => ({
137
- ok: true,
138
- status: 200,
139
- json: async () => ({
140
- usageEvents: [buildUsageEvent({ conversationId: 'cost-task' })],
141
- pagination: { hasNextPage: false },
142
- }),
143
- }));
144
- await fetchCursorUsageFromAdminApi({ cwd: testCwd, apiKey, fetchImpl });
145
- const cost = await getTaskCostStats('cost-task', testCwd);
146
- expect(cost.actual.isEstimated).toBe(false);
147
- expect(cost.actual.costUsd).toBeGreaterThan(0);
148
- expect(cost.actual.pricingBasis).toBe('observed-cursor-charged-cents');
149
- });
150
- it('targets the filtered usage events endpoint', () => {
151
- expect(CURSOR_FILTERED_USAGE_URL).toBe('https://api.cursor.com/teams/filtered-usage-events');
152
- });
153
- it('errors when pagination exceeds the defensive page limit', async () => {
154
- let page = 0;
155
- const fetchImpl = vi.fn(async () => {
156
- page += 1;
157
- return {
158
- ok: true,
159
- status: 200,
160
- json: async () => ({
161
- usageEvents: [buildUsageEvent({ conversationId: `conv-page-${page}` })],
162
- pagination: { hasNextPage: true, currentPage: page },
163
- }),
164
- };
165
- });
166
- await expect(fetchCursorUsageFromAdminApi({
167
- cwd: testCwd,
168
- apiKey,
169
- fetchImpl,
170
- maxPages: 2,
171
- })).rejects.toThrow(/truncated after 2 pages/);
172
- });
173
- });
@@ -1 +0,0 @@
1
- export {};