@gitkraken/core-gitlens 0.7.1 → 0.8.0

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 (188) hide show
  1. package/CHANGELOG.md +48 -1
  2. package/dist/git/cache.d.ts +18 -5
  3. package/dist/git/cache.js +26 -8
  4. package/dist/git/cache.js.map +1 -1
  5. package/dist/git/context.d.ts +5 -1
  6. package/dist/git/context.js.map +1 -1
  7. package/dist/git/errors.d.ts +28 -1
  8. package/dist/git/errors.js +45 -1
  9. package/dist/git/errors.js.map +1 -1
  10. package/dist/git/providers/branches.d.ts +5 -1
  11. package/dist/git/providers/commits.d.ts +24 -1
  12. package/dist/git/providers/config.d.ts +7 -1
  13. package/dist/git/providers/operations.d.ts +6 -0
  14. package/dist/git/providers/provider.d.ts +17 -0
  15. package/dist/git/providers/refs.d.ts +38 -3
  16. package/dist/git/providers/revision.d.ts +5 -1
  17. package/dist/git/providers/staging.d.ts +12 -0
  18. package/dist/git/providers/worktrees.d.ts +23 -3
  19. package/dist/git/run.types.d.ts +6 -0
  20. package/dist/git-cli/cliGitProvider.d.ts +17 -1
  21. package/dist/git-cli/cliGitProvider.js +37 -1
  22. package/dist/git-cli/cliGitProvider.js.map +1 -1
  23. package/dist/git-cli/exec/git.d.ts +12 -2
  24. package/dist/git-cli/exec/git.js +48 -17
  25. package/dist/git-cli/exec/git.js.map +1 -1
  26. package/dist/git-cli/exec/gitQueue.d.ts +4 -1
  27. package/dist/git-cli/exec/gitQueue.js +1 -1
  28. package/dist/git-cli/exec/gitQueue.js.map +1 -1
  29. package/dist/git-cli/providers/branches.d.ts +10 -1
  30. package/dist/git-cli/providers/branches.js +15 -8
  31. package/dist/git-cli/providers/branches.js.map +1 -1
  32. package/dist/git-cli/providers/commits.d.ts +23 -1
  33. package/dist/git-cli/providers/commits.js +57 -8
  34. package/dist/git-cli/providers/commits.js.map +1 -1
  35. package/dist/git-cli/providers/config.js +4 -3
  36. package/dist/git-cli/providers/config.js.map +1 -1
  37. package/dist/git-cli/providers/operations.d.ts +1 -0
  38. package/dist/git-cli/providers/operations.js +39 -3
  39. package/dist/git-cli/providers/operations.js.map +1 -1
  40. package/dist/git-cli/providers/patch.js +20 -41
  41. package/dist/git-cli/providers/patch.js.map +1 -1
  42. package/dist/git-cli/providers/pausedOperations.js +2 -2
  43. package/dist/git-cli/providers/pausedOperations.js.map +1 -1
  44. package/dist/git-cli/providers/refs.d.ts +15 -3
  45. package/dist/git-cli/providers/refs.js +115 -10
  46. package/dist/git-cli/providers/refs.js.map +1 -1
  47. package/dist/git-cli/providers/revision.d.ts +3 -1
  48. package/dist/git-cli/providers/revision.js +9 -5
  49. package/dist/git-cli/providers/revision.js.map +1 -1
  50. package/dist/git-cli/providers/staging.d.ts +8 -0
  51. package/dist/git-cli/providers/staging.js +59 -7
  52. package/dist/git-cli/providers/staging.js.map +1 -1
  53. package/dist/git-cli/providers/worktrees.d.ts +31 -2
  54. package/dist/git-cli/providers/worktrees.js +75 -7
  55. package/dist/git-cli/providers/worktrees.js.map +1 -1
  56. package/dist/ipc/discovery.js +1 -1
  57. package/dist/plus/agents/providers/claudeCodeTranscript.js +6 -6
  58. package/dist/plus/agents/providers/claudeCodeTranscript.js.map +1 -1
  59. package/dist/plus/ai/providers/openAICompatibleProviderBase.js +1 -1
  60. package/dist/plus/git-github/api/github.d.ts +64 -3
  61. package/dist/plus/git-github/api/github.js +237 -24
  62. package/dist/plus/git-github/api/github.js.map +1 -1
  63. package/dist/plus/git-github/providers/github/branches.d.ts +3 -1
  64. package/dist/plus/git-github/providers/github/branches.js +2 -1
  65. package/dist/plus/git-github/providers/github/branches.js.map +1 -1
  66. package/dist/plus/git-github/providers/github/refs.d.ts +10 -3
  67. package/dist/plus/git-github/providers/github/refs.js +4 -4
  68. package/dist/plus/git-github/providers/github/refs.js.map +1 -1
  69. package/dist/plus/git-github/providers/github/revision.d.ts +3 -1
  70. package/dist/plus/git-github/providers/github/revision.js +1 -1
  71. package/dist/plus/git-github/providers/github/revision.js.map +1 -1
  72. package/dist/plus/integrations/collectionMetadata.d.ts +48 -3
  73. package/dist/plus/integrations/collectionMetadata.js +138 -8
  74. package/dist/plus/integrations/collectionMetadata.js.map +1 -1
  75. package/dist/plus/integrations/errors.d.ts +3 -1
  76. package/dist/plus/integrations/errors.js +7 -3
  77. package/dist/plus/integrations/errors.js.map +1 -1
  78. package/dist/plus/integrations/index.d.ts +6 -3
  79. package/dist/plus/integrations/index.js.map +1 -1
  80. package/dist/plus/integrations/integrationService.d.ts +36 -10
  81. package/dist/plus/integrations/integrationService.js +16 -3
  82. package/dist/plus/integrations/integrationService.js.map +1 -1
  83. package/dist/plus/integrations/manager.d.ts +107 -37
  84. package/dist/plus/integrations/models/gitHostIntegration.d.ts +129 -14
  85. package/dist/plus/integrations/models/gitHostIntegration.js +142 -18
  86. package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
  87. package/dist/plus/integrations/models/integration.d.ts +117 -11
  88. package/dist/plus/integrations/models/integration.js +175 -13
  89. package/dist/plus/integrations/models/integration.js.map +1 -1
  90. package/dist/plus/integrations/models/issuesIntegration.d.ts +18 -5
  91. package/dist/plus/integrations/models/issuesIntegration.js +42 -10
  92. package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
  93. package/dist/plus/integrations/providers/azure/azure.d.ts +1 -1
  94. package/dist/plus/integrations/providers/azure/azure.js +14 -18
  95. package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
  96. package/dist/plus/integrations/providers/azure/models.d.ts +29 -0
  97. package/dist/plus/integrations/providers/azure/models.js +112 -1
  98. package/dist/plus/integrations/providers/azure/models.js.map +1 -1
  99. package/dist/plus/integrations/providers/azureDevOps.d.ts +73 -3
  100. package/dist/plus/integrations/providers/azureDevOps.js +117 -3
  101. package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
  102. package/dist/plus/integrations/providers/azureDevOpsServer.js +2 -1
  103. package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
  104. package/dist/plus/integrations/providers/bitbucket/bitbucket.d.ts +61 -0
  105. package/dist/plus/integrations/providers/bitbucket/bitbucket.js +165 -23
  106. package/dist/plus/integrations/providers/bitbucket/bitbucket.js.map +1 -1
  107. package/dist/plus/integrations/providers/bitbucket/models.d.ts +6 -1
  108. package/dist/plus/integrations/providers/bitbucket/models.js +4 -3
  109. package/dist/plus/integrations/providers/bitbucket/models.js.map +1 -1
  110. package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js +2 -2
  111. package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js.map +1 -1
  112. package/dist/plus/integrations/providers/bitbucket-server.d.ts +48 -2
  113. package/dist/plus/integrations/providers/bitbucket-server.js +51 -1
  114. package/dist/plus/integrations/providers/bitbucket-server.js.map +1 -1
  115. package/dist/plus/integrations/providers/bitbucket.d.ts +45 -2
  116. package/dist/plus/integrations/providers/bitbucket.js +41 -2
  117. package/dist/plus/integrations/providers/bitbucket.js.map +1 -1
  118. package/dist/plus/integrations/providers/github.d.ts +49 -5
  119. package/dist/plus/integrations/providers/github.js +102 -7
  120. package/dist/plus/integrations/providers/github.js.map +1 -1
  121. package/dist/plus/integrations/providers/gitlab/gitlab.d.ts +35 -0
  122. package/dist/plus/integrations/providers/gitlab/gitlab.js +132 -15
  123. package/dist/plus/integrations/providers/gitlab/gitlab.js.map +1 -1
  124. package/dist/plus/integrations/providers/gitlab/models.d.ts +1 -0
  125. package/dist/plus/integrations/providers/gitlab/models.js +1 -1
  126. package/dist/plus/integrations/providers/gitlab/models.js.map +1 -1
  127. package/dist/plus/integrations/providers/gitlab.d.ts +49 -2
  128. package/dist/plus/integrations/providers/gitlab.js +80 -4
  129. package/dist/plus/integrations/providers/gitlab.js.map +1 -1
  130. package/dist/plus/integrations/providers/jira-server.d.ts +3 -3
  131. package/dist/plus/integrations/providers/jira-server.js +5 -8
  132. package/dist/plus/integrations/providers/jira-server.js.map +1 -1
  133. package/dist/plus/integrations/providers/jira.d.ts +9 -0
  134. package/dist/plus/integrations/providers/jira.js +13 -1
  135. package/dist/plus/integrations/providers/jira.js.map +1 -1
  136. package/dist/plus/integrations/providers/linear.js +1 -1
  137. package/dist/plus/integrations/providers/models.d.ts +13 -2
  138. package/dist/plus/integrations/providers/models.js +11 -1
  139. package/dist/plus/integrations/providers/models.js.map +1 -1
  140. package/dist/plus/integrations/providers/providerErrors.d.ts +9 -1
  141. package/dist/plus/integrations/providers/providerErrors.js +10 -1
  142. package/dist/plus/integrations/providers/providerErrors.js.map +1 -1
  143. package/dist/plus/integrations/providers/providersApi.d.ts +60 -1
  144. package/dist/plus/integrations/providers/providersApi.js +137 -2
  145. package/dist/plus/integrations/providers/providersApi.js.map +1 -1
  146. package/dist/plus/integrations/providers/utils/providerPaging.d.ts +42 -0
  147. package/dist/plus/integrations/providers/utils/providerPaging.js +58 -1
  148. package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
  149. package/dist/plus/integrations/providers/utils.d.ts +1 -1
  150. package/dist/plus/integrations/reads/broaden.js +1 -1
  151. package/dist/plus/integrations/reads/context.d.ts +1 -1
  152. package/dist/plus/integrations/reads/counts.js +1 -1
  153. package/dist/plus/integrations/reads/currentAccount.d.ts +35 -0
  154. package/dist/plus/integrations/reads/currentAccount.js +46 -0
  155. package/dist/plus/integrations/reads/currentAccount.js.map +1 -0
  156. package/dist/plus/integrations/reads/drains.d.ts +5 -2
  157. package/dist/plus/integrations/reads/drains.js +8 -4
  158. package/dist/plus/integrations/reads/drains.js.map +1 -1
  159. package/dist/plus/integrations/reads/hierarchy.js +1 -1
  160. package/dist/plus/integrations/reads/hierarchy.utils.js +1 -1
  161. package/dist/plus/integrations/reads/issueBatch.d.ts +72 -12
  162. package/dist/plus/integrations/reads/issueBatch.js +193 -48
  163. package/dist/plus/integrations/reads/issueBatch.js.map +1 -1
  164. package/dist/plus/integrations/reads/issueTracker.js +1 -1
  165. package/dist/plus/integrations/reads/issues.js +1 -1
  166. package/dist/plus/integrations/reads/pullRequestBatch.d.ts +57 -0
  167. package/dist/plus/integrations/reads/pullRequestBatch.js +80 -0
  168. package/dist/plus/integrations/reads/pullRequestBatch.js.map +1 -0
  169. package/dist/plus/integrations/reads/pullRequestBranches.d.ts +58 -0
  170. package/dist/plus/integrations/reads/pullRequestBranches.js +111 -0
  171. package/dist/plus/integrations/reads/pullRequestBranches.js.map +1 -0
  172. package/dist/plus/integrations/reads/pullRequests.js +3 -3
  173. package/dist/plus/integrations/reads/pullRequests.js.map +1 -1
  174. package/dist/plus/integrations/reads/resolveRepository.js +1 -1
  175. package/dist/plus/integrations/reads/sweeps.js +4 -4
  176. package/dist/plus/integrations/reads/sweeps.js.map +1 -1
  177. package/dist/plus/integrations/results.d.ts +87 -6
  178. package/dist/plus/integrations/results.js +9 -2
  179. package/dist/plus/integrations/results.js.map +1 -1
  180. package/dist/utils/env/node/exec.js +3 -3
  181. package/dist/utils/env/node/exec.js.map +1 -1
  182. package/docs/integrations.md +175 -35
  183. package/docs/kepler-git-write-api-parity.md +239 -0
  184. package/docs/kepler-read-api-parity.md +75 -28
  185. package/package.json +1 -1
  186. package/dist/plus/integrations/reads/trackerIssue.d.ts +0 -33
  187. package/dist/plus/integrations/reads/trackerIssue.js +0 -60
  188. package/dist/plus/integrations/reads/trackerIssue.js.map +0 -1
