@humanbased/crosscheck 1.2.0 → 1.3.0-beta.82

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 (331) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +182 -375
  3. package/README.zh.md +1 -1
  4. package/assets/icon-256.png +0 -0
  5. package/assets/linear-comment.svg +18 -0
  6. package/assets/linear-onboard.svg +30 -0
  7. package/assets/linear-status.svg +23 -0
  8. package/assets/linear-test.svg +34 -0
  9. package/assets/skills/code-review/.crosscheck-skill.json +9 -0
  10. package/assets/skills/code-review/LICENSE +21 -0
  11. package/assets/skills/code-review/SKILL.md +89 -0
  12. package/assets/skills/code-review/agents/openai.yaml +3 -0
  13. package/assets/skills/code-review-skill/.crosscheck-skill.json +9 -0
  14. package/assets/skills/code-review-skill/LICENSE +21 -0
  15. package/assets/skills/code-review-skill/SKILL.md +231 -0
  16. package/assets/skills/code-review-skill/assets/pr-review-template.md +137 -0
  17. package/assets/skills/code-review-skill/assets/review-checklist.md +123 -0
  18. package/assets/skills/code-review-skill/reference/angular.md +768 -0
  19. package/assets/skills/code-review-skill/reference/architecture-review-guide.md +472 -0
  20. package/assets/skills/code-review-skill/reference/c.md +890 -0
  21. package/assets/skills/code-review-skill/reference/code-quality-universal.md +488 -0
  22. package/assets/skills/code-review-skill/reference/code-review-best-practices.md +136 -0
  23. package/assets/skills/code-review-skill/reference/common-bugs-checklist.md +286 -0
  24. package/assets/skills/code-review-skill/reference/cpp.md +893 -0
  25. package/assets/skills/code-review-skill/reference/cross-cutting/async-concurrency-patterns.md +515 -0
  26. package/assets/skills/code-review-skill/reference/cross-cutting/error-handling-principles.md +492 -0
  27. package/assets/skills/code-review-skill/reference/cross-cutting/n-plus-one-queries.md +309 -0
  28. package/assets/skills/code-review-skill/reference/cross-cutting/sql-injection-prevention.md +308 -0
  29. package/assets/skills/code-review-skill/reference/cross-cutting/xss-prevention.md +264 -0
  30. package/assets/skills/code-review-skill/reference/csharp.md +525 -0
  31. package/assets/skills/code-review-skill/reference/css-less-sass.md +661 -0
  32. package/assets/skills/code-review-skill/reference/django.md +985 -0
  33. package/assets/skills/code-review-skill/reference/fastapi.md +580 -0
  34. package/assets/skills/code-review-skill/reference/go.md +993 -0
  35. package/assets/skills/code-review-skill/reference/java.md +409 -0
  36. package/assets/skills/code-review-skill/reference/java8.md +586 -0
  37. package/assets/skills/code-review-skill/reference/kotlin.md +1018 -0
  38. package/assets/skills/code-review-skill/reference/nestjs.md +593 -0
  39. package/assets/skills/code-review-skill/reference/performance-review-guide.md +816 -0
  40. package/assets/skills/code-review-skill/reference/php.md +684 -0
  41. package/assets/skills/code-review-skill/reference/python.md +1073 -0
  42. package/assets/skills/code-review-skill/reference/qt.md +757 -0
  43. package/assets/skills/code-review-skill/reference/react.md +871 -0
  44. package/assets/skills/code-review-skill/reference/ruby.md +964 -0
  45. package/assets/skills/code-review-skill/reference/rust.md +846 -0
  46. package/assets/skills/code-review-skill/reference/security-review-guide.md +494 -0
  47. package/assets/skills/code-review-skill/reference/svelte.md +1064 -0
  48. package/assets/skills/code-review-skill/reference/swift.md +936 -0
  49. package/assets/skills/code-review-skill/reference/typescript.md +1016 -0
  50. package/assets/skills/code-review-skill/reference/vue.md +924 -0
  51. package/assets/skills/code-review-skill/reference/zig.md +440 -0
  52. package/assets/skills/code-review-skill/scripts/pr-analyzer.py +435 -0
  53. package/assets/skills/code-review-skill/scripts/test_pr_analyzer.py +380 -0
  54. package/assets/skills/codebase-design/.crosscheck-skill.json +9 -0
  55. package/assets/skills/codebase-design/DEEPENING.md +37 -0
  56. package/assets/skills/codebase-design/DESIGN-IT-TWICE.md +44 -0
  57. package/assets/skills/codebase-design/LICENSE +21 -0
  58. package/assets/skills/codebase-design/SKILL.md +114 -0
  59. package/assets/skills/codebase-design/agents/openai.yaml +3 -0
  60. package/assets/skills/diagnosing-bugs/.crosscheck-skill.json +9 -0
  61. package/assets/skills/diagnosing-bugs/LICENSE +21 -0
  62. package/assets/skills/diagnosing-bugs/SKILL.md +134 -0
  63. package/assets/skills/diagnosing-bugs/agents/openai.yaml +3 -0
  64. package/assets/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
  65. package/crosscheck.config.example.yml +101 -9
  66. package/dist/__tests__/board.test.js +11 -0
  67. package/dist/__tests__/board.test.js.map +1 -1
  68. package/dist/__tests__/can-write-verdict.test.d.ts +2 -0
  69. package/dist/__tests__/can-write-verdict.test.d.ts.map +1 -0
  70. package/dist/__tests__/can-write-verdict.test.js +31 -0
  71. package/dist/__tests__/can-write-verdict.test.js.map +1 -0
  72. package/dist/__tests__/codex.test.js +14 -27
  73. package/dist/__tests__/codex.test.js.map +1 -1
  74. package/dist/__tests__/comment-bodies.test.js +49 -1
  75. package/dist/__tests__/comment-bodies.test.js.map +1 -1
  76. package/dist/__tests__/conflict-resolve.test.js +44 -1
  77. package/dist/__tests__/conflict-resolve.test.js.map +1 -1
  78. package/dist/__tests__/fix.test.js +33 -0
  79. package/dist/__tests__/fix.test.js.map +1 -1
  80. package/dist/__tests__/linear-branding.test.d.ts +2 -0
  81. package/dist/__tests__/linear-branding.test.d.ts.map +1 -0
  82. package/dist/__tests__/linear-branding.test.js +156 -0
  83. package/dist/__tests__/linear-branding.test.js.map +1 -0
  84. package/dist/__tests__/linear-client.test.d.ts +2 -0
  85. package/dist/__tests__/linear-client.test.d.ts.map +1 -0
  86. package/dist/__tests__/linear-client.test.js +120 -0
  87. package/dist/__tests__/linear-client.test.js.map +1 -0
  88. package/dist/__tests__/linear-comment.test.d.ts +2 -0
  89. package/dist/__tests__/linear-comment.test.d.ts.map +1 -0
  90. package/dist/__tests__/linear-comment.test.js +151 -0
  91. package/dist/__tests__/linear-comment.test.js.map +1 -0
  92. package/dist/__tests__/linear-identity.test.d.ts +2 -0
  93. package/dist/__tests__/linear-identity.test.d.ts.map +1 -0
  94. package/dist/__tests__/linear-identity.test.js +253 -0
  95. package/dist/__tests__/linear-identity.test.js.map +1 -0
  96. package/dist/__tests__/linear-notify.test.d.ts +2 -0
  97. package/dist/__tests__/linear-notify.test.d.ts.map +1 -0
  98. package/dist/__tests__/linear-notify.test.js +144 -0
  99. package/dist/__tests__/linear-notify.test.js.map +1 -0
  100. package/dist/__tests__/linear-ref.test.d.ts +2 -0
  101. package/dist/__tests__/linear-ref.test.d.ts.map +1 -0
  102. package/dist/__tests__/linear-ref.test.js +261 -0
  103. package/dist/__tests__/linear-ref.test.js.map +1 -0
  104. package/dist/__tests__/linear-test-ref.test.d.ts +2 -0
  105. package/dist/__tests__/linear-test-ref.test.d.ts.map +1 -0
  106. package/dist/__tests__/linear-test-ref.test.js +81 -0
  107. package/dist/__tests__/linear-test-ref.test.js.map +1 -0
  108. package/dist/__tests__/linear-verify.test.d.ts +2 -0
  109. package/dist/__tests__/linear-verify.test.d.ts.map +1 -0
  110. package/dist/__tests__/linear-verify.test.js +132 -0
  111. package/dist/__tests__/linear-verify.test.js.map +1 -0
  112. package/dist/__tests__/linear-worker.test.d.ts +2 -0
  113. package/dist/__tests__/linear-worker.test.d.ts.map +1 -0
  114. package/dist/__tests__/linear-worker.test.js +83 -0
  115. package/dist/__tests__/linear-worker.test.js.map +1 -0
  116. package/dist/__tests__/linear-write-possible.test.d.ts +2 -0
  117. package/dist/__tests__/linear-write-possible.test.d.ts.map +1 -0
  118. package/dist/__tests__/linear-write-possible.test.js +30 -0
  119. package/dist/__tests__/linear-write-possible.test.js.map +1 -0
  120. package/dist/__tests__/onboard-preservation.test.js +59 -3
  121. package/dist/__tests__/onboard-preservation.test.js.map +1 -1
  122. package/dist/__tests__/optimize.test.js +2 -0
  123. package/dist/__tests__/optimize.test.js.map +1 -1
  124. package/dist/__tests__/pr-status.test.js +163 -2
  125. package/dist/__tests__/pr-status.test.js.map +1 -1
  126. package/dist/__tests__/pr-workflow-state.test.js +102 -1
  127. package/dist/__tests__/pr-workflow-state.test.js.map +1 -1
  128. package/dist/__tests__/repo-picker.test.js +7 -1
  129. package/dist/__tests__/repo-picker.test.js.map +1 -1
  130. package/dist/__tests__/repository-guidance.test.d.ts +2 -0
  131. package/dist/__tests__/repository-guidance.test.d.ts.map +1 -0
  132. package/dist/__tests__/repository-guidance.test.js +107 -0
  133. package/dist/__tests__/repository-guidance.test.js.map +1 -0
  134. package/dist/__tests__/review-comment-body.test.js +35 -0
  135. package/dist/__tests__/review-comment-body.test.js.map +1 -1
  136. package/dist/__tests__/review-models.test.js +19 -3
  137. package/dist/__tests__/review-models.test.js.map +1 -1
  138. package/dist/__tests__/review-strategy.test.d.ts +2 -0
  139. package/dist/__tests__/review-strategy.test.d.ts.map +1 -0
  140. package/dist/__tests__/review-strategy.test.js +397 -0
  141. package/dist/__tests__/review-strategy.test.js.map +1 -0
  142. package/dist/__tests__/runner.test.js +29 -1
  143. package/dist/__tests__/runner.test.js.map +1 -1
  144. package/dist/__tests__/skill-attribution.test.d.ts +2 -0
  145. package/dist/__tests__/skill-attribution.test.d.ts.map +1 -0
  146. package/dist/__tests__/skill-attribution.test.js +53 -0
  147. package/dist/__tests__/skill-attribution.test.js.map +1 -0
  148. package/dist/__tests__/skill-broker.test.d.ts +2 -0
  149. package/dist/__tests__/skill-broker.test.d.ts.map +1 -0
  150. package/dist/__tests__/skill-broker.test.js +144 -0
  151. package/dist/__tests__/skill-broker.test.js.map +1 -0
  152. package/dist/__tests__/skill-catalog.test.d.ts +2 -0
  153. package/dist/__tests__/skill-catalog.test.d.ts.map +1 -0
  154. package/dist/__tests__/skill-catalog.test.js +40 -0
  155. package/dist/__tests__/skill-catalog.test.js.map +1 -0
  156. package/dist/__tests__/skill-installer.test.d.ts +2 -0
  157. package/dist/__tests__/skill-installer.test.d.ts.map +1 -0
  158. package/dist/__tests__/skill-installer.test.js +96 -0
  159. package/dist/__tests__/skill-installer.test.js.map +1 -0
  160. package/dist/__tests__/skills-config.test.d.ts +2 -0
  161. package/dist/__tests__/skills-config.test.d.ts.map +1 -0
  162. package/dist/__tests__/skills-config.test.js +12 -0
  163. package/dist/__tests__/skills-config.test.js.map +1 -0
  164. package/dist/cli.js +29 -0
  165. package/dist/cli.js.map +1 -1
  166. package/dist/commands/detect-step.d.ts.map +1 -1
  167. package/dist/commands/detect-step.js +4 -0
  168. package/dist/commands/detect-step.js.map +1 -1
  169. package/dist/commands/kickass.d.ts.map +1 -1
  170. package/dist/commands/kickass.js +3 -2
  171. package/dist/commands/kickass.js.map +1 -1
  172. package/dist/commands/linear-test.d.ts +18 -0
  173. package/dist/commands/linear-test.d.ts.map +1 -0
  174. package/dist/commands/linear-test.js +130 -0
  175. package/dist/commands/linear-test.js.map +1 -0
  176. package/dist/commands/onboard.d.ts +36 -3
  177. package/dist/commands/onboard.d.ts.map +1 -1
  178. package/dist/commands/onboard.js +233 -42
  179. package/dist/commands/onboard.js.map +1 -1
  180. package/dist/commands/review.d.ts.map +1 -1
  181. package/dist/commands/review.js +65 -6
  182. package/dist/commands/review.js.map +1 -1
  183. package/dist/commands/run.d.ts.map +1 -1
  184. package/dist/commands/run.js +51 -7
  185. package/dist/commands/run.js.map +1 -1
  186. package/dist/commands/skill.d.ts +2 -0
  187. package/dist/commands/skill.d.ts.map +1 -0
  188. package/dist/commands/skill.js +16 -0
  189. package/dist/commands/skill.js.map +1 -0
  190. package/dist/commands/status.d.ts.map +1 -1
  191. package/dist/commands/status.js +53 -1
  192. package/dist/commands/status.js.map +1 -1
  193. package/dist/commands/watch.d.ts.map +1 -1
  194. package/dist/commands/watch.js +169 -64
  195. package/dist/commands/watch.js.map +1 -1
  196. package/dist/config/loader.d.ts +3 -1
  197. package/dist/config/loader.d.ts.map +1 -1
  198. package/dist/config/loader.js +13 -0
  199. package/dist/config/loader.js.map +1 -1
  200. package/dist/config/review-model-tiers.json +3 -3
  201. package/dist/config/review-strategy.json +204 -0
  202. package/dist/config/schema.d.ts +261 -15
  203. package/dist/config/schema.d.ts.map +1 -1
  204. package/dist/config/schema.js +90 -8
  205. package/dist/config/schema.js.map +1 -1
  206. package/dist/github/client.d.ts +21 -1
  207. package/dist/github/client.d.ts.map +1 -1
  208. package/dist/github/client.js +46 -7
  209. package/dist/github/client.js.map +1 -1
  210. package/dist/github/webhook.d.ts +4 -0
  211. package/dist/github/webhook.d.ts.map +1 -1
  212. package/dist/github/webhook.js.map +1 -1
  213. package/dist/issues/ticket-ref.d.ts.map +1 -1
  214. package/dist/issues/ticket-ref.js +6 -5
  215. package/dist/issues/ticket-ref.js.map +1 -1
  216. package/dist/lib/annotation.d.ts +7 -0
  217. package/dist/lib/annotation.d.ts.map +1 -1
  218. package/dist/lib/annotation.js +11 -1
  219. package/dist/lib/annotation.js.map +1 -1
  220. package/dist/lib/board.d.ts +3 -0
  221. package/dist/lib/board.d.ts.map +1 -1
  222. package/dist/lib/board.js +4 -2
  223. package/dist/lib/board.js.map +1 -1
  224. package/dist/lib/clone.d.ts +1 -0
  225. package/dist/lib/clone.d.ts.map +1 -1
  226. package/dist/lib/clone.js +32 -10
  227. package/dist/lib/clone.js.map +1 -1
  228. package/dist/lib/comment-bodies.d.ts +37 -0
  229. package/dist/lib/comment-bodies.d.ts.map +1 -1
  230. package/dist/lib/comment-bodies.js +47 -9
  231. package/dist/lib/comment-bodies.js.map +1 -1
  232. package/dist/lib/pr-status.d.ts.map +1 -1
  233. package/dist/lib/pr-status.js +36 -2
  234. package/dist/lib/pr-status.js.map +1 -1
  235. package/dist/lib/pr-workflow-state.d.ts +5 -0
  236. package/dist/lib/pr-workflow-state.d.ts.map +1 -1
  237. package/dist/lib/pr-workflow-state.js +36 -1
  238. package/dist/lib/pr-workflow-state.js.map +1 -1
  239. package/dist/lib/repo-picker.d.ts +3 -0
  240. package/dist/lib/repo-picker.d.ts.map +1 -1
  241. package/dist/lib/repo-picker.js +45 -9
  242. package/dist/lib/repo-picker.js.map +1 -1
  243. package/dist/lib/repository-guidance.d.ts +2 -0
  244. package/dist/lib/repository-guidance.d.ts.map +1 -0
  245. package/dist/lib/repository-guidance.js +55 -0
  246. package/dist/lib/repository-guidance.js.map +1 -0
  247. package/dist/lib/review-models.d.ts +15 -2
  248. package/dist/lib/review-models.d.ts.map +1 -1
  249. package/dist/lib/review-models.js +26 -6
  250. package/dist/lib/review-models.js.map +1 -1
  251. package/dist/lib/review-strategy.d.ts +92 -0
  252. package/dist/lib/review-strategy.d.ts.map +1 -0
  253. package/dist/lib/review-strategy.js +282 -0
  254. package/dist/lib/review-strategy.js.map +1 -0
  255. package/dist/lib/runner.d.ts +92 -0
  256. package/dist/lib/runner.d.ts.map +1 -1
  257. package/dist/lib/runner.js +470 -54
  258. package/dist/lib/runner.js.map +1 -1
  259. package/dist/lib/workflow.d.ts +9 -0
  260. package/dist/lib/workflow.d.ts.map +1 -1
  261. package/dist/lib/workflow.js +20 -0
  262. package/dist/lib/workflow.js.map +1 -1
  263. package/dist/linear/client.d.ts +18 -0
  264. package/dist/linear/client.d.ts.map +1 -0
  265. package/dist/linear/client.js +67 -0
  266. package/dist/linear/client.js.map +1 -0
  267. package/dist/linear/comment.d.ts +20 -0
  268. package/dist/linear/comment.d.ts.map +1 -0
  269. package/dist/linear/comment.js +57 -0
  270. package/dist/linear/comment.js.map +1 -0
  271. package/dist/linear/identity.d.ts +59 -0
  272. package/dist/linear/identity.d.ts.map +1 -0
  273. package/dist/linear/identity.js +187 -0
  274. package/dist/linear/identity.js.map +1 -0
  275. package/dist/linear/notify.d.ts +35 -0
  276. package/dist/linear/notify.d.ts.map +1 -0
  277. package/dist/linear/notify.js +76 -0
  278. package/dist/linear/notify.js.map +1 -0
  279. package/dist/linear/ref.d.ts +13 -0
  280. package/dist/linear/ref.d.ts.map +1 -0
  281. package/dist/linear/ref.js +90 -0
  282. package/dist/linear/ref.js.map +1 -0
  283. package/dist/linear/verify.d.ts +26 -0
  284. package/dist/linear/verify.d.ts.map +1 -0
  285. package/dist/linear/verify.js +67 -0
  286. package/dist/linear/verify.js.map +1 -0
  287. package/dist/reviewers/claude.d.ts +4 -1
  288. package/dist/reviewers/claude.d.ts.map +1 -1
  289. package/dist/reviewers/claude.js +39 -7
  290. package/dist/reviewers/claude.js.map +1 -1
  291. package/dist/reviewers/codex.d.ts +3 -1
  292. package/dist/reviewers/codex.d.ts.map +1 -1
  293. package/dist/reviewers/codex.js +76 -70
  294. package/dist/reviewers/codex.js.map +1 -1
  295. package/dist/reviewers/conflict-resolve.d.ts +3 -1
  296. package/dist/reviewers/conflict-resolve.d.ts.map +1 -1
  297. package/dist/reviewers/conflict-resolve.js +21 -6
  298. package/dist/reviewers/conflict-resolve.js.map +1 -1
  299. package/dist/reviewers/fix.d.ts +5 -2
  300. package/dist/reviewers/fix.d.ts.map +1 -1
  301. package/dist/reviewers/fix.js +26 -10
  302. package/dist/reviewers/fix.js.map +1 -1
  303. package/dist/skills/attribution.d.ts +4 -0
  304. package/dist/skills/attribution.d.ts.map +1 -0
  305. package/dist/skills/attribution.js +14 -0
  306. package/dist/skills/attribution.js.map +1 -0
  307. package/dist/skills/broker-server.d.ts +2 -0
  308. package/dist/skills/broker-server.d.ts.map +1 -0
  309. package/dist/skills/broker-server.js +17 -0
  310. package/dist/skills/broker-server.js.map +1 -0
  311. package/dist/skills/broker.d.ts +42 -0
  312. package/dist/skills/broker.d.ts.map +1 -0
  313. package/dist/skills/broker.js +285 -0
  314. package/dist/skills/broker.js.map +1 -0
  315. package/dist/skills/catalog.d.ts +28 -0
  316. package/dist/skills/catalog.d.ts.map +1 -0
  317. package/dist/skills/catalog.js +104 -0
  318. package/dist/skills/catalog.js.map +1 -0
  319. package/dist/skills/installer.d.ts +10 -0
  320. package/dist/skills/installer.d.ts.map +1 -0
  321. package/dist/skills/installer.js +138 -0
  322. package/dist/skills/installer.js.map +1 -0
  323. package/dist/skills/integrity.d.ts +4 -0
  324. package/dist/skills/integrity.d.ts.map +1 -0
  325. package/dist/skills/integrity.js +36 -0
  326. package/dist/skills/integrity.js.map +1 -0
  327. package/docs/dynamic-thoroughness.md +738 -0
  328. package/docs/linear-identity-contract.md +139 -0
  329. package/docs/linear-identity.md +293 -0
  330. package/get-started.md +223 -11
  331. package/package.json +4 -3
