@gitkraken/core-gitlens 0.5.113 → 0.5.115

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 (349) hide show
  1. package/CHANGELOG.md +42 -1
  2. package/dist/git/cache.d.ts +4 -0
  3. package/dist/git/cache.d.ts.map +1 -1
  4. package/dist/git/cache.js +33 -0
  5. package/dist/git/cache.js.map +1 -1
  6. package/dist/git/context.d.ts +10 -0
  7. package/dist/git/context.d.ts.map +1 -1
  8. package/dist/git/context.js.map +1 -1
  9. package/dist/git/errors.d.ts +6 -3
  10. package/dist/git/errors.d.ts.map +1 -1
  11. package/dist/git/errors.js +9 -1
  12. package/dist/git/errors.js.map +1 -1
  13. package/dist/git/features.d.ts +1 -1
  14. package/dist/git/features.d.ts.map +1 -1
  15. package/dist/git/features.js +38 -0
  16. package/dist/git/features.js.map +1 -1
  17. package/dist/git/gitHealth.d.ts +153 -0
  18. package/dist/git/gitHealth.d.ts.map +1 -0
  19. package/dist/git/gitHealth.js +353 -0
  20. package/dist/git/gitHealth.js.map +1 -0
  21. package/dist/git/models/search.d.ts +6 -3
  22. package/dist/git/models/search.d.ts.map +1 -1
  23. package/dist/git/models/search.js +5 -1
  24. package/dist/git/models/search.js.map +1 -1
  25. package/dist/git/parsers/diffParser.d.ts +8 -2
  26. package/dist/git/parsers/diffParser.d.ts.map +1 -1
  27. package/dist/git/parsers/diffParser.js +27 -4
  28. package/dist/git/parsers/diffParser.js.map +1 -1
  29. package/dist/git/providers/config.d.ts +36 -3
  30. package/dist/git/providers/config.d.ts.map +1 -1
  31. package/dist/git/providers/graph.d.ts +9 -0
  32. package/dist/git/providers/graph.d.ts.map +1 -1
  33. package/dist/git/providers/maintenance.d.ts +222 -0
  34. package/dist/git/providers/maintenance.d.ts.map +1 -0
  35. package/dist/git/providers/maintenance.js +20 -0
  36. package/dist/git/providers/maintenance.js.map +1 -0
  37. package/dist/git/providers/operations.d.ts +10 -0
  38. package/dist/git/providers/operations.d.ts.map +1 -1
  39. package/dist/git/providers/pausedOperations.d.ts +3 -0
  40. package/dist/git/providers/pausedOperations.d.ts.map +1 -1
  41. package/dist/git/providers/provider.d.ts +2 -0
  42. package/dist/git/providers/provider.d.ts.map +1 -1
  43. package/dist/git/providers/tags.d.ts +3 -0
  44. package/dist/git/providers/tags.d.ts.map +1 -1
  45. package/dist/git/repositoryService.d.ts +1 -0
  46. package/dist/git/repositoryService.d.ts.map +1 -1
  47. package/dist/git/repositoryService.js.map +1 -1
  48. package/dist/git/run.types.d.ts +8 -0
  49. package/dist/git/run.types.d.ts.map +1 -1
  50. package/dist/git/service.js +3 -0
  51. package/dist/git/service.js.map +1 -1
  52. package/dist/git/utils/search.utils.d.ts.map +1 -1
  53. package/dist/git/utils/search.utils.js +112 -5
  54. package/dist/git/utils/search.utils.js.map +1 -1
  55. package/dist/git/watching/watchService.d.ts +3 -0
  56. package/dist/git/watching/watchService.d.ts.map +1 -1
  57. package/dist/git/watching/watchService.js +8 -0
  58. package/dist/git/watching/watchService.js.map +1 -1
  59. package/dist/git-cli/cliGitProvider.d.ts +11 -0
  60. package/dist/git-cli/cliGitProvider.d.ts.map +1 -1
  61. package/dist/git-cli/cliGitProvider.js +12 -0
  62. package/dist/git-cli/cliGitProvider.js.map +1 -1
  63. package/dist/git-cli/exec/git.d.ts +23 -1
  64. package/dist/git-cli/exec/git.d.ts.map +1 -1
  65. package/dist/git-cli/exec/git.js +83 -8
  66. package/dist/git-cli/exec/git.js.map +1 -1
  67. package/dist/git-cli/exec/gitQueue.d.ts +2 -0
  68. package/dist/git-cli/exec/gitQueue.d.ts.map +1 -1
  69. package/dist/git-cli/exec/gitQueue.js +19 -17
  70. package/dist/git-cli/exec/gitQueue.js.map +1 -1
  71. package/dist/git-cli/parsers/logParser.d.ts.map +1 -1
  72. package/dist/git-cli/parsers/logParser.js +4 -1
  73. package/dist/git-cli/parsers/logParser.js.map +1 -1
  74. package/dist/git-cli/providers/blame.d.ts.map +1 -1
  75. package/dist/git-cli/providers/blame.js +4 -7
  76. package/dist/git-cli/providers/blame.js.map +1 -1
  77. package/dist/git-cli/providers/branches.d.ts.map +1 -1
  78. package/dist/git-cli/providers/branches.js +13 -3
  79. package/dist/git-cli/providers/branches.js.map +1 -1
  80. package/dist/git-cli/providers/commits.d.ts.map +1 -1
  81. package/dist/git-cli/providers/commits.js +34 -3
  82. package/dist/git-cli/providers/commits.js.map +1 -1
  83. package/dist/git-cli/providers/config.d.ts +27 -0
  84. package/dist/git-cli/providers/config.d.ts.map +1 -1
  85. package/dist/git-cli/providers/config.js +31 -6
  86. package/dist/git-cli/providers/config.js.map +1 -1
  87. package/dist/git-cli/providers/graph.d.ts +8 -0
  88. package/dist/git-cli/providers/graph.d.ts.map +1 -1
  89. package/dist/git-cli/providers/graph.js +85 -6
  90. package/dist/git-cli/providers/graph.js.map +1 -1
  91. package/dist/git-cli/providers/maintenance.d.ts +306 -0
  92. package/dist/git-cli/providers/maintenance.d.ts.map +1 -0
  93. package/dist/git-cli/providers/maintenance.js +2129 -0
  94. package/dist/git-cli/providers/maintenance.js.map +1 -0
  95. package/dist/git-cli/providers/operations.d.ts +1 -0
  96. package/dist/git-cli/providers/operations.d.ts.map +1 -1
  97. package/dist/git-cli/providers/operations.js +8 -0
  98. package/dist/git-cli/providers/operations.js.map +1 -1
  99. package/dist/git-cli/providers/pausedOperations.d.ts +1 -0
  100. package/dist/git-cli/providers/pausedOperations.d.ts.map +1 -1
  101. package/dist/git-cli/providers/pausedOperations.js +1 -1
  102. package/dist/git-cli/providers/pausedOperations.js.map +1 -1
  103. package/dist/git-cli/providers/tags.d.ts +3 -0
  104. package/dist/git-cli/providers/tags.d.ts.map +1 -1
  105. package/dist/git-cli/providers/tags.js +23 -1
  106. package/dist/git-cli/providers/tags.js.map +1 -1
  107. package/dist/plus/agents/agentCapabilities.d.ts +77 -0
  108. package/dist/plus/agents/agentCapabilities.d.ts.map +1 -0
  109. package/dist/plus/agents/agentCapabilities.js +197 -0
  110. package/dist/plus/agents/agentCapabilities.js.map +1 -0
  111. package/dist/plus/agents/providers/claudeCodeTranscript.d.ts +77 -13
  112. package/dist/plus/agents/providers/claudeCodeTranscript.d.ts.map +1 -1
  113. package/dist/plus/agents/providers/claudeCodeTranscript.js +263 -55
  114. package/dist/plus/agents/providers/claudeCodeTranscript.js.map +1 -1
  115. package/dist/plus/agents/providers/{claudeCodeProvider.d.ts → gkAgentProvider.d.ts} +52 -19
  116. package/dist/plus/agents/providers/gkAgentProvider.d.ts.map +1 -0
  117. package/dist/plus/agents/providers/{claudeCodeProvider.js → gkAgentProvider.js} +935 -262
  118. package/dist/plus/agents/providers/gkAgentProvider.js.map +1 -0
  119. package/dist/plus/agents/types.d.ts +113 -35
  120. package/dist/plus/agents/types.d.ts.map +1 -1
  121. package/dist/plus/agents/types.js +7 -4
  122. package/dist/plus/agents/types.js.map +1 -1
  123. package/dist/plus/ai/models/promptTemplates.d.ts +1 -1
  124. package/dist/plus/ai/models/promptTemplates.d.ts.map +1 -1
  125. package/dist/plus/ai/prompts.d.ts +4 -0
  126. package/dist/plus/ai/prompts.d.ts.map +1 -1
  127. package/dist/plus/ai/prompts.js +59 -4
  128. package/dist/plus/ai/prompts.js.map +1 -1
  129. package/dist/plus/git-github/api/github.d.ts +44 -1
  130. package/dist/plus/git-github/api/github.d.ts.map +1 -1
  131. package/dist/plus/git-github/api/github.js +355 -62
  132. package/dist/plus/git-github/api/github.js.map +1 -1
  133. package/dist/plus/git-github/api/issueSearchQuery.d.ts +13 -0
  134. package/dist/plus/git-github/api/issueSearchQuery.d.ts.map +1 -1
  135. package/dist/plus/git-github/api/issueSearchQuery.js +41 -0
  136. package/dist/plus/git-github/api/issueSearchQuery.js.map +1 -1
  137. package/dist/plus/git-github/providers/github/blame.d.ts.map +1 -1
  138. package/dist/plus/git-github/providers/github/blame.js +2 -1
  139. package/dist/plus/git-github/providers/github/blame.js.map +1 -1
  140. package/dist/plus/integrations/collectionMetadata.d.ts +2 -0
  141. package/dist/plus/integrations/collectionMetadata.d.ts.map +1 -1
  142. package/dist/plus/integrations/collectionMetadata.js +17 -1
  143. package/dist/plus/integrations/collectionMetadata.js.map +1 -1
  144. package/dist/plus/integrations/context.d.ts +4 -1
  145. package/dist/plus/integrations/context.d.ts.map +1 -1
  146. package/dist/plus/integrations/index.d.ts +1 -0
  147. package/dist/plus/integrations/index.d.ts.map +1 -1
  148. package/dist/plus/integrations/integrationService.d.ts +20 -1
  149. package/dist/plus/integrations/integrationService.d.ts.map +1 -1
  150. package/dist/plus/integrations/integrationService.js +12 -4
  151. package/dist/plus/integrations/integrationService.js.map +1 -1
  152. package/dist/plus/integrations/manager.d.ts +48 -0
  153. package/dist/plus/integrations/manager.d.ts.map +1 -1
  154. package/dist/plus/integrations/models/gitHostIntegration.d.ts +53 -26
  155. package/dist/plus/integrations/models/gitHostIntegration.d.ts.map +1 -1
  156. package/dist/plus/integrations/models/gitHostIntegration.js +123 -47
  157. package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
  158. package/dist/plus/integrations/models/integration.d.ts +10 -1
  159. package/dist/plus/integrations/models/integration.d.ts.map +1 -1
  160. package/dist/plus/integrations/models/integration.js +52 -19
  161. package/dist/plus/integrations/models/integration.js.map +1 -1
  162. package/dist/plus/integrations/models/issueCache.d.ts +16 -0
  163. package/dist/plus/integrations/models/issueCache.d.ts.map +1 -0
  164. package/dist/plus/integrations/models/issueCache.js +9 -0
  165. package/dist/plus/integrations/models/issueCache.js.map +1 -0
  166. package/dist/plus/integrations/models/issueReads.d.ts +13 -0
  167. package/dist/plus/integrations/models/issueReads.d.ts.map +1 -1
  168. package/dist/plus/integrations/models/issuesIntegration.d.ts +11 -13
  169. package/dist/plus/integrations/models/issuesIntegration.d.ts.map +1 -1
  170. package/dist/plus/integrations/models/issuesIntegration.js +15 -1
  171. package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
  172. package/dist/plus/integrations/providers/azureDevOps.d.ts +3 -5
  173. package/dist/plus/integrations/providers/azureDevOps.d.ts.map +1 -1
  174. package/dist/plus/integrations/providers/azureDevOps.js +9 -8
  175. package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
  176. package/dist/plus/integrations/providers/bitbucket-server.d.ts +3 -5
  177. package/dist/plus/integrations/providers/bitbucket-server.d.ts.map +1 -1
  178. package/dist/plus/integrations/providers/bitbucket-server.js +2 -2
  179. package/dist/plus/integrations/providers/bitbucket-server.js.map +1 -1
  180. package/dist/plus/integrations/providers/bitbucket.d.ts +3 -5
  181. package/dist/plus/integrations/providers/bitbucket.d.ts.map +1 -1
  182. package/dist/plus/integrations/providers/bitbucket.js +2 -1
  183. package/dist/plus/integrations/providers/bitbucket.js.map +1 -1
  184. package/dist/plus/integrations/providers/github.d.ts +12 -5
  185. package/dist/plus/integrations/providers/github.d.ts.map +1 -1
  186. package/dist/plus/integrations/providers/github.js +10 -3
  187. package/dist/plus/integrations/providers/github.js.map +1 -1
  188. package/dist/plus/integrations/providers/gitlab.d.ts +3 -5
  189. package/dist/plus/integrations/providers/gitlab.d.ts.map +1 -1
  190. package/dist/plus/integrations/providers/gitlab.js +2 -2
  191. package/dist/plus/integrations/providers/gitlab.js.map +1 -1
  192. package/dist/plus/integrations/providers/jira.d.ts +3 -7
  193. package/dist/plus/integrations/providers/jira.d.ts.map +1 -1
  194. package/dist/plus/integrations/providers/jira.js +35 -22
  195. package/dist/plus/integrations/providers/jira.js.map +1 -1
  196. package/dist/plus/integrations/providers/jiraIssueByKey.d.ts +7 -0
  197. package/dist/plus/integrations/providers/jiraIssueByKey.d.ts.map +1 -0
  198. package/dist/plus/integrations/providers/jiraIssueByKey.js +102 -0
  199. package/dist/plus/integrations/providers/jiraIssueByKey.js.map +1 -0
  200. package/dist/plus/integrations/providers/linear.d.ts +3 -7
  201. package/dist/plus/integrations/providers/linear.d.ts.map +1 -1
  202. package/dist/plus/integrations/providers/linear.js +23 -22
  203. package/dist/plus/integrations/providers/linear.js.map +1 -1
  204. package/dist/plus/integrations/providers/models.d.ts +3 -1
  205. package/dist/plus/integrations/providers/models.d.ts.map +1 -1
  206. package/dist/plus/integrations/providers/models.js +10 -4
  207. package/dist/plus/integrations/providers/models.js.map +1 -1
  208. package/dist/plus/integrations/providers/providerErrors.d.ts +7 -0
  209. package/dist/plus/integrations/providers/providerErrors.d.ts.map +1 -0
  210. package/dist/plus/integrations/providers/providerErrors.js +91 -0
  211. package/dist/plus/integrations/providers/providerErrors.js.map +1 -0
  212. package/dist/plus/integrations/providers/providersApi.d.ts +3 -2
  213. package/dist/plus/integrations/providers/providersApi.d.ts.map +1 -1
  214. package/dist/plus/integrations/providers/providersApi.js +18 -52
  215. package/dist/plus/integrations/providers/providersApi.js.map +1 -1
  216. package/dist/plus/integrations/providers/trello.d.ts +2 -7
  217. package/dist/plus/integrations/providers/trello.d.ts.map +1 -1
  218. package/dist/plus/integrations/providers/trello.js +3 -1
  219. package/dist/plus/integrations/providers/trello.js.map +1 -1
  220. package/dist/plus/integrations/providers/utils/providerPaging.d.ts +2 -0
  221. package/dist/plus/integrations/providers/utils/providerPaging.d.ts.map +1 -1
  222. package/dist/plus/integrations/providers/utils/providerPaging.js +12 -7
  223. package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
  224. package/dist/plus/integrations/reads/broaden.d.ts +47 -10
  225. package/dist/plus/integrations/reads/broaden.d.ts.map +1 -1
  226. package/dist/plus/integrations/reads/broaden.js +144 -33
  227. package/dist/plus/integrations/reads/broaden.js.map +1 -1
  228. package/dist/plus/integrations/reads/filters.d.ts +18 -0
  229. package/dist/plus/integrations/reads/filters.d.ts.map +1 -1
  230. package/dist/plus/integrations/reads/filters.js +37 -1
  231. package/dist/plus/integrations/reads/filters.js.map +1 -1
  232. package/dist/plus/integrations/reads/issueBatch.d.ts +51 -0
  233. package/dist/plus/integrations/reads/issueBatch.d.ts.map +1 -0
  234. package/dist/plus/integrations/reads/issueBatch.js +91 -0
  235. package/dist/plus/integrations/reads/issueBatch.js.map +1 -0
  236. package/dist/plus/integrations/reads/issueTracker.d.ts.map +1 -1
  237. package/dist/plus/integrations/reads/issueTracker.js +23 -17
  238. package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
  239. package/dist/plus/integrations/reads/sweeps.d.ts.map +1 -1
  240. package/dist/plus/integrations/reads/sweeps.js +5 -3
  241. package/dist/plus/integrations/reads/sweeps.js.map +1 -1
  242. package/dist/plus/integrations/reads/trackerIssue.d.ts +21 -0
  243. package/dist/plus/integrations/reads/trackerIssue.d.ts.map +1 -0
  244. package/dist/plus/integrations/reads/trackerIssue.js +51 -0
  245. package/dist/plus/integrations/reads/trackerIssue.js.map +1 -0
  246. package/dist/plus/integrations/reads/warnings.d.ts +2 -15
  247. package/dist/plus/integrations/reads/warnings.d.ts.map +1 -1
  248. package/dist/plus/integrations/reads/warnings.js +19 -1
  249. package/dist/plus/integrations/reads/warnings.js.map +1 -1
  250. package/dist/plus/integrations/results.d.ts +4 -20
  251. package/dist/plus/integrations/results.d.ts.map +1 -1
  252. package/dist/plus/integrations/results.js.map +1 -1
  253. package/dist/utils/promise.d.ts +2 -0
  254. package/dist/utils/promise.d.ts.map +1 -1
  255. package/dist/utils/promise.js +11 -0
  256. package/dist/utils/promise.js.map +1 -1
  257. package/dist/utils/promiseCache.d.ts +19 -0
  258. package/dist/utils/promiseCache.d.ts.map +1 -1
  259. package/dist/utils/promiseCache.js +30 -0
  260. package/dist/utils/promiseCache.js.map +1 -1
  261. package/dist/utils/resourceUsage.d.ts +5 -0
  262. package/dist/utils/resourceUsage.d.ts.map +1 -0
  263. package/dist/utils/resourceUsage.js +2 -0
  264. package/dist/utils/resourceUsage.js.map +1 -0
  265. package/dist/utils/string.d.ts +12 -0
  266. package/dist/utils/string.d.ts.map +1 -1
  267. package/dist/utils/string.js +36 -1
  268. package/dist/utils/string.js.map +1 -1
  269. package/docs/integrations.md +48 -33
  270. package/docs/kepler-read-api-parity.md +25 -4
  271. package/package.json +12 -4
  272. package/src/git/cache.ts +36 -0
  273. package/src/git/context.ts +11 -0
  274. package/src/git/errors.ts +13 -1
  275. package/src/git/features.ts +51 -0
  276. package/src/git/gitHealth.ts +522 -0
  277. package/src/git/models/search.ts +19 -4
  278. package/src/git/parsers/diffParser.ts +27 -4
  279. package/src/git/providers/config.ts +42 -1
  280. package/src/git/providers/graph.ts +12 -0
  281. package/src/git/providers/maintenance.ts +224 -0
  282. package/src/git/providers/operations.ts +10 -0
  283. package/src/git/providers/pausedOperations.ts +3 -0
  284. package/src/git/providers/provider.ts +2 -0
  285. package/src/git/providers/tags.ts +1 -0
  286. package/src/git/repositoryService.ts +1 -0
  287. package/src/git/run.types.ts +8 -0
  288. package/src/git/service.ts +3 -0
  289. package/src/git/utils/search.utils.ts +129 -5
  290. package/src/git/watching/watchService.ts +10 -0
  291. package/src/git-cli/cliGitProvider.ts +25 -0
  292. package/src/git-cli/exec/git.ts +120 -6
  293. package/src/git-cli/exec/gitQueue.ts +19 -17
  294. package/src/git-cli/parsers/logParser.ts +4 -1
  295. package/src/git-cli/providers/blame.ts +3 -8
  296. package/src/git-cli/providers/branches.ts +28 -8
  297. package/src/git-cli/providers/commits.ts +34 -2
  298. package/src/git-cli/providers/config.ts +38 -6
  299. package/src/git-cli/providers/graph.ts +107 -7
  300. package/src/git-cli/providers/maintenance.ts +2599 -0
  301. package/src/git-cli/providers/operations.ts +11 -0
  302. package/src/git-cli/providers/pausedOperations.ts +7 -2
  303. package/src/git-cli/providers/tags.ts +36 -1
  304. package/src/plus/agents/agentCapabilities.ts +286 -0
  305. package/src/plus/agents/providers/claudeCodeTranscript.ts +313 -58
  306. package/src/plus/agents/providers/{claudeCodeProvider.ts → gkAgentProvider.ts} +1123 -277
  307. package/src/plus/agents/types.ts +137 -40
  308. package/src/plus/ai/models/promptTemplates.ts +1 -1
  309. package/src/plus/ai/prompts.ts +62 -4
  310. package/src/plus/git-github/api/github.ts +404 -82
  311. package/src/plus/git-github/api/issueSearchQuery.ts +49 -1
  312. package/src/plus/git-github/providers/github/blame.ts +2 -1
  313. package/src/plus/integrations/collectionMetadata.ts +23 -1
  314. package/src/plus/integrations/context.ts +1 -1
  315. package/src/plus/integrations/index.ts +1 -0
  316. package/src/plus/integrations/integrationService.ts +29 -4
  317. package/src/plus/integrations/manager.ts +48 -0
  318. package/src/plus/integrations/models/gitHostIntegration.ts +165 -129
  319. package/src/plus/integrations/models/integration.ts +57 -22
  320. package/src/plus/integrations/models/issueCache.ts +29 -0
  321. package/src/plus/integrations/models/issueReads.ts +15 -0
  322. package/src/plus/integrations/models/issuesIntegration.ts +38 -8
  323. package/src/plus/integrations/providers/azureDevOps.ts +16 -13
  324. package/src/plus/integrations/providers/bitbucket-server.ts +4 -6
  325. package/src/plus/integrations/providers/bitbucket.ts +3 -4
  326. package/src/plus/integrations/providers/github.ts +23 -7
  327. package/src/plus/integrations/providers/gitlab.ts +4 -6
  328. package/src/plus/integrations/providers/jira.ts +52 -26
  329. package/src/plus/integrations/providers/jiraIssueByKey.ts +155 -0
  330. package/src/plus/integrations/providers/linear.ts +38 -25
  331. package/src/plus/integrations/providers/models.ts +17 -5
  332. package/src/plus/integrations/providers/providerErrors.ts +115 -0
  333. package/src/plus/integrations/providers/providersApi.ts +26 -68
  334. package/src/plus/integrations/providers/trello.ts +5 -4
  335. package/src/plus/integrations/providers/utils/providerPaging.ts +14 -7
  336. package/src/plus/integrations/reads/broaden.ts +282 -70
  337. package/src/plus/integrations/reads/filters.ts +39 -1
  338. package/src/plus/integrations/reads/issueBatch.ts +197 -0
  339. package/src/plus/integrations/reads/issueTracker.ts +26 -24
  340. package/src/plus/integrations/reads/sweeps.ts +19 -12
  341. package/src/plus/integrations/reads/trackerIssue.ts +105 -0
  342. package/src/plus/integrations/reads/warnings.ts +23 -17
  343. package/src/plus/integrations/results.ts +4 -20
  344. package/src/utils/promise.ts +15 -0
  345. package/src/utils/promiseCache.ts +43 -0
  346. package/src/utils/resourceUsage.ts +5 -0
  347. package/src/utils/string.ts +38 -1
  348. package/dist/plus/agents/providers/claudeCodeProvider.d.ts.map +0 -1
  349. package/dist/plus/agents/providers/claudeCodeProvider.js.map +0 -1