@@ -113,42 +113,87 @@ provider before calling the token backend; never reuse an id discovered under a
113
113
  Every read returns `ProviderResult<T>` (`items` + `warnings` + `fetchFailed?`), and every paged read extends
114
114
  it with `page` + `hasMore` + `cursor?`. **No read throws for a provider-side failure** — see §6.
115
115
 
116
- | Method | Returns | Scope |
117
- | ---------------------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------- |
118
- | `listOrgs` | `ProviderOrganization` | Orgs / workspaces / groups / Bitbucket DC projects; issue-tracker resources (Jira sites, …). |
119
- | `listProjects` | `ProviderOrganization` | The project tier: Azure DevOps, and issue-tracker projects. |
120
- | `listRepos` | `ProviderRepositoryShape` | Repos of an `org`, or account-wide user-affiliated repos when `org` is omitted. |
121
- | `listPullRequestsPage` | `PullRequestShape` | With `repos`: those repos' PRs. Without: the user's PRs account-wide. |
122
- | `searchPullRequestsPage` | `PullRequestShape` | PRs involving the user that match structured criteria, optionally repo/org-scoped. |
123
- | `countPullRequests` | `PullRequestCountResult` | How many PRs match each scope, fetching none where the provider can count. See §5.1. |
124
- | `listIssuesPage` | `IssueShape` | Same split, for a **git host**'s issues. |
125
- | `searchIssuesPage` | `IssueShape` | Issues matching structured criteria over a repo/org scope — **no** `@me` binding. |
126
- | `countIssues` | `IssueCountResult` | How many match each scope, fetching none of them. See §5.1. |
127
- | `getIssuesBatch` | `IssueBatchResult` | Resolves N `(owner, repo, number)` coordinates in one request; an absence is proven. |
128
- | `getTrackerIssue` | `TrackerIssueResult` | Resolves ONE tracker issue by key within a resource; an absence is proven. Jira (Cloud + Data Center) / Linear. |
129
- | `listIssueTrackerIssuesPage` | `IssueShape` | Jira (Cloud + Data Center) / Linear / Trello (issues live under resource → project). |
130
- | `sweepPullRequests` | `ProviderSweepResult` | Drains **every** page across providers (`maxPages`, default 100). |
131
- | `sweepClosedPullRequests` | `ProviderSweepResult` | Same, pinned to `['closed','merged']`. |
132
- | `broadenIssues` | `ProviderBroadenResult` | Per-org fan-out for every visible issue, unfiltered by assignee. |
133
- | `resolveRepository` | `ResolveRepositoryResult` | Remote URL → canonical provider identity (the `gk repo resolve` equivalent). |
134
- | `getSupportedFilters` | filter capability table | Static, connection-free. See §7. |
116
+ | Method | Returns | Scope |
117
+ | ---------------------------- | ------------------------- | -------------------------------------------------------------------------------------------- |
118
+ | `listOrgs` | `ProviderOrganization` | Orgs / workspaces / groups / Bitbucket DC projects; issue-tracker resources (Jira sites, …). |
119
+ | `listProjects` | `ProviderOrganization` | The project tier: Azure DevOps, and issue-tracker projects. |
120
+ | `listRepos` | `ProviderRepositoryShape` | Repos of an `org`, or account-wide user-affiliated repos when `org` is omitted. |
121
+ | `listPullRequestsPage` | `PullRequestShape` | With `repos`: those repos' PRs. Without: the user's PRs account-wide. |
122
+ | `searchPullRequestsPage` | `PullRequestShape` | PRs involving the user that match structured criteria, optionally repo/org-scoped. |
123
+ | `countPullRequests` | `PullRequestCountResult` | How many PRs match each scope, fetching none where the provider can count. See §5.1. |
124
+ | `listIssuesPage` | `IssueShape` | Same split, for a **git host**'s issues. |
125
+ | `searchIssuesPage` | `IssueShape` | Issues matching structured criteria over a repo/org scope — **no** `@me` binding. |
126
+ | `countIssues` | `IssueCountResult` | How many match each scope, fetching none of them. See §5.1. |
127
+ | `getIssuesBatch` | `IssueBatchResult` | Resolves N issues by coordinate or tracker identifier; an absence is proven. |
128
+ | `getPullRequestsBatch` | `PullRequestBatchResult` | Resolves N PRs by `(owner, repo, number)`, in any state; an absence is proven. |
129
+ | `getPullRequestsForBranches` | `PullRequestBranchResult` | Each branch's PRs, in any state and fork-aware; an empty list is a proven none. |
130
+ | `listIssueTrackerIssuesPage` | `IssueShape` | Jira (Cloud + Data Center) / Linear / Trello (issues live under resource → project). |
131
+ | `sweepPullRequests` | `ProviderSweepResult` | Drains **every** page across providers (`maxPages`, default 100). |
132
+ | `sweepClosedPullRequests` | `ProviderSweepResult` | Same, pinned to `['closed','merged']`. |
133
+ | `broadenIssues` | `ProviderBroadenResult` | Per-org fan-out for every visible issue, unfiltered by assignee. |
134
+ | `resolveRepository` | `ResolveRepositoryResult` | Remote URL → canonical provider identity (the `gk repo resolve` equivalent). |
135
+ | `getCurrentAccount` | `CurrentAccountResult` | Who a git host connection is signed in as; trackers refuse. |
136
+ | `getSupportedFilters` | filter capability table | Static, connection-free. See §7. |
135
137
 
