@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,133 +0,0 @@
1
- import { execFileSync } from 'child_process';
2
- import fs from 'fs';
3
- import os from 'os';
4
- import path from 'path';
5
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
6
- import { appendTaskEvent, readThread, taskOf, threadsDir, threadText } from './thread.js';
7
- let repo;
8
- const git = (...args) => execFileSync('git', ['-C', repo, ...args], { encoding: 'utf8' }).trim();
9
- beforeEach(() => {
10
- repo = fs.mkdtempSync(path.join(os.tmpdir(), 'thread-'));
11
- git('init', '-q', '-b', 'main');
12
- git('config', 'user.email', 't@example.com');
13
- git('config', 'user.name', 't');
14
- git('config', 'commit.gpgsign', 'false');
15
- fs.writeFileSync(path.join(repo, 'a.ts'), 'export const a = 1;\n');
16
- git('add', '-A');
17
- git('commit', '-qm', 'init');
18
- });
19
- afterEach(() => fs.rmSync(repo, { recursive: true, force: true }));
20
- describe('the engineering task', () => {
21
- const commit = (subject) => {
22
- fs.appendFileSync(path.join(repo, 'a.ts'), `// ${subject}\n`);
23
- git('commit', '-qam', subject);
24
- };
25
- it('is the ticket the branch names only when its own commits name it too; a version token is never a ticket', () => {
26
- for (const branch of ['chore/pin-node-22', 'fix/utf-8-decoding', 'feat/http-2-push', 'release-1.4', 'bump/python-3-12']) {
27
- git('checkout', '-q', 'main');
28
- git('checkout', '-qb', branch);
29
- commit(`${branch.split('/').pop()}: bump it`); // the version token in the subject, as people write it
30
- expect(taskOf(repo)?.key).toBe(`branch:${branch}`);
31
- }
32
- git('checkout', '-q', 'main');
33
- git('checkout', '-qb', 'feat/proj-123-resume-emails');
34
- expect(taskOf(repo)?.key).toBe('branch:feat/proj-123-resume-emails'); // named by the branch, not yet by a commit
35
- commit('proj-123 resume emails');
36
- expect(taskOf(repo)?.key).toBe('branch:feat/proj-123-resume-emails'); // lower case is how versions are written, not tickets
37
- commit('PROJ-123: resume emails');
38
- expect(taskOf(repo)).toMatchObject({ key: 'PROJ-123', branch: 'feat/proj-123-resume-emails' });
39
- git('checkout', '-qb', 'fix-the-thing');
40
- expect(taskOf(repo)?.key).toBe('branch:fix-the-thing'); // a branch with no ticket in its name
41
- git('checkout', '-q', '--detach');
42
- expect(taskOf(repo)).toBeUndefined();
43
- expect(appendTaskEvent(repo, { kind: 'push', passed: true })).toBeUndefined();
44
- });
45
- it("takes the ticket from the pull request's title once a review recorded it, when the commits use another scope", () => {
46
- git('checkout', '-qb', 'feat/PROJ-7-retry');
47
- commit('fix(retry): back off on 429');
48
- appendTaskEvent(repo, { kind: 'edit-check', files: ['src/retry.ts'], findings: 0 });
49
- expect(taskOf(repo)?.key).toBe('branch:feat/PROJ-7-retry');
50
- appendTaskEvent(repo, { kind: 'review', pr: 7, pr_title: 'feat(PROJ-7): retry with backoff', outcome: 'passed', blocking: 0 });
51
- expect(taskOf(repo)?.key).toBe('PROJ-7'); // the same commit: the title confirmed it, and the kept task was dropped
52
- appendTaskEvent(repo, { kind: 'push', passed: true });
53
- expect(readThread(repo, 'PROJ-7')?.events.map(e => [e.kind, e.task])).toEqual([['edit-check', 'branch:feat/PROJ-7-retry'], ['review', 'branch:feat/PROJ-7-retry'], ['push', 'PROJ-7']]);
54
- });
55
- it('works the task out once per commit and keeps it, so the edit hook does not read the branch history on every edit', () => {
56
- git('checkout', '-qb', 'feat/PROJ-9-cache');
57
- commit('PROJ-9: start');
58
- expect(taskOf(repo)?.key).toBe('PROJ-9');
59
- const cache = path.join(repo, '.git', 'rigour', 'task-cache.json');
60
- expect(JSON.parse(fs.readFileSync(cache, 'utf8'))['feat/PROJ-9-cache']).toEqual({ head: git('rev-parse', 'HEAD'), key: 'PROJ-9' });
61
- fs.writeFileSync(cache, JSON.stringify({ 'feat/PROJ-9-cache': { head: git('rev-parse', 'HEAD'), key: 'KEPT-1' } }));
62
- expect(taskOf(repo)?.key).toBe('KEPT-1'); // read from what was kept, not worked out again
63
- commit('PROJ-9: more');
64
- expect(taskOf(repo)?.key).toBe('PROJ-9'); // a new commit: worked out again
65
- });
66
- it('keeps one file per exact branch: a/b and a_b never share a thread', () => {
67
- git('checkout', '-qb', 'feat/a/b');
68
- appendTaskEvent(repo, { kind: 'push', passed: true });
69
- git('checkout', '-q', 'main');
70
- git('checkout', '-qb', 'feat/a_b');
71
- appendTaskEvent(repo, { kind: 'push', passed: false, failed: 2 });
72
- expect(readThread(repo, 'feat/a/b')?.events.map(e => [e.branch, e.passed])).toEqual([['feat/a/b', true]]);
73
- expect(readThread(repo, 'feat/a_b')?.events.map(e => [e.branch, e.passed])).toEqual([['feat/a_b', false]]);
74
- expect(fs.readdirSync(threadsDir(repo))).toHaveLength(2);
75
- });
76
- it('gathers a ticket across the branches and worktrees that worked on it, from their first event', () => {
77
- git('checkout', '-qb', 'feat/PROJ-5-api');
78
- appendTaskEvent(repo, { kind: 'edit-check', files: ['src/api.ts'], findings: 1 }); // before the first commit names the ticket
79
- commit('PROJ-5: the api');
80
- appendTaskEvent(repo, { kind: 'push', passed: true });
81
- const other = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'thread-wt-')), 'wt');
82
- git('worktree', 'add', '-q', '-b', 'feat/PROJ-5-ui', other, 'main');
83
- fs.appendFileSync(path.join(other, 'a.ts'), '// ui\n');
84
- execFileSync('git', ['-C', other, 'commit', '-qam', 'PROJ-5 the ui'], { encoding: 'utf8' });
85
- appendTaskEvent(other, { kind: 'push', passed: true }); // written from the other worktree, into the same folder
86
- const thread = readThread(repo, 'proj-5');
87
- expect(thread?.task).toBe('PROJ-5');
88
- expect(thread?.events.map(e => [e.kind, e.branch])).toEqual([['edit-check', 'feat/PROJ-5-api'], ['push', 'feat/PROJ-5-api'], ['push', 'feat/PROJ-5-ui']]);
89
- expect(readThread(repo, 'feat/PROJ-5-ui')?.events).toHaveLength(1);
90
- git('worktree', 'remove', '--force', other);
91
- });
92
- it('keeps an append-only thread, read by ticket, branch, pull request or the checkout, oldest first, skipping a broken line', () => {
93
- git('checkout', '-qb', 'feat/PROJ-7-retry');
94
- commit('PROJ-7 retry');
95
- appendTaskEvent(repo, { kind: 'edit-check', session: 'sess-aaaa1111', agent: 'claude', files: ['src/job.ts'], findings: 2 });
96
- appendTaskEvent(repo, { kind: 'edit-check', session: 'sess-aaaa1111', agent: 'claude', files: ['src/job.ts'], findings: 0 });
97
- appendTaskEvent(repo, { kind: 'stop-review', session: 'sess-aaaa1111', agent: 'claude', blocked: true, blocking: 1 });
98
- appendTaskEvent(repo, { kind: 'push', passed: false, failed: 1 });
99
- appendTaskEvent(repo, { kind: 'push', passed: true, failed: 0 });
100
- appendTaskEvent(repo, { kind: 'review', pr: 42, outcome: 'passed', blocking: 0, should_fix: 1, integrity: 'abcdef0123456789abcdef' });
101
- const [name] = fs.readdirSync(threadsDir(repo));
102
- const file = path.join(threadsDir(repo), name);
103
- expect(name).toMatch(/^feat_PROJ-7-retry-[0-9a-f]{8}\.jsonl$/);
104
- expect(fs.realpathSync(file)).toBe(fs.realpathSync(path.join(repo, '.git', 'rigour', 'threads', name)));
105
- expect(git('status', '--porcelain')).toBe(''); // nothing in the working tree
106
- fs.appendFileSync(file, 'not json\n{"kind":"push"}\n');
107
- const byTicket = readThread(repo, 'proj-7');
108
- expect(byTicket?.task).toBe('PROJ-7');
109
- expect(byTicket?.events.map(e => e.kind)).toEqual(['edit-check', 'edit-check', 'stop-review', 'push', 'push', 'review']);
110
- expect(readThread(repo, 'feat/PROJ-7-retry')?.events).toHaveLength(6);
111
- expect(readThread(repo, '#42')?.task).toBe('PROJ-7');
112
- expect(readThread(repo, '42')?.task).toBe('PROJ-7');
113
- expect(readThread(repo)?.events).toHaveLength(6);
114
- expect(readThread(repo, '#43')).toBeUndefined();
115
- expect(readThread(repo, 'OTHER-1')).toBeUndefined();
116
- expect(byTicket.events[0]).toMatchObject({ task: 'PROJ-7', branch: 'feat/PROJ-7-retry', head: git('rev-parse', 'HEAD') });
117
- const text = threadText(byTicket).join('\n');
118
- expect(text).toContain('PROJ-7 · feat/PROJ-7-retry · PR #42');
119
- expect(text).toContain('1 agent session(s) (claude)');
120
- expect(text).toContain('findings caught while writing: 2, files clean again after a finding: 1');
121
- expect(text).toContain('stop reviews: 1, blocked: 1');
122
- expect(text).toContain('pushes: 2, blocked: 1');
123
- expect(text).toContain('reviews: 1, last: passed with 0 blocking (record abcdef0123456789)');
124
- expect(text).toContain('blocked: 1 check(s) failed');
125
- });
126
- it('never fails the hook it runs in: an unwritable thread folder is skipped', () => {
127
- git('checkout', '-qb', 'feat/PROJ-8');
128
- fs.writeFileSync(path.join(repo, '.git', 'rigour'), 'a file where the folder should be');
129
- expect(appendTaskEvent(repo, { kind: 'push', passed: true })).toBeUndefined();
130
- expect(readThread(repo)?.events ?? []).toEqual([]);
131
- expect(threadText({ task: 'PROJ-8', events: [] })).toEqual(['PROJ-8: nothing recorded yet']);
132
- });
133
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,64 +0,0 @@
1
- import fs from 'fs';
2
- import os from 'os';
3
- import path from 'path';
4
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
- import { countUsage, durationBucket, flushDailyUsage, isTelemetryEnabled, readTelemetryState, setTelemetryEnabled, shouldAskTelemetry, trackUsage } from './telemetry.js';
6
- let home;
7
- const env = { RIGOUR_MIXPANEL_TOKEN: 'tok' };
8
- beforeEach(() => { home = fs.mkdtempSync(path.join(os.tmpdir(), 'telemetry-')); });
9
- afterEach(() => { fs.rmSync(home, { recursive: true, force: true }); });
10
- describe('telemetry consent', () => {
11
- it('sends nothing until the person says yes, and the environment always wins', () => {
12
- expect(isTelemetryEnabled({ env, home })).toBe(false);
13
- expect(shouldAskTelemetry(true, { env, home })).toBe(true);
14
- setTelemetryEnabled(true, home);
15
- expect(isTelemetryEnabled({ env, home })).toBe(true);
16
- expect(shouldAskTelemetry(true, { env, home })).toBe(false); // asked once
17
- expect(isTelemetryEnabled({ env: { ...env, DO_NOT_TRACK: '1' }, home })).toBe(false);
18
- expect(isTelemetryEnabled({ env: { ...env, RIGOUR_TELEMETRY: '0' }, home })).toBe(false);
19
- expect(isTelemetryEnabled({ env: { ...env, CI: 'true' }, home })).toBe(false);
20
- expect(isTelemetryEnabled({ env: {}, home })).toBe(false); // a build without a token never sends
21
- });
22
- it('never asks in CI or without a terminal, and keeps one random install id', () => {
23
- expect(shouldAskTelemetry(false, { env, home })).toBe(false);
24
- expect(shouldAskTelemetry(true, { env: { ...env, GITHUB_ACTIONS: 'true' }, home })).toBe(false);
25
- setTelemetryEnabled(true, home);
26
- for (const ci of ['JENKINS_URL', 'TF_BUILD', 'TEAMCITY_VERSION']) {
27
- expect(isTelemetryEnabled({ env: { ...env, [ci]: 'x' }, home })).toBe(false); // a build agent's stored yes is not a CI opt-in
28
- expect(isTelemetryEnabled({ env: { ...env, [ci]: 'x', RIGOUR_TELEMETRY: '1' }, home })).toBe(true);
29
- }
30
- const first = setTelemetryEnabled(false, home).installId;
31
- expect(readTelemetryState(home).installId).toBe(first);
32
- expect(first).toMatch(/^[0-9a-f-]{36}$/);
33
- });
34
- });
35
- describe('telemetry sending', () => {
36
- it('sends one event with the install id and no IP, and swallows a failing network', async () => {
37
- setTelemetryEnabled(true, home);
38
- const fetch = vi.fn().mockResolvedValue({ ok: true });
39
- await trackUsage('review_completed', { status: 'PASS', findings_by_gate: { 'semantic-bugs': 1 } }, { env, home, fetch, version: '6.5.0' });
40
- const [url, init] = fetch.mock.calls[0];
41
- expect(url).toBe('https://api.mixpanel.com/track?ip=0');
42
- const [event] = JSON.parse(init.body);
43
- expect(event.event).toBe('review_completed');
44
- expect(event.properties).toMatchObject({ token: 'tok', distinct_id: readTelemetryState(home).installId, version: '6.5.0', status: 'PASS' });
45
- expect(JSON.stringify(event)).not.toContain(home);
46
- await expect(trackUsage('x', {}, { env, home, fetch: vi.fn().mockRejectedValue(new Error('offline')) })).resolves.toBeUndefined();
47
- });
48
- it('counts hot-path activity locally and sends it as one daily event', async () => {
49
- setTelemetryEnabled(true, home);
50
- const fetch = vi.fn().mockResolvedValue({ ok: true });
51
- const day = 24 * 60 * 60 * 1000;
52
- countUsage('hook_check', 1, { env, home, now: 0 });
53
- countUsage('hook_check', 1, { env, home, now: 1000 });
54
- countUsage('mcp:rigour_review', 1, { env, home, now: 2000 });
55
- expect(await flushDailyUsage({ env, home, fetch, now: 5000 })).toBe(false); // not a day yet
56
- expect(await flushDailyUsage({ env, home, fetch, now: day + 1 })).toBe(true);
57
- const [event] = JSON.parse(fetch.mock.calls[0][1].body);
58
- expect(event).toMatchObject({ event: 'daily_usage', properties: { hook_check: 2, 'mcp:rigour_review': 1 } });
59
- expect(await flushDailyUsage({ env, home, fetch, now: 2 * day + 2 })).toBe(false); // counters reset
60
- });
61
- it('buckets durations so timing never identifies a run', () => {
62
- expect([500, 3000, 20000, 60000, 600000].map(durationBucket)).toEqual(['<1s', '1-5s', '5-30s', '30s-2m', '>2m']);
63
- });
64
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,33 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import yaml from 'yaml';
3
- import { ConfigSchema } from './index.js';
4
- describe('rigour.yml', () => {
5
- it('still loads a file that sets the settings no check ever read, and keeps everything else', () => {
6
- const old = yaml.parse(`
7
- version: 1
8
- planned: [Layer boundaries]
9
- hooks: { enabled: true, tools: [claude], fast_gates: [file-size], timeout_ms: 5000, block_on_failure: true, dlp: false, require_review_ack: true }
10
- gates:
11
- max_file_lines: 300
12
- forbid_paths: [secret/]
13
- ast: { complexity: 7, max_nesting: 3, max_function_lines: 40 }
14
- adaptive: { enabled: true, forced_tier: enterprise }
15
- input_validation: { enabled: true, min_secret_length: 12 }
16
- deep: { enabled: true, provider: claude, api_key: sk-test, model_name: x, threads: 8, temperature: 0.2 }
17
- test_quality: { check_snapshot_abuse: false, ignore_patterns: [x], max_mocks_per_test: 3 }
18
- `);
19
- const config = ConfigSchema.parse(old);
20
- expect(config.gates.max_file_lines).toBe(300);
21
- expect(config.gates.ast.complexity).toBe(7);
22
- expect(config.gates.deep.temperature).toBe(0.2);
23
- expect(config.gates.test_quality.max_mocks_per_test).toBe(3);
24
- expect(config.hooks.require_review_ack).toBe(true);
25
- // What nothing read is dropped, so no code can start depending on it by accident.
26
- expect(config).not.toHaveProperty('version');
27
- expect(config).not.toHaveProperty('planned');
28
- expect(config.gates).not.toHaveProperty('adaptive');
29
- expect(config.gates).not.toHaveProperty('input_validation');
30
- expect(config.gates.deep).not.toHaveProperty('api_key');
31
- expect(config.hooks).not.toHaveProperty('block_on_failure');
32
- });
33
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,8 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { splitCommand } from './command-line.js';
3
- describe('splitCommand', () => {
4
- it('splits on spaces and keeps a quoted argument whole, without its quotes', () => {
5
- expect(splitCommand('npm run lint')).toEqual({ bin: 'npm', args: ['run', 'lint'] });
6
- expect(splitCommand(`vitest run "src/a b.test.ts" --reporter 'dot'`)).toEqual({ bin: 'vitest', args: ['run', 'src/a b.test.ts', '--reporter', 'dot'] });
7
- });
8
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,29 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { parseDiff, removedByFile } from './diff.js';
3
- const DIFF = `diff --git a/src/a.ts b/src/a.ts
4
- --- a/src/a.ts
5
- +++ b/src/a.ts
6
- @@ -1,5 +1,4 @@
7
- export function load(id: string) {
8
- - if (!id) return null;
9
- - audit(id);
10
- + audit(id ?? '');
11
- return fetch(id);
12
- }
13
- @@ -20,2 +19,1 @@
14
- -const legacy = true;
15
- export const x = 1;
16
- `;
17
- describe('removedByFile', () => {
18
- it('groups removed lines into blocks anchored at the new-side line', () => {
19
- expect(removedByFile(DIFF)).toEqual({
20
- 'src/a.ts': [
21
- { line: 2, text: [' if (!id) return null;', ' audit(id);'] },
22
- { line: 19, text: ['const legacy = true;'] },
23
- ],
24
- });
25
- });
26
- it('leaves added-line parsing unchanged', () => {
27
- expect([...parseDiff(DIFF)['src/a.ts']]).toEqual([2]);
28
- });
29
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,38 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { changedLinesByFile, parseDiff } from './diff.js';
3
- const lines = (m) => changedLinesByFile(m);
4
- describe('parseDiff', () => {
5
- it('maps added lines to new-side line numbers', () => {
6
- const diff = [
7
- 'diff --git a/src/a.ts b/src/a.ts',
8
- '--- a/src/a.ts',
9
- '+++ b/src/a.ts',
10
- '@@ -1,3 +1,4 @@',
11
- ' const a = 1;',
12
- '-const b = 2;',
13
- '+const b = 3;',
14
- '+const c = 4;',
15
- ' export { a };',
16
- ].join('\n');
17
- expect(lines(parseDiff(diff))).toEqual({ 'src/a.ts': [2, 3] });
18
- });
19
- it('handles several files and hunks', () => {
20
- const diff = [
21
- '+++ b/a.ts', '@@ -10,2 +10,3 @@', ' x', '+y', ' z',
22
- '+++ b/b.ts', '@@ -1 +1 @@', '-old', '+new',
23
- ].join('\n');
24
- expect(lines(parseDiff(diff))).toEqual({ 'a.ts': [11], 'b.ts': [1] });
25
- });
26
- it('does not mistake content lines starting with +++ or --- for headers', () => {
27
- const diff = ['+++ b/a.ts', '@@ -1,2 +1,3 @@', ' let i = 0;', '+++i;', '---j;', ' done();'].join('\n');
28
- expect(lines(parseDiff(diff))).toEqual({ 'a.ts': [2] });
29
- });
30
- it('does not count "No newline at end of file" as a line', () => {
31
- const diff = ['+++ b/a.ts', '@@ -1,1 +1,2 @@', ' a', '\', '+b'].join('\n');
32
- expect(lines(parseDiff(diff))).toEqual({ 'a.ts': [2] });
33
- });
34
- it('ignores deleted files', () => {
35
- const diff = ['diff --git a/gone.ts b/gone.ts', '--- a/gone.ts', '+++ /dev/null', '@@ -1,2 +0,0 @@', '-a', '-b'].join('\n');
36
- expect(parseDiff(diff)).toEqual({});
37
- });
38
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,40 +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 { FileScanner } from './scanner.js';
6
- import { directoriesAsGlobs, withDirectoryForms } from './glob-paths.js';
7
- let root;
8
- beforeEach(() => {
9
- root = fs.mkdtempSync(path.join(os.tmpdir(), 'glob-paths-'));
10
- fs.mkdirSync(path.join(root, 'src'));
11
- fs.mkdirSync(path.join(root, 'legacy'));
12
- fs.writeFileSync(path.join(root, 'src', 'a.ts'), 'export const a = 1;\n');
13
- fs.writeFileSync(path.join(root, 'legacy', 'old.ts'), 'export const b = 2;\n');
14
- });
15
- afterEach(() => fs.rmSync(root, { recursive: true, force: true }));
16
- describe('withDirectoryForms', () => {
17
- it('adds the directory form to plain paths and leaves globs alone', () => {
18
- expect(withDirectoryForms(['legacy', 'build/', '.git/**', '**/*.min.js'])).toEqual(['legacy', 'legacy/**', 'build/', 'build/**', '.git/**', '**/*.min.js']);
19
- });
20
- });
21
- describe('directoriesAsGlobs', () => {
22
- it('turns a directory into dir/** and leaves files, missing paths and globs alone', () => {
23
- expect(directoriesAsGlobs(root, ['src', 'src/a.ts', 'missing', '**/*.ts'])).toEqual(['src/**', 'src/a.ts', 'missing', '**/*.ts']);
24
- });
25
- });
26
- describe('FileScanner in a git worktree', () => {
27
- it('scans when .git is a file and the config ignores .git/**', async () => {
28
- fs.writeFileSync(path.join(root, '.git'), 'gitdir: /elsewhere/.git/worktrees/x\n');
29
- const files = await FileScanner.findFiles({ cwd: root, patterns: ['**/*.ts'], ignore: ['.git/**'] });
30
- expect(files.sort()).toEqual(['legacy/old.ts', 'src/a.ts']);
31
- });
32
- it('finds a file or a whole directory named by a plain path', async () => {
33
- expect(await FileScanner.findFiles({ cwd: root, patterns: ['src/a.ts'] })).toEqual(['src/a.ts']);
34
- expect(await FileScanner.findFiles({ cwd: root, patterns: ['legacy'] })).toEqual(['legacy/old.ts']);
35
- });
36
- it('still ignores a whole directory named by a plain path', async () => {
37
- const files = await FileScanner.findFiles({ cwd: root, patterns: ['**/*.ts'], ignore: ['legacy'] });
38
- expect(files).toEqual(['src/a.ts']);
39
- });
40
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,68 +0,0 @@
1
- import { execFileSync } from 'child_process';
2
- import fs from 'fs';
3
- import os from 'os';
4
- import path from 'path';
5
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
6
- import { applyProfile, hostPath, profileFor, profileMismatch } from './profile.js';
7
- let root;
8
- const saved = { ...process.env };
9
- const repo = (rel, remote) => {
10
- const dir = path.join(root, rel);
11
- fs.mkdirSync(dir, { recursive: true });
12
- execFileSync('git', ['init', '-q'], { cwd: dir });
13
- if (remote)
14
- execFileSync('git', ['remote', 'add', 'origin', remote], { cwd: dir });
15
- return dir;
16
- };
17
- beforeEach(() => { root = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'profiles-'))); });
18
- afterEach(() => { process.env = { ...saved }; fs.rmSync(root, { recursive: true, force: true }); });
19
- describe('profiles', () => {
20
- it('matches a repository by path prefix or by origin remote, first match winning', () => {
21
- const byPath = repo('work/acme/api');
22
- const byRemote = repo('elsewhere/web', 'git@github.com:Acme/web.git');
23
- const other = repo('personal/tool', 'https://github.com/me/tool');
24
- const profiles = [
25
- { name: 'acme-path', match: [path.join(root, 'work/acme')] },
26
- { name: 'acme-remote', match: ['github.com/acme/*'] },
27
- ];
28
- expect(profileFor(path.join(byPath, 'src'), profiles)?.name).toBe('acme-path');
29
- expect(profileFor(byRemote, profiles)?.name).toBe('acme-remote');
30
- expect(profileFor(other, profiles)).toBeUndefined();
31
- expect(profileFor(path.join(root, 'work/acme-evil'), profiles)).toBeUndefined();
32
- expect(hostPath('https://GitHub.com/Acme/web.git/')).toBe('github.com/acme/web');
33
- });
34
- it("applies the profile's home and team, clearing team settings inherited from the shell", () => {
35
- const dir = repo('work/acme/api');
36
- delete process.env.RIGOUR_HOME; // no home of your own: the profile chooses it
37
- process.env.RIGOUR_TEAM_DATABASE_URL = 'postgres://other-org';
38
- process.env.RIGOUR_ORGANIZATION_ID = 'other-org';
39
- applyProfile(dir, [{ name: 'acme', match: [path.join(root, 'work/acme')], home: path.join(root, 'acme-home'), githubAccount: 'acme-dev' }]);
40
- expect(process.env.RIGOUR_HOME).toBe(path.join(root, 'acme-home'));
41
- expect(process.env.RIGOUR_PROFILE).toBe('acme');
42
- expect(process.env.RIGOUR_GITHUB_ACCOUNT).toBe('acme-dev');
43
- expect(process.env.RIGOUR_TEAM_DATABASE_URL).toBeUndefined();
44
- expect(process.env.RIGOUR_ORGANIZATION_ID).toBeUndefined();
45
- applyProfile(dir, [{ name: 'acme', match: [dir], team: { organization: 'acme', team: 'web', actor: 'dev', repositories: ['github.com/acme/*'], databaseUrlCommand: 'echo url' } }]);
46
- expect([process.env.RIGOUR_ORGANIZATION_ID, process.env.RIGOUR_TEAM_REPOSITORIES, process.env.RIGOUR_TEAM_DATABASE_URL_COMMAND]).toEqual(['acme', 'github.com/acme/*', 'echo url']);
47
- });
48
- it('leaves a RIGOUR_HOME you set yourself alone: no profile replaces it or brings its team', () => {
49
- const dir = repo('work/acme/api');
50
- delete process.env.RIGOUR_PROFILE;
51
- process.env.RIGOUR_HOME = path.join(root, 'isolated');
52
- const applied = applyProfile(dir, [{ name: 'acme', match: [path.join(root, 'work/acme')], home: path.join(root, 'acme-home'), team: { organization: 'acme', team: 'web', actor: 'dev' } }]);
53
- expect(applied).toBeUndefined();
54
- expect(process.env.RIGOUR_HOME).toBe(path.join(root, 'isolated'));
55
- expect(process.env.RIGOUR_ORGANIZATION_ID).toBeUndefined();
56
- });
57
- it('refuses to act for a repository of another profile than the one the process started with', () => {
58
- const acme = repo('work/acme/api');
59
- const mine = repo('personal/tool');
60
- const profiles = [{ name: 'acme', match: [path.join(root, 'work/acme')] }];
61
- delete process.env.RIGOUR_PROFILE;
62
- expect(profileMismatch(mine, profiles)).toBeNull();
63
- expect(profileMismatch(acme, profiles)).toContain('belongs to the Rigour profile "acme"');
64
- process.env.RIGOUR_PROFILE = 'acme';
65
- expect(profileMismatch(acme, profiles)).toBeNull();
66
- expect(profileMismatch(mine, profiles)).toContain('started as "acme"');
67
- });
68
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,48 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { FileScanner } from './scanner.js';
3
- import { globby } from 'globby';
4
- vi.mock('globby', async (importOriginal) => ({
5
- isDynamicPattern: (await importOriginal()).isDynamicPattern,
6
- globby: vi.fn(async () => []),
7
- }));
8
- describe('FileScanner', () => {
9
- it('returns files in a fixed order, whatever order the walk found them in', async () => {
10
- vi.mocked(globby).mockResolvedValueOnce(['src/z.ts', 'scripts/a.ts', 'src/a.ts']);
11
- expect(await FileScanner.findFiles({ cwd: '/test' })).toEqual(['scripts/a.ts', 'src/a.ts', 'src/z.ts']);
12
- });
13
- it('should merge default ignores with user ignores', async () => {
14
- const options = {
15
- cwd: '/test',
16
- ignore: ['custom-ignore']
17
- };
18
- await FileScanner.findFiles(options);
19
- const call = vi.mocked(globby).mock.calls.at(-1);
20
- const ignore = call[1].ignore;
21
- expect(ignore).toContain('**/node_modules/**');
22
- expect(ignore).toContain('custom-ignore');
23
- });
24
- it('should normalize Windows paths to forward slashes', async () => {
25
- const platform = Object.getOwnPropertyDescriptor(process, 'platform');
26
- Object.defineProperty(process, 'platform', { value: 'win32' });
27
- try {
28
- await FileScanner.findFiles({ cwd: 'C:\\test\\path', patterns: ['**\\*.ts', 'app\\\\[key\\]\\route.ts'] });
29
- }
30
- finally {
31
- Object.defineProperty(process, 'platform', platform);
32
- }
33
- const call = vi.mocked(globby).mock.calls.at(-1);
34
- expect(call[0]).toEqual(['**/*.ts', 'app/\\[key\\]/route.ts']);
35
- expect(call[1]?.cwd).toBe('C:/test/path');
36
- });
37
- it('should keep glob escapes on POSIX', async () => {
38
- const platform = Object.getOwnPropertyDescriptor(process, 'platform');
39
- Object.defineProperty(process, 'platform', { value: 'linux' });
40
- try {
41
- await FileScanner.findFiles({ cwd: '/repo', patterns: ['src/app/\\[key\\]/route.ts'] });
42
- }
43
- finally {
44
- Object.defineProperty(process, 'platform', platform);
45
- }
46
- expect(vi.mocked(globby).mock.calls.at(-1)[0]).toEqual(['src/app/\\[key\\]/route.ts']);
47
- });
48
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,38 +0,0 @@
1
- import fs from 'fs-extra';
2
- import os from 'os';
3
- import path from 'path';
4
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
5
- import { isScoped, normalizeScopePatterns, resolveScopedFiles, touchesScope } from './scope.js';
6
- describe('scope', () => {
7
- let cwd;
8
- beforeEach(async () => {
9
- cwd = await fs.mkdtemp(path.join(os.tmpdir(), 'rigour-scope-'));
10
- await fs.outputFile(path.join(cwd, 'src/app/[key]/route.ts'), 'export {}');
11
- await fs.outputFile(path.join(cwd, 'src/app/k/route.ts'), 'export {}');
12
- await fs.outputFile(path.join(cwd, 'src/lib/a.ts'), 'export {}');
13
- });
14
- afterEach(async () => { await fs.remove(cwd); });
15
- it('turns absolute, relative and directory inputs into cwd-relative patterns', async () => {
16
- const patterns = await normalizeScopePatterns(cwd, [path.join(cwd, 'src/lib/a.ts'), './src/lib', 'src/lib/a.ts']);
17
- expect(patterns).toEqual(['src/lib/a.ts', 'src/lib/**/*', 'src/lib/a.ts']);
18
- });
19
- it('escapes glob characters so a Next.js [key] route matches only itself', async () => {
20
- const patterns = await normalizeScopePatterns(cwd, ['src/app/[key]/route.ts']);
21
- expect([...await resolveScopedFiles(cwd, patterns)]).toEqual(['src/app/[key]/route.ts']);
22
- });
23
- it('keeps a non-existent input as the glob the user wrote', async () => {
24
- expect(await normalizeScopePatterns(cwd, ['src/**/*.ts'])).toEqual(['src/**/*.ts']);
25
- });
26
- it('treats the repo root as everything', async () => {
27
- expect(await normalizeScopePatterns(cwd, ['.'])).toEqual(['**/*']);
28
- });
29
- it('detects scoped runs and scope membership', () => {
30
- expect(isScoped(undefined)).toBe(false);
31
- expect(isScoped([])).toBe(false);
32
- expect(isScoped(['a.ts'])).toBe(true);
33
- const scoped = new Set(['src/lib/a.ts']);
34
- expect(touchesScope(['./src/lib/a.ts', 'b.ts'], scoped)).toBe(true);
35
- expect(touchesScope(['b.ts'], scoped)).toBe(false);
36
- expect(touchesScope(undefined, scoped)).toBe(false);
37
- });
38
- });