@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
@@ -27,7 +27,7 @@ import { parseUri } from '../../../utils/uri.js';
27
27
  import { fromString, satisfies } from '../../../utils/version.js';
28
28
  import { fromGitHubIssue, fromGitHubIssueOrPullRequestState, fromGitHubPullRequest, fromGitHubPullRequestLite, } from '../models.js';
29
29
  import { githubSearchResultLimit } from './config.js';
30
- import { gitHubIssueSearchRelationships, toGitHubIssueSearchQualifiers, toGitHubIssueSearchScopeQualifiers, toGitHubIssueSortQualifier, } from './issueSearchQuery.js';
30
+ import { gitHubIssueSearchRelationships, toGitHubIssueSearchQualifiers, toGitHubIssueSearchScopeQualifiers, toGitHubIssueSearchSlideQualifier, toGitHubIssueSortQualifier, } from './issueSearchQuery.js';
31
31
  import { toGitHubPullRequestSearchFacets } from './pullRequestSearchQuery.js';
32
32
  const emptyPagedResult = Object.freeze({ values: [] });
33
33
  /**
@@ -110,6 +110,48 @@ function getRequestRetryDelay(attempt) {
110
110
  const backoff = Math.min(requestRetryMaxDelay, requestRetryBaseDelay * 2 ** (attempt - 1));
111
111
  return Math.round(backoff / 2 + Math.random() * (backoff / 2));
112
112
  }
113
+ /**
114
+ * The sort value of the last item on a page, which is the boundary a ceiling slide continues from.
115
+ *
116
+ * Reads the DATE off the issue rather than taking a caller-supplied position, so the boundary can only ever be a
117
+ * point this walk actually reached. Returns `undefined` for an empty page or a key carrying no date, which is the
118
+ * signal to report the ceiling instead of sliding.
119
+ */
120
+ function lastSortBoundary(issues, sort) {
121
+ const last = issues.at(-1);
122
+ if (last == null)
123
+ return undefined;
124
+ const value = sort.startsWith('created:')
125
+ ? last.createdDate
126
+ : sort.startsWith('updated:')
127
+ ? last.updatedDate
128
+ : undefined;
129
+ return value instanceof Date && !Number.isNaN(value.getTime()) ? value : undefined;
130
+ }
131
+ /**
132
+ * The per-alias ceiling-slide state, read defensively off a composite cursor.
133
+ *
134
+ * Keys are `<field>:<alias>`. The colon is what makes them collision-proof: a GraphQL alias is a name (a letter
135
+ * or underscore followed by letters, digits or underscores) and so can never contain one, which is why these need
136
+ * no entry in the reserved-alias check and that list does not have to grow with every field added here.
137
+ *
138
+ * Everything is validated to the shape this module emits, because a cursor is caller-supplied data on a round
139
+ * trip: an unrecognized value restarts the walk instead of reaching the query, which is the difference between a
140
+ * repeated page and an attacker-chosen qualifier re-scoping someone's search.
141
+ */
142
+ function cursorString(cursor, key) {
143
+ const value = cursor?.[key];
144
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
145
+ }
146
+ function cursorNumber(cursor, key) {
147
+ const value = cursor?.[key];
148
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
149
+ }
150
+ /** The urls a prior page already served at this alias's inclusive slide boundary. */
151
+ function slideSeenFor(cursor, alias) {
152
+ const value = cursorString(cursor, `slideSeen:${alias}`);
153
+ return value != null ? new Set(value.split(' ')) : undefined;
154
+ }
113
155
  // Matches a GraphQL document whose operation is definitely a read query — the `query` keyword as
114
156
  // the first significant token, after any leading whitespace or `#` comment lines. We retry only
115
157
  // when this matches; a mutation (even one prefixed by a comment), a subscription, or anything we