136
138
  A provider that cannot serve a surface says so explicitly — a warning explaining that the operation is
137
139
  unsupported plus `fetchFailed`, never a silent empty page. That distinction is the whole point of the result
138
140
  shape: an empty `items` with no warning means "this account genuinely has nothing".
139
141
 
140
- `getTrackerIssue` takes `resourceId` for every supported tracker. Jira Cloud also takes `resourceUrl`, the site URL
141
- returned by `listOrgs`; the REST response only supplies an API `self` link, so the caller provides the already-known
142
- site identity rather than making this point read perform resource discovery. Linear does not need it, and neither
143
- does Jira Data Center, whose browser link is built from the connection's own base URL.
144
-
145
- For Jira Data Center `resourceId` is the host — the instance's single resource, as `listOrgs` reports it — and the
146
- read takes the `domain` its siblings take to select the instance. Unlike them it never falls back to the primary
147
- connection: a self-managed tracker requires a `domain` or a `connectionId` with a configured host, and is refused
148
- (warning + `fetchFailed`) otherwise. Two self-hosted instances routinely share project and issue keys, and this
149
- read's `issue: undefined` is a proven absence a caller may cache, so an answer from whichever host happens to be
150
- primary would be cached under a key that names a different instance. For the same reason a `resourceId` that names
151
- a different host than the one the read resolves to is refused rather than read.
142
+ `getIssuesBatch` takes the target form its provider addresses an issue by: `{ key, owner, repo, number, project? }` on
143
+ GitHub/GHE, GitLab and Azure DevOps, and `{ key, resourceId, resourceUrl?, identifier }` on Jira (Cloud and Data
144
+ Center) and Linear; a call carrying the other form is refused whole. GitHub/GHE resolve up to 25 coordinates per
145
+ request; GitLab, Azure DevOps and the trackers cost one request per target. Azure DevOps requires `project` and
146
+ ignores `repo`, since work items belong to the project. Rows take the repository-scoped `listIssuesPage` conversion,
147
+ except that an Azure DevOps row has no `project`, which only the account-wide read fills from project discovery. What
148
+ counts as a proven absence is per host. On GitLab it is a null project or issue, and a miss costs a second request to
149
+ confirm it, since provider-apis reports a reply carrying only GraphQL errors the same way; a 404 or an empty response
150
+ fails the target. On Azure DevOps it is Azure's own 404 body naming the work item (`WorkItemUnauthorizedAccessException`,
151
+ which Azure also uses for a work item this connection cannot read) or the project
152
+ (`ProjectDoesNotExistWithNameException`); an HTML 404, a 410 or any other `typeKey` fails the target. Bitbucket and
153
+ Bitbucket DC have no issues and refuse. A tracker target's `resourceId` is trusted, so the read performs no resource
154
+ discovery. Jira Cloud also requires `resourceUrl`, the site URL returned by `listOrgs`; the REST response only supplies
155
+ an API `self` link, so the caller provides the already-known site identity. Linear does not need it, and neither does
156
+ Jira Data Center, whose browser link is built from the connection's own base URL. A key asked of several resources is
157
+ one call, with one target per resource.
158
+
159
+ For Jira Data Center a target's `resourceId` is the host — the instance's single resource, as `listOrgs` reports it —
160
+ and the call takes the `domain` its siblings take to select the instance. Unlike them it never falls back to the
161
+ primary connection: a self-managed tracker requires a `domain` or a `connectionId` with a configured host, and the
162
+ whole call is refused (warning + `fetchFailed`) otherwise. Two self-hosted instances routinely share project and
163
+ issue keys, and this read's `issue: undefined` is a proven absence a caller may cache, so an answer from whichever
164
+ host happens to be primary would be cached under a key that names a different instance. For the same reason, a call
165
+ in which any target's `resourceId` names a different host than the one the read resolves to is refused whole rather
166
+ than read.
167
+
168
+ `getPullRequestsBatch` is the pull request counterpart of `getIssuesBatch`, with the same absence/failure contract,
169
+ and it serves every git host. Azure DevOps also requires `project` on each target. GitHub/GHE resolve up to 25
170
+ targets per request; every other host costs one request per target, and GitLab a second one to confirm a miss. A
171
+ target GitHub refuses on its own, e.g. in an org enforcing SAML SSO the token isn't authorized for, fails only that
172
+ target. Its rows carry the same fields as the list reads' rows, except on Bitbucket Cloud — which has no single pull
173
+ request read in provider-apis, so those rows come from GitLens' own REST read and lack `commentsCount`, `isDraft`
174
+ and the clone URLs. On GitLab, a miss the confirming read then contradicts fails the target rather than answering
175
+ with the confirming read's own, differently-identified row. It is uncached and bypasses the
176
+ host's `IntegrationCacheProvider.getPullRequest`, so the caller owns caching the answer.
177
+
178
+ `getPullRequestsForBranches` answers "which pull requests have this branch as their head" without the user's
179
+ relationship to them, so unlike the sweeps it finds a teammate's pull request from the user's branch. A target names
180
+ the repository the pull requests are opened against, the branch's short name and, for a branch in a fork,
181
+ `headOwner`; one equal to the target's owner means the base repository. A pull request matches only when its head
182
+ repository is that base repository or that fork, so a `main` never claims every fork's `main`. The match is on the
183
+ head branch name in every state, so a merged pull request whose branch was deleted is still found. Each target
184
+ returns up to 10, most recently updated first. An empty list without `truncated` is a proven none, a missing base
185
+ repository included; `truncated` means more may match than were returned. GitHub/GHE answer up to 25 targets per
186
+ request, and every other host costs one request per target. GitLab and Azure DevOps, where that request only finds
187
+ the matching numbers, then resolve each match (typically 0–1 per branch) through `getPullRequestsBatch`'s own read,
188
+ so their rows are exactly its rows; a match that read can't check fails its whole branch. Rows carry the list reads'
189
+ fields on GitHub/GHE and Bitbucket DC and the by-id read's on Bitbucket Cloud. Bitbucket DC and Azure DevOps refuse a
190
+ `headOwner` naming another owner, since neither can find a fork by its owner. It is uncached and bypasses
191
+ `IntegrationCacheProvider.getPullRequestForBranch`.
192
+
193
+ `getCurrentAccount` answers who a git host connection is signed in as. It returns a single `account?` rather
194
+ than `items`, and `account` is never absent without a warning: no session, a failed request, or an issue tracker,
195
+ which has only a per-resource account and refuses (see §8). It goes through the host-supplied
196
+ `IntegrationManagerCacheProvider.getCurrentAccount` cache rather than adding a second one.
152
197
 
