@gitkraken/core-gitlens 0.5.110 → 0.5.112

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 (362) hide show
  1. package/CHANGELOG.md +19 -1
  2. package/dist/git/cache.d.ts +111 -6
  3. package/dist/git/cache.d.ts.map +1 -1
  4. package/dist/git/cache.js +312 -43
  5. package/dist/git/cache.js.map +1 -1
  6. package/dist/git/context.d.ts +11 -0
  7. package/dist/git/context.d.ts.map +1 -1
  8. package/dist/git/context.js.map +1 -1
  9. package/dist/git/models/fileStatus.d.ts +1 -0
  10. package/dist/git/models/fileStatus.d.ts.map +1 -1
  11. package/dist/git/models/fileStatus.js +1 -0
  12. package/dist/git/models/fileStatus.js.map +1 -1
  13. package/dist/git/models/issue.d.ts +4 -0
  14. package/dist/git/models/issue.d.ts.map +1 -1
  15. package/dist/git/models/mergeConflicts.d.ts +2 -0
  16. package/dist/git/models/mergeConflicts.d.ts.map +1 -1
  17. package/dist/git/models/pullRequest.d.ts +38 -2
  18. package/dist/git/models/pullRequest.d.ts.map +1 -1
  19. package/dist/git/models/pullRequest.js +12 -1
  20. package/dist/git/models/pullRequest.js.map +1 -1
  21. package/dist/git/models/repository.d.ts +32 -5
  22. package/dist/git/models/repository.d.ts.map +1 -1
  23. package/dist/git/models/repository.js +58 -12
  24. package/dist/git/models/repository.js.map +1 -1
  25. package/dist/git/models/statusFile.d.ts.map +1 -1
  26. package/dist/git/models/statusFile.js +3 -0
  27. package/dist/git/models/statusFile.js.map +1 -1
  28. package/dist/git/providers/branches.d.ts +2 -0
  29. package/dist/git/providers/branches.d.ts.map +1 -1
  30. package/dist/git/providers/commits.d.ts +8 -0
  31. package/dist/git/providers/commits.d.ts.map +1 -1
  32. package/dist/git/providers/config.d.ts +19 -1
  33. package/dist/git/providers/config.d.ts.map +1 -1
  34. package/dist/git/providers/operations.d.ts +1 -0
  35. package/dist/git/providers/operations.d.ts.map +1 -1
  36. package/dist/git/providers/pausedOperations.d.ts +2 -0
  37. package/dist/git/providers/pausedOperations.d.ts.map +1 -1
  38. package/dist/git/providers/status.d.ts +1 -0
  39. package/dist/git/providers/status.d.ts.map +1 -1
  40. package/dist/git/utils/config.utils.d.ts +3 -0
  41. package/dist/git/utils/config.utils.d.ts.map +1 -0
  42. package/dist/git/utils/config.utils.js +8 -0
  43. package/dist/git/utils/config.utils.js.map +1 -0
  44. package/dist/git/utils/issue.utils.d.ts.map +1 -1
  45. package/dist/git/utils/issue.utils.js +2 -0
  46. package/dist/git/utils/issue.utils.js.map +1 -1
  47. package/dist/git/utils/pausedOperationStatus.utils.d.ts +14 -0
  48. package/dist/git/utils/pausedOperationStatus.utils.d.ts.map +1 -1
  49. package/dist/git/utils/pausedOperationStatus.utils.js +23 -0
  50. package/dist/git/utils/pausedOperationStatus.utils.js.map +1 -1
  51. package/dist/git/utils/pullRequest.utils.d.ts +14 -1
  52. package/dist/git/utils/pullRequest.utils.d.ts.map +1 -1
  53. package/dist/git/utils/pullRequest.utils.js +31 -0
  54. package/dist/git/utils/pullRequest.utils.js.map +1 -1
  55. package/dist/git/watching/changeEvent.d.ts +6 -1
  56. package/dist/git/watching/changeEvent.d.ts.map +1 -1
  57. package/dist/git/watching/classifyChange.d.ts.map +1 -1
  58. package/dist/git/watching/classifyChange.js +14 -1
  59. package/dist/git/watching/classifyChange.js.map +1 -1
  60. package/dist/git/watching/watchService.d.ts.map +1 -1
  61. package/dist/git/watching/watchService.js +18 -3
  62. package/dist/git/watching/watchService.js.map +1 -1
  63. package/dist/git/watching/watchSession.d.ts +8 -1
  64. package/dist/git/watching/watchSession.d.ts.map +1 -1
  65. package/dist/git/watching/watchSession.js +38 -6
  66. package/dist/git/watching/watchSession.js.map +1 -1
  67. package/dist/git/watching/watcherPatterns.d.ts +2 -2
  68. package/dist/git/watching/watcherPatterns.d.ts.map +1 -1
  69. package/dist/git/watching/watcherPatterns.js +1 -1
  70. package/dist/git/watching/watcherPatterns.js.map +1 -1
  71. package/dist/git-cli/exec/git.d.ts +1 -1
  72. package/dist/git-cli/exec/git.d.ts.map +1 -1
  73. package/dist/git-cli/exec/git.js +13 -3
  74. package/dist/git-cli/exec/git.js.map +1 -1
  75. package/dist/git-cli/providers/branches.d.ts +19 -0
  76. package/dist/git-cli/providers/branches.d.ts.map +1 -1
  77. package/dist/git-cli/providers/branches.js +282 -78
  78. package/dist/git-cli/providers/branches.js.map +1 -1
  79. package/dist/git-cli/providers/commits.d.ts +4 -0
  80. package/dist/git-cli/providers/commits.d.ts.map +1 -1
  81. package/dist/git-cli/providers/commits.js +84 -11
  82. package/dist/git-cli/providers/commits.js.map +1 -1
  83. package/dist/git-cli/providers/config.d.ts +42 -0
  84. package/dist/git-cli/providers/config.d.ts.map +1 -1
  85. package/dist/git-cli/providers/config.js +186 -5
  86. package/dist/git-cli/providers/config.js.map +1 -1
  87. package/dist/git-cli/providers/contributors.d.ts.map +1 -1
  88. package/dist/git-cli/providers/contributors.js +1 -1
  89. package/dist/git-cli/providers/contributors.js.map +1 -1
  90. package/dist/git-cli/providers/graph.d.ts +2 -2
  91. package/dist/git-cli/providers/graph.d.ts.map +1 -1
  92. package/dist/git-cli/providers/graph.js +27 -9
  93. package/dist/git-cli/providers/graph.js.map +1 -1
  94. package/dist/git-cli/providers/operations.d.ts.map +1 -1
  95. package/dist/git-cli/providers/operations.js +40 -12
  96. package/dist/git-cli/providers/operations.js.map +1 -1
  97. package/dist/git-cli/providers/pausedOperations.d.ts +5 -0
  98. package/dist/git-cli/providers/pausedOperations.d.ts.map +1 -1
  99. package/dist/git-cli/providers/pausedOperations.js +87 -16
  100. package/dist/git-cli/providers/pausedOperations.js.map +1 -1
  101. package/dist/git-cli/providers/refs.d.ts +5 -3
  102. package/dist/git-cli/providers/refs.d.ts.map +1 -1
  103. package/dist/git-cli/providers/refs.js +33 -4
  104. package/dist/git-cli/providers/refs.js.map +1 -1
  105. package/dist/git-cli/providers/status.d.ts +1 -0
  106. package/dist/git-cli/providers/status.d.ts.map +1 -1
  107. package/dist/git-cli/providers/status.js +18 -3
  108. package/dist/git-cli/providers/status.js.map +1 -1
  109. package/dist/git-cli/providers/worktrees.d.ts.map +1 -1
  110. package/dist/git-cli/providers/worktrees.js +4 -0
  111. package/dist/git-cli/providers/worktrees.js.map +1 -1
  112. package/dist/git-cli/service.d.ts.map +1 -1
  113. package/dist/git-cli/service.js.map +1 -1
  114. package/dist/plus/agents/providers/claudeCodeProvider.d.ts +39 -1
  115. package/dist/plus/agents/providers/claudeCodeProvider.d.ts.map +1 -1
  116. package/dist/plus/agents/providers/claudeCodeProvider.js +978 -71
  117. package/dist/plus/agents/providers/claudeCodeProvider.js.map +1 -1
  118. package/dist/plus/agents/providers/claudeCodeTranscript.d.ts +24 -2
  119. package/dist/plus/agents/providers/claudeCodeTranscript.d.ts.map +1 -1
  120. package/dist/plus/agents/providers/claudeCodeTranscript.js +84 -9
  121. package/dist/plus/agents/providers/claudeCodeTranscript.js.map +1 -1
  122. package/dist/plus/agents/types.d.ts +37 -2
  123. package/dist/plus/agents/types.d.ts.map +1 -1
  124. package/dist/plus/agents/types.js +2 -0
  125. package/dist/plus/agents/types.js.map +1 -1
  126. package/dist/plus/ai/errors.d.ts +10 -0
  127. package/dist/plus/ai/errors.d.ts.map +1 -1
  128. package/dist/plus/ai/errors.js +29 -0
  129. package/dist/plus/ai/errors.js.map +1 -1
  130. package/dist/plus/ai/models/model.d.ts +4 -0
  131. package/dist/plus/ai/models/model.d.ts.map +1 -1
  132. package/dist/plus/ai/models/model.js +51 -6
  133. package/dist/plus/ai/models/model.js.map +1 -1
  134. package/dist/plus/ai/models/promptTemplates.d.ts +1 -1
  135. package/dist/plus/ai/models/promptTemplates.d.ts.map +1 -1
  136. package/dist/plus/ai/models/provider.d.ts +60 -2
  137. package/dist/plus/ai/models/provider.d.ts.map +1 -1
  138. package/dist/plus/ai/prompts.d.ts +12 -0
  139. package/dist/plus/ai/prompts.d.ts.map +1 -1
  140. package/dist/plus/ai/prompts.js +177 -61
  141. package/dist/plus/ai/prompts.js.map +1 -1
  142. package/dist/plus/ai/providers/anthropicProvider.d.ts +10 -2
  143. package/dist/plus/ai/providers/anthropicProvider.d.ts.map +1 -1
  144. package/dist/plus/ai/providers/anthropicProvider.js +139 -46
  145. package/dist/plus/ai/providers/anthropicProvider.js.map +1 -1
  146. package/dist/plus/ai/providers/deepSeekProvider.d.ts +2 -0
  147. package/dist/plus/ai/providers/deepSeekProvider.d.ts.map +1 -1
  148. package/dist/plus/ai/providers/deepSeekProvider.js +10 -21
  149. package/dist/plus/ai/providers/deepSeekProvider.js.map +1 -1
  150. package/dist/plus/ai/providers/geminiProvider.d.ts +1 -0
  151. package/dist/plus/ai/providers/geminiProvider.d.ts.map +1 -1
  152. package/dist/plus/ai/providers/geminiProvider.js +19 -0
  153. package/dist/plus/ai/providers/geminiProvider.js.map +1 -1
  154. package/dist/plus/ai/providers/gitkrakenProvider.d.ts +5 -1
  155. package/dist/plus/ai/providers/gitkrakenProvider.d.ts.map +1 -1
  156. package/dist/plus/ai/providers/gitkrakenProvider.js +118 -3
  157. package/dist/plus/ai/providers/gitkrakenProvider.js.map +1 -1
  158. package/dist/plus/ai/providers/huggingFaceProvider.d.ts +1 -0
  159. package/dist/plus/ai/providers/huggingFaceProvider.d.ts.map +1 -1
  160. package/dist/plus/ai/providers/huggingFaceProvider.js +5 -0
  161. package/dist/plus/ai/providers/huggingFaceProvider.js.map +1 -1
  162. package/dist/plus/ai/providers/mistralProvider.d.ts +3 -1
  163. package/dist/plus/ai/providers/mistralProvider.d.ts.map +1 -1
  164. package/dist/plus/ai/providers/mistralProvider.js +99 -19
  165. package/dist/plus/ai/providers/mistralProvider.js.map +1 -1
  166. package/dist/plus/ai/providers/ollamaProvider.d.ts +4 -3
  167. package/dist/plus/ai/providers/ollamaProvider.d.ts.map +1 -1
  168. package/dist/plus/ai/providers/ollamaProvider.js +38 -3
  169. package/dist/plus/ai/providers/ollamaProvider.js.map +1 -1
  170. package/dist/plus/ai/providers/openAICompatibleProvider.d.ts +1 -0
  171. package/dist/plus/ai/providers/openAICompatibleProvider.d.ts.map +1 -1
  172. package/dist/plus/ai/providers/openAICompatibleProvider.js +4 -0
  173. package/dist/plus/ai/providers/openAICompatibleProvider.js.map +1 -1
  174. package/dist/plus/ai/providers/openAICompatibleProviderBase.d.ts +154 -5
  175. package/dist/plus/ai/providers/openAICompatibleProviderBase.d.ts.map +1 -1
  176. package/dist/plus/ai/providers/openAICompatibleProviderBase.js +259 -10
  177. package/dist/plus/ai/providers/openAICompatibleProviderBase.js.map +1 -1
  178. package/dist/plus/ai/providers/openRouterProvider.d.ts +4 -0
  179. package/dist/plus/ai/providers/openRouterProvider.d.ts.map +1 -1
  180. package/dist/plus/ai/providers/openRouterProvider.js +9 -0
  181. package/dist/plus/ai/providers/openRouterProvider.js.map +1 -1
  182. package/dist/plus/ai/providers/openaiProvider.d.ts +1 -0
  183. package/dist/plus/ai/providers/openaiProvider.d.ts.map +1 -1
  184. package/dist/plus/ai/providers/openaiProvider.js +1 -0
  185. package/dist/plus/ai/providers/openaiProvider.js.map +1 -1
  186. package/dist/plus/ai/providers/responseFormatCache.d.ts +8 -0
  187. package/dist/plus/ai/providers/responseFormatCache.d.ts.map +1 -0
  188. package/dist/plus/ai/providers/responseFormatCache.js +25 -0
  189. package/dist/plus/ai/providers/responseFormatCache.js.map +1 -0
  190. package/dist/plus/ai/providers/xaiProvider.d.ts +1 -0
  191. package/dist/plus/ai/providers/xaiProvider.d.ts.map +1 -1
  192. package/dist/plus/ai/providers/xaiProvider.js +10 -1
  193. package/dist/plus/ai/providers/xaiProvider.js.map +1 -1
  194. package/dist/plus/ai/utils/ai.utils.d.ts +3 -0
  195. package/dist/plus/ai/utils/ai.utils.d.ts.map +1 -1
  196. package/dist/plus/ai/utils/ai.utils.js +13 -0
  197. package/dist/plus/ai/utils/ai.utils.js.map +1 -1
  198. package/dist/plus/ai/utils/results.utils.d.ts +16 -2
  199. package/dist/plus/ai/utils/results.utils.d.ts.map +1 -1
  200. package/dist/plus/ai/utils/results.utils.js +204 -28
  201. package/dist/plus/ai/utils/results.utils.js.map +1 -1
  202. package/dist/plus/git-github/api/github.d.ts +55 -0
  203. package/dist/plus/git-github/api/github.d.ts.map +1 -1
  204. package/dist/plus/git-github/api/github.js +177 -4
  205. package/dist/plus/git-github/api/github.js.map +1 -1
  206. package/dist/plus/git-github/models.d.ts +16 -0
  207. package/dist/plus/git-github/models.d.ts.map +1 -1
  208. package/dist/plus/git-github/models.js +29 -3
  209. package/dist/plus/git-github/models.js.map +1 -1
  210. package/dist/plus/git-github/providers/github/status.d.ts +1 -0
  211. package/dist/plus/git-github/providers/github/status.d.ts.map +1 -1
  212. package/dist/plus/git-github/providers/github/status.js.map +1 -1
  213. package/dist/plus/integrations/context.d.ts +4 -0
  214. package/dist/plus/integrations/context.d.ts.map +1 -1
  215. package/dist/plus/integrations/index.d.ts.map +1 -1
  216. package/dist/plus/integrations/index.js +2 -0
  217. package/dist/plus/integrations/index.js.map +1 -1
  218. package/dist/plus/integrations/models/gitHostIntegration.d.ts +6 -3
  219. package/dist/plus/integrations/models/gitHostIntegration.d.ts.map +1 -1
  220. package/dist/plus/integrations/models/gitHostIntegration.js +12 -2
  221. package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
  222. package/dist/plus/integrations/models/integration.d.ts +3 -1
  223. package/dist/plus/integrations/models/integration.d.ts.map +1 -1
  224. package/dist/plus/integrations/models/integration.js +2 -2
  225. package/dist/plus/integrations/models/integration.js.map +1 -1
  226. package/dist/plus/integrations/providers/accounts.d.ts.map +1 -1
  227. package/dist/plus/integrations/providers/accounts.js +4 -0
  228. package/dist/plus/integrations/providers/accounts.js.map +1 -1
  229. package/dist/plus/integrations/providers/azure/models.d.ts.map +1 -1
  230. package/dist/plus/integrations/providers/azure/models.js +3 -0
  231. package/dist/plus/integrations/providers/azure/models.js.map +1 -1
  232. package/dist/plus/integrations/providers/bitbucket/models.d.ts.map +1 -1
  233. package/dist/plus/integrations/providers/bitbucket/models.js +1 -0
  234. package/dist/plus/integrations/providers/bitbucket/models.js.map +1 -1
  235. package/dist/plus/integrations/providers/github.d.ts +14 -2
  236. package/dist/plus/integrations/providers/github.d.ts.map +1 -1
  237. package/dist/plus/integrations/providers/github.js +55 -3
  238. package/dist/plus/integrations/providers/github.js.map +1 -1
  239. package/dist/plus/integrations/providers/gitlab.d.ts.map +1 -1
  240. package/dist/plus/integrations/providers/gitlab.js.map +1 -1
  241. package/dist/plus/integrations/providers/models.d.ts.map +1 -1
  242. package/dist/plus/integrations/providers/models.js +30 -4
  243. package/dist/plus/integrations/providers/models.js.map +1 -1
  244. package/dist/plus/integrations/reads/broaden.d.ts.map +1 -1
  245. package/dist/plus/integrations/reads/broaden.js +6 -1
  246. package/dist/plus/integrations/reads/broaden.js.map +1 -1
  247. package/dist/plus/integrations/reads/drains.d.ts.map +1 -1
  248. package/dist/plus/integrations/reads/drains.js +7 -4
  249. package/dist/plus/integrations/reads/drains.js.map +1 -1
  250. package/dist/plus/integrations/reads/hierarchy.d.ts.map +1 -1
  251. package/dist/plus/integrations/reads/hierarchy.js +3 -1
  252. package/dist/plus/integrations/reads/hierarchy.js.map +1 -1
  253. package/dist/plus/integrations/reads/issueTracker.d.ts.map +1 -1
  254. package/dist/plus/integrations/reads/issueTracker.js +8 -3
  255. package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
  256. package/dist/plus/integrations/reads/issues.d.ts.map +1 -1
  257. package/dist/plus/integrations/reads/issues.js +9 -4
  258. package/dist/plus/integrations/reads/issues.js.map +1 -1
  259. package/dist/plus/integrations/reads/pullRequests.d.ts.map +1 -1
  260. package/dist/plus/integrations/reads/pullRequests.js +9 -4
  261. package/dist/plus/integrations/reads/pullRequests.js.map +1 -1
  262. package/dist/utils/env/browser/base64.d.ts +1 -1
  263. package/dist/utils/env/browser/base64.d.ts.map +1 -1
  264. package/dist/utils/env/node/base64.d.ts +1 -1
  265. package/dist/utils/env/node/base64.d.ts.map +1 -1
  266. package/dist/utils/env/node/base64.js +2 -0
  267. package/dist/utils/env/node/base64.js.map +1 -1
  268. package/dist/utils/keys/chord.d.ts +52 -0
  269. package/dist/utils/keys/chord.d.ts.map +1 -0
  270. package/dist/utils/keys/chord.js +135 -0
  271. package/dist/utils/keys/chord.js.map +1 -0
  272. package/dist/utils/keys/keybinding.d.ts +53 -0
  273. package/dist/utils/keys/keybinding.d.ts.map +1 -0
  274. package/dist/utils/keys/keybinding.js +58 -0
  275. package/dist/utils/keys/keybinding.js.map +1 -0
  276. package/dist/utils/path.d.ts +11 -0
  277. package/dist/utils/path.d.ts.map +1 -1
  278. package/dist/utils/path.js +13 -0
  279. package/dist/utils/path.js.map +1 -1
  280. package/package.json +13 -5
  281. package/src/git/cache.ts +383 -47
  282. package/src/git/context.ts +11 -0
  283. package/src/git/models/fileStatus.ts +1 -0
  284. package/src/git/models/issue.ts +4 -0
  285. package/src/git/models/mergeConflicts.ts +3 -2
  286. package/src/git/models/pullRequest.ts +33 -1
  287. package/src/git/models/repository.ts +58 -17
  288. package/src/git/models/statusFile.ts +3 -0
  289. package/src/git/providers/branches.ts +6 -0
  290. package/src/git/providers/commits.ts +11 -0
  291. package/src/git/providers/config.ts +18 -0
  292. package/src/git/providers/operations.ts +1 -0
  293. package/src/git/providers/pausedOperations.ts +2 -0
  294. package/src/git/providers/status.ts +1 -0
  295. package/src/git/utils/config.utils.ts +8 -0
  296. package/src/git/utils/issue.utils.ts +2 -0
  297. package/src/git/utils/pausedOperationStatus.utils.ts +28 -0
  298. package/src/git/utils/pullRequest.utils.ts +36 -0
  299. package/src/git/watching/changeEvent.ts +6 -1
  300. package/src/git/watching/classifyChange.ts +15 -1
  301. package/src/git/watching/watchService.ts +20 -3
  302. package/src/git/watching/watchSession.ts +41 -6
  303. package/src/git/watching/watcherPatterns.ts +1 -1
  304. package/src/git-cli/exec/git.ts +13 -3
  305. package/src/git-cli/providers/branches.ts +384 -132
  306. package/src/git-cli/providers/commits.ts +106 -15
  307. package/src/git-cli/providers/config.ts +229 -8
  308. package/src/git-cli/providers/contributors.ts +5 -1
  309. package/src/git-cli/providers/graph.ts +26 -13
  310. package/src/git-cli/providers/operations.ts +45 -20
  311. package/src/git-cli/providers/pausedOperations.ts +109 -17
  312. package/src/git-cli/providers/refs.ts +33 -3
  313. package/src/git-cli/providers/status.ts +25 -4
  314. package/src/git-cli/providers/worktrees.ts +5 -0
  315. package/src/git-cli/service.ts +0 -1
  316. package/src/plus/agents/providers/claudeCodeProvider.ts +1130 -87
  317. package/src/plus/agents/providers/claudeCodeTranscript.ts +95 -9
  318. package/src/plus/agents/types.ts +46 -2
  319. package/src/plus/ai/errors.ts +30 -0
  320. package/src/plus/ai/models/model.ts +56 -6
  321. package/src/plus/ai/models/promptTemplates.ts +1 -1
  322. package/src/plus/ai/models/provider.ts +69 -2
  323. package/src/plus/ai/prompts.ts +186 -61
  324. package/src/plus/ai/providers/anthropicProvider.ts +155 -47
  325. package/src/plus/ai/providers/deepSeekProvider.ts +11 -21
  326. package/src/plus/ai/providers/geminiProvider.ts +19 -0
  327. package/src/plus/ai/providers/gitkrakenProvider.ts +126 -3
  328. package/src/plus/ai/providers/huggingFaceProvider.ts +6 -0
  329. package/src/plus/ai/providers/mistralProvider.ts +102 -19
  330. package/src/plus/ai/providers/ollamaProvider.ts +55 -4
  331. package/src/plus/ai/providers/openAICompatibleProvider.ts +5 -0
  332. package/src/plus/ai/providers/openAICompatibleProviderBase.ts +360 -17
  333. package/src/plus/ai/providers/openRouterProvider.ts +17 -0
  334. package/src/plus/ai/providers/openaiProvider.ts +1 -0
  335. package/src/plus/ai/providers/responseFormatCache.ts +39 -0
  336. package/src/plus/ai/providers/xaiProvider.ts +10 -1
  337. package/src/plus/ai/utils/ai.utils.ts +14 -0
  338. package/src/plus/ai/utils/results.utils.ts +255 -31
  339. package/src/plus/git-github/api/github.ts +291 -7
  340. package/src/plus/git-github/models.ts +48 -2
  341. package/src/plus/git-github/providers/github/status.ts +1 -0
  342. package/src/plus/integrations/context.ts +4 -0
  343. package/src/plus/integrations/index.ts +2 -0
  344. package/src/plus/integrations/models/gitHostIntegration.ts +25 -2
  345. package/src/plus/integrations/models/integration.ts +23 -13
  346. package/src/plus/integrations/providers/accounts.ts +4 -0
  347. package/src/plus/integrations/providers/azure/models.ts +3 -0
  348. package/src/plus/integrations/providers/bitbucket/models.ts +1 -0
  349. package/src/plus/integrations/providers/github.ts +85 -1
  350. package/src/plus/integrations/providers/gitlab.ts +2 -10
  351. package/src/plus/integrations/providers/models.ts +36 -4
  352. package/src/plus/integrations/reads/broaden.ts +6 -1
  353. package/src/plus/integrations/reads/drains.ts +7 -4
  354. package/src/plus/integrations/reads/hierarchy.ts +3 -1
  355. package/src/plus/integrations/reads/issueTracker.ts +9 -3
  356. package/src/plus/integrations/reads/issues.ts +9 -4
  357. package/src/plus/integrations/reads/pullRequests.ts +9 -4
  358. package/src/utils/env/browser/base64.ts +1 -1
  359. package/src/utils/env/node/base64.ts +3 -1
  360. package/src/utils/keys/chord.ts +184 -0
  361. package/src/utils/keys/keybinding.ts +116 -0
  362. package/src/utils/path.ts +14 -0