@@ -1960,7 +2002,19 @@ export class GitHubApi {
1960
2002
  // Inflight dedupe: identical concurrent GraphQL requests share a single promise
1961
2003
  // to cut redundant traffic during graph/details enrichment bursts.
1962
2004
  _pendingGraphQL = new Map();
1963
- async graphql(provider, token, query, variables, scope, cancellation) {
2005
+ async graphql(provider, token, query, variables, scope, cancellation,
2006
+ /**
2007
+ * Accept a response whose only failures are NOT_FOUND, returning its PARTIAL data instead of throwing.
2008
+ *
2009
+ * For a single-entity query a NOT_FOUND is the whole answer, so throwing is right. For an ALIASED batch it
2010
+ * is one slot's answer: GitHub replies 200 with every resolvable alias populated and a NOT_FOUND per
2011
+ * missing one, so throwing discards the results that did resolve. Since a missing coordinate is the common
2012
+ * case for a batch, that would make the read useless for the very question it answers.
2013
+ *
2014
+ * Narrow on purpose: any error that is NOT a NOT_FOUND still throws, so auth, rate-limit and query-cost
2015
+ * failures keep their existing handling rather than being silently reported as a batch of absences.
2016
+ */
2017
+ allowPartialNotFound) {
1964
2018
  const { accessToken, ...tokenInfo } = token;
1965
2019
  // Only dedupe when no cancellation/request option is in play — sharing a promise that
1966
2020
  // carries one caller's AbortSignal would let one cancellation cancel for everyone.
@@ -1997,6 +2051,13 @@ export class GitHubApi {
1997
2051
  }
1998
2052
  catch (ex) {
1999
2053
  if (ex instanceof GraphqlResponseError) {
2054
+ // `every`, not `[0]`: a batch can report several, and one non-NOT_FOUND among them is a real
2055
+ // failure that must not be reported as a set of absences.
2056
+ if (allowPartialNotFound &&
2057
+ ex.data != null &&
2058
+ (ex.errors?.every(e => e.type === 'NOT_FOUND') ?? false)) {
2059
+ return ex.data;
2060
+ }
2000
2061
  switch (ex.errors?.[0]?.type) {
2001
2062
  case 'NOT_FOUND':
2002
2063
  throw new RequestNotFoundError(ex);
@@ -2414,15 +2475,29 @@ export class GitHubApi {
2414
2475
  // - the ORDER is assigned → mentioned → authored, which is the order the union is emitted in and, because
2415
2476
  // the dedupe keeps the first occurrence of a url, also the precedence between the three. An issue that
2416
2477
  // is both assigned to and authored by the user surfaces as the assigned one.
2478
+ // The per-alias ceiling-slide bound a prior page sealed. Applied HERE because this method owns its query
2479
+ // text: without it the bound would be recorded and never honoured, so the continuation would re-issue the
2480
+ // unbounded query, serve the same first 1.000, and have every one of them filtered out as already-seen —
2481
+ // a page of nothing, repeated until the walk gave up (measured: 20 requests to do 10 requests' work).
2482
+ const bound = this.parseSlidesFromCursor(options?.cursor);
2417
2483
  const searches = [];
2418
2484
  if (requested.assigned) {
2419
- searches.push({ alias: 'assigned', query: `${search} ${baseFilters} ${assignedQualifier}`.trim() });
2485
+ searches.push({
2486
+ alias: 'assigned',
2487
+ query: `${search} ${baseFilters} ${assignedQualifier} ${bound('assigned') ?? ''}`.trim(),
2488
+ });
2420
2489
  }
2421
2490
  if (requested.mentioned) {
2422
- searches.push({ alias: 'mentioned', query: `${search} ${baseFilters} mentions:@me`.trim() });
2491
+ searches.push({
2492
+ alias: 'mentioned',
2493
+ query: `${search} ${baseFilters} mentions:@me ${bound('mentioned') ?? ''}`.trim(),
2494
+ });
2423
2495
  }
2424
2496
  if (requested.authored) {
2425
- searches.push({ alias: 'authored', query: `${search} ${baseFilters} author:@me`.trim() });
2497
+ searches.push({
2498
+ alias: 'authored',
2499
+ query: `${search} ${baseFilters} author:@me ${bound('authored') ?? ''}`.trim(),
2500
+ });
2426
2501
  }
2427
2502
  // Field by field, like `searchIssuesPage`: `options` also carries `repos`/`includeAllAssignees`/`categories`,
2428
2503
  // already folded into `searches[].query` above and undeclared by the callee.
@@ -2450,7 +2525,15 @@ export class GitHubApi {
2450
2525
  *
2451
2526
  * With more than one relationship the page is a UNION of several searches, each ordered by the provider; the
2452
2527
  * merged page is re-sorted here so the whole page honors the requested key. Across pages the order is still
2453
- * per-alias — see {@link searchIssuesByAlias}.
2528
+ * per-alias — see {@link searchIssuesByAlias}. Note a union can also serve the same issue on two pages when
2529
+ * its relationships OVERLAP (an issue both authored by and assigned to the user sits at different depths in
2530
+ * the two), which predates the ceiling slide and is a property of unioning independent walks: measured at 50
2531
+ * repeats over 450 issues with no ceiling involved. Dedupe by url if that matters to the surface.
2532
+ *
2533
+ * GitHub's per-query result ceiling is NOT terminal here: an alias that runs out of pages while capped
2534
+ * continues bounded to the far side of its own last item, which is a fresh query with a fresh budget. Bounded
2535
+ * per alias, since they exhaust independently. Only a ceiling an alias cannot slide past (a sort key with no
2536
+ * usable range qualifier) is reported as an omission.
2454
2537
  *
2455
2538
  * Each requested relationship becomes its own aliased search, unioned and deduped by url; with none, a single
2456
2539
  * search runs over the scope alone. `criteria.text` and the other free-form values are sanitized so user input
@@ -2464,16 +2547,21 @@ export class GitHubApi {
2464
2547
  ...toGitHubIssueSearchScopeQualifiers(options?.org, options?.repos),
2465
2548
  ...toGitHubIssueSearchQualifiers(options?.criteria, sort),
2466
2549
  ].join(' ');
2550
+ // A cursor from a walk that already slid past the ceiling carries the bound each alias slid to, and the
2551
+ // query has to be rebuilt with it or that alias would restart at the top and re-serve its first 1.000.
2552
+ // PER ALIAS, because they slide independently: one bound shared across them would sit below where an
2553
+ // early-finishing alias stopped and re-serve everything it had already delivered.
2554
+ const bound = this.parseSlidesFromCursor(options?.cursor);
2467
2555
  // One aliased search per relationship, OR-ed by union. They can't be one query: GitHub AND-s qualifiers,
2468
2556
  // so `author:@me assignee:@me` would return the intersection — issues the user both opened and is assigned
2469
2557
  // to — instead of either set. With no relationship the scope + criteria are already the whole query.
2470
2558
  const relationships = options?.criteria?.relationships;
2471
2559
  const searches = relationships?.length
2472
- ? relationships.map(r => ({
2473
- alias: gitHubIssueSearchRelationships[r].alias,
2474
- query: `${base} ${gitHubIssueSearchRelationships[r].qualifier}`.trim(),
2475
- }))
2476
- : [{ alias: 'matched', query: base }];
2560
+ ? relationships.map(r => {
2561
+ const { alias, qualifier } = gitHubIssueSearchRelationships[r];
2562
+ return { alias: alias, query: `${base} ${qualifier} ${bound(alias) ?? ''}`.trim() };
2563
+ })
2564
+ : [{ alias: 'matched', query: `${base} ${bound('matched') ?? ''}`.trim() }];
2477
2565
  // Forwarded field by field rather than spread: `options` also carries `repos`/`org`/`criteria`, which are
2478
2566
  // already baked into `searches[].query` above and which the callee declares nothing about. `sort` is the
2479
2567
  // EFFECTIVE key, since the merged page and the cursor's fingerprint must both use the one the query used.
@@ -2490,6 +2578,31 @@ export class GitHubApi {
2490
2578
  legacySort: defaultIssueSort,
2491
2579
  }, cancellation);
2492
2580
  }
2581
+ /**
2582
+ * Reads back the per-alias ceiling-slide bounds a prior page sealed into its cursor.
2583
+ *
2584
+ * Each value is validated against the EXACT shape {@link toGitHubIssueSearchSlideQualifier} emits, not merely
2585
+ * checked for being a string. A cursor is caller-supplied data on a round trip, so an unvalidated value here
2586
+ * would be a qualifier of someone else's choosing appended to the search: `org:evil-corp` in a cursor would
2587
+ * re-scope the read. Anything unrecognized yields no bound, which restarts that alias at the top — a repeated
2588
+ * page, which is the safe failure.
2589
+ */
2590
+ parseSlidesFromCursor(cursor) {
2591
+ let parsed;
2592
+ if (cursor != null) {
2593
+ try {
2594
+ parsed = JSON.parse(cursor);
2595
+ }
2596
+ catch { }
2597
+ }
2598
+ return alias => {
2599
+ const slide = parsed?.[`slide:${alias}`];
2600
+ return typeof slide === 'string' &&
2601
+ /^(?:created|updated):(?:<=|>=)\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/.test(slide)
2602
+ ? slide
2603
+ : undefined;
2604
+ };
2605
+ }
2493
2606
  /**
2494
2607
  * Counts issues for several scopes in ONE request, transferring no issues at all — each scope is an aliased
2495
2608
  * `search` selecting only `issueCount`, with `first: 0` so no nodes are fetched.
@@ -2548,6 +2661,80 @@ export class GitHubApi {
2548
2661
  throw this.handleException(ex, provider, scope);
2549
2662
  }
2550
2663
  }
2664
+ /**
2665
+ * Resolves several issues BY COORDINATE — `(owner, repo, number)` — in one request, via aliased `repository`
2666
+ * fields rather than aliased searches.
2667
+ *
2668
+ * Aliasing the point read rather than {@link searchIssuesByAlias} is the whole design, and each difference
2669
+ * matters to the caller this exists for (correlating a branch name to the issue it references):
2670
+ * - it resolves by EXACT NUMBER, where a search answers a relevance question;
2671
+ * - no result ceiling applies, so there is no partial window to reason about;
2672
+ * - and a null is a PROVEN ABSENCE rather than "not found within a page budget", which is what makes a miss
2673
+ * cacheable. That last one is the point: a caller that cannot prove absence re-walks its budget forever.
2674
+ *
2675
+ * Returns POSITIONALLY — one slot per input coordinate, in order — for the same reason {@link countIssues}
2676
+ * does: a caller's key is arbitrary text and would break the GraphQL document, so the aliases are generated
2677
+ * and the caller maps back by index. `undefined` in a slot means the issue does not exist (or is not visible
2678
+ * to this token), never that the read failed; a failure throws.
2679
+ */
2680
+ async getIssuesBatch(provider, token, coordinates, options, cancellation) {
2681
+ const scope = getScopedLogger();
2682
+ if (coordinates.length === 0)
2683
+ return [];
2684
+ const params = coordinates
2685
+ .map((_, i) => `$o${i}: String!\n\t\t\t\t$n${i}: String!\n\t\t\t\t$k${i}: Int!`)
2686
+ .join('\n\t\t\t\t');
2687
+ const fields = coordinates
2688
+ .map((_, i) => `i${i}: repository(owner: $o${i}, name: $n${i}) {
2689
+ issue(number: $k${i}) {
2690
+ ${gqIssueFragment}${options?.includeBody ? '\n\t\t\t\t\t\tbody' : ''}
2691
+ }
2692
+ }`)
2693
+ .join('\n\t\t\t\t');
2694
+ const query = `query getIssuesBatch(
2695
+ ${params}
2696
+ $avatarSize: Int
2697
+ ) {
2698
+ ${fields}
2699
+ }`;
2700
+ const variables = {
2701
+ baseUrl: options?.baseUrl,
2702
+ avatarSize: options?.avatarSize,
2703
+ };
2704
+ coordinates.forEach((c, i) => {
2705
+ variables[`o${i}`] = c.owner;
2706
+ variables[`n${i}`] = c.repo;
2707
+ variables[`k${i}`] = c.number;
2708
+ });
2709
+ try {
2710
+ // `allowPartialNotFound`: a coordinate that does not exist is this read's ANSWER for that slot, not a
2711
+ // failure of the batch. GitHub replies 200 with the resolvable aliases populated and a NOT_FOUND per
2712
+ // missing one, so without this a single bad coordinate would discard every good result — and a miss
2713
+ // is the common outcome for the caller this read exists for.
2714
+ const rsp = await this.graphql(provider, token, query, variables, scope, cancellation, true);
2715
+ if (rsp == null)
2716
+ return coordinates.map(() => undefined);
2717
+ // Mapped slot by slot so one unmappable issue can't discard the whole batch, matching the aliased
2718
+ // search's node-by-node mapping. An unmappable issue reads as absent, which is the safe direction
2719
+ // here only because the caller is asking "does this exist", and a false absent costs a re-read
2720
+ // rather than a wrong issue.
2721
+ return coordinates.map((c, i) => {
2722
+ const node = rsp[`i${i}`]?.issue;
2723
+ if (node == null)
2724
+ return undefined;
2725
+ try {
2726
+ return fromGitHubIssue(node, provider);
2727
+ }
2728
+ catch (ex) {
2729
+ scope?.warn(`skipped unmappable issue; ${c.owner}/${c.repo}#${c.number}, ex=${ex}`);
2730
+ return undefined;
2731
+ }
2732
+ });
2733
+ }
2734
+ catch (ex) {
2735
+ throw this.handleException(ex, provider, scope);
2736
+ }
2737
+ }
2551
2738
  /**
2552
2739
  * The PR twin of {@link countIssues}: counts pull requests for several scopes in ONE request via aliased
2553
2740
  * `search` fields selecting only `issueCount` with `first: 0`. Same positional/undefined contract as the issue
@@ -2741,20 +2928,32 @@ export class GitHubApi {
2741
2928
  const rsp = await this.graphql(provider, token, query, variables, scope, cancellation);
2742
2929
  if (rsp == null)
2743
2930
  return { values: [], hasMore: false, page: page, truncated: false };
2744
- // Map node-by-node so one unmappable issue can't discard the whole result set
2745
- const issues = [];
2931
+ // Map node-by-node so one unmappable issue can't discard the whole result set. Kept PER ALIAS rather
2932
+ // than flattened straight away: a ceiling slide bounds each alias at its OWN last item, so the
2933
+ // boundary has to be read off that alias's own page and not off the merged one (see below).
2934
+ const issuesByAlias = new Map();
2746
2935
  for (const s of active) {
2936
+ const mapped = [];
2937
+ // Drop what a previous page already served at this alias's INCLUSIVE slide boundary. Applied
2938
+ // before the merge so the dedupe's precedence rules only ever see genuinely new items.
2939
+ const seen = slideSeenFor(cursor, s.alias);
2747
2940
  for (const node of rsp[s.alias]?.nodes ?? []) {
2748
2941
  if (node?.id == null)
2749
2942
  continue;
2750
2943
  try {
2751
- issues.push(fromGitHubIssue(node, provider));
2944
+ const issue = fromGitHubIssue(node, provider);
2945
+ if (seen?.has(issue.url) !== true) {
2946
+ mapped.push(issue);
2947
+ }
2752
2948
  }
2753
2949
  catch (ex) {
2754
2950
  scope?.warn(`skipped unmappable issue; id=${node.id}, url=${node.url}, ex=${ex}`);
2755
2951
  }
2756
2952
  }
2953
+ issuesByAlias.set(s.alias, mapped);
2757
2954
  }
2955
+ // Flattened in `active` order, which is `searches` order, so the dedupe's precedence is unchanged.
2956
+ const issues = active.flatMap(s => issuesByAlias.get(s.alias) ?? []);
2758
2957
  // Dedupe by `url`, not `IssueShape.id`: for some providers `id` is a per-repository number, so an
2759
2958
  // id-keyed map would collapse distinct issues across repositories.
2760
2959
  const deduped = [
@@ -2786,27 +2985,118 @@ export class GitHubApi {
2786
2985
  let hasMore = false;
2787
2986
  let continuationMissing = false;
2788
2987
  let maxIssueCount = 0;
2988
+ // Whether ANY alias slid this round. A slide is forward progress, so it makes the read continuable
2989
+ // exactly as an ordinary next-cursor does.
2990
+ let slid = false;
2991
+ // Whether any alias ran out of pages against a capped query WITHOUT being able to slide, which is the
2992
+ // only remaining way this read leaves results unreachable.
2993
+ let strandedByCeiling = false;
2994
+ const sortKey = options.sort;
2789
2995
  for (const s of searches) {
2790
2996
  const category = rsp[s.alias];
2791
2997
  const endCursor = category?.pageInfo?.hasNextPage && category.pageInfo.endCursor ? category.pageInfo.endCursor : null;
2792
- next[s.alias] = endCursor;
2793
- if (endCursor != null) {
2794
- hasMore = true;
2795
- }
2796
2998
  if (category?.pageInfo?.hasNextPage === true && category.pageInfo.endCursor == null) {
2797
2999
  continuationMissing = true;
2798
3000
  }
2799
- maxIssueCount = Math.max(maxIssueCount, category?.issueCount ?? 0);
3001
+ const issueCount = category?.issueCount ?? 0;
3002
+ maxIssueCount = Math.max(maxIssueCount, issueCount);
3003
+ // The urls already served at THIS alias's trailing timestamp, which its inclusive slide re-serves.
3004
+ // Accumulated across pages while that timestamp holds, and reset when it moves, so it carries one
3005
+ // second's worth of urls rather than the walk's — a tied block from a bulk edit can span several
3006
+ // pages, and rebuilding this from the last page alone would re-serve the earlier ones.
3007
+ const mapped = issuesByAlias.get(s.alias);
3008
+ const trailingTs = mapped != null ? lastSortBoundary(mapped, sortKey ?? defaultIssueSort)?.getTime() : undefined;
3009
+ const trailing = new Set(trailingTs != null && cursorNumber(cursor, `slideTs:${s.alias}`) === trailingTs
3010
+ ? (slideSeenFor(cursor, s.alias) ?? [])
3011
+ : []);
3012
+ // Accumulated only while this alias is CAPPED, since only a capped alias can reach a slide. Without
3013
+ // that test every page of every search would seal its trailing second's urls for a slide that
3014
+ // will never happen — a rounding error for an ordinary search, where a second holds one or two
3015
+ // issues, but this rides in a cursor `broadenIssues` nests once per org.
3016
+ //
3017
+ // The carry-forward below is NOT gated the same way: a slid query's remainder is often under the
3018
+ // cap, and its first page still needs the filter for the boundary second the inclusive bound
3019
+ // deliberately re-serves. Gating both re-served that block (measured: 100 duplicates).
3020
+ //
3021
+ // Bounded by the reachable window: one second's worth of urls drawn from at most
3022
+ // `githubSearchResultLimit` items, so ~45 KB per alias at the very worst, which takes a bulk edit
3023
+ // stamping a thousand issues with a single timestamp.
3024
+ const carryBoundary = issueCount > githubSearchResultLimit;
3025
+ if (trailingTs != null && carryBoundary) {
3026
+ for (const issue of mapped ?? []) {
3027
+ if (lastSortBoundary([issue], sortKey ?? defaultIssueSort)?.getTime() === trailingTs) {
3028
+ trailing.add(issue.url);
3029
+ }
3030
+ }
3031
+ next[`slideTs:${s.alias}`] = trailingTs;
3032
+ next[`slideSeen:${s.alias}`] = [...trailing].join(' ');
3033
+ }
3034
+ else {
3035
+ // A page can come back EMPTY right after a slide: the re-issued query restarts at the top of
3036
+ // the bounded range, so its first page can be entirely items this set already filtered out.
3037
+ // Dropping the set here would un-filter them on the page after, so carry it forward untouched.
3038
+ next[`slideTs:${s.alias}`] = cursorNumber(cursor, `slideTs:${s.alias}`);
3039
+ next[`slideSeen:${s.alias}`] = cursorString(cursor, `slideSeen:${s.alias}`);
3040
+ }
3041
+ // A CEILING SLIDE turns the per-query cap from terminal into continuable: re-issuing THIS alias's
3042
+ // search bounded to the far side of its own last item gives its remainder a full budget. Bounded
3043
+ // PER ALIAS and not once for the page, because aliases exhaust independently: one bound taken
3044
+ // from the merged page would sit below where an early-finishing alias stopped and re-serve
3045
+ // everything it had already delivered.
3046
+ let aliasSlide;
3047
+ // Capped AND out of pages is where the ceiling actually bites. Evaluated on its own rather than
3048
+ // inside the slide attempt below: a read that cannot slide at all (no order requested, so no
3049
+ // boundary to slide from) must still REPORT the ceiling, and folding the two together silently
3050
+ // dropped that for `searchMyIssues`, whose ordering is opt-in.
3051
+ const strandedHere = endCursor == null && issueCount > githubSearchResultLimit;
3052
+ if (strandedHere && sortKey != null) {
3053
+ const boundary = mapped != null ? lastSortBoundary(mapped, sortKey) : undefined;
3054
+ aliasSlide = boundary != null ? toGitHubIssueSearchSlideQualifier(sortKey, boundary) : undefined;
3055
+ // A bound identical to the one this alias is ALREADY under would re-issue the same query
3056
+ // forever. NOT needed for correctness, and that is measured rather than assumed: against a
3057
+ // provider that honours the bound there is no walk that reaches it. The nearest case, a tied
3058
+ // block larger than the cap, terminates on its own instead — every page inside the block is
3059
+ // filtered away by `slideSeen` until the walk ends on an empty page with no boundary to slide
3060
+ // from, so the second slide is never computed (verified: 1.200 issues sharing one second serve
3061
+ // the reachable 1.000 and report the ceiling, with this guard never firing).
3062
+ //
3063
+ // It is here purely as defence in depth against a bound that silently does nothing, because
3064
+ // that failure mode is an unbounded request loop rather than a wrong answer, and this read
3065
+ // walks until a provider tells it to stop. Cheap insurance against the one bug class that
3066
+ // cannot be shrugged off.
3067
+ if (aliasSlide != null && aliasSlide === cursorString(cursor, `slide:${s.alias}`)) {
3068
+ aliasSlide = undefined;
3069
+ }
3070
+ }
3071
+ if (strandedHere && aliasSlide == null) {
3072
+ strandedByCeiling = true;
3073
+ }
3074
+ if (aliasSlide != null) {
3075
+ // Restart this alias inside its new bound: a position from the old query is meaningless there.
3076
+ next[s.alias] = undefined;
3077
+ next[`slide:${s.alias}`] = aliasSlide;
3078
+ slid = true;
3079
+ }
3080
+ else {
3081
+ next[s.alias] = endCursor;
3082
+ // Carry the bound this alias is already walking under, or it would restart at the top.
3083
+ next[`slide:${s.alias}`] = cursorString(cursor, `slide:${s.alias}`);
3084
+ if (endCursor != null) {
3085
+ hasMore = true;
3086
+ }
3087
+ }
2800
3088
  }
2801
- // GitHub search exposes at most `githubSearchResultLimit` results PER SEARCH, so the ceiling is
2802
- // reached as soon as any one alias exceeds it. Paging removes the old 100-item truncation; only that
2803
- // upstream ceiling or an unusable continuation leaves the read incomplete.
2804
- const truncated = cursor?.truncated === true || maxIssueCount > githubSearchResultLimit || continuationMissing;
3089
+ // Only the ceiling an alias could NOT slide past is incompleteness. A capped alias with pages left has
3090
+ // nothing to slide past yet, and one that slid has its remainder reachable through the cursor: marking
3091
+ // either would seal `truncated: true` and keep it there for the rest of the read, so a completed walk
3092
+ // would report results it went on to fetch as omitted.
3093
+ const truncated = cursor?.truncated === true || strandedByCeiling || continuationMissing;
2805
3094
  next.truncated = truncated || undefined;
3095
+ const continuable = hasMore || slid;
2806
3096
  return {
2807
3097
  values: deduped,
2808
- cursor: hasMore ? JSON.stringify(next) : undefined,
2809
- hasMore: hasMore,
3098
+ cursor: continuable ? JSON.stringify(next) : undefined,
3099
+ hasMore: continuable,
2810
3100
  page: page,
2811
3101
  truncated: truncated,
2812
3102
  totalCount: maxIssueCount,
@@ -3008,25 +3298,31 @@ export class GitHubApi {
3008
3298
  const scope = getScopedLogger();
3009
3299
  const pageSize = 10;
3010
3300
  const include = options?.include?.length ? options.include : undefined;
3011
- const requiresPagination = shouldPaginateGitHubSearchState(include);
3301
+ const stateSearches = requiresGitHubPullRequestStateFacets(include)
3302
+ ? [
3303
+ { alias: 'opened', qualifier: 'is:open' },
3304
+ { alias: 'merged', qualifier: 'is:merged' },
3305
+ ]
3306
+ : [{ alias: 'search', qualifier: toGitHubSearchStateQualifier(include) }];
3012
3307
  try {
3013
- const query = `query searchPullRequests(
3014
- $searchQuery: String!
3015
- $cursor: String
3016
- $avatarSize: Int
3017
- ) {
3018
- search(first: ${pageSize}, after: $cursor, query: $searchQuery, type: ISSUE) {
3019
- pageInfo {
3020
- endCursor
3021
- hasNextPage
3022
- }
3308
+ const params = stateSearches.map(({ alias }) => alias === 'search' ? '$searchQuery: String!' : `$${alias}SearchQuery: String!`);
3309
+ const fields = stateSearches.map(({ alias }) => {
3310
+ const fieldAlias = alias === 'search' ? '' : `${alias}: `;
3311
+ const variable = alias === 'search' ? 'searchQuery' : `${alias}SearchQuery`;
3312
+ return `${fieldAlias}search(first: ${pageSize}, query: $${variable}, type: ISSUE) {
3023
3313
  nodes {
3024
3314
  ...on PullRequest {
3025
3315
  ${gqlPullRequestFragment}
3026
3316
  ${gqlPullRequestStackFragmentFor(options)}
3027
3317
  }
3028
3318
  }
3029
- }
3319
+ }`;
3320
+ });
3321
+ const query = `query searchPullRequests(
3322
+ ${params.join('\n\t')}
3323
+ $avatarSize: Int
3324
+ ) {
3325
+ ${fields.join('\n\t')}
3030
3326
  }`;
3031
3327
  let search = options?.search?.trim() ?? '';
3032
3328
  if (options?.user) {
@@ -3036,31 +3332,26 @@ export class GitHubApi {
3036
3332
  const repo = ' repo:';
3037
3333
  search += `${repo}${options.repos.join(repo)}`;
3038
3334
  }
3039
- const searchQuery = ['is:pr', toGitHubSearchStateQualifier(include), 'archived:false', search.trim()]
3040
- .filter(Boolean)
3041
- .join(' ');
3042
- // Bound the paginated case with a defensive page backstop like the other paged provider drains, so a
3043
- // large, low-match result set can't fan out into an unbounded request loop.
3044
- const maxSearchPages = 20;
3045
- let cursor;
3335
+ const variables = {
3336
+ baseUrl: options?.baseUrl,
3337
+ avatarSize: options?.avatarSize,
3338
+ };
3339
+ for (const { alias, qualifier } of stateSearches) {
3340
+ const variable = alias === 'search' ? 'searchQuery' : `${alias}SearchQuery`;
3341
+ variables[variable] = ['is:pr', qualifier, 'archived:false', search.trim()].filter(Boolean).join(' ');
3342
+ }
3343
+ const rsp = await this.graphql(provider, token, query, variables, scope, cancellation);
3344
+ if (rsp == null)
3345
+ return [];
3046
3346
  const results = [];
3047
- for (let page = 0; page < maxSearchPages; page++) {
3048
- const rsp = await this.graphql(provider, token, query, {
3049
- searchQuery: searchQuery,
3050
- cursor: cursor,
3051
- baseUrl: options?.baseUrl,
3052
- avatarSize: options?.avatarSize,
3053
- }, scope, cancellation);
3054
- if (rsp == null)
3055
- return results;
3056
- const pageResults = filterPullRequestsBySearchState(rsp.search.nodes.map(pr => fromGitHubPullRequest(pr, provider)), include);
3057
- results.push(...pageResults);
3058
- cursor = rsp.search.pageInfo.endCursor ?? undefined;
3059
- if (!requiresPagination || results.length >= pageSize || !rsp.search.pageInfo.hasNextPage) {
3060
- break;
3347
+ for (const { alias } of stateSearches) {
3348
+ for (const node of rsp[alias]?.nodes ?? []) {
3349
+ results.push(fromGitHubPullRequest(node, provider));
3061
3350
  }
3062
3351
  }
3063
- return results.slice(0, pageSize);
3352
+ return [
3353
+ ...uniqueBy(filterPullRequestsBySearchState(results, include), pr => pr.url, (original, _current) => original),
3354
+ ].slice(0, pageSize);
3064
3355
  }
3065
3356
  catch (ex) {
3066
3357
  throw this.handleException(ex, provider, scope);
@@ -3495,6 +3786,9 @@ __decorate([
3495
3786
  __decorate([
3496
3787
  trace({ args: (provider, token) => ({ provider: provider.name, token: `<token:${token.microHash}>` }) })
3497
3788
  ], GitHubApi.prototype, "countIssues", null);
3789
+ __decorate([
3790
+ trace({ args: (provider, token) => ({ provider: provider.name, token: `<token:${token.microHash}>` }) })
3791
+ ], GitHubApi.prototype, "getIssuesBatch", null);
3498
3792
  __decorate([
3499
3793
  trace({ args: (provider, token) => ({ provider: provider.name, token: `<token:${token.microHash}>` }) })
3500
3794
  ], GitHubApi.prototype, "countPullRequests", null);
@@ -3555,9 +3849,8 @@ export function toGitHubSearchStateQualifier(include) {
3555
3849
  return 'is:unmerged';
3556
3850
  if (closed && merged)
3557
3851
  return 'is:closed';
3558
- // `opened && merged` isn't expressible as a single AND qualifier; omit it here and post-filter.
3559
3852
  if (opened && merged)
3560
- return '';
3853
+ throw new Error('GitHub cannot express opened and merged as one search state qualifier');
3561
3854
  if (opened)
3562
3855
  return 'is:open';
3563
3856
  if (merged)
@@ -3575,7 +3868,7 @@ export function filterPullRequestsBySearchState(pullRequests, include) {
3575
3868
  return pullRequests;
3576
3869
  return pullRequests.filter(pr => allowedStates.has(pr.state));
3577
3870
  }
3578
- function shouldPaginateGitHubSearchState(include) {
3871
+ function requiresGitHubPullRequestStateFacets(include) {
3579
3872
  if (include == null || include.length === 0)
3580
3873
  return false;
3581
3874
  const uniqueStates = new Set(include);