@@ -1,9 +1,11 @@
1
1
  import type { IssueShape } from '../../../git/models/issue.js';
2
+ import { effectiveIssueSort } from '../../../git/utils/issue.utils.js';
2
3
  import { mapBounded } from '../../../utils/promise.js';
3
4
  import { mergeAssessmentInto } from '../collectionMetadata.js';
4
5
  import type { IntegrationIds } from '../constants.js';
5
6
  import { providerFanOutConcurrency } from '../constants.js';
6
7
  import type { ProviderBroadenOrg } from '../manager.js';
8
+ import type { GitHostIntegration } from '../models/gitHostIntegration.js';
7
9
  import type { ProviderReposInput } from '../providers/models.js';
8
10
  import type { ProviderBroadenResult, ProviderWarning } from '../results.js';
9
11
  import { appendDedupedWarning } from '../results.js';
@@ -13,32 +15,78 @@ import type { ProviderReadContext } from './context.js';
13
15
  import type { BroadenIssuesExhaustedOrg, BroadenIssuesOrgCursor } from './cursors.js';
14
16
  import { getBroadenIssuesCursor, isBroadenIssuesOrgExhausted, toBroadenIssuesCursor } from './cursors.js';
15
17
  import { drainRepositories, runCaptured } from './drains.js';