package/src/git/cache.ts CHANGED
@@ -61,7 +61,26 @@ export type CachedGitTypes =
61
61
  | 'tracking'
62
62
  | 'worktrees';
63
63
 
64
- export type ConflictDetectionCacheKey = `apply:${string}:${string}:${string}` | `merge:${string}:${string}`;
64
+ /**
65
+ * What a gk-config reconcile did, so `Cache` knows whether the coarse fallback is still owed.
66
+ *
67
+ * `'deferred'` is NOT a failure — a pass already in flight folds the change into its trailing pass — and
68
+ * must not trigger the fallback, or a burst of gk writes would coarse-clear on every one, which is exactly
69
+ * the churn the key-aware reconcile replaced.
70
+ */
71
+ export type GkReconcileOutcome = 'reconciled' | 'deferred' | 'failed';
72
+
73
+ export type ConflictDetectionCacheKey =
74
+ | `apply:${string}:${string}:${string}`
75
+ | `merge:${string}:${string}`
76
+ | `reapply:${string}:${string}`;
77
+
78
+ /**
79
+ * Ceiling on how long a derived branch base is trusted. Long enough that repeated reads during normal use
80
+ * are served from cache, short enough that a base that changed by a route with no eviction signal (an
81
+ * external delete-and-recreate of a branch that is never checked out) self-heals without a reload.
82
+ */
83
+ const baseBranchNameTTL = 5 * 60 * 1000; // 5 minutes
65
84
 