153
198
  ## 5. Paging
154
199
 
@@ -361,7 +406,7 @@ prove from structured errors:
361
406
 
362
407
  | `kind` | Meaning | Reasonable response |
363
408
  | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
364
- | `auth` | Token rejected (401/403 that isn't a throttle). | Prompt to reconnect that connection. |
409
+ | `auth` | Token rejected (401/403 that isn't a throttle). | Prompt to reconnect that connection. A scoped one is narrower: see `scope` below. |
365
410
  | `rate-limit` | Throttled (429, or a 403 whose body says so). | Back off and retry; keep the last snapshot. |
366
411
  | `not-found` | 404/410/422 on the requested scope. | Drop that scope; don't reconnect. |
367
412
  | `no-connection` | The requested `connectionId`/`domain` doesn't resolve. | Re-resolve the target or re-authenticate. |
@@ -373,6 +418,95 @@ Conversely, `other` is intentionally not a complete failure taxonomy. Treat `mes
373
418
  text rather than a stable protocol; use `fetchFailed`, `page.truncated`, and `page.allPages` for completeness
374
419
  and keep unknown failures conservative.
375
420
 
421
+ ### `scope` — which part of the read failed
422
+
423
+ A fan-out read records a failure against the organization, project or repository it happened in.
424
+ `ProviderWarning.scope` forwards that attribution (`resourceId`, `projectId`, `repositoryId`, whichever the
425
+ provider reported), so a consumer can tell "one organization refused this token" from "the connection's token
426
+ is dead" without parsing `message`:
427
+
428
+ ```ts
429
+ if (warning.kind === 'auth' && warning.scope == null) {
430
+ promptToReconnect(warning.providerId, warning.connectionId);
431
+ } else if (warning.kind === 'auth') {
432
+ // One organization/project/repository refused the credential, e.g. an Azure DevOps organization with
433
+ // third-party OAuth access disabled, one in another Entra tenant, or a Conditional Access policy.
434
+ // Reconnecting cannot fix that, so mark only that scope unavailable and keep the others.
435
+ markScopeUnavailable(warning.providerId, warning.connectionId, warning.scope);
436
+ }
437
+ ```
438
+
439
+ `kind` and `isAuth` do not change with it: a scoped 401 is still an authentication failure, and `scope` says
440
+ how far it reaches. **Its absence means account-wide or unattributed**, so a consumer that ignores the field
441
+ keeps its existing behavior. It is set only on warnings derived from a structured scope failure, and names at
442
+ least one ID when present; a failure attributed to nothing below the provider carries none. A warning built
443
+ from a caught exception never carries one, even when that call targeted a single organization, and an omission
444
+ keeps its attribution in `omission.scope` instead. The batch reads (`getIssuesBatch`, `getPullRequestsBatch`,
445
+ `getPullRequestsForBranches`) are the exception: a refused target's warning names the scope its target names —
446
+ the Azure DevOps organization and project, the repository on other git hosts, or the tracker's resource.
447
+
448
+ A scoped `auth` failure also means **the credential itself was accepted**. A dead token can come back as
449
+ nothing but scoped refusals wherever a read reaches its scopes without an uncached request to the connection
450
+ first: discovery served from a per-token cache (Azure DevOps, Bitbucket and Jira Cloud cache the account, its
451
+ organizations, workspaces or sites, and their projects), or an SDK fan-out across the requested repositories
452
+ (Bitbucket Data Center). So when a read's only auth failures are scoped, those providers confirm the credential
453
+ with one uncached check before reporting them; a batch read does so only when every target was refused, since a
454
+ target that answered already proved the credential. A refused credential fails the whole read instead: an
455
+ unscoped `auth` warning, `fetchFailed`, no results served from the cache, and the usual connection recovery.
456
+ A refusal the provider pins on the credential itself, like Bitbucket's or Jira Cloud's for a token missing the
457
+ OAuth scopes the read needs, is published unscoped too, once for the connection, because a reconnect
458
+ (consenting to them again) is what fixes it; the scopes that answered keep their results.
459
+
460
+ The promise holds for a read that carries no unscoped `auth` warning of its own: one that does already asks for a
461
+ reconnect, and its other scoped refusals are not checked. Two cases stay scoped even then. A check that could not
462
+ complete or was denied (a network error, a throttle, a `403`, Bitbucket Data Center refusing a credential it
463
+ authenticated, or a Bitbucket Data Center project access token, whose user the check cannot find) proves nothing,
464
+ so the warnings are published unconfirmed and carry no `cause`, and the next read checks again: a credential the
465
+ check can never confirm, like a project access token, costs one check on every read that has scoped refusals. And a
466
+ credential confirmed within the last minute is not checked again, so a scope that keeps refusing it costs at most
467
+ one extra check a minute, and a revocation can take up to a minute to surface as a connection failure.
468
+
469
+ `scope.resourceId` is the resource as the read addressed it: its id on most reads, its name on the few that
470
+ address it by name (Azure DevOps' repo-scoped reads, Bitbucket's workspace reads). Match it against both
471
+ `ProviderOrganization.id` and `name`.
472
+
473
+ Warnings also dedup on `scope` and `cause`, so failures of two scopes stay two warnings.
474
+
475
+ ### `cause` — why a sound credential was refused
476
+
477
+ A scoped `auth` warning can also say **why** the scope refused, so a consumer recommends the fix instead of a
478
+ reconnect. `ProviderWarning.cause` carries a closed `reason` to switch on (also exported as
479
+ `ProviderWarningCauseReason`), the provider's own `code` when it reports one, and a `remedyUrl` when this layer
480
+ can address the setting behind the refusal. `message` says the same in prose.
481
+
482
+ ```ts
483
+ switch (warning.cause?.reason) {
484
+ case 'oauth-app-not-allowed':
485
+ // An admin enables the org's third-party OAuth policy at `remedyUrl`, or the user connects with a PAT.
486
+ suggestAllowingOAuthApps(warning.scope, warning.cause.remedyUrl);
487
+ break;
488
+ case 'access-denied':
489
+ suggestRequestingAccess(warning.scope);
490
+ break;
491
+ case 'conditional-access':
492
+ suggestAskingTheTenantAdmin(warning.scope);
493
+ break;
494
+ }
495
+ ```
496
+
497
+ | `cause.reason` | Means | Fixed by |
498
+ | ----------------------- | ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
499
+ | `oauth-app-not-allowed` | The organization does not let third-party OAuth apps in. | An organization admin enabling **Third-party application access via OAuth** (Azure DevOps; off by default for new organizations), or a PAT, which the policy does not govern. |
500
+ | `access-denied` | The account has no access to that organization or project (not a member, no permission). | Someone who administers it granting access. |
501
+ | `conditional-access` | A Microsoft Entra Conditional Access policy blocked the request (`VS403463`). | The tenant admin exempting the request. |
502
+
503
+ It is set **only on a scoped `auth` warning whose credential was confirmed** (see `scope` above), because until
504
+ then these refusals look exactly like a dead credential: Azure DevOps answers a third-party OAuth app its
505
+ organization disallows with the same bare `401` it gives an expired token. Only Azure DevOps names causes today,
506
+ from answers captured against the live service. **Its absence proves nothing**: a refusal this layer cannot name
507
+ still carries the provider's own explanation, when it gave one, in `message`, e.g. an organization that only
508
+ allowlists global personal access tokens.
509
+
376
510
  ### `omission` — succeeded, but withheld results
377
511
 
378
512
  `other` covers two facts with **opposite remedies**: a request that failed, and a request that succeeded while
@@ -583,12 +717,14 @@ Derived from the provider models and `providersMetadata`. ✓ supported · ✗ r
583
717
  | Issues, account-wide | ✓ | ✓ | ✗ | ✗ | ✓ | — | — | — |
584
718
  | `searchIssuesPage` | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
585
719
  | `countIssues` | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
586
- | `getIssuesBatch` | ✓ | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
587
- | `getTrackerIssue` | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✗ |
720
+ | `getIssuesBatch` | ✓ | ✓ | ✗ | ✗ | ✓ | ✓ | ✓ | ✗ |
721
+ | `getPullRequestsBatch` | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ |
722
+ | `getPullRequestsForBranches` | ✓ | ✓ | ✓ | ✓¹ | ✓¹ | ✗ | ✗ | ✗ |
588
723
  | Issues by `org`/`project` | ✗ | ✗ | ✗ | ✗ | ✓ | ✓ | ✓ | ✓ |
589
724
  | `listIssueTrackerIssuesPage` | — | — | — | — | — | ✓ | ✓ | ✓ |
590
725
  | `broadenIssues` | ✓ | ✓ | ✗ | ✗ | ✓ | ✗ | ✗ | ✗ |
591
726
  | `resolveRepository` | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ |
727
+ | `getCurrentAccount` | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ |
592
728
 
593
729
  Bitbucket Data Center exposes its projects through `listOrgs`: both `id` and `name` are the project key.
594
730
  Pass that key as `org` to `listRepos` to select a project, or omit `org` to enumerate all accessible repositories,
@@ -601,11 +737,15 @@ HTTPS links are rebuilt from the configured URL and an SSH link, whose host and
601
737
 
602
738
  Jira Data Center inherits Jira Cloud's row. Its reads are addressed per host, so `domain` selects the instance and
603
739
  every read below is scoped to that one connection; a paged read that omits it gets the primary configured host,
604
- while `getTrackerIssue` refuses instead (see §4). `listOrgs` returns exactly one resource — the
740
+ while `getIssuesBatch` refuses instead (see §4). `listOrgs` returns exactly one resource — the
605
741
  instance itself, synthesized from the configured host rather than fetched — and its projects carry the display name
606
742
  as `key`, because `/rest/api/2/project` reports no project key; reads address the project by id regardless. No
607
743
  autolinks are registered for the same reason: an autolink prefix has to be the project key.
608
744
 
745
+ ¹ Branches in the base repository only: a target whose `headOwner` names another owner is refused, since Bitbucket DC
746
+ finds a branch's pull requests only through the repository the branch lives in, and an Azure DevOps fork shares its
747
+ organization.
748
+
609
749
  Repo-scoped PR filters: GitHub/GHE `Author, Assignee, ReviewRequested, Mention` · GitLab `Author, Assignee,
610
750
  ReviewRequested` · Bitbucket + Bitbucket DC `Author, ReviewRequested` · Azure `Author, Assignee,
611
751
  ReviewRequested`.
@@ -0,0 +1,239 @@
1
+ # Kepler git write-API parity
2
+
3
+ Decision record for the git WRITE-path gaps Kepler hit while routing every one of its git calls through
4
+ `@gitkraken/core-gitlens` (gitkraken/kepler, branch `kepler/migrate-raw-git-spawns`). It is the sibling of
5
+ [`kepler-read-api-parity.md`](./kepler-read-api-parity.md), which covers provider reads; this one covers the
6
+ `git` sub-providers and the raw-command escape hatch.
7
+
8
+ The shape of the problem: Kepler drives three libraries (`@gitkraken/sync-tools`, `conflict-tools`,
9
+ `compose-tools`) that take a `{ exec }` git port plus optional typed ops, and know nothing of core; the host
10
+ supplies a typed op only where a core call serves it exactly. Kepler also has its own worktree, branch and
11
+ sync services. After the migration every command runs through core's executor, so it already shares the queue, environment, binary
12
+ resolution and logging. What it could NOT do is use a typed sub-provider method for a set of commands core had
13
+ no method for, or had one whose contract did not fit. Each such site stayed on `provider.git.run` with a
14
+ comment naming the missing API. This document is the list of those APIs, what was added for each, and what was
15
+ deliberately left alone.
16
+
17
+ Verdict legend: **added** (a typed method now exists) · **extended** (an existing method gained the missing
18
+ option) · **deferred** (left on the raw escape hatch on purpose, with the reason).
19
+
20
+ ## 1. Compare-and-swap ref updates — added
21
+
22
+ `refs.updateReference` ran `git update-ref <ref> <sha>` and swallowed every failure (logged, resolved
23
+ `undefined`), and there was no delete form. `@gitkraken/sync-tools` (0.4.0) publishes, applies and discards a
24
+ sync result itself (`applyPublishedSync`, `discardPublishedSync`), and every ref move in that flow is a
25
+ compare-and-swap: the real branch advances only if nothing else moved it since the library last read it. The
26
+ library asks its host for that through a typed `updateRef(ref, sha, { previousSha, mustNotExist, remove })`
27
+ op, and git's own `update-ref <ref> <new> <old>` is what makes it atomic. A writer that cannot refuse is a
28
+ clobber, so without this a host could only serve the op through the raw escape hatch.
29
+
30
+ - `updateReference(repoPath, ref, sha, options?: { expected?: string | 'absent' }, cancellation?)` now passes
31
+ the old value to git when `expected` is a sha, and the empty old value when it is `'absent'` (create only if
32
+ the ref does not exist yet). It THROWS a typed `ReferenceUpdateError` — reasons `conflict` (the ref moved,
33
+ or exists when it must not), `invalidRef`, `invalidObject`, `notFound` — instead of swallowing.
34
+ - `deleteReference(repoPath, ref, options?: { expected?: string }, cancellation?)` runs
35
+ `update-ref -d --no-deref <ref> [<expected>]` with the same error — `--no-deref` so a symbolic ref is itself
36
+ deleted rather than the ref it points to — and refuses `HEAD` (`checkedOut`). `update-ref -d` has none of
37
+ `git branch -d`'s guards or cleanup, so for a local branch (`refs/heads/<name>`) `deleteReference` supplies
38
+ them: it refuses a branch checked out in any worktree (`checkedOut`, read from `git worktree list`, not the
39
+ cached list), and removes the `branch.<name>` config section and GitLens's per-branch metadata with the ref.
40
+ Only `git branch -d`'s merged check is left out, since a compare-and-swap caller has already decided.
41
+ - Both fire the cache and repository hooks a branch, remote-tracking, tag or `HEAD` move fires; a ref in another
42
+ namespace (`refs/kepler/...`) announces nothing, since nothing core caches can observe it and an `unknown`
43
+ change would make a host refresh everything.
44
+
45
+ The sync-tools op maps onto these one-to-one:
46
+
47
+ | `updateRef` option | core call |
48
+ | ------------------ | ---------------------------------------------------------------- |
49
+ | `previousSha` | `updateReference(repoPath, ref, sha, { expected: previousSha })` |
50
+ | `mustNotExist` | `updateReference(repoPath, ref, sha, { expected: 'absent' })` |
51
+ | `remove` | `deleteReference(repoPath, ref, { expected: previousSha })` |
52
+ | none | `updateReference(repoPath, ref, sha)` |
53
+
54
+ `'absent'` against an existing ref rejects as `conflict` and leaves the ref where it was. Deleting a ref that
55
+ is already gone rejects as `notFound` when `expected` is given (the caller named a value it expected to
56
+ remove), and resolves when it is not.
57
+
58
+ The library reads any rejection as the ref's answer, so the host must tell a cancellation apart from a
59
+ refusal. A cancelled call rejects with core's `CancellationError` (`name === 'CancellationError'`), never a
60
+ `ReferenceUpdateError`, and that includes a signal already aborted when the call is made; a host adapting to
61
+ a library that expects `AbortError` maps it by name. A cancel that lands while git is running says nothing
62
+ about whether the ref moved, so re-read it before acting on the outcome.
63
+
64
+ **Breaking:** a caller that relied on a failed `updateReference` resolving silently now gets a rejection. No
65
+ caller in this repository did.
66
+
67
+ ## 2. Who clears after a write, and telling core about a raw one — changed / added
68
+
69
+ A typed mutator announces its write through `hooks.cache.onReset`, and core used to clear nothing itself on
70
+ that path: each host's handler was expected to forward to `Cache.clearCaches`. Two things followed. A host
71
+ with no handler kept serving pre-write reads after every typed write. And `clearCaches` only soft-invalidates
72
+ a shared entry that is still in flight, so even a wired host could hand a caller that arrived after the write
73
+ a read that started before it; GitLens also dropped the executor's pending commands on every reset, Kepler did
74
+ not.
75
+
76
+ A consumer that mutates through `provider.git.run` (a rebase driven by a library, a `clean`, a ref bookkeeping
77
+ write) had nothing to call at all, and typed reads served the pre-mutation answer until a file watcher caught
78
+ up, or forever with no watcher. Kepler classified verbs itself and called `clearCaches` after each one. The
79
+ sharpest case: it fetches PR heads through a raw `fetch` (it needs a per-process credential environment the
80
+ typed fetch cannot carry), and a typed `refs.validateReference` right after it served the pre-fetch tip for up
81
+ to 60s. `clearCaches` fixed that read but fired none of the host's hooks, so nothing else in the host learned
82
+ the refs moved.
83
+
84
+ - The provider wraps `onReset` when it is constructed. On every reset it hard-evicts its own caches for those
85
+ types (`Cache.evictCaches`, so a later caller never joins pre-write work), drops pending commands, then calls
86
+ the host's handler. Clears driven by a file watcher reach `clearCaches` directly and keep sharing in-flight
87
+ reads, which is what stops a burst of file events from spawning duplicate reads. A host's handler now only
88
+ has host-side state to update; forwarding to `clearCaches` as well is redundant but harmless.
89
+ - `provider.notifyChanged(repoPath, changes: RepositoryChange[], options?: { cache?: CachedGitTypes[] | 'all' })`
90
+ fires the same `cache.onReset` (all types by default) and `repository.onChanged` a typed write would, so a
91
+ raw mutation gets exactly the same treatment, host listeners included.
92
+
93
+ ## 3. Worktree administration — added / extended
94
+
95
+ - `worktrees.pruneWorktrees(repoPath, options?: { expire? })` — `git worktree prune`. Kepler prunes
96
+ after a directory it removed by hand, and before reusing a path git still lists. Like `deleteWorktree`, it
97
+ unregisters each worktree it removed from the cache.
98
+ - `worktrees.lockWorktree(repoPath, path, options?: { reason? })` — `git worktree lock --reason`. The reason
99
+ is how Kepler marks a worktree as task-owned; only `unlockWorktree` existed.
100
+ - `worktrees.createWorktree(repoPath, path, options?, runOptions?)` and
101
+ `deleteWorktree(repoPath, path, options?, runOptions?)` — gained run options. A large worktree's checkout or
102
+ removal legitimately outlasts the default per-command timeout, and a caller may need to cancel either. A
103
+ cancelled call rejects as `CancellationError`, not a worktree error. The thrown `WorktreeDeleteError` keeps git's
104
+ raw output reachable on `original`, which is what a platform-specific retry classifier needs.
105
+
106
+ ## 4. `git clean`, and the staging sub-provider's missing hooks — added / fixed
107
+
108
+ - `staging.clean(repoPath, options?: { paths?, directories?, force?, ignored? })` — the only way to
109
+ discard untracked files; `ops.restore` cannot.
110
+ - Every staging mutation (`stageFile(s)`, `stageDirectory`, `stageAll`, `unstage*`, `removeFile(s)`, `clean`)
111
+ now fires `onReset('status', 'diff', 'tracking')` and the index change, except when it targets a temporary
112
+ index, which is not the repository's. Before this, a consumer that staged a conflict resolution through the
113
+ typed API and then read `getConflictingFiles()` could be answered from the index as it stood before the
114
+ stage.
115
+
116
+ ## 5. Upstream and hooks config keys — added
117
+
118
+ Kepler writes `branch.<name>.remote` / `branch.<name>.merge` (to set an upstream whose remote ref does not
119
+ exist yet, which `--set-upstream-to` refuses) and reads `core.hooksPath`. All three are now documented members
120
+ of `GitConfigKeys`, so `config.getConfig` / `setConfig` serve them with the usual cache. Writing either
121
+ upstream key also resets the cached branch list, which reports the upstream.
122
+
123
+ ## 6. `git pull --ff-only` — extended
124
+
125
+ `ops.pull` gained `fastForward: 'only'`, spelled as `ops.merge` spells it. Kepler advances a local base branch
126
+ before forking a task worktree from it, and must not create a merge commit or start a rebase in a checkout it
127
+ does not own. A refused fast-forward is a `PullError` with the new `noFastForward` reason (the name
128
+ `FetchError` already uses); before this it fell through to `other`. A base branch checked out nowhere has no
129
+ working tree to pull into, so it is advanced by fetching `<upstream>:<branch>`, which git refuses unless it
130
+ fast-forwards; without the option that path only fetches. That fetch runs without `--update-head-ok`: "checked
131
+ out nowhere" comes from the cached worktree list, so git's own refusal is what keeps a branch that is in fact
132
+ checked out from moving. A branch with no upstream is `PullError` reason `noUpstream`, on this path and for a
133
+ checked-out pull alike. `merge`'s `true` / `false` are not offered: a pull configured to rebase ignores `--ff`
134
+ / `--no-ff`.
135
+
136
+ ## 7. A commit from a tree with several parents and an author — added
137
+
138
+ A compose or stack engine writes merge commits and replays commits with their original author, which the
139
+ single-parent, identity-less `commits.createUnreachableCommitFromTree` could not.
140
+
141
+ - `commits.createCommitFromTree(repoPath, tree, { parents, message, author?, committer?, sign?, source? })` runs
142
+ `commit-tree` with the message on stdin and the identity in the per-call environment, returning the sha.
143
+ With `sign` it reports a signing failure as `SigningError` and fires the signing hooks.
144
+ - It is the only path that writes a commit object. `createUnreachableCommitFromTree` is removed, and the patch
145
+ sub-provider's commits (`createUnreachableCommitForPatch`, `createUnreachableCommitsFromPatches`,
146
+ `createEmptyInitialCommit`) go through it with unchanged results.
147
+
148
+ ## 8. `git init` — added
149
+
150
+ `GitProvider.clone` existed; `init(path, options?: { defaultBranch?, bare? })` did not. Kepler creates a
151
+ repository for a folder the user adds.
152
+
153
+ ## 9. Forced ref reads — added
154
+
155
+ After a change core neither made nor was told about (another process moving a branch, a raw `fetch` the
156
+ consumer has not reported yet), an ordinary read can answer from cache until its TTL runs out. `notifyChanged`
157
+ is the fix when the consumer made the change; `force` is for a read that must be current regardless.
158
+
159
+ - `refs.validateReference(repoPath, ref, { relativePath?, force? }, cancellation?)`,
160
+ `refs.getReference(repoPath, ref, { force? }, cancellation?)`,
161
+ `branches.getBranch(repoPath, name?, { force? }, cancellation?)` and
162
+ `revision.resolveRevision(repoPath, ref, pathOrUri?, { force? })`. A forced read drops its cache entry before
163
+ reading, so it neither returns the cached answer nor joins a read already in flight (a fresh entry gets a
164
+ fresh cancellation aggregate, whose id is part of the executor's dedup key), and it stores the fresh answer
165
+ for later unforced reads. `getStatus({ force })` and `getPausedOperationStatus({ force })` are the precedent.
166
+ - `getBranch({ force })` evicts the whole branch cascade first: branch lists, ref tips, merge bases, commit
167
+ counts and resolved revisions, the set a branch move clears, since each can be derived from a ref that moved.
168
+ `getReference({ force })` forces its validation and branch lookup; tags are not forced.
169
+ - `validateReference`'s `relativePath` moved into the options object, and `getBranch` / `getReference` take
170
+ options before `cancellation`, matching `getStatus`.
171
+
172
+ ## Overlaps with existing APIs, and how each was settled
173
+
174
+ - **Refs containing a sha.** `branches.getBranchesWithCommits(repoPath, [sha], undefined, { all, mode })` and
175
+ `tags.getTagsWithCommit` already answer it, so `getRefTips` did not gain `--contains` / `--points-at`
176
+ filters. Kepler's one use (is a sha on any other local or remote branch) is `getBranchesWithCommits`.
177
+ - **Config keys.** Added to the documented union rather than served by a separate untyped
178
+ `getConfigValue` / `setConfigValue`, which would have been a second way to read every key.
179
+ - **Commit objects.** `createCommitFromTree` replaced `createUnreachableCommitFromTree` and the patch
180
+ sub-provider's own `commit-tree` calls, rather than sitting beside them.
181
+ - **Deleting refs.** `deleteReference` stays beside `branches.deleteLocalBranch` and `tags.deleteTag` on
182
+ purpose. `deleteLocalBranch` keeps `git branch -d`'s merged check, which the delete flow's force-retry relies
183
+ on, and takes several names; `deleteTag` reports a `TagError` the UI renders. `deleteReference` is the
184
+ compare-and-swap form for any namespace, and shares the branch cleanup with `deleteLocalBranch`.
185
+ - **Clearing caches.** `evictCaches` stays beside `clearCaches`: a write hard-evicts, a watcher clear keeps
186
+ sharing in-flight reads (see the decisions below).
187
+
188
+ ## Deferred, deliberately
189
+
190
+ - **Index and object plumbing** (`read-tree`, `write-tree`, `update-index --index-info`, `mktree`,
191
+ `hash-object -w --stdin`, `cat-file -p`). The libraries drive these through `exec` with a scratch
192
+ `GIT_INDEX_FILE` and parse the results themselves; a typed surface would re-encode the same argv with more
193
+ room to drift. Revisit when a second consumer needs them.
194
+ - **A reflog read filtered by message.** Kepler reads `git reflog <branch> --grep-reflog='branch: Created from'`
195
+ to recover a branch's creation point. `getIncomingActivity` walks `HEAD`'s reflog for a different purpose;
196
+ bolting a grep onto it would change its paging. Worth its own method on `branches` once the base-branch
197
+ inference core already does privately is unified with it.
198
+ - **Generic `for-each-ref` with a caller-supplied format.** The `RefRecord` shape is private on purpose.
199
+ - **A typed `worktree remove` for every consumer.** Kept the raw form where a consumer classifies platform
200
+ failures from stderr; §3's `original` makes the typed form usable for that too.
201
+
202
+ ## Bridging core into the tools libraries
203
+
204
+ The libraries take their own port and know nothing of core, so a host serving one through core reshapes what
205
+ core rejects with:
206
+
207
+ - **Cancellation.** Core rejects with `CancellationError` (`name === 'CancellationError'`); a library that
208
+ expects `AbortError` gets it mapped by name.
209
+ - **"Git answered no" versus a structural failure.** sync-tools tells them apart by whether `err.code` is a
210
+ number, and compose-tools reads `err.stderr`: the shape of Node's `execFile` error. Core's `GitError`
211
+ carries `stderr` but reports the exit as `exitCode`, which is a number or numeric string for a real exit and
212
+ an errno string (`'ENOENT'`) when git never started. A typed method's own error, such as
213
+ `ReferenceUpdateError`, keeps that `GitError` on `original`.
214
+
215
+ ## Decisions worth recording, because each closes off a plausible-looking alternative
216
+
217
+ - `updateReference` throws rather than gaining an `errors: 'throw'` option. A ref writer whose failures are
218
+ invisible by default is the bug, not a mode.
219
+ - `'absent'` rather than `expected: ''`. The empty old value is git's encoding of "must not exist"; a caller
220
+ spelling it as an empty string reads like a mistake, and an argv filter that drops empty strings would turn
221
+ a create into an unconditional overwrite.
222
+ - New config keys join `GitConfigKeys` rather than widening it to `string` or adding an untyped read. The
223
+ union's value is that every listed key is documented; an escape hatch would keep the autocomplete and lose
224
+ the guarantee.
225
+ - `deleteReference` gives a branch `git branch -d`'s guard and cleanup rather than refusing branches. A
226
+ compare-and-swap delete of a branch is legitimate (sync-tools' port contract exercises one), and refusing it
227
+ would push the caller back to a raw `update-ref -d` that orphans the same state.
228
+ - A write hard-evicts while a watcher clear keeps sharing in-flight reads, rather than one policy for both. A
229
+ write is a known point after which an older read is wrong; a watcher tick has no such ordering, and sharing
230
+ the in-flight read is what keeps a burst of file events from multiplying git processes.
231
+ - `notifyChanged` on the provider rather than an option on `git.run`. The executor has no context and no
232
+ hooks; the provider does. A consumer that runs a raw mutation knows what it changed.
233
+ - Staging hooks skip the temporary-index case rather than firing unconditionally. A scratch index is not the
234
+ repository's; announcing an index change for it would invalidate status for nothing.
235
+
236
+ **Kepler-side follow-up**, once a core release carries these: serve sync-tools' `updateRef` op through
237
+ `updateReference` / `deleteReference` (mapping in §1), delete the review ref with `deleteReference`, drop the
238
+ verb classifier in `core-git-port.ts` in favour of `notifyChanged`, and retire the remaining
239
+ `TODO(core-gitlens)` residuals.