16
- import { resolveContinuation, toProviderPageInfo } from './paging.js';
17
- import { gitHostOnlySurfaceWarning, issuesUnsupportedWarning, noConnectionWarning, otherWarning } from './warnings.js';
18
+ import { resolveIssueSearchScope, supportsFilteredIssueSearch } from './filters.js';
19
+ import { resolveContinuation, toProviderPageInfo, usableCursor } from './paging.js';
20
+ import {
21
+ gitHostOnlySurfaceWarning,
22
+ issueSearchCapResultWarning,
23
+ issuesUnsupportedWarning,
24
+ noConnectionWarning,
25
+ otherWarning,
26
+ truncationWarning,
27
+ unsupportedIssueSearchCriteriaWarning,
28
+ } from './warnings.js';
18
29
 
19
30
  /**
20
- * The issue-broadening fan-out: per org, drain that org's repositories, then read every visible issue in them.
31
+ * The issue-broadening fan-out: every visible issue across a set of orgs, one page at a time.
21
32
  *
22
33
  * What makes this its own read rather than a variant of `listIssuesPage` is that one logical page spans several
23
34
  * provider positions — one per org — so its continuation is a per-org cursor BUNDLE (see `cursors.ts`) and its
24
35
  * failure attribution is per provider across those orgs. Both are the reason a page number alone can't address a
25
36
  * later page, and why {@link broadenIssues} walks prior pages itself when given only `page`.
26
37
  *
27
- * SUPERSEDED for a caller that already knows its repositories: `searchIssuesPage({ repos, criteria })` answers
28
- * the same question in one request per page, with no repository drain and no route through the SDK read whose
29
- * over-limit recovery walk can spend up to 128 requests. This read stays for "fan out across these orgs,
30
- * whatever repos they contain", whose per-provider attribution the single-provider search doesn't produce.
38
+ * Each org slice runs on ONE OF TWO ENGINES, chosen by what the provider declares (#5804):
39
+ * - the org-scoped FILTERED SEARCH where the provider has one (GitHub/GHE): one request per page, no repositories
40
+ * needed at all, since the search reaches the scope by naming the org;
41
+ * - the REPOSITORY DRAIN plus the SDK's repo-scoped read where it doesn't (Azure DevOps, GitLab): up to 100
42
+ * requests to enumerate the org, and a read whose over-limit recovery walk can spend 128 more per repository
43
+ * and still return an incomplete set.
31
44
  *
32
- * If you migrate a caller, note the semantics carefully: broadening means ALL VISIBLE it passes
33
- * `includeAllAssignees: true`, which resolves to no assignee constraint at all, so unassigned issues ARE
34
- * included. The equivalent is an OMITTED `relationships`, not `['any-assignee']`: `assignee:*` means "has some
45
+ * Measured by running this read against the live API, three times per engine per org (median shown; each
46
+ * engine returned a byte-identical result set on all three, so the sets below are not sampling noise):
47
+ *
48
+ * | | repo drain + SDK read | org-scoped search |
49
+ * | --- | --- | --- |
50
+ * | BELOW the ceiling (146 issues) | 9.3s, 11 requests, 146 issues | 6.2s, 2 requests, 146 issues |
51
+ * | ABOVE it (1.475 issues, 217 repos) | 111s, 133 requests, 1.118 issues | 40s, 10 requests, 1.000 issues |
52
+ * | ...of the 1.000 most recently updated | 644 | 1.000 |
53
+ * | ...omissions reported | 99 × `recovery-budget`, no count | 1, carrying `totalCount: 1474` |
54
+ *
55
+ * BELOW the ceiling the two return the IDENTICAL SET (verified as set equality), so there the swap is a straight
56
+ * saving and nothing else. ABOVE it they return DIFFERENT SETS — overlapping on 644 items, so the divergence is
57
+ * not confined to the edge.
58
+ *
59
+ * Above the ceiling the search returns FEWER items and is nonetheless the more useful of the two, which is the
60
+ * whole reason to prefer it: its 1.000 are exactly the 1.000 most recently updated (again set equality against a
61
+ * ceiling-free enumeration, not a count), whereas the SDK read's 1.118 are an arbitrary subset — 474 of them from
62
+ * beyond that window, at the cost of omitting 356 issues that ARE in it. Its recovery walk does not close the gap
63
+ * it spends the budget on: all 99 of its omissions were exhausted budget.
64
+ *
65
+ * The second difference is what a consumer can SAY about the gap. At the ceiling the search succeeds and reports
66
+ * one quantified omission (`issueSearchCapResultWarning` carries `totalCount`, `limit` and the order the window
67
+ * was selected under), so a consumer can render "1.474 matched, showing the 1.000 most recently updated". The
68
+ * SDK read reports one `recovery-budget` omission per exhausted repository, none of which carries how many issues
69
+ * were missed — incompleteness that can be announced but not quantified.
70
+ *
71
+ * Note the time saving is the SMALLER half of this: 1.5× below the ceiling and 2.8× above it, against 5.5× and
72
+ * 13.3× fewer requests. The request count is what matters for a rate limit shared with every other read.
73
+ *
74
+ * The public surface is the SAME on both: the options, the per-org cursor bundle, the `ProviderAttribution`
75
+ * split, the warning dedupe and the return type. A consumer needs to know which engine ran only insofar as the
76
+ * warnings say so.
77
+ *
78
+ * Broadening means ALL VISIBLE, and each engine expresses that differently — `includeAllAssignees: true` on the
79
+ * SDK read, an OMITTED `relationships` on the search. Both resolve to no assignee constraint at all, so
80
+ * unassigned issues ARE included. The search's equivalent is NOT `['any-assignee']`: `assignee:*` means "has some
35
81
  * assignee" and would silently exclude every unassigned issue, which is the opposite of broadening.
36
82
  *
37
83
  * Takes no `sort`, unlike `listIssuesPage` and `searchIssuesPage`. One logical page here spans several orgs, each
38
84
  * at its own provider position in a cursor bundle, so honoring an order across them would need a k-way merge with
39
85
  * a buffer per org rather than a sort of what arrived — the page is a slice of several independent walks, not a
40
- * union of one round's results. `searchIssuesPage({ repos, criteria: { sort } })` answers the ordered version of
41
- * this question for a caller that knows its repositories, and is already the recommended migration above.
86
+ * union of one round's results. The search engine still orders each org's OWN query (it must: a ceiling policy of
87
+ * "the N most recent" is only correct under a guaranteed order), so a slice is ordered where the merged page is
88
+ * not. `searchIssuesPage({ repos, criteria: { sort } })` answers the ordered version of this question for a
89
+ * caller that knows its repositories.
42
90
  */