66
85
  /**
67
86
  * gkConfig keys that participate in the `branchOverviews` cache's mergeTarget/mergeBase lineage.
@@ -215,6 +234,29 @@ export class Cache implements Disposable {
215
234
  private _worktreesByCommonPath = new Map<string, Set<RepoPath>>();
216
235
  /** Monotonic per-worktree "status clock" — see {@link getStatusGeneration}. */
217
236
  private _statusGenerations = new Map<RepoPath, number>();
237
+ /** Monotonic per-path "close clock" — see {@link getCloseGeneration}. */
238
+ private _closeGenerations = new Map<RepoPath, number>();
239
+
240
+ /**
241
+ * Per-commonPath snapshot of the merge-relevant subset of `.git/gk/config` (keys matching
242
+ * {@link branchOverviewGkConfigKeysRegex}), used by {@link reconcileGkConfigMap} to diff a watcher
243
+ * `'gkConfig'` event down to only the keys that actually changed, instead of coarsely re-deriving
244
+ * `baseBranchName`/`branchOverviews` on every gk write. Absent means "never observed".
245
+ */
246
+ private _gkConfigMergeSnapshots = new Map<string, Map<string, string>>();
247
+
248
+ /**
249
+ * Reconciler registered by the CLI config sub-provider (see {@link setGkConfigReconciler}),
250
+ * invoked on a watcher-observed `'gkConfig'` change instead of the coarse `clearCaches(repoPath,
251
+ * 'gkConfig')` cascade. `undefined` (e.g. the GitHub provider, which has no `.git/gk/config`)
252
+ * falls back to the coarse clear.
253
+ */
254
+ private _gkConfigReconciler:
255
+ | ((
256
+ repoPath: string,
257
+ priorSnapshot: ReadonlyMap<string, string> | undefined,
258
+ ) => PromiseOrValue<GkReconcileOutcome>)
259
+ | undefined;
218
260
 
219
261
  [Symbol.dispose](): void {
220
262
  this.dispose();
@@ -526,9 +568,25 @@ export class Cache implements Disposable {
526
568
  keysToClear.add('resolvedRevisions');
527
569
  }
528
570
 
529
- // Shared branch caches (branch list, metadata)
530
- if (types.includes('branches')) {
571
+ // Shared branch caches (branch list, metadata). `baseBranchName` is deliberately NOT cleared
572
+ // here — a branch's base is set at creation and only changes when its stored key changes
573
+ // (`gkConfig`/`config` below) or the branch itself is created/renamed/deleted. Every op that
574
+ // does one of those calls `deleteBaseBranchName` and moves or drops the branch's persisted
575
+ // `branch.<ref>.gk-*` keys: `createBranch`/`deleteLocalBranch`/`renameBranch`, plus the two
576
+ // paths that create a branch as a side effect (`checkout -b`, `worktree add -b`). Tip movement
577
+ // can't change a base, so clearing on every commit only forced a `git reflog` re-derivation.
578
+ if (types.includes('branch')) {
579
+ // `'branch'` comes from a HEAD change — a checkout — which appends `checkout: moving from X
580
+ // to Y` to the reflog, exactly the entry `getBaseBranchName` greps for when nothing is
581
+ // stored. A base derived as "none" BEFORE that entry existed is cached with no TTL, so
582
+ // without this it stays "none" for the session even though the answer now exists.
583
+ //
584
+ // Deliberately not under `'branches'`: a tip move can't change a branch's base, so re-deriving
585
+ // on every commit would be pure cost. Checkouts are user-driven and rare by comparison.
531
586
  keysToClear.add('baseBranchName');
587
+ }
588
+
589
+ if (types.includes('branches')) {
532
590
  keysToClear.add('branchMetadataMap');
533
591
  keysToClear.add('branches');
534
592
  keysToClear.add('sharedBranches');
@@ -540,6 +598,10 @@ export class Cache implements Disposable {
540
598
  }
541
599
 
542
600
  if (types.includes('config')) {
601
+ // `getBaseBranchName` falls back to `branch.<ref>.vscode-merge-base` (written by VS Code's
602
+ // built-in Git), which lives in `.git/config` — so a config change is the only signal that
603
+ // source changed. Cheap: config events are rare.
604
+ keysToClear.add('baseBranchName');
543
605
  keysToClear.add('configKeys');
544
606
  keysToClear.add('configPatterns');
545
607
  keysToClear.add('currentBranchReference');
@@ -601,6 +663,11 @@ export class Cache implements Disposable {
601
663
  keysToClear.add('tags');
602
664
  keysToClear.add('refs');
603
665
  keysToClear.add('refTips');
666
+ // `commit`/`commitCount` can be keyed by a tag name (e.g. the tag node's load-more count),
667
+ // which re-points on a force-moved or deleted-and-recreated tag — same drift the
668
+ // `branch`/`branches` cascade above clears for.
669
+ keysToClear.add('commit');
670
+ keysToClear.add('commitCount');
604
671
  }
605
672
 
606
673
  if (types.includes('tracking')) {
@@ -625,35 +692,44 @@ export class Cache implements Disposable {
625
692
  if (repoPath == null) {
626
693
  cache.clear();
627
694
  } else if (sharedCacheKeys.has(key)) {
628
- const commonPath = this.getCommonPath(repoPath);
629
- // For shared caches, prefer `invalidate` where supported so in-flight work is
630
- // shared across new callers and self-evicts on settle rather than spawning a
631
- // duplicate factory. `invalidate` does the right thing per-entry: entries with
632
- // a `CacheController` (created via `getOrCreate`) are marked invalidated; entries
633
- // without one (created via plain `.set()`, e.g. per-worktree mapper results) are
634
- // hard-deleted. Caches that don't support `invalidate` fall back to `delete`.
635
- const invalidate = (cache as { invalidate?: (k: string) => void }).invalidate;
636
- if (typeof invalidate === 'function') {
637
- invalidate.call(cache, commonPath);
638
- for (const worktreePath of this.getWorktreePaths(commonPath)) {
639
- if (worktreePath === commonPath) continue;
640
-
641
- invalidate.call(cache, worktreePath);
642
- }
643
- } else {
644
- cache.delete(commonPath);
645
- for (const worktreePath of this.getWorktreePaths(commonPath)) {
646
- if (worktreePath === commonPath) continue;
647
-
648
- cache.delete(worktreePath);
649
- }
650
- }
695
+ this.evictShared(key, repoPath);
651
696
  } else {
652
697
  cache.delete(repoPath);
653
698
  }
654
699
  }
655
700
  }
656
701
 
702
+ /**
703
+ * Evicts a single shared (commonPath-keyed) cache entry for `repoPath` and its sibling worktrees.
704
+ * Prefers `invalidate` where supported so in-flight work is shared across new callers and
705
+ * self-evicts on settle rather than spawning a duplicate factory. `invalidate` does the right
706
+ * thing per-entry: entries with a `CacheController` (created via `getOrCreate`) are marked
707
+ * invalidated; entries without one (created via plain `.set()`, e.g. per-worktree mapper results)
708
+ * are hard-deleted. Caches that don't support `invalidate` fall back to `delete`.
709
+ */
710
+ private evictShared(key: keyof AllCaches, repoPath: string): void {
711
+ const cache = this._caches[key];
712
+ if (cache == null) return;
713
+
714
+ const commonPath = this.getCommonPath(repoPath);
715
+ const invalidate = (cache as { invalidate?: (k: string) => void }).invalidate;
716
+ if (typeof invalidate === 'function') {
717
+ invalidate.call(cache, commonPath);
718
+ for (const worktreePath of this.getWorktreePaths(commonPath)) {
719
+ if (worktreePath === commonPath) continue;
720
+
721
+ invalidate.call(cache, worktreePath);
722
+ }
723
+ } else {
724
+ cache.delete(commonPath);
725
+ for (const worktreePath of this.getWorktreePaths(commonPath)) {
726
+ if (worktreePath === commonPath) continue;
727
+
728
+ cache.delete(worktreePath);
729
+ }
730
+ }
731
+ }
732
+
657
733
  /**
658
734
  * Monotonic per-worktree counter, advanced whenever anything that can change `git status` output is
659
735
  * observed. `git status` and its siblings are point-in-time reads of mutable state, so deduplicating them
@@ -714,6 +790,22 @@ export class Cache implements Disposable {
714
790
  return commonPath != null && commonPath !== repoPath;
715
791
  }
716
792
 
793
+ /** Whether `repoPath` is currently registered (via {@link registerRepoPath}, not yet unregistered). */
794
+ isRegistered(repoPath: string): boolean {
795
+ return this._commonPathRegistry.has(repoPath);
796
+ }
797
+
798
+ /**
799
+ * Monotonic per-path counter advanced whenever `repoPath` is unregistered (repo closed, worktree
800
+ * removed). Lets an async post-read step — e.g. the gk-config reconcile — detect that its target was
801
+ * closed (and possibly reopened) mid-flight and bail. Unlike an `isRegistered` check it doesn't
802
+ * require the caller to have registered in the first place, so it stays correct for consumers of
803
+ * this package that don't wire up the host's repo lifecycle.
804
+ */
805
+ getCloseGeneration(repoPath: string): number {
806
+ return this._closeGenerations.get(repoPath) ?? 0;
807
+ }
808
+
717
809
  /** Clears file-scoped caches (blame, diff, fileLog) for a specific path within a repo */
718
810
  clearForPath(repoPath: string, path: string, ...types: UriScopedCachedGitTypes[]): void {
719
811
  const prefix = `${normalizePath(path)}:`;
@@ -765,6 +857,9 @@ export class Cache implements Disposable {
765
857
  // Advance the status clock on close so a status read still in flight from this registration can't be
766
858
  // joined by a read issued after the path is reopened (which would otherwise reuse the same generation).
767
859
  this.incrementStatusGeneration(repoPath);
860
+ // Advance the close clock so an async step that captured it before its read (see `getCloseGeneration`)
861
+ // can tell its target went away mid-flight.
862
+ this._closeGenerations.set(repoPath, this.getCloseGeneration(repoPath) + 1);
768
863
 
769
864
  const commonPath = this._commonPathRegistry.get(repoPath) ?? repoPath;
770
865
  const worktrees = this._worktreesByCommonPath.get(commonPath);
@@ -799,6 +894,11 @@ export class Cache implements Disposable {
799
894
  }
800
895
  }
801
896
 
897
+ // Shared, commonPath-keyed like `gkConfigMap` — only evict once no worktree still depends on it.
898
+ if (isLastWorktree) {
899
+ this._gkConfigMergeSnapshots.delete(commonPath);
900
+ }
901
+
802
902
  this._commonPathRegistry.delete(repoPath);
803
903
 
804
904
  if (worktrees != null) {
@@ -811,9 +911,20 @@ export class Cache implements Disposable {
811
911
 
812
912
  @debug({ onlyExit: true })
813
913
  reset(): void {
914
+ // Advance the close clock BEFORE dropping the registry it reads from. Advancing rather than
915
+ // clearing is the point: an async step that captured a generation must see a mismatch and bail,
916
+ // where a cleared map would hand it a fresh `0` that matches and let it repopulate what was just
917
+ // torn down. Union of registered paths and any already carrying a generation, mirroring
918
+ // `incrementStatusGenerations`.
919
+ for (const path of new Set([...this._commonPathRegistry.keys(), ...this._closeGenerations.keys()])) {
920
+ this._closeGenerations.set(path, this.getCloseGeneration(path) + 1);
921
+ }
922
+
814
923
  this._commonPathRegistry.clear();
815
924
  this._worktreesByCommonPath.clear();
816
925
  this._statusGenerations.clear();
926
+ this._gkConfigMergeSnapshots.clear();
927
+ this._gkConfigReconciler = undefined;
817
928
  this._caches = createEmptyCaches();
818
929
  }
819
930
 
@@ -874,7 +985,10 @@ export class Cache implements Disposable {
874
985
  }
875
986
 
876
987
  if (hasAny('gkConfig')) {
877
- types.add('gkConfig');
988
+ // Handled separately (not folded into `types`/`clearCaches` below) — a gk write is usually a
989
+ // bookkeeping timestamp that can't affect `baseBranchName`/`branchOverviews`, so this reconciles
990
+ // down to only the keys that actually changed instead of coarsely clearing both on every write.
991
+ this.handleGkConfigChanged(repoPath);
878
992
  }
879
993
 
880
994
  if (hasAny('lastFetched')) {
@@ -907,6 +1021,55 @@ export class Cache implements Disposable {
907
1021
  }
908
1022
  }
909
1023
 
1024
+ /**
1025
+ * Handles a watcher-observed `'gkConfig'` change. Always evicts the cached bulk map (forcing the
1026
+ * next read to hit disk). If a reconciler is registered (see {@link setGkConfigReconciler}),
1027
+ * defers to it instead of coarsely clearing `baseBranchName`/`branchOverviews` — it re-reads the
1028
+ * map and diffs it against the snapshot captured here (i.e. as it stood right before this change),
1029
+ * cascading only the refs whose merge-relevant keys actually changed. Falls back to the coarse
1030
+ * clear when no reconciler is registered (e.g. the GitHub provider) so nothing can go stale.
1031
+ */
1032
+ private handleGkConfigChanged(repoPath: string): void {
1033
+ const commonPath = this.getCommonPath(repoPath);
1034
+ // Hard-delete rather than `evictShared`'s soft-invalidate: the reconcile below needs a
1035
+ // point-in-time read of the post-change file, and a soft-invalidated entry stays joinable — the
1036
+ // re-read would ride a bulk read that started before this change and diff it as unchanged.
1037
+ // Same reasoning as `refs.ts`'s `force: true` path and `deleteGkConfig`'s own hard delete.
1038
+ this._caches.gkConfigMap?.delete(commonPath);
1039
+
1040
+ if (this._gkConfigReconciler == null) {
1041
+ this.clearCaches(repoPath, 'gkConfig');
1042
+ return;
1043
+ }
1044
+
1045
+ const priorSnapshot = this._gkConfigMergeSnapshots.get(commonPath);
1046
+ // Fire-and-forget, but the outcome still decides whether the coarse clear is owed: a reconcile that
1047
+ // FAILED leaves every derived cache holding pre-change values with nothing else to correct them, so
1048
+ // it falls back to the blunt behavior that registering a reconciler replaced. Kept here rather than
1049
+ // in the reconciler so a bail path added later can't silently skip it — the return type makes each
1050
+ // one say what it did.
1051
+ void Promise.resolve(this._gkConfigReconciler(repoPath, priorSnapshot)).then(
1052
+ outcome => {
1053
+ if (outcome === 'failed') {
1054
+ this.clearCaches(repoPath, 'gkConfig');
1055
+ }
1056
+ },
1057
+ // An exception escaping the reconciler is a failure by definition — and one its own `catch`
1058
+ // never saw, so nothing else will have cascaded.
1059
+ () => this.clearCaches(repoPath, 'gkConfig'),
1060
+ );
1061
+ }
1062
+
1063
+ /** Hard-evicts the cached bulk `.git/gk/config` map so the next read hits disk. */
1064
+ deleteGkConfigMap(repoPath: string): void {
1065
+ this._caches.gkConfigMap?.delete(this.getCommonPath(repoPath));
1066
+ }
1067
+
1068
+ /** The merge-relevant gk config snapshot currently serving as the reconcile baseline. */
1069
+ getGkConfigMergeSnapshot(repoPath: string): ReadonlyMap<string, string> | undefined {
1070
+ return this._gkConfigMergeSnapshots.get(this.getCommonPath(repoPath));
1071
+ }
1072
+
910
1073
  getBranches(
911
1074
  repoPath: string,
912
1075
  factory: (
@@ -1013,10 +1176,38 @@ export class Cache implements Disposable {
1013
1176
  /**
1014
1177
  * The whole `.git/gk/config` is read once and cached as a single map per commonPath; per-key and
1015
1178
  * per-namespace gk lookups are served from it in-memory (see `getGkConfigMap` in the CLI config
1016
- * sub-provider). Invalidated by `deleteGkConfig` (write) and the coarse `'gkConfig'` clear (watcher).
1179
+ * sub-provider). Invalidated by `deleteGkConfig` (write) and, on a watcher `'gkConfig'` event, by
1180
+ * `handleGkConfigChanged` (reconciled precisely when a reconciler is registered, or the coarse
1181
+ * clear otherwise). The first fresh read seeds the merge-relevant snapshot used by
1182
+ * `reconcileGkConfigMap`; later refreshes of that snapshot are owned by the reconcile itself.
1017
1183
  */
1018
- getGkConfigMap(repoPath: string, factory: () => PromiseOrValue<Map<string, string>>): Promise<Map<string, string>> {
1019
- return this.getSharedSimple(this.gkConfigMap, repoPath, factory);
1184
+ getGkConfigMap(
1185
+ repoPath: string,
1186
+ factory: (cacheable: CacheController) => PromiseOrValue<Map<string, string>>,
1187
+ ): Promise<Map<string, string>> {
1188
+ return this.getSharedSimple(this.gkConfigMap, repoPath, async (commonPath, cacheable) => {
1189
+ const map = await factory(cacheable);
1190
+ // Seed the reconcile baseline on first observation only. Refreshes belong to
1191
+ // `reconcileGkConfigMap` (after it has cascaded) and `recordGkConfigWrite` (our own writes):
1192
+ // an incidental read that happens to land between an external write and the watcher event
1193
+ // would otherwise advance the baseline to the post-change value, so the reconcile would diff
1194
+ // that value against itself and silently drop the change.
1195
+ if (!this._gkConfigMergeSnapshots.has(commonPath)) {
1196
+ this._gkConfigMergeSnapshots.set(commonPath, this.extractMergeRelevantGkConfig(map));
1197
+ }
1198
+ return map;
1199
+ });
1200
+ }
1201
+
1202
+ /** Extracts the subset of a gk config map whose keys participate in the `branchOverviews`/`baseBranchName` lineage. */
1203
+ private extractMergeRelevantGkConfig(map: ReadonlyMap<string, string>): Map<string, string> {
1204
+ const subset = new Map<string, string>();
1205
+ for (const [key, value] of map) {
1206
+ if (branchOverviewGkConfigKeysRegex.test(key)) {
1207
+ subset.set(key, value);
1208
+ }
1209
+ }
1210
+ return subset;
1020
1211
  }
1021
1212
 
1022
1213
  /**
@@ -1035,20 +1226,106 @@ export class Cache implements Disposable {
1035
1226
  key: string,
1036
1227
  options?: { skipInvalidation?: readonly GkConfigInvalidationTarget[] },
1037
1228
  ): void {
1038
- const cacheKey = this.getCommonPath(repoPath);
1039
- this._caches.gkConfigMap?.delete(cacheKey);
1229
+ const commonPath = this.getCommonPath(repoPath);
1230
+ this._caches.gkConfigMap?.delete(commonPath);
1231
+ this.cascadeGkConfigKeyChange(commonPath, key, options?.skipInvalidation);
1232
+ }
1233
+
1234
+ /**
1235
+ * Keeps the merge-relevant gk config snapshot in sync with OUR OWN writes, so the next
1236
+ * watcher-triggered `reconcileGkConfigMap` diffs a self-write as unchanged rather than as an
1237
+ * external change (which would otherwise defeat `skipInvalidation`). No-ops for keys outside the
1238
+ * merge-relevant subset, and if no snapshot exists yet for this repo — nothing to keep in sync;
1239
+ * the next real read (`getGkConfigMap`) populates one from scratch.
1240
+ */
1241
+ recordGkConfigWrite(repoPath: string, key: string, value: string | undefined): void {
1242
+ if (!branchOverviewGkConfigKeysRegex.test(key)) return;
1243
+
1244
+ const commonPath = this.getCommonPath(repoPath);
1245
+ const snapshot = this._gkConfigMergeSnapshots.get(commonPath);
1246
+ if (snapshot == null) return;
1247
+
1248
+ // Copy-on-write: a reconcile in flight is holding this map as its `priorSnapshot` baseline, so
1249
+ // mutating it in place would silently move that baseline mid-diff.
1250
+ const next = new Map(snapshot);
1251
+ if (value == null) {
1252
+ next.delete(key);
1253
+ } else {
1254
+ next.set(key, value);
1255
+ }
1256
+ this._gkConfigMergeSnapshots.set(commonPath, next);
1257
+ }
1040
1258
 
1259
+ /**
1260
+ * Registers the reconciler invoked by {@link handleGkConfigChanged} on a watcher-observed
1261
+ * `'gkConfig'` change. Only one reconciler is kept — later registrations replace earlier ones,
1262
+ * which is harmless since every CLI config sub-provider sharing this `Cache` reconciles the same
1263
+ * underlying `.git/gk/config` file identically.
1264
+ */
1265
+ setGkConfigReconciler(
1266
+ reconciler: (
1267
+ repoPath: string,
1268
+ priorSnapshot: ReadonlyMap<string, string> | undefined,
1269
+ ) => PromiseOrValue<GkReconcileOutcome>,
1270
+ ): void {
1271
+ this._gkConfigReconciler = reconciler;
1272
+ }
1273
+
1274
+ /**
1275
+ * Diffs a freshly-read gk config map's merge-relevant subset against `priorSnapshot` (captured by
1276
+ * `handleGkConfigChanged` before the reconcile started, so it reflects the pre-change state even
1277
+ * though this call's own re-read already raced ahead and refreshed the live snapshot), cascading
1278
+ * `baseBranchName`/`branchOverviews` for every ref whose merge-relevant key actually changed.
1279
+ * `priorSnapshot` of `undefined` (never observed before this reconcile) is treated as "everything
1280
+ * may have changed" — a full coarse clear, same as the pre-existing watcher behavior. Returns
1281
+ * whether anything changed (i.e. whether a follow-up repository-changed event is warranted).
1282
+ */
1283
+ reconcileGkConfigMap(
1284
+ repoPath: string,
1285
+ priorSnapshot: ReadonlyMap<string, string> | undefined,
1286
+ freshMap: ReadonlyMap<string, string>,
1287
+ ): boolean {
1288
+ const commonPath = this.getCommonPath(repoPath);
1289
+ const newSubset = this.extractMergeRelevantGkConfig(freshMap);
1290
+ this._gkConfigMergeSnapshots.set(commonPath, newSubset);
1291
+
1292
+ if (priorSnapshot == null) {
1293
+ this.evictShared('baseBranchName', repoPath);
1294
+ this.evictShared('branchOverviews', repoPath);
1295
+ return true;
1296
+ }
1297
+
1298
+ let changed = false;
1299
+ for (const key of new Set([...priorSnapshot.keys(), ...newSubset.keys()])) {
1300
+ if (priorSnapshot.get(key) !== newSubset.get(key)) {
1301
+ changed = true;
1302
+ this.cascadeGkConfigKeyChange(commonPath, key);
1303
+ }
1304
+ }
1305
+ return changed;
1306
+ }
1307
+
1308
+ /**
1309
+ * Shared per-ref cascade for a changed `branch.<ref>.gk-...` key: invalidates `baseBranchName` (for
1310
+ * `gk-merge-base`) and `branchOverviews` (for any merge-relevant key), honoring `skipInvalidation`.
1311
+ * Used by both a direct write (`deleteGkConfig`) and a reconciled external change
1312
+ * (`reconcileGkConfigMap`). No-ops for keys outside the merge-relevant subset.
1313
+ */
1314
+ private cascadeGkConfigKeyChange(
1315
+ commonPath: string,
1316
+ key: string,
1317
+ skip?: readonly GkConfigInvalidationTarget[],
1318
+ ): void {
1041
1319
  const refMatch = key.match(branchOverviewGkConfigKeysRegex);
1042
1320
  if (refMatch == null) return;
1043
1321
 
1044
1322
  const ref = refMatch[1];
1045
- const skip = options?.skipInvalidation;
1046
1323
 
1047
1324
  // `getBaseBranchName` reads `branch.<ref>.gk-merge-base` and caches the resolved value
1048
- // per-ref. A write to that key must invalidate the cached base or Tier 3 of
1049
- // `getBranchContributionsOverview` will resolve `mergeTarget` against the pre-write value.
1325
+ // per-ref. A change to that key must invalidate the cached base or Tier 3 of
1326
+ // `getBranchContributionsOverview` will resolve `mergeTarget` against the pre-change value.
1050
1327
  if (key.endsWith('.gk-merge-base') && !skip?.includes('baseBranchName')) {
1051
- this._caches.baseBranchName?.delete(cacheKey, ref);
1328
+ this._caches.baseBranchName?.delete(commonPath, ref);
1052
1329
  }
1053
1330
 
1054
1331
  if (skip?.includes('branchOverviews')) return;
@@ -1059,7 +1336,18 @@ export class Cache implements Disposable {
1059
1336
  // invalidate every entry for the affected ref regardless of which target it resolved to.
1060
1337
  // Uses `invalidateByKeyPrefix` (not `deleteByKeyPrefix`) so an in-flight factory is still
1061
1338
  // shared with new callers instead of triggering a duplicate fetch.
1062
- this._caches.branchOverviews?.invalidateByKeyPrefix(cacheKey, `${ref}|`);
1339
+ //
1340
+ // Sibling worktrees get their own bucket, not just the shared one: `getSharedOrCreateWithKey`
1341
+ // stores a per-worktree *mapped* entry under that worktree's own path, and prefix-invalidation
1342
+ // only reaches one bucket. The in-flight case self-propagates on settle, but an already-settled
1343
+ // overview would otherwise survive here — which the coarse `evictShared` path this replaced did
1344
+ // clear, since it iterated the worktrees.
1345
+ this._caches.branchOverviews?.invalidateByKeyPrefix(commonPath, `${ref}|`);
1346
+ for (const worktreePath of this.getWorktreePaths(commonPath)) {
1347
+ if (worktreePath === commonPath) continue;
1348
+
1349
+ this._caches.branchOverviews?.invalidateByKeyPrefix(worktreePath, `${ref}|`);
1350
+ }
1063
1351
  }
1064
1352
 
1065
1353
  async getBranchOverview(
@@ -1135,7 +1423,11 @@ export class Cache implements Disposable {
1135
1423
  getContributorsStats(
1136
1424
  repoPath: string,
1137
1425
  cacheKey: string,
1138
- factory: (commonPath: string, cancellation?: AbortSignal) => PromiseOrValue<GitContributorsStats | undefined>,
1426
+ factory: (
1427
+ commonPath: string,
1428
+ cacheable: CacheController,
1429
+ cancellation?: AbortSignal,
1430
+ ) => PromiseOrValue<GitContributorsStats | undefined>,
1139
1431
  options?: { accessTTL?: number; cancellation?: AbortSignal },
1140
1432
  ): Promise<GitContributorsStats | undefined> {
1141
1433
  return this.getSharedSimpleWithKey(this.contributorsStats, repoPath, cacheKey, factory, options);
@@ -1144,14 +1436,41 @@ export class Cache implements Disposable {
1144
1436
  getBaseBranchName(
1145
1437
  repoPath: string,
1146
1438
  ref: string,
1147
- factory: (commonPath: string, cancellation?: AbortSignal) => PromiseOrValue<string | undefined>,
1439
+ factory: (
1440
+ commonPath: string,
1441
+ cacheable: CacheController,
1442
+ cancellation?: AbortSignal,
1443
+ ) => PromiseOrValue<string | undefined>,
1148
1444
  cancellation?: AbortSignal,
1149
1445
  ): Promise<string | undefined> {
1150
1446
  return this.getSharedSimpleWithKey(this.baseBranchName, repoPath, ref, factory, {
1447
+ // A bounded ceiling, because the eviction signals can't cover every way a base changes. The
1448
+ // `'branches'` cascade deliberately doesn't clear this — a tip move can't change a base, and
1449
+ // re-deriving per commit is the cost this exists to avoid — but that leaves a hole: a branch
1450
+ // deleted and recreated OUTSIDE GitLens, and never checked out, emits only `heads`. A base
1451
+ // derived as "none" before that would otherwise stay "none" for the session.
1452
+ //
1453
+ // `createTTL`, NOT `accessTTL`: the sliding variant resets on every read, so a branch card
1454
+ // polling this entry would hold it alive forever and never re-derive — the exact case the
1455
+ // ceiling exists for. Absolute from creation bounds staleness regardless of read frequency.
1456
+ createTTL: baseBranchNameTTL,
1151
1457
  cancellation: cancellation,
1152
1458
  });
1153
1459
  }
1154
1460
 
1461
+ /**
1462
+ * Clears the cached base branch for `ref`. Call from ops that create/rename/delete a branch — the
1463
+ * `'branches'` cascade no longer clears these, since tip movement can't change a branch's base.
1464
+ *
1465
+ * Only clears the cache. A branch's base is *persisted* as `branch.<ref>.gk-merge-base` and read back
1466
+ * before the reflog fallback, so an op that ends a branch's identity under a name must drop or move
1467
+ * that key too (`removeGkConfigBranchSection`/`renameGkConfigBranchSection`) — otherwise the
1468
+ * re-derivation this triggers just reads the stale value straight back.
1469
+ */
1470
+ deleteBaseBranchName(repoPath: string, ref: string): void {
1471
+ this._caches.baseBranchName?.delete(this.getCommonPath(repoPath), ref);
1472
+ }
1473
+
1155
1474
  getBranchMergedStatus(
1156
1475
  repoPath: string,
1157
1476
  cacheKey: string,
@@ -1198,9 +1517,21 @@ export class Cache implements Disposable {
1198
1517
  getDefaultBranchName(
1199
1518
  repoPath: string,
1200
1519
  remote: string,
1201
- factory: (commonPath: string) => PromiseOrValue<string | undefined>,
1520
+ factory: (
1521
+ commonPath: string,
1522
+ cacheable: CacheController,
1523
+ cancellation?: AbortSignal,
1524
+ ) => PromiseOrValue<string | undefined>,
1525
+ cancellation?: AbortSignal,
1202
1526
  ): Promise<string | undefined> {
1203
- return this.getSharedSimpleWithKey(this.defaultBranchName, repoPath, remote, factory);
1527
+ return this.getSharedSimpleWithKey(this.defaultBranchName, repoPath, remote, factory, {
1528
+ cancellation: cancellation,
1529
+ });
1530
+ }
1531
+
1532
+ /** Clears the cached default branch for one `remote` key (the networked lookup or its `:local` variant). */
1533
+ deleteDefaultBranchName(repoPath: string, remote: string): void {
1534
+ this._caches.defaultBranchName?.delete(this.getCommonPath(repoPath), remote);
1204
1535
  }
1205
1536
 
1206
1537
  getInitialCommitSha(
@@ -1455,7 +1786,9 @@ export class Cache implements Disposable {
1455
1786
  private async getSharedSimple<T>(
1456
1787
  cache: PromiseMap<string, T> | PromiseCache<string, T>,
1457
1788
  repoPath: string,
1458
- factory: (commonPath: string) => PromiseOrValue<T>,
1789
+ // `cacheable` is forwarded so a factory can refuse the entry when the read FAILED rather than
1790
+ // genuinely produced nothing — see `getSharedSimpleWithKey` for why that distinction matters.
1791
+ factory: (commonPath: string, cacheable: CacheController) => PromiseOrValue<T>,
1459
1792
  ): Promise<T> {
1460
1793
  const commonPath = this.getCommonPath(repoPath);
1461
1794
 
@@ -1463,24 +1796,27 @@ export class Cache implements Disposable {
1463
1796
  // `PromiseCache.getOrCreate(key, factory, options?)` have different 3rd-argument shapes,
1464
1797
  // so we dispatch on cache type. Both register a `CacheController` so `invalidate` works.
1465
1798
  if (cache instanceof PromiseCache) {
1466
- return cache.getOrCreate(commonPath, () => Promise.resolve(factory(commonPath)));
1799
+ return cache.getOrCreate(commonPath, cacheable => Promise.resolve(factory(commonPath, cacheable)));
1467
1800
  }
1468
- return cache.getOrCreate(commonPath, () => Promise.resolve(factory(commonPath)));
1801
+ return cache.getOrCreate(commonPath, cacheable => Promise.resolve(factory(commonPath, cacheable)));
1469
1802
  }
1470
1803
 
1471
1804
  private async getSharedSimpleWithKey<T>(
1472
1805
  cache: RepoPromiseCacheMap<string, T>,
1473
1806
  repoPath: string,
1474
1807
  cacheKey: string,
1475
- factory: (commonPath: string, cancellation?: AbortSignal) => PromiseOrValue<T>,
1476
- options?: { accessTTL?: number; cancellation?: AbortSignal },
1808
+ // `cacheable` is forwarded so a factory can distinguish "the answer is genuinely nothing" from "the
1809
+ // read failed": callers here set no TTL by default, so a failure resolved as `undefined` would
1810
+ // otherwise be served as a real answer until something explicitly evicts it.
1811
+ factory: (commonPath: string, cacheable: CacheController, cancellation?: AbortSignal) => PromiseOrValue<T>,
1812
+ options?: { accessTTL?: number; createTTL?: number; cancellation?: AbortSignal },
1477
1813
  ): Promise<T> {
1478
1814
  const commonPath = this.getCommonPath(repoPath);
1479
1815
 
1480
1816
  return cache.getOrCreate(
1481
1817
  commonPath,
1482
1818
  cacheKey,
1483
- (_cacheable, signal) => Promise.resolve(factory(commonPath, signal)),
1819
+ (cacheable, signal) => Promise.resolve(factory(commonPath, cacheable, signal)),
1484
1820
  options,
1485
1821
  );
1486
1822
  }
@@ -159,6 +159,17 @@ export interface GitServiceHooks {
159
159
  onConflicted?(command: GitConflictCommand, conflicts?: GitConflictFile[]): void;
160
160
  /** Called when getGitDir resolves to a non-existent .git directory or rev-parse fails */
161
161
  onGitDirResolveFailed?(repoPath: string, gitDir: string, errorMessage: string): void;
162
+ /**
163
+ * Called when a git operation that can start a rebase begins (`'started'`, before the
164
+ * process runs) and again when the command exits (`'ended'`, on success and failure alike).
165
+ * Fired at start (not from the result) because `git rebase` exits 0 when stopping at
166
+ * `edit`/`break`, and fired for every pull because `pull.rebase=true` config can start
167
+ * a rebase the host can't predict from the arguments. The `'ended'` phase lets the host
168
+ * check whether the operation actually left a rebase in progress.
169
+ * @param repoPath The path the rebase state will appear under (the worktree path for
170
+ * worktree-targeted pulls)
171
+ */
172
+ onRebaseCapableOperation?(repoPath: string, command: 'rebase' | 'pull', phase: 'started' | 'ended'): void;
162
173
  };
163
174
  }
164
175
 
@@ -27,6 +27,7 @@ export type GitFileIndexStatus = (typeof GitFileIndexStatus)[keyof typeof GitFil
27
27
 
28
28
  export const GitFileWorkingTreeStatus = {
29
29
  Modified: 'M',
30
+ TypeChanged: 'T',
30
31
  Added: 'A',
31
32
  Deleted: 'D',
32
33
  Untracked: '?',
@@ -227,6 +227,10 @@ export interface IssueMember {
227
227
  * Each consumer picks its own presentation.
228
228
  */
229
229
  name?: string;
230
+ /** The provider's handle for this person, when it has one — GitHub's login, Azure's `uniqueName` (a UPN,
231
+ * so an email), Bitbucket's mutable `nickname`. Display/labelling only: it is neither guaranteed present
232
+ * (GitLab's native mapper has none) nor a stable identity, so never key a match off it. */
233
+ username?: string;
230
234
  avatarUrl?: string;
231
235
  url?: string;
232
236
  }
@@ -13,6 +13,7 @@ export interface MergeConflictFile {
13
13
  export type ConflictDetectionErrorReason = 'unsupported' | 'noParent' | 'noMergeBase' | 'refNotFound' | 'other';
14
14
 
15
15
  export type ConflictDetectionResult =
16
- | { status: 'clean' }
17
- | { status: 'conflicts'; conflict: MergeConflicts; stoppedOnFirstConflict?: boolean }
16
+ // `treeOid` is the tree the simulated integration produced, when the provider computed one — callers layer further simulations onto it
17
+ | { status: 'clean'; treeOid?: string }
18
+ | { status: 'conflicts'; conflict: MergeConflicts; stoppedOnFirstConflict?: boolean; treeOid?: string }
18
19
  | { status: 'error'; reason: ConflictDetectionErrorReason; message: string };