@@ -0,0 +1,144 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { notifyLinear, issueBelongsToWorkspace } from '../linear/notify.js';
3
+ import { LinearConfigSchema } from '../config/schema.js';
4
+ const AUTH = {
5
+ mode: 'client_credentials',
6
+ token: 'minted',
7
+ bearer: true,
8
+ actor: 'crosscheck',
9
+ signature: '🤖 crosscheck · crosscheck',
10
+ signatureTemplate: '🤖 {actor} · {product}',
11
+ product: 'crosscheck',
12
+ createAsUser: 'crosscheck',
13
+ };
14
+ const PR = {
15
+ branch: 'feat/in-2269-identity',
16
+ title: 'feat: tiered Linear identity',
17
+ body: '',
18
+ url: 'https://github.com/acme/app/pull/12',
19
+ sha: 'abc1234',
20
+ };
21
+ const BASE = { auth: AUTH, pr: PR, verdict: 'NEEDS_WORK', reviewer: 'codex', origin: 'claude', model: 'gpt-5' };
22
+ function cfg(overrides = {}) {
23
+ return LinearConfigSchema.parse({ enabled: true, team_keys: ['IN'], ...overrides });
24
+ }
25
+ /** Replies to the issue lookup, then the comment mutation. */
26
+ function scriptedFetch(issueNodes, mutation) {
27
+ let call = 0;
28
+ return vi.fn(async () => {
29
+ call++;
30
+ if (call === 1)
31
+ return new Response(JSON.stringify({ data: { issues: { nodes: issueNodes } } }), { status: 200 });
32
+ return new Response(JSON.stringify({
33
+ data: mutation ?? { commentCreate: { success: true, comment: { id: 'c1', url: 'https://linear.app/x/c1' } } },
34
+ }), { status: 200 });
35
+ });
36
+ }
37
+ const FOUND = [{ id: 'uuid-1', identifier: 'IN-2269', url: 'https://linear.app/x/issue/IN-2269' }];
38
+ describe('notifyLinear', () => {
39
+ it('posts to the issue referenced by the branch', async () => {
40
+ const fetchImpl = scriptedFetch(FOUND);
41
+ const result = await notifyLinear({ ...BASE, config: cfg() }, { fetchImpl });
42
+ expect(result).toEqual({ status: 'posted', identifier: 'IN-2269', url: 'https://linear.app/x/c1' });
43
+ });
44
+ it('sends a body that leads with the signature and carries createAsUser', async () => {
45
+ const fetchImpl = scriptedFetch(FOUND);
46
+ await notifyLinear({ ...BASE, config: cfg() }, { fetchImpl });
47
+ const calls = fetchImpl.mock.calls;
48
+ const mutation = JSON.parse(calls[1][1].body);
49
+ expect(mutation.variables.createAsUser).toBe('crosscheck');
50
+ expect(mutation.variables.body.split('\n')[0]).toBe('🤖 crosscheck · crosscheck');
51
+ });
52
+ it('skips when the verdict is not in comment_on', async () => {
53
+ const fetchImpl = scriptedFetch(FOUND);
54
+ const result = await notifyLinear({ ...BASE, config: cfg({ comment_on: ['BLOCK'] }) }, { fetchImpl });
55
+ expect(result).toEqual({ status: 'skipped', reason: 'verdict-not-configured' });
56
+ expect(fetchImpl).not.toHaveBeenCalled();
57
+ });
58
+ it('skips when the PR references no Linear issue', async () => {
59
+ const fetchImpl = scriptedFetch(FOUND);
60
+ const pr = { ...PR, branch: 'feat/no-ref', title: 'chore: bump', body: '' };
61
+ const result = await notifyLinear({ ...BASE, pr, config: cfg() }, { fetchImpl });
62
+ expect(result).toEqual({ status: 'skipped', reason: 'no-issue-ref' });
63
+ expect(fetchImpl).not.toHaveBeenCalled();
64
+ });
65
+ it('skips when the referenced issue does not exist', async () => {
66
+ const result = await notifyLinear({ ...BASE, config: cfg() }, { fetchImpl: scriptedFetch([]) });
67
+ expect(result).toEqual({ status: 'skipped', reason: 'issue-not-found', identifier: 'IN-2269' });
68
+ });
69
+ it('reports failure instead of throwing when the API errors', async () => {
70
+ const fetchImpl = vi.fn(async () => new Response('{}', { status: 500 }));
71
+ const result = await notifyLinear({ ...BASE, config: cfg() }, { fetchImpl });
72
+ expect(result.status).toBe('failed');
73
+ expect(result.reason).toContain('500');
74
+ });
75
+ it('reports failure when the mutation is rejected', async () => {
76
+ const fetchImpl = scriptedFetch(FOUND, { commentCreate: { success: false, comment: null } });
77
+ const result = await notifyLinear({ ...BASE, config: cfg() }, { fetchImpl });
78
+ expect(result.status).toBe('failed');
79
+ expect(result.reason).toMatch(/rejected/i);
80
+ });
81
+ it('never leaks the token in a failure reason', async () => {
82
+ const fetchImpl = vi.fn(async () => new Response('{}', { status: 401 }));
83
+ const result = await notifyLinear({ ...BASE, config: cfg() }, { fetchImpl });
84
+ expect(result.reason).not.toContain('minted');
85
+ });
86
+ it('follows a linear.app URL in the body when no team_keys are configured', async () => {
87
+ const pr = { ...PR, branch: 'feat/nope', title: 'chore', body: 'https://linear.app/x/issue/IN-2269/z' };
88
+ const result = await notifyLinear({ ...BASE, pr, config: cfg({ team_keys: [] }) }, { fetchImpl: scriptedFetch(FOUND) });
89
+ expect(result.status).toBe('posted');
90
+ });
91
+ });
92
+ describe('workspace mismatch', () => {
93
+ const scoped = (issueUrl) => {
94
+ let call = 0;
95
+ return vi.fn(async () => {
96
+ call++;
97
+ if (call === 1) {
98
+ return new Response(JSON.stringify({
99
+ data: { issues: { nodes: [{ id: 'u1', identifier: 'IN-2269', url: issueUrl }] } },
100
+ }), { status: 200 });
101
+ }
102
+ return new Response(JSON.stringify({
103
+ data: { commentCreate: { success: true, comment: { id: 'c1', url: 'https://linear.app/x/c1' } } },
104
+ }), { status: 200 });
105
+ });
106
+ };
107
+ const prWithUrl = (url) => ({ ...PR, branch: 'feat/none', title: 'chore', body: url });
108
+ it('posts when the resolved issue is in the URL-named workspace', async () => {
109
+ const fetchImpl = scoped('https://linear.app/acme/issue/IN-2269/slug');
110
+ const result = await notifyLinear({ ...BASE, pr: prWithUrl('https://linear.app/acme/issue/IN-2269'), config: cfg({ team_keys: [] }) }, { fetchImpl });
111
+ expect(result.status).toBe('posted');
112
+ });
113
+ it('skips when the credentials resolve a different workspace', async () => {
114
+ // Same identifier, different workspace — a real issue, but not the one meant.
115
+ const fetchImpl = scoped('https://linear.app/other-corp/issue/IN-2269/slug');
116
+ const result = await notifyLinear({ ...BASE, pr: prWithUrl('https://linear.app/acme/issue/IN-2269'), config: cfg({ team_keys: [] }) }, { fetchImpl });
117
+ expect(result).toEqual({ status: 'skipped', reason: 'workspace-mismatch', identifier: 'IN-2269' });
118
+ });
119
+ it('does not post the comment when the workspace mismatches', async () => {
120
+ const fetchImpl = scoped('https://linear.app/other-corp/issue/IN-2269/slug');
121
+ await notifyLinear({ ...BASE, pr: prWithUrl('https://linear.app/acme/issue/IN-2269'), config: cfg({ team_keys: [] }) }, { fetchImpl });
122
+ expect(fetchImpl.mock.calls).toHaveLength(1);
123
+ });
124
+ it('does not constrain a bare ref, which names no workspace', async () => {
125
+ const fetchImpl = scoped('https://linear.app/whatever/issue/IN-2269/slug');
126
+ const result = await notifyLinear({ ...BASE, config: cfg() }, { fetchImpl });
127
+ expect(result.status).toBe('posted');
128
+ });
129
+ });
130
+ describe('issueBelongsToWorkspace', () => {
131
+ it('matches the first path segment', () => {
132
+ expect(issueBelongsToWorkspace('https://linear.app/acme/issue/IN-1', 'acme')).toBe(true);
133
+ });
134
+ it('is case insensitive', () => {
135
+ expect(issueBelongsToWorkspace('https://linear.app/ACME/issue/IN-1', 'acme')).toBe(true);
136
+ });
137
+ it('rejects a different workspace', () => {
138
+ expect(issueBelongsToWorkspace('https://linear.app/other/issue/IN-1', 'acme')).toBe(false);
139
+ });
140
+ it('fails closed on an unparseable URL', () => {
141
+ expect(issueBelongsToWorkspace('not a url', 'acme')).toBe(false);
142
+ });
143
+ });
144
+ //# sourceMappingURL=linear-notify.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linear-notify.test.js","sourceRoot":"","sources":["../../src/__tests__/linear-notify.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=linear-ref.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linear-ref.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/linear-ref.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,261 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { extractLinearRef } from '../linear/ref.js';
3
+ describe('extractLinearRef', () => {
4
+ describe('Linear URLs (no team_keys needed)', () => {
5
+ it('finds an issue URL in the PR body', () => {
6
+ const ref = extractLinearRef({ body: 'Closes https://linear.app/inductive-network/issue/IN-2269/some-slug' }, []);
7
+ expect(ref).toEqual({ id: 'IN-2269', key: 'IN', number: 2269, source: 'body', workspace: 'inductive-network' });
8
+ });
9
+ it('finds an issue URL in the PR title', () => {
10
+ const ref = extractLinearRef({ title: 'fix: see linear.app/acme/issue/ENG-7/thing' }, []);
11
+ expect(ref?.id).toBe('ENG-7');
12
+ expect(ref?.source).toBe('title');
13
+ });
14
+ it('accepts a URL with no trailing slug', () => {
15
+ const ref = extractLinearRef({ body: 'https://linear.app/acme/issue/AB-12' }, []);
16
+ expect(ref?.id).toBe('AB-12');
17
+ });
18
+ });
19
+ describe('bare identifiers require configured team_keys', () => {
20
+ it('ignores bare identifiers when team_keys is empty', () => {
21
+ expect(extractLinearRef({ branch: 'feat/in-2269-thing', title: 'IN-2269 fix' }, [])).toBeNull();
22
+ });
23
+ it('matches a configured key in a branch name, case-insensitively', () => {
24
+ const ref = extractLinearRef({ branch: 'feat/in-2269-tiered-identity' }, ['IN']);
25
+ expect(ref).toEqual({ id: 'IN-2269', key: 'IN', number: 2269, source: 'branch' });
26
+ });
27
+ it('matches an uppercase key in a title', () => {
28
+ const ref = extractLinearRef({ title: 'IN-543: unified resolver' }, ['IN']);
29
+ expect(ref?.id).toBe('IN-543');
30
+ expect(ref?.source).toBe('title');
31
+ });
32
+ it('does not match a key that is not configured', () => {
33
+ expect(extractLinearRef({ branch: 'feat/eng-1-thing' }, ['IN'])).toBeNull();
34
+ });
35
+ });
36
+ describe('false-positive resistance', () => {
37
+ // The reason bare-identifier matching is gated behind team_keys: these all
38
+ // look exactly like Linear identifiers to a naive regex.
39
+ const traps = ['UTF-8', 'SHA-256', 'ISO-8601', 'GPT-5', 'RFC-2119'];
40
+ for (const trap of traps) {
41
+ it(`does not treat ${trap} as an issue ref`, () => {
42
+ expect(extractLinearRef({ title: `chore: switch to ${trap}` }, [])).toBeNull();
43
+ });
44
+ }
45
+ it('still ignores traps when unrelated team_keys are configured', () => {
46
+ expect(extractLinearRef({ title: 'chore: SHA-256 hashing' }, ['IN'])).toBeNull();
47
+ });
48
+ it('requires a word boundary so SUBIN-1 does not match key IN', () => {
49
+ expect(extractLinearRef({ title: 'SUBIN-1 nope' }, ['IN'])).toBeNull();
50
+ });
51
+ });
52
+ describe('precedence', () => {
53
+ it('prefers branch over title and body', () => {
54
+ const ref = extractLinearRef({ branch: 'feat/in-1-a', title: 'IN-2 b', body: 'IN-3 c' }, ['IN']);
55
+ expect(ref?.id).toBe('IN-1');
56
+ expect(ref?.source).toBe('branch');
57
+ });
58
+ it('prefers title over body', () => {
59
+ const ref = extractLinearRef({ title: 'IN-2 b', body: 'IN-3 c' }, ['IN']);
60
+ expect(ref?.id).toBe('IN-2');
61
+ });
62
+ it('prefers an unambiguous URL over a bare key in the same field', () => {
63
+ const ref = extractLinearRef({ body: 'IN-99 mentioned, real one: https://linear.app/x/issue/IN-2269/z' }, ['IN']);
64
+ expect(ref?.id).toBe('IN-2269');
65
+ });
66
+ });
67
+ it('returns null when nothing matches', () => {
68
+ expect(extractLinearRef({ branch: 'main', title: 'chore: bump', body: '' }, ['IN'])).toBeNull();
69
+ });
70
+ it('handles absent fields', () => {
71
+ expect(extractLinearRef({}, ['IN'])).toBeNull();
72
+ });
73
+ });
74
+ describe('hostname anchoring', () => {
75
+ // Regression: an unanchored `linear.app/` fragment let any domain ending in
76
+ // "linear.app" bypass the team_keys gate and target an arbitrary issue.
77
+ const impostors = [
78
+ 'https://notlinear.app/acme/issue/IN-2269',
79
+ 'https://evil-linear.app/acme/issue/IN-2269',
80
+ 'https://mylinear.app/x/issue/IN-1/slug',
81
+ ];
82
+ for (const url of impostors) {
83
+ it(`does not trust ${url.replace('https://', '').split('/')[0]}`, () => {
84
+ expect(extractLinearRef({ body: url }, [])).toBeNull();
85
+ });
86
+ }
87
+ it('still trusts the real host', () => {
88
+ expect(extractLinearRef({ body: 'https://linear.app/acme/issue/IN-2269' }, [])?.id).toBe('IN-2269');
89
+ });
90
+ it('trusts it bare, without a scheme', () => {
91
+ expect(extractLinearRef({ body: 'see linear.app/acme/issue/IN-7' }, [])?.id).toBe('IN-7');
92
+ });
93
+ it('trusts it at the very start of a field', () => {
94
+ expect(extractLinearRef({ body: 'linear.app/acme/issue/IN-8' }, [])?.id).toBe('IN-8');
95
+ });
96
+ it('falls through to team_keys when an impostor URL is present', () => {
97
+ // The impostor must not shadow a legitimate bare ref elsewhere in the text.
98
+ const ref = extractLinearRef({ body: 'https://notlinear.app/x/issue/IN-999 but really IN-42' }, ['IN']);
99
+ expect(ref?.id).toBe('IN-999');
100
+ });
101
+ });
102
+ describe('hostname is parsed, not pattern-matched', () => {
103
+ // A path segment named linear.app is not the linear.app host. A boundary check
104
+ // on the surrounding character passes here; only real parsing rejects it.
105
+ const impostors = [
106
+ 'https://evil.example/linear.app/acme/issue/IN-2269',
107
+ 'https://evil.example/?u=https://linear.app/acme/issue/IN-2269',
108
+ 'https://linear.app.evil.example/acme/issue/IN-2269',
109
+ 'https://user@evil.example/linear.app/acme/issue/IN-1',
110
+ ];
111
+ for (const url of impostors) {
112
+ it(`rejects ${url.slice(8, 40)}...`, () => {
113
+ expect(extractLinearRef({ body: url }, [])).toBeNull();
114
+ });
115
+ }
116
+ it('accepts the real host over https', () => {
117
+ expect(extractLinearRef({ body: 'https://linear.app/acme/issue/IN-2269/slug' }, [])?.id).toBe('IN-2269');
118
+ });
119
+ it('accepts the real host case-insensitively', () => {
120
+ expect(extractLinearRef({ body: 'https://LINEAR.APP/acme/issue/IN-3' }, [])?.id).toBe('IN-3');
121
+ });
122
+ it('ignores a malformed URL without throwing', () => {
123
+ expect(extractLinearRef({ body: 'https://[not-a-url/issue/IN-9' }, [])).toBeNull();
124
+ });
125
+ it('finds a real URL after an impostor one', () => {
126
+ const body = 'https://evil.example/linear.app/x/issue/IN-1 and https://linear.app/acme/issue/IN-42';
127
+ expect(extractLinearRef({ body }, [])?.id).toBe('IN-42');
128
+ });
129
+ });
130
+ describe('workspace and issue-number boundaries', () => {
131
+ // Identifiers are unique only within a workspace, so an explicit URL must carry
132
+ // its slug — otherwise a URL for workspace A resolves against credentials for
133
+ // workspace B and comments on B's same-numbered issue.
134
+ it('carries the workspace slug from an absolute URL', () => {
135
+ const ref = extractLinearRef({ body: 'https://linear.app/acme/issue/IN-42' }, []);
136
+ expect(ref?.workspace).toBe('acme');
137
+ });
138
+ it('carries it from a scheme-less URL', () => {
139
+ expect(extractLinearRef({ body: 'see linear.app/beta-corp/issue/IN-42' }, [])?.workspace).toBe('beta-corp');
140
+ });
141
+ it('lower-cases the slug for comparison', () => {
142
+ expect(extractLinearRef({ body: 'https://linear.app/ACME/issue/IN-42' }, [])?.workspace).toBe('acme');
143
+ });
144
+ it('leaves a bare ref with no workspace, since it names none', () => {
145
+ const ref = extractLinearRef({ branch: 'feat/in-42-x' }, ['IN']);
146
+ expect(ref?.id).toBe('IN-42');
147
+ expect(ref?.workspace).toBeUndefined();
148
+ });
149
+ // Without a boundary after the digits, /issue/IN-123abc matched the IN-123
150
+ // prefix and could post to an unrelated issue while bypassing team_keys.
151
+ it('rejects a malformed identifier with a trailing suffix', () => {
152
+ expect(extractLinearRef({ body: 'https://linear.app/acme/issue/IN-123abc' }, [])).toBeNull();
153
+ });
154
+ it('accepts an identifier followed by a slug', () => {
155
+ expect(extractLinearRef({ body: 'https://linear.app/acme/issue/IN-123/some-slug' }, [])?.id).toBe('IN-123');
156
+ });
157
+ it('accepts an identifier at the end of the path', () => {
158
+ expect(extractLinearRef({ body: 'https://linear.app/acme/issue/IN-123' }, [])?.id).toBe('IN-123');
159
+ });
160
+ it('rejects a suffixed identifier in the scheme-less form too', () => {
161
+ expect(extractLinearRef({ body: 'see linear.app/acme/issue/IN-123abc' }, [])).toBeNull();
162
+ });
163
+ });
164
+ describe('punctuation after a URL', () => {
165
+ // A ref at the end of a sentence is still a ref. Requiring a slash or the exact
166
+ // end of the path silently missed these.
167
+ const cases = [
168
+ ['Fixes https://linear.app/acme/issue/IN-42.', 'IN-42'],
169
+ ['See https://linear.app/acme/issue/IN-42, then merge', 'IN-42'],
170
+ ['(https://linear.app/acme/issue/IN-42)', 'IN-42'],
171
+ ['see linear.app/acme/issue/IN-42.', 'IN-42'],
172
+ ['see linear.app/acme/issue/IN-42, ok', 'IN-42'],
173
+ ];
174
+ for (const [body, expected] of cases) {
175
+ it(`resolves ${expected} from ${JSON.stringify(body.slice(0, 46))}`, () => {
176
+ expect(extractLinearRef({ body }, [])?.id).toBe(expected);
177
+ });
178
+ }
179
+ it('still rejects an alphanumeric suffix', () => {
180
+ expect(extractLinearRef({ body: 'https://linear.app/acme/issue/IN-123abc' }, [])).toBeNull();
181
+ expect(extractLinearRef({ body: 'see linear.app/acme/issue/IN-123abc' }, [])).toBeNull();
182
+ });
183
+ it('still rejects extra digits', () => {
184
+ expect(extractLinearRef({ body: 'https://linear.app/acme/issue/IN-12/x' }, [])?.id).toBe('IN-12');
185
+ });
186
+ });
187
+ describe('the identifier must be a whole path segment', () => {
188
+ // Two lookahead attempts got this wrong in opposite directions: requiring a
189
+ // slash missed "IN-42." at the end of a sentence; allowing any non-alphanumeric
190
+ // accepted "IN-123-typo" as IN-123. The segment is now parsed whole.
191
+ const rejected = [
192
+ 'https://linear.app/acme/issue/IN-123-typo',
193
+ 'https://linear.app/acme/issue/IN-123abc',
194
+ 'https://linear.app/acme/issue/IN-123_x',
195
+ 'see linear.app/acme/issue/IN-123-typo',
196
+ ];
197
+ for (const url of rejected) {
198
+ it(`rejects ${url.split('/issue/')[1]}`, () => {
199
+ expect(extractLinearRef({ body: url }, [])).toBeNull();
200
+ });
201
+ }
202
+ const accepted = [
203
+ ['https://linear.app/acme/issue/IN-42', 'IN-42'],
204
+ ['https://linear.app/acme/issue/IN-42/some-slug', 'IN-42'],
205
+ ['Fixes https://linear.app/acme/issue/IN-42.', 'IN-42'],
206
+ ['See https://linear.app/acme/issue/IN-42, then merge', 'IN-42'],
207
+ ['(https://linear.app/acme/issue/IN-42)', 'IN-42'],
208
+ ['see linear.app/acme/issue/IN-42.', 'IN-42'],
209
+ ];
210
+ for (const [body, expected] of accepted) {
211
+ it(`accepts ${JSON.stringify(body.slice(0, 44))}`, () => {
212
+ expect(extractLinearRef({ body }, [])?.id).toBe(expected);
213
+ });
214
+ }
215
+ });
216
+ describe('a malformed candidate does not hide a valid one', () => {
217
+ it('skips a malformed scheme-less URL and finds the later valid one', () => {
218
+ const body = 'linear.app/acme/issue/IN-bad and then linear.app/acme/issue/IN-42';
219
+ expect(extractLinearRef({ body }, [])?.id).toBe('IN-42');
220
+ });
221
+ it('skips a malformed absolute URL and finds the later valid one', () => {
222
+ const body = 'https://linear.app/acme/issue/IN-bad https://linear.app/acme/issue/IN-42';
223
+ expect(extractLinearRef({ body }, [])?.id).toBe('IN-42');
224
+ });
225
+ it('returns null when every candidate is malformed', () => {
226
+ expect(extractLinearRef({ body: 'linear.app/a/issue/IN-bad linear.app/b/issue/X' }, [])).toBeNull();
227
+ });
228
+ it('keeps the workspace of the candidate that actually resolved', () => {
229
+ const body = 'linear.app/wrong/issue/IN-bad linear.app/right/issue/IN-42';
230
+ expect(extractLinearRef({ body }, [])?.workspace).toBe('right');
231
+ });
232
+ });
233
+ describe('punctuated scheme-less URLs keep their workspace', () => {
234
+ // Regression: `(linear.app/...)` missed the URL matcher, fell through to the
235
+ // bare-ID extractor, and came back without a workspace — silently losing the
236
+ // workspace-mismatch protection that an explicit URL is supposed to carry.
237
+ const wrapped = ['(linear.app/acme/issue/IN-42)', '[linear.app/acme/issue/IN-42]', '"linear.app/acme/issue/IN-42"', 'see: linear.app/acme/issue/IN-42'];
238
+ for (const body of wrapped) {
239
+ it(`keeps the workspace for ${JSON.stringify(body.slice(0, 32))}`, () => {
240
+ const ref = extractLinearRef({ body }, ['IN']);
241
+ expect(ref?.id).toBe('IN-42');
242
+ expect(ref?.workspace).toBe('acme');
243
+ });
244
+ }
245
+ it('still refuses a lookalike host after punctuation', () => {
246
+ expect(extractLinearRef({ body: '(notlinear.app/acme/issue/IN-42)' }, [])).toBeNull();
247
+ });
248
+ });
249
+ describe('one-character team keys', () => {
250
+ // Linear permits them; requiring two silently skipped those workspaces.
251
+ it('resolves a one-character key from a URL', () => {
252
+ expect(extractLinearRef({ body: 'https://linear.app/acme/issue/X-42' }, [])?.id).toBe('X-42');
253
+ });
254
+ it('resolves a one-character key as a configured bare ref', () => {
255
+ expect(extractLinearRef({ branch: 'feat/x-42-thing' }, ['X'])?.id).toBe('X-42');
256
+ });
257
+ it('still rejects a bare ref whose key is not configured', () => {
258
+ expect(extractLinearRef({ branch: 'feat/y-42-thing' }, ['X'])).toBeNull();
259
+ });
260
+ });
261
+ //# sourceMappingURL=linear-ref.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linear-ref.test.js","sourceRoot":"","sources":["../../src/__tests__/linear-ref.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=linear-test-ref.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linear-test-ref.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/linear-test-ref.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,81 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { resolveTestRef, softWrap } from '../commands/linear-test.js';
3
+ describe('resolveTestRef', () => {
4
+ it('takes a named issue verbatim, upper-cased', () => {
5
+ expect(resolveTestRef('eng-42', {}, [])).toEqual({ id: 'ENG-42', source: 'argument' });
6
+ });
7
+ it('lets a named issue bypass the team_keys gate', () => {
8
+ // Typing the ref is unambiguous in a way that finding it in a branch is not,
9
+ // so `crosscheck linear-test ENG-42` works before team_keys is configured.
10
+ expect(resolveTestRef('ENG-42', {}, [])).toEqual({ id: 'ENG-42', source: 'argument' });
11
+ });
12
+ it('prefers the named issue over anything inferable', () => {
13
+ expect(resolveTestRef('ENG-1', { branch: 'feat/in-2269-x' }, ['IN']))
14
+ .toEqual({ id: 'ENG-1', source: 'argument' });
15
+ });
16
+ it('falls back to the branch when no issue is named', () => {
17
+ expect(resolveTestRef(undefined, { branch: 'feat/in-2269-x' }, ['IN']))
18
+ .toEqual({ id: 'IN-2269', source: 'branch' });
19
+ });
20
+ it('falls back to the title', () => {
21
+ expect(resolveTestRef(undefined, { title: 'IN-543: resolver' }, ['IN']))
22
+ .toEqual({ id: 'IN-543', source: 'title' });
23
+ });
24
+ it('still honours the team_keys gate for inferred refs', () => {
25
+ expect(resolveTestRef(undefined, { branch: 'feat/in-2269-x' }, [])).toBeNull();
26
+ });
27
+ it('returns null with nothing to go on', () => {
28
+ expect(resolveTestRef(undefined, {}, ['IN'])).toBeNull();
29
+ });
30
+ it('treats a blank argument as absent', () => {
31
+ expect(resolveTestRef(' ', { branch: 'feat/in-7-x' }, ['IN']))
32
+ .toEqual({ id: 'IN-7', source: 'branch' });
33
+ });
34
+ });
35
+ describe('softWrap', () => {
36
+ it('leaves a short line alone', () => {
37
+ expect(softWrap('short', 20)).toEqual(['short']);
38
+ });
39
+ it('wraps on whitespace at the boundary', () => {
40
+ expect(softWrap('aaa bbb ccc ddd', 7)).toEqual(['aaa bbb', 'ccc ddd']);
41
+ });
42
+ it('never breaks a token, even one longer than the width', () => {
43
+ const url = 'https://github.com/acme/app/pull/42';
44
+ expect(softWrap(`see ${url} ok`, 10)).toEqual(['see', url, 'ok']);
45
+ });
46
+ it('wraps the annotation without splitting a field', () => {
47
+ const line = '<!-- crosscheck: origin=claude reviewer=codex model=gpt-5.6-terra type=review round=1 verdict=NEEDS_WORK -->';
48
+ for (const chunk of softWrap(line, 80))
49
+ expect(chunk.length).toBeLessThanOrEqual(80);
50
+ expect(softWrap(line, 80).join(' ')).toBe(line);
51
+ });
52
+ it('preserves content exactly when rejoined', () => {
53
+ const line = 'the quick brown fox jumps over the lazy dog';
54
+ expect(softWrap(line, 12).join(' ')).toBe(line);
55
+ });
56
+ });
57
+ describe('onboard Linear decision — regression guards', () => {
58
+ // These encode the two onboarding defects the recheck found. The prompt itself
59
+ // needs a TTY, so these pin the decision rules the prompt implements.
60
+ it('Enter must map to the current mode, not to off', () => {
61
+ const decide = (answer, currentMode) => answer === '1' ? 'off'
62
+ : answer === '2' ? 'api_key'
63
+ : answer === '3' ? 'client_credentials'
64
+ : currentMode;
65
+ expect(decide('', 'client_credentials')).toBe('client_credentials');
66
+ expect(decide('', 'api_key')).toBe('api_key');
67
+ expect(decide('', 'off')).toBe('off');
68
+ expect(decide('1', 'client_credentials')).toBe('off');
69
+ expect(decide('2', 'off')).toBe('api_key');
70
+ expect(decide('3', 'off')).toBe('client_credentials');
71
+ });
72
+ it('an explicit clear must be distinguishable from Enter', () => {
73
+ const resolveKeys = (answer, current) => answer === '-' ? []
74
+ : answer ? answer.split(',').map(k => k.trim().toUpperCase()).filter(Boolean)
75
+ : current;
76
+ expect(resolveKeys('', ['IN'])).toEqual(['IN']);
77
+ expect(resolveKeys('-', ['IN'])).toEqual([]);
78
+ expect(resolveKeys('eng,ops', ['IN'])).toEqual(['ENG', 'OPS']);
79
+ });
80
+ });
81
+ //# sourceMappingURL=linear-test-ref.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linear-test-ref.test.js","sourceRoot":"","sources":["../../src/__tests__/linear-test-ref.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=linear-verify.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linear-verify.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/linear-verify.test.ts"],"names":[],"mappings":""}