43
91
 
44
92
  export interface BroadenIssuesOptions {
@@ -276,7 +324,210 @@ async function traverseToRequestedPage(
276
324
  };
277
325
  }
278
326
 
279
- /** Reads one org's visible issues: drain the org's repos, then read the issues across them. */
327
+ /**
328
+ * One org's issues as either engine below reports them, normalized to the single shape {@link readOrgSlice}'s
329
+ * bookkeeping consumes.
330
+ *
331
+ * The two engines return different envelopes — the SDK read's `PagedResult` + `paging`, the search's flat page —
332
+ * and normalizing HERE rather than per engine is what keeps the continuation, retry, attribution and exhaustion
333
+ * rules literally the same code on both paths. A second copy of those rules per engine is how one path would come
334
+ * to advertise a continuation the other calls terminal.
335
+ */
336
+ interface OrgIssuesRead {
337
+ /** The page the provider served, or `undefined` when the read returned none (which `warning` explains). */
338
+ value?: { items: IssueShape[]; hasMore: boolean; cursor?: string; truncated: boolean };
339
+ /** The read's own failure. Already included in `warnings`; carried separately because the retry rules key off it. */
340
+ warning?: ProviderWarning;
341
+ /** Everything the engine produced, the read's own failure included. */
342
+ warnings: ProviderWarning[];
343
+ fetchFailed: boolean;
344
+ truncated: boolean;
345
+ /**
346
+ * The engine established there is NOTHING to read for this org — the repository drain returned no
347
+ * repositories — so the slice is terminal and empty. Distinct from `value == null`, which means a read WAS
348
+ * issued and came back with no page, and so is a position to retry.
349
+ */
350
+ nothingToRead?: boolean;
351
+ }
352
+
353
+ /**
354
+ * Whether this org can be read through the FILTERED issue search instead of the repository drain.
355
+ *
356
+ * Two conditions, and the second is not a formality: an empty org name is dropped by the provider's scope
357
+ * translation rather than rejected, which would leave a search of the WHOLE HOST — so the scope is validated
358
+ * through {@link resolveIssueSearchScope}, the same rule `searchIssuesPage` refuses on, rather than by an
359
+ * `org.name.length > 0` written here and free to drift from it.
360
+ */
361
+ function canSearchOrgIssues(org: ProviderBroadenOrg): boolean {
362
+ return (
363
+ supportsFilteredIssueSearch(org.providerId) &&
364
+ resolveIssueSearchScope(undefined, org.name, undefined).rejection == null
365
+ );
366
+ }
367
+
368
+ /**
369
+ * The org-scoped filtered search: ONE request per page, whatever the org contains.
370
+ *
371
+ * Replaces the drain + SDK read for every provider that declares a filtered issue search — the repository drain
372
+ * costs up to 100 requests per org, and the SDK's repo-scoped read past GitHub's result ceiling enters a
373
+ * per-repository recovery walk that can spend 128 more and still return an incomplete set. Measured on an org
374
+ * past that ceiling: 133 requests and 111s, against this read's 10 and 40s. The search reaches the same scope by
375
+ * NAMING the org, so it needs no repositories at all.
376
+ *
377
+ * It is also the more COMPLETE of the two over the window a consumer reads first, despite returning fewer items —
378
+ * see the module docstring's table for the measurement and why that is not a contradiction.
379
+ *
380
+ * `criteria` is omitted entirely, and each half of that is deliberate:
381
+ * - `relationships` OMITTED is what drops the assignee constraint, which is the substitution `includeAllAssignees:
382
+ * true` performs on the read this replaces. NOT `['any-assignee']`, which means "assigned to somebody" and would
383
+ * exclude every unassigned issue — the opposite of broadening.
384
+ * - every other criterion left unset resolves to the provider's own default, which is the state this read already
385
+ * served (open issues only, archived repositories excluded).
386
+ *
387
+ * At the result ceiling this SUCCEEDS and reports how many matched instead of walking for the rest — see the
388
+ * ceiling warning below.
389
+ */
390
+ async function readOrgIssuesViaSearch(
391
+ integration: GitHostIntegration,
392
+ org: ProviderBroadenOrg,
393
+ domain: string | undefined,
394
+ cursor: string | undefined,
395
+ ): Promise<OrgIssuesRead> {
396
+ const connectionId = org.connectionId;
397
+ const captured = await runCaptured(
398
+ org.providerId,
399
+ domain,
400
+ connectionId,
401
+ () => integration.searchIssuesPageResult({ org: org.name, cursor: cursor }, undefined, connectionId),
402
+ { warnOnMissingSession: true },
403
+ );
404
+
405
+ // A provider that declares the capability but implements no search hook answers `undefined` with no error.
406
+ // Reported as the explicit unsupported failure rather than left as a silent empty page, so it can't be mistaken
407
+ // for an org with no issues — and as the read's `warning`, so the slice treats it as the read failure it is.
408
+ const warning =
409
+ captured.warning ??
410
+ (captured.value == null
411
+ ? unsupportedIssueSearchCriteriaWarning(org.providerId, domain, connectionId, {
412
+ reason: 'unsupported-search',
413
+ })
414
+ : undefined);
415
+ const warnings = warning != null ? [warning] : [];
416
+
417
+ let value: OrgIssuesRead['value'];
418
+ if (captured.value != null) {
419
+ const page = captured.value;
420
+ value = {
421
+ items: page.values,
422
+ hasMore: page.hasMore,
423
+ cursor: usableCursor(page.cursor),
424
+ truncated: page.truncated,
425
+ };
426
+ // The result ceiling is the one incompleteness this read can QUANTIFY, so it carries the total rather than
427
+ // a bare "may be incomplete" — which is what lets a consumer say "1.474 matched, showing the 1.000 most
428
+ // recently updated" instead of silently serving a truncated list. Only when nothing else already explained
429
+ // the incompleteness, and only the ceiling gets the number: any other cause falls back to the generic
430
+ // wording rather than reporting a limit it didn't hit. `exhausted`: nothing this read exposes would return
431
+ // the withheld items.
432
+ if (page.truncated && warnings.length === 0) {
433
+ warnings.push(
434
+ issueSearchCapResultWarning(
435
+ org.providerId,
436
+ domain,
437
+ connectionId,
438
+ page.totalCount,
439
+ effectiveIssueSort(undefined),
440
+ ) ?? truncationWarning(org.providerId, domain, connectionId, 'Issue search', 'exhausted'),
441
+ );
442
+ }
443
+ }
444
+
445
+ return { value: value, warning: warning, warnings: warnings, fetchFailed: false, truncated: false };
446
+ }
447
+
448
+ /**
449
+ * The original engine: drain the org's repositories, then read the issues across them.
450
+ *
451
+ * Kept for a provider that declares NO filtered issue search (Azure DevOps, GitLab), where refusing the org would
452
+ * be a regression rather than a saving. Its cost, and the arbitrary subset it serves past the result ceiling, are
453
+ * what {@link readOrgIssuesViaSearch} exists to avoid — see the module docstring's measurement. Those providers
454
+ * keep both, because on them there is nothing better to switch to.
455
+ */
456
+ async function readOrgIssuesViaRepoDrain(
457
+ integration: GitHostIntegration,
458
+ org: ProviderBroadenOrg,
459
+ domain: string | undefined,
460
+ cursor: string | undefined,
461
+ ): Promise<OrgIssuesRead> {
462
+ const connectionId = org.connectionId;
463
+ const reposDrain = await drainRepositories(
464
+ integration,
465
+ org.providerId,
466
+ domain,
467
+ org.name,
468
+ undefined,
469
+ connectionId,
470
+ 100,
471
+ );
472
+ const warnings: ProviderWarning[] = [...reposDrain.warnings];
473
+
474
+ const repos: ProviderReposInput = reposDrain.repos.map(r => ({ ...r }));
475
+ if (repos.length === 0) {
476
+ return {
477
+ warnings: warnings,
478
+ fetchFailed: reposDrain.fetchFailed,
479
+ truncated: reposDrain.truncated,
480
+ nothingToRead: true,
481
+ };
482
+ }
483
+
484
+ // Broaden = "all visible": drop the assigned-to-me filter so unassigned issues are included.
485
+ const captured = await runCaptured(
486
+ org.providerId,
487
+ domain,
488
+ connectionId,
489
+ () =>
490
+ // Normalized shapes seam, uniform with listIssuesPage.
491
+ integration.getMyIssuesForReposAsShapesResult(
492
+ repos,
493
+ { includeAllAssignees: true, cursor: cursor },
494
+ connectionId,
495
+ ),
496
+ { warnOnMissingSession: true },
497
+ );
498
+ if (captured.warning != null) {
499
+ warnings.push(captured.warning);
500
+ }
501
+ const assessment = mergeAssessmentInto(warnings, org.providerId, domain, connectionId, captured.value?.metadata);
502
+
503
+ let value: OrgIssuesRead['value'];
504
+ if (captured.value != null) {
505
+ const paged = toProviderPageInfo(captured.value.values.length, captured.value.paging);
506
+ value = {
507
+ items: captured.value.values,
508
+ hasMore: paged.hasMore,
509
+ cursor: paged.cursor,
510
+ // Carry a truncation signal from the issue read too: a provider that couldn't confirm it drained a
511
+ // repo (`paging.truncated`) means this org's issues may be incomplete, on top of any repo-drain
512
+ // truncation already captured above.
513
+ truncated: paged.truncated || assessment.truncated,
514
+ };
515
+ }
516
+
517
+ return {
518
+ value: value,
519
+ warning: captured.warning,
520
+ warnings: warnings,
521
+ fetchFailed: reposDrain.fetchFailed || assessment.fetchFailed,
522
+ truncated: reposDrain.truncated,
523
+ };
524
+ }
525
+
526
+ /**
527
+ * Reads one org's visible issues, through the org-scoped filtered search where the provider has one and through
528
+ * the repository drain where it doesn't. Everything below the engine — the position, the retry slot, the
529
+ * attribution and the exhaustion mark — is the same on both paths.
530
+ */
280
531
  async function readOrgSlice(
281
532
  ctx: ProviderReadContext,
282
533
  options: BroadenIssuesOptions,
@@ -362,71 +613,32 @@ async function readOrgSlice(
362
613
  await ctx.forceRefreshIfRequested(integration, options.forceSync, connectionId);
363
614
 
364
615
  const domain = ctx.domainForRead(integration, org.providerId, connectionId, requestedDomain);
365
- const reposDrain = await drainRepositories(
366
- integration,
367
- org.providerId,
368
- domain,
369
- org.name,
370
- undefined,
371
- connectionId,
372
- 100,
373
- );
374
- const warnings: ProviderWarning[] = [...reposDrain.warnings];
375
- const fetchFailed = reposDrain.fetchFailed;
376
- const truncated = reposDrain.truncated;
377
-
378
- const repos: ProviderReposInput = reposDrain.repos.map(r => ({ ...r }));
379
- if (repos.length === 0) {
380
- return barrenSlice(warnings, { fetchFailed: fetchFailed, truncated: truncated });
381
- }
382
-
383
- // Broaden = "all visible": drop the assigned-to-me filter so unassigned issues are included.
384
616
  const cursor = getBroadenIssuesCursor(options.cursor, org, page, options.orgs.length);
385
- const issuesCaptured = await runCaptured(
386
- org.providerId,
387
- domain,
388
- connectionId,
389
- () =>
390
- // Normalized shapes seam, uniform with listIssuesPage.
391
- integration.getMyIssuesForReposAsShapesResult(
392
- repos,
393
- { includeAllAssignees: true, cursor: cursor },
394
- connectionId,
395
- ),
396
- { warnOnMissingSession: true },
397
- );
398
- if (issuesCaptured.warning != null) {
399
- warnings.push(issuesCaptured.warning);
617
+ const read = canSearchOrgIssues(org)
618
+ ? await readOrgIssuesViaSearch(integration, org, domain, cursor)
619
+ : await readOrgIssuesViaRepoDrain(integration, org, domain, cursor);
620
+ if (read.nothingToRead === true) {
621
+ return barrenSlice(read.warnings, { fetchFailed: read.fetchFailed, truncated: read.truncated });
400
622
  }
401
- const issuesAssessment = mergeAssessmentInto(
402
- warnings,
403
- org.providerId,
404
- domain,
405
- connectionId,
406
- issuesCaptured.value?.metadata,
407
- );
408
- let issuesFetchFailed =
409
- issuesAssessment.fetchFailed || (issuesCaptured.warning != null && issuesCaptured.value == null);
623
+
624
+ const warnings = read.warnings;
625
+ let issuesFetchFailed = read.fetchFailed || (read.warning != null && read.value == null);
410
626
  const items: IssueShape[] = [];
411
627
  let hasMore = false;
412
628
  let nextCursor: string | undefined;
413
629
  let retryPage: number | undefined;
414
- // Carry a truncation signal from the issue read too: a provider that couldn't confirm it drained
415
- // a repo (`paging.truncated`) means this org's issues may be incomplete, on top of any repo-drain
416
- // truncation already captured above.
417
630
  let issuesTruncated = false;
418
- if (issuesCaptured.value != null) {
419
- items.push(...issuesCaptured.value.values);
420
- const paged = toProviderPageInfo(issuesCaptured.value.values.length, issuesCaptured.value.paging);
631
+ if (read.value != null) {
632
+ items.push(...read.value.items);
421
633
  // An org that reports another page but no usable cursor can't be resumed: it would neither be
422
634
  // recorded in the composite cursor nor marked exhausted, so the next round would re-read its
423
635
  // page 1 and repeat every issue. Treat it as terminal-but-incomplete (which also marks the org
424
636
  // exhausted below, since `exhausted` keys off `!hasMore`).
425
- const continuation = resolveContinuation(paged, undefined);
637
+ const continuation = resolveContinuation(read.value, undefined);
426
638
  hasMore = continuation.hasMore;
427
639
  nextCursor = continuation.cursor;
428
- issuesTruncated = continuation.truncated || issuesAssessment.truncated;
429
- } else if (issuesCaptured.warning != null || cursor != null) {
640
+ issuesTruncated = continuation.truncated;
641
+ } else if (read.warning != null || cursor != null) {
430
642
  // Keep the exact position that failed. Without this retry cursor a multi-org continuation would
431
643
  // omit this org from the bundle, then synthesize the next numbered page and silently skip the
432
644
  // failed page. The synthesized page cursor is also actionable for a first-page cursor-only read:
@@ -435,7 +647,7 @@ async function readOrgSlice(
435
647
  // query request it forever. Healthy sibling continuations set `hasMore` independently.
436
648
  if (cursor != null) {
437
649
  nextCursor = cursor;
438
- if (issuesCaptured.warning == null) {
650
+ if (read.warning == null) {
439
651
  appendDedupedWarning(
440
652
  warnings,
441
653
  otherWarning(
@@ -456,7 +668,7 @@ async function readOrgSlice(
456
668
  return {
457
669
  items: items,
458
670
  warnings: warnings,
459
- broadenedProviderIds: issuesCaptured.value != null ? [org.providerId] : [],
671
+ broadenedProviderIds: read.value != null ? [org.providerId] : [],
460
672
  providerId: org.providerId,
461
673
  org: org.name,
462
674
  connectionId: connectionId,
@@ -466,8 +678,8 @@ async function readOrgSlice(
466
678
  hasMore: hasMore,
467
679
  // Exhausted once a successful read reports no more pages — recorded in the cursor so later
468
680
  // rounds skip it while other orgs keep paging.
469
- exhausted: issuesCaptured.value != null && !hasMore,
470
- fetchFailed: fetchFailed || issuesFetchFailed,
471
- truncated: truncated || issuesTruncated,
681
+ exhausted: read.value != null && !hasMore,
682
+ fetchFailed: issuesFetchFailed,
683
+ truncated: read.truncated || issuesTruncated,
472
684
  };
473
685
  }
@@ -196,6 +196,20 @@ export type IssueSearchCriteriaRejection =
196
196
  /** `any-assignee` and `unassigned` partition the scope between them; asking for both asks for nothing. */
197
197
  | { reason: 'contradictory-relationships' };
198
198
 
199
+ /**
200
+ * Whether a provider exposes the FILTERED issue search at all — the one predicate a caller can check BEFORE
201
+ * building criteria, so a read that would only be refused is never issued.
202
+ *
203
+ * The same test {@link resolveIssueSearchCriteria} makes for its `unsupported-search` rejection, named rather
204
+ * than re-derived at each call site: a caller writing `supportedIssueSearch != null` itself is a copy free to
205
+ * disagree with the validator about what "has a search" means. `broadenIssues` reads it to pick its engine —
206
+ * the org-scoped search where there is one, the repository drain where there isn't — which is a CHOICE rather
207
+ * than a refusal, so it needs the predicate without the rejection.
208
+ */
209
+ export function supportsFilteredIssueSearch(id: IntegrationIds): boolean {
210
+ return providersMetadata[id]?.supportedIssueSearch != null;
211
+ }
212
+
199
213
  /**
200
214
  * Validates a filtered issue search's criteria against {@link ProviderMetadata.supportedIssueSearch}.
201
215
  *
@@ -311,6 +325,23 @@ export type IssueSearchScopeRejection =
311
325
  /** Repositories given as ids. A search names repositories by PATH, so ids can't express a scope. */
312
326
  | 'repo-ids';
313
327
 
328
+ /**
329
+ * Whether a scope name survives the provider's sanitizing, i.e. whether it will actually constrain the query.
330
+ *
331
+ * Provider-NEUTRAL by design, and deliberately not an import of GitHub's `sanitizeGitHubQualifierValue`: this
332
+ * module validates for every provider, and a GitHub-specific rule reaching in here would be wrong for the next
333
+ * one that declares a search. What is common to any query language is the part that matters: a value made only of
334
+ * whitespace, quotes and control characters carries no name, and every sanitizer strips exactly those (quotes
335
+ * because they would close their own qualifier, control characters because they cannot appear in a query at all).
336
+ *
337
+ * Strictly weaker than any provider's own sanitizing, which is the safe direction: this can only pass a name the
338
+ * provider would then narrow further, never reject one the provider would have accepted.
339
+ */
340
+ function isUsableSearchScopeName(name: string): boolean {
341
+ // eslint-disable-next-line no-control-regex
342
+ return name.replace(/["\u0000-\u001f\u007f\s]/g, '').length > 0;
343
+ }
344
+
314
345
  /**
315
346
  * Validates that a filtered issue search is scoped at all, and narrows `repos` to the descriptor form the
316
347
  * provider query needs.
@@ -322,6 +353,13 @@ export type IssueSearchScopeRejection =
322
353
  *
323
354
  * The two rejections are mutually exclusive — `repo-ids` requires repositories and `unscoped` requires none — so
324
355
  * the order they're checked in cannot change the outcome.
356
+ *
357
+ * An org is checked for what SURVIVES SANITIZING, not merely for being non-empty, and that distinction is a
358
+ * SECURITY one rather than a nicety: the provider query drops a value that sanitizes away rather than rejecting
359
+ * it (`toGitHubIssueSearchScopeQualifiers` emits no bare `org:`, which GitHub would reject), so an org of `' '`
360
+ * or `'"'` would pass a length check, emit NO scope qualifier at all, and leave a search of the entire host —
361
+ * measured at 52 million issues across unrelated accounts. Whitespace and quotes are exactly what a name pasted
362
+ * from a config or a URL degrades to, so this is reachable without anything adversarial.
325
363
  */
326
364
  export function resolveIssueSearchScope(
327
365
  repos: ProviderReposInput | undefined,
@@ -335,7 +373,7 @@ export function resolveIssueSearchScope(
335
373
  return { repos: repos as ProviderRepoInput[] };
336
374
  }
337
375
 
338
- if (org != null && org.length > 0) return {};
376
+ if (org != null && isUsableSearchScopeName(org)) return {};
339
377
  if (criteria?.relationships?.some(r => userScopingIssueSearchRelationships.includes(r)) === true) return {};
340
378
 
341
379
  return { rejection: 'unscoped' };