@gitkraken/core-gitlens 0.1.0 → 0.2.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 (223) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/dist/git/cache.d.ts +21 -5
  3. package/dist/git/cache.d.ts.map +1 -1
  4. package/dist/git/cache.js +102 -6
  5. package/dist/git/cache.js.map +1 -1
  6. package/dist/git/context.d.ts +11 -2
  7. package/dist/git/context.d.ts.map +1 -1
  8. package/dist/git/context.js.map +1 -1
  9. package/dist/git/models/branch.d.ts +1 -0
  10. package/dist/git/models/branch.d.ts.map +1 -1
  11. package/dist/git/models/branch.js.map +1 -1
  12. package/dist/git/models/commit.d.ts +1 -9
  13. package/dist/git/models/commit.d.ts.map +1 -1
  14. package/dist/git/models/commit.js +4 -9
  15. package/dist/git/models/commit.js.map +1 -1
  16. package/dist/git/models/graph.d.ts +2 -4
  17. package/dist/git/models/graph.d.ts.map +1 -1
  18. package/dist/git/models/repository.d.ts +12 -3
  19. package/dist/git/models/repository.d.ts.map +1 -1
  20. package/dist/git/models/repository.js +25 -4
  21. package/dist/git/models/repository.js.map +1 -1
  22. package/dist/git/models/worktree.d.ts +1 -1
  23. package/dist/git/models/worktree.d.ts.map +1 -1
  24. package/dist/git/models/worktree.js +2 -2
  25. package/dist/git/models/worktree.js.map +1 -1
  26. package/dist/git/parsers/diffParser.d.ts +0 -1
  27. package/dist/git/parsers/diffParser.d.ts.map +1 -1
  28. package/dist/git/parsers/diffParser.js +22 -61
  29. package/dist/git/parsers/diffParser.js.map +1 -1
  30. package/dist/git/providers/branches.d.ts +3 -0
  31. package/dist/git/providers/branches.d.ts.map +1 -1
  32. package/dist/git/providers/commits.d.ts +6 -0
  33. package/dist/git/providers/commits.d.ts.map +1 -1
  34. package/dist/git/providers/config.d.ts +2 -0
  35. package/dist/git/providers/config.d.ts.map +1 -1
  36. package/dist/git/providers/diff.d.ts +2 -0
  37. package/dist/git/providers/diff.d.ts.map +1 -1
  38. package/dist/git/providers/operations.d.ts +6 -0
  39. package/dist/git/providers/operations.d.ts.map +1 -1
  40. package/dist/git/providers/revision.d.ts +1 -1
  41. package/dist/git/providers/revision.d.ts.map +1 -1
  42. package/dist/git/providers/staging.d.ts +2 -0
  43. package/dist/git/providers/staging.d.ts.map +1 -1
  44. package/dist/git/utils/sorting.d.ts +6 -0
  45. package/dist/git/utils/sorting.d.ts.map +1 -1
  46. package/dist/git/utils/sorting.js +19 -0
  47. package/dist/git/utils/sorting.js.map +1 -1
  48. package/dist/git-cli/cliGitProvider.js +1 -1
  49. package/dist/git-cli/cliGitProvider.js.map +1 -1
  50. package/dist/git-cli/exec/git.d.ts +29 -4
  51. package/dist/git-cli/exec/git.d.ts.map +1 -1
  52. package/dist/git-cli/exec/git.js +70 -9
  53. package/dist/git-cli/exec/git.js.map +1 -1
  54. package/dist/git-cli/exec/gitQueue.d.ts +12 -4
  55. package/dist/git-cli/exec/gitQueue.d.ts.map +1 -1
  56. package/dist/git-cli/exec/gitQueue.js +49 -12
  57. package/dist/git-cli/exec/gitQueue.js.map +1 -1
  58. package/dist/git-cli/parsers/logParser.d.ts +1 -0
  59. package/dist/git-cli/parsers/logParser.d.ts.map +1 -1
  60. package/dist/git-cli/parsers/logParser.js +4 -0
  61. package/dist/git-cli/parsers/logParser.js.map +1 -1
  62. package/dist/git-cli/providers/blame.js +1 -1
  63. package/dist/git-cli/providers/blame.js.map +1 -1
  64. package/dist/git-cli/providers/branches.d.ts +2 -0
  65. package/dist/git-cli/providers/branches.d.ts.map +1 -1
  66. package/dist/git-cli/providers/branches.js +176 -123
  67. package/dist/git-cli/providers/branches.js.map +1 -1
  68. package/dist/git-cli/providers/commits.d.ts +4 -0
  69. package/dist/git-cli/providers/commits.d.ts.map +1 -1
  70. package/dist/git-cli/providers/commits.js +58 -17
  71. package/dist/git-cli/providers/commits.js.map +1 -1
  72. package/dist/git-cli/providers/config.d.ts.map +1 -1
  73. package/dist/git-cli/providers/config.js +9 -9
  74. package/dist/git-cli/providers/config.js.map +1 -1
  75. package/dist/git-cli/providers/contributors.d.ts.map +1 -1
  76. package/dist/git-cli/providers/contributors.js +7 -4
  77. package/dist/git-cli/providers/contributors.js.map +1 -1
  78. package/dist/git-cli/providers/diff.d.ts +2 -0
  79. package/dist/git-cli/providers/diff.d.ts.map +1 -1
  80. package/dist/git-cli/providers/diff.js +89 -19
  81. package/dist/git-cli/providers/diff.js.map +1 -1
  82. package/dist/git-cli/providers/graph.d.ts.map +1 -1
  83. package/dist/git-cli/providers/graph.js +43 -27
  84. package/dist/git-cli/providers/graph.js.map +1 -1
  85. package/dist/git-cli/providers/operations.d.ts +17 -0
  86. package/dist/git-cli/providers/operations.d.ts.map +1 -1
  87. package/dist/git-cli/providers/operations.js +47 -14
  88. package/dist/git-cli/providers/operations.js.map +1 -1
  89. package/dist/git-cli/providers/patch.d.ts.map +1 -1
  90. package/dist/git-cli/providers/patch.js +15 -30
  91. package/dist/git-cli/providers/patch.js.map +1 -1
  92. package/dist/git-cli/providers/pausedOperations.js +6 -6
  93. package/dist/git-cli/providers/pausedOperations.js.map +1 -1
  94. package/dist/git-cli/providers/refs.d.ts.map +1 -1
  95. package/dist/git-cli/providers/refs.js +26 -7
  96. package/dist/git-cli/providers/refs.js.map +1 -1
  97. package/dist/git-cli/providers/remotes.js +4 -4
  98. package/dist/git-cli/providers/remotes.js.map +1 -1
  99. package/dist/git-cli/providers/revision.d.ts +1 -1
  100. package/dist/git-cli/providers/revision.d.ts.map +1 -1
  101. package/dist/git-cli/providers/revision.js +15 -11
  102. package/dist/git-cli/providers/revision.js.map +1 -1
  103. package/dist/git-cli/providers/staging.d.ts +2 -0
  104. package/dist/git-cli/providers/staging.d.ts.map +1 -1
  105. package/dist/git-cli/providers/staging.js +20 -8
  106. package/dist/git-cli/providers/staging.js.map +1 -1
  107. package/dist/git-cli/providers/stash.d.ts +0 -8
  108. package/dist/git-cli/providers/stash.d.ts.map +1 -1
  109. package/dist/git-cli/providers/stash.js +63 -119
  110. package/dist/git-cli/providers/stash.js.map +1 -1
  111. package/dist/git-cli/providers/status.js +6 -6
  112. package/dist/git-cli/providers/status.js.map +1 -1
  113. package/dist/git-cli/providers/tags.js +4 -4
  114. package/dist/git-cli/providers/tags.js.map +1 -1
  115. package/dist/git-cli/providers/worktrees.js +3 -3
  116. package/dist/git-cli/providers/worktrees.js.map +1 -1
  117. package/dist/plus/ai/constants.d.ts +2 -1
  118. package/dist/plus/ai/constants.d.ts.map +1 -1
  119. package/dist/plus/ai/constants.js +7 -0
  120. package/dist/plus/ai/constants.js.map +1 -1
  121. package/dist/plus/ai/models/model.d.ts +1 -1
  122. package/dist/plus/ai/models/model.d.ts.map +1 -1
  123. package/dist/plus/ai/models/model.js.map +1 -1
  124. package/dist/plus/ai/models/promptTemplates.d.ts +19 -2
  125. package/dist/plus/ai/models/promptTemplates.d.ts.map +1 -1
  126. package/dist/plus/ai/models/results.d.ts +28 -0
  127. package/dist/plus/ai/models/results.d.ts.map +1 -1
  128. package/dist/plus/ai/prompts.d.ts +3 -0
  129. package/dist/plus/ai/prompts.d.ts.map +1 -1
  130. package/dist/plus/ai/prompts.js +126 -0
  131. package/dist/plus/ai/prompts.js.map +1 -1
  132. package/dist/plus/ai/utils/ai.utils.d.ts.map +1 -1
  133. package/dist/plus/ai/utils/ai.utils.js +2 -0
  134. package/dist/plus/ai/utils/ai.utils.js.map +1 -1
  135. package/dist/plus/ai/utils/results.utils.d.ts +3 -0
  136. package/dist/plus/ai/utils/results.utils.d.ts.map +1 -1
  137. package/dist/plus/ai/utils/results.utils.js +75 -0
  138. package/dist/plus/ai/utils/results.utils.js.map +1 -1
  139. package/dist/plus/ai/utils/truncation.utils.d.ts +1 -1
  140. package/dist/plus/ai/utils/truncation.utils.d.ts.map +1 -1
  141. package/dist/plus/ai/utils/truncation.utils.js.map +1 -1
  142. package/dist/plus/git-github/providers/github/branches.d.ts.map +1 -1
  143. package/dist/plus/git-github/providers/github/branches.js +16 -1
  144. package/dist/plus/git-github/providers/github/branches.js.map +1 -1
  145. package/dist/plus/git-github/providers/github/diff.d.ts +2 -0
  146. package/dist/plus/git-github/providers/github/diff.d.ts.map +1 -1
  147. package/dist/plus/git-github/providers/github/diff.js.map +1 -1
  148. package/dist/utils/decorators/log.js +1 -1
  149. package/dist/utils/decorators/log.js.map +1 -1
  150. package/dist/utils/decorators/sequentialize.d.ts.map +1 -1
  151. package/dist/utils/decorators/sequentialize.js +4 -2
  152. package/dist/utils/decorators/sequentialize.js.map +1 -1
  153. package/dist/utils/env/node/base64.js.map +1 -1
  154. package/dist/utils/env/node/exec.js.map +1 -1
  155. package/dist/utils/formatter.d.ts.map +1 -1
  156. package/dist/utils/formatter.js +4 -10
  157. package/dist/utils/formatter.js.map +1 -1
  158. package/dist/utils/iterable.js.map +1 -1
  159. package/dist/utils/lruMap.d.ts +40 -0
  160. package/dist/utils/lruMap.d.ts.map +1 -0
  161. package/dist/utils/lruMap.js +81 -0
  162. package/dist/utils/lruMap.js.map +1 -0
  163. package/dist/utils/version.d.ts +14 -0
  164. package/dist/utils/version.d.ts.map +1 -1
  165. package/dist/utils/version.js +48 -0
  166. package/dist/utils/version.js.map +1 -1
  167. package/package.json +1 -1
  168. package/src/git/cache.ts +179 -13
  169. package/src/git/context.ts +11 -2
  170. package/src/git/models/branch.ts +1 -0
  171. package/src/git/models/commit.ts +1 -17
  172. package/src/git/models/graph.ts +2 -4
  173. package/src/git/models/repository.ts +26 -6
  174. package/src/git/models/worktree.ts +2 -2
  175. package/src/git/parsers/diffParser.ts +7 -44
  176. package/src/git/providers/branches.ts +3 -0
  177. package/src/git/providers/commits.ts +7 -0
  178. package/src/git/providers/config.ts +2 -0
  179. package/src/git/providers/diff.ts +8 -2
  180. package/src/git/providers/operations.ts +10 -3
  181. package/src/git/providers/revision.ts +5 -1
  182. package/src/git/providers/staging.ts +2 -0
  183. package/src/git/utils/sorting.ts +21 -0
  184. package/src/git-cli/cliGitProvider.ts +1 -1
  185. package/src/git-cli/exec/git.ts +70 -18
  186. package/src/git-cli/exec/gitQueue.ts +57 -13
  187. package/src/git-cli/parsers/logParser.ts +5 -1
  188. package/src/git-cli/providers/blame.ts +1 -1
  189. package/src/git-cli/providers/branches.ts +207 -133
  190. package/src/git-cli/providers/commits.ts +71 -18
  191. package/src/git-cli/providers/config.ts +9 -14
  192. package/src/git-cli/providers/contributors.ts +7 -12
  193. package/src/git-cli/providers/diff.ts +100 -19
  194. package/src/git-cli/providers/graph.ts +47 -27
  195. package/src/git-cli/providers/operations.ts +77 -18
  196. package/src/git-cli/providers/patch.ts +15 -37
  197. package/src/git-cli/providers/pausedOperations.ts +6 -6
  198. package/src/git-cli/providers/refs.ts +27 -8
  199. package/src/git-cli/providers/remotes.ts +4 -4
  200. package/src/git-cli/providers/revision.ts +20 -13
  201. package/src/git-cli/providers/staging.ts +18 -8
  202. package/src/git-cli/providers/stash.ts +67 -134
  203. package/src/git-cli/providers/status.ts +6 -6
  204. package/src/git-cli/providers/tags.ts +4 -4
  205. package/src/git-cli/providers/worktrees.ts +3 -3
  206. package/src/plus/ai/constants.ts +8 -0
  207. package/src/plus/ai/models/model.ts +1 -0
  208. package/src/plus/ai/models/promptTemplates.ts +29 -0
  209. package/src/plus/ai/models/results.ts +30 -0
  210. package/src/plus/ai/prompts.ts +129 -0
  211. package/src/plus/ai/utils/ai.utils.ts +2 -0
  212. package/src/plus/ai/utils/results.utils.ts +99 -0
  213. package/src/plus/ai/utils/truncation.utils.ts +3 -1
  214. package/src/plus/git-github/providers/github/branches.ts +17 -5
  215. package/src/plus/git-github/providers/github/diff.ts +8 -2
  216. package/src/utils/decorators/log.ts +1 -1
  217. package/src/utils/decorators/sequentialize.ts +8 -6
  218. package/src/utils/env/node/base64.ts +1 -1
  219. package/src/utils/env/node/exec.ts +2 -2
  220. package/src/utils/formatter.ts +4 -11
  221. package/src/utils/iterable.ts +1 -1
  222. package/src/utils/lruMap.ts +87 -0
  223. package/src/utils/version.ts +48 -0
@@ -142,7 +142,7 @@ export class StatusGitSubProvider implements GitStatusSubProvider {
142
142
  );
143
143
  }
144
144
 
145
- return this.git.exec(
145
+ return this.git.run(
146
146
  {
147
147
  cwd: repoPath,
148
148
  cancellation: cancellation,
@@ -168,7 +168,7 @@ export class StatusGitSubProvider implements GitStatusSubProvider {
168
168
  const staged = options?.staged ?? true;
169
169
  const unstaged = options?.unstaged ?? true;
170
170
  if (staged || unstaged) {
171
- const result = await this.git.exec(
171
+ const result = await this.git.run(
172
172
  { cwd: repoPath, cancellation: cancellation, errors: 'ignore' },
173
173
  'diff',
174
174
  '--quiet',
@@ -218,14 +218,14 @@ export class StatusGitSubProvider implements GitStatusSubProvider {
218
218
  try {
219
219
  const [stagedResult, unstagedResult, untrackedResult] = await Promise.allSettled([
220
220
  // Check for staged changes
221
- this.git.exec(
221
+ this.git.run(
222
222
  { cwd: repoPath, cancellation: cancellation, errors: 'ignore' },
223
223
  'diff',
224
224
  '--quiet',
225
225
  '--staged',
226
226
  ),
227
227
  // Check for unstaged changes
228
- this.git.exec({ cwd: repoPath, cancellation: cancellation, errors: 'ignore' }, 'diff', '--quiet'),
228
+ this.git.run({ cwd: repoPath, cancellation: cancellation, errors: 'ignore' }, 'diff', '--quiet'),
229
229
  // Check for untracked files
230
230
  this.hasUntrackedFiles(repoPath, cancellation),
231
231
  ]);
@@ -279,7 +279,7 @@ export class StatusGitSubProvider implements GitStatusSubProvider {
279
279
  const scope = getScopedLogger();
280
280
 
281
281
  try {
282
- const result = await this.git.exec(
282
+ const result = await this.git.run(
283
283
  { cwd: repoPath, cancellation: cancellation, errors: 'ignore' },
284
284
  'ls-files',
285
285
  '-z',
@@ -337,7 +337,7 @@ export class StatusGitSubProvider implements GitStatusSubProvider {
337
337
  const scope = getScopedLogger();
338
338
 
339
339
  try {
340
- const result = await this.git.exec(
340
+ const result = await this.git.run(
341
341
  { cwd: repoPath, cancellation: cancellation, errors: 'ignore' },
342
342
  'ls-files',
343
343
  '-z',
@@ -56,7 +56,7 @@ export class TagsGitSubProvider implements GitTagsSubProvider {
56
56
  try {
57
57
  const parser = getTagParser();
58
58
 
59
- const result = await this.git.exec(
59
+ const result = await this.git.run(
60
60
  { cwd: repoPath, cancellation: signal },
61
61
  'for-each-ref',
62
62
  ...parser.arguments,
@@ -130,7 +130,7 @@ export class TagsGitSubProvider implements GitTagsSubProvider {
130
130
  const params: string[] = ['tag', '--format=%(refname:short)'];
131
131
  params.push(options?.mode === 'pointsAt' ? `--points-at=${sha}` : `--contains=${sha}`);
132
132
 
133
- return this.git.exec(
133
+ return this.git.run(
134
134
  {
135
135
  cwd: repoPath,
136
136
  cancellation: cancellation,
@@ -149,7 +149,7 @@ export class TagsGitSubProvider implements GitTagsSubProvider {
149
149
  }
150
150
 
151
151
  try {
152
- await this.git.exec({ cwd: repoPath }, ...args);
152
+ await this.git.run({ cwd: repoPath }, ...args);
153
153
  this.context.hooks?.cache?.onReset?.(repoPath, 'tags');
154
154
  this.context.hooks?.repository?.onChanged?.(repoPath, ['tags']);
155
155
  } catch (ex) {
@@ -170,7 +170,7 @@ export class TagsGitSubProvider implements GitTagsSubProvider {
170
170
  const args = ['tag', '-d', name];
171
171
 
172
172
  try {
173
- await this.git.exec({ cwd: repoPath }, ...args);
173
+ await this.git.run({ cwd: repoPath }, ...args);
174
174
  this.context.hooks?.cache?.onReset?.(repoPath, 'tags');
175
175
  this.context.hooks?.repository?.onChanged?.(repoPath, ['tags']);
176
176
  } catch (ex) {
@@ -49,7 +49,7 @@ export class WorktreesGitSubProvider implements GitWorktreesSubProvider {
49
49
  }
50
50
 
51
51
  try {
52
- await this.git.exec({ cwd: repoPath }, ...args);
52
+ await this.git.run({ cwd: repoPath }, ...args);
53
53
 
54
54
  this.context.hooks?.cache?.onReset?.(
55
55
  repoPath,
@@ -106,7 +106,7 @@ export class WorktreesGitSubProvider implements GitWorktreesSubProvider {
106
106
  // Prefer the aggregate signal from the cache; fall back to the caller's cancellation.
107
107
  signal ??= cancellation;
108
108
  const [dataResult, branchesResult] = await Promise.allSettled([
109
- this.git.exec({ cwd: commonPath, cancellation: signal }, 'worktree', 'list', '--porcelain'),
109
+ this.git.run({ cwd: commonPath, cancellation: signal }, 'worktree', 'list', '--porcelain'),
110
110
  this.provider.branches.getBranches(commonPath, undefined, signal),
111
111
  ]);
112
112
 
@@ -154,7 +154,7 @@ export class WorktreesGitSubProvider implements GitWorktreesSubProvider {
154
154
 
155
155
  let deleted = false;
156
156
  try {
157
- await this.git.exec({ cwd: repoPath, errors: 'throw' }, ...args);
157
+ await this.git.run({ cwd: repoPath, errors: 'throw' }, ...args);
158
158
  deleted = true;
159
159
  } catch (ex) {
160
160
  scope?.error(ex);
@@ -13,6 +13,7 @@ export type AIProviders =
13
13
  | 'openai'
14
14
  | 'openaicompatible'
15
15
  | 'openrouter'
16
+ | 'simulator'
16
17
  | 'vscode'
17
18
  | 'xai';
18
19
  export type AIPrimaryProviders = Extract<AIProviders, 'gitkraken' | 'vscode'>;
@@ -119,3 +120,10 @@ export const ollamaProviderDescriptor: AIProviderDescriptor<'ollama'> = {
119
120
  requiresAccount: true,
120
121
  requiresUserKey: false,
121
122
  } as const;
123
+ export const simulatorProviderDescriptor: AIProviderDescriptor<'simulator'> = {
124
+ id: 'simulator',
125
+ name: 'Simulator (Debugging)',
126
+ primary: false,
127
+ requiresAccount: false,
128
+ requiresUserKey: false,
129
+ } as const;
@@ -22,6 +22,7 @@ export interface AIModelDescriptor<Provider extends AIProviders = AIProviders, M
22
22
 
23
23
  export type AIActionType =
24
24
  | 'explain-changes'
25
+ | 'review-changes'
25
26
  | 'generate-commitMessage'
26
27
  | 'generate-stashMessage'
27
28
  | 'generate-changelog'
@@ -78,6 +78,26 @@ interface ReviewPullRequestPromptTemplateContext {
78
78
  instructions?: string;
79
79
  }
80
80
 
81
+ interface ReviewChangesPromptTemplateContext {
82
+ diff: string;
83
+ message: string;
84
+ instructions?: string;
85
+ }
86
+
87
+ interface ReviewOverviewPromptTemplateContext {
88
+ files: string;
89
+ message: string;
90
+ instructions?: string;
91
+ }
92
+
93
+ interface ReviewDetailPromptTemplateContext {
94
+ diff: string;
95
+ overview: string;
96
+ message: string;
97
+ focusArea: string;
98
+ instructions?: string;
99
+ }
100
+
81
101
  interface StartWorkIssuePromptTemplateContext {
82
102
  issue: string;
83
103
  instructions?: string;
@@ -91,6 +111,9 @@ export type PromptTemplateType =
91
111
  | 'generate-commits'
92
112
  | 'generate-searchQuery'
93
113
  | 'explain-changes'
114
+ | 'review-changes'
115
+ | 'review-overview'
116
+ | 'review-detail'
94
117
  | 'start-review-pullRequest'
95
118
  | 'start-work-issue';
96
119
 
@@ -115,6 +138,12 @@ export type PromptTemplateContext<T extends PromptTemplateType> = T extends 'gen
115
138
  ? SearchQueryPromptTemplateContext
116
139
  : T extends 'explain-changes'
117
140
  ? ExplainChangesPromptTemplateContext
141
+ : T extends 'review-changes'
142
+ ? ReviewChangesPromptTemplateContext
143
+ : T extends 'review-overview'
144
+ ? ReviewOverviewPromptTemplateContext
145
+ : T extends 'review-detail'
146
+ ? ReviewDetailPromptTemplateContext
118
147
  : T extends 'start-review-pullRequest'
119
148
  ? ReviewPullRequestPromptTemplateContext
120
149
  : T extends 'start-work-issue'
@@ -1 +1,31 @@
1
1
  export type AISummarizedResult = { summary: string; body: string };
2
+
3
+ export type AIReviewSeverity = 'critical' | 'warning' | 'suggestion';
4
+
5
+ export interface AIReviewFinding {
6
+ readonly id: string;
7
+ readonly severity: AIReviewSeverity;
8
+ readonly title: string;
9
+ readonly description: string;
10
+ readonly filePath?: string;
11
+ readonly lineRange?: { readonly start: number; readonly end: number };
12
+ }
13
+
14
+ export interface AIReviewFocusArea {
15
+ readonly id: string;
16
+ readonly label: string;
17
+ readonly rationale: string;
18
+ readonly severity: AIReviewSeverity;
19
+ readonly files: readonly string[];
20
+ readonly findings?: readonly AIReviewFinding[];
21
+ }
22
+
23
+ export interface AIReviewResult {
24
+ readonly overview: string;
25
+ readonly focusAreas: readonly AIReviewFocusArea[];
26
+ readonly mode: 'single-pass' | 'two-pass';
27
+ }
28
+
29
+ export interface AIReviewDetailResult {
30
+ readonly findings: readonly AIReviewFinding[];
31
+ }
@@ -526,3 +526,132 @@ You can use GitKraken MCP tools to gather additional context about the repositor
526
526
 
527
527
  Now, proceed with your analysis and provide a comprehensive review of the PR. Return only the relevant information without any additional text.`,
528
528
  };
529
+
530
+ export const reviewChanges: PromptTemplate<'review-changes'> = {
531
+ id: 'review-changes',
532
+ variables: ['diff', 'message', 'instructions'],
533
+ template: `You are an expert code reviewer analyzing a set of code changes. Your goal is to identify meaningful issues — bugs, logic errors, security vulnerabilities, missing error handling, and potential regressions — while ignoring style preferences and linter-level concerns. Focus on problems a careful human reviewer would catch.
534
+
535
+ Examine the following code changes in Git diff format:
536
+ <~~diff~~>
537
+ \${diff}
538
+ </~~diff~~>
539
+
540
+ Author's description of the changes:
541
+ <~~message~~>
542
+ \${message}
543
+ </~~message~~>
544
+
545
+ Produce a structured review in the following XML format. Include ONLY the XML tags described — no other text:
546
+
547
+ <overview>
548
+ A concise 1-3 sentence summary of what these changes do and their overall quality. Note any systemic concerns.
549
+ </overview>
550
+ <focus-areas>
551
+ <area severity="critical|warning|suggestion" files="comma-separated file paths">
552
+ <label>Short title of the concern (under 60 chars)</label>
553
+ <rationale>Why this matters — what could go wrong or what is suboptimal</rationale>
554
+ <findings>
555
+ <finding severity="critical|warning|suggestion" file="path/to/file.ts" lines="start-end">
556
+ <title>Specific issue title</title>
557
+ <description>Clear explanation of the problem and how to address it</description>
558
+ </finding>
559
+ </findings>
560
+ </area>
561
+ </focus-areas>
562
+
563
+ Guidelines:
564
+ - Severity levels: "critical" = bugs, security issues, data loss risks; "warning" = logic concerns, missing error handling, potential regressions; "suggestion" = improvements, maintainability, performance
565
+ - Group related findings into focus areas by theme, not by file
566
+ - If changes look correct and well-structured, say so in the overview and include zero focus areas
567
+ - 3-5 high-quality findings are better than 15 low-quality ones
568
+ - Reference specific lines from the diff when possible
569
+ - Do not flag style issues, naming preferences, or things a linter would catch
570
+ - Base conclusions only on the code shown — do not speculate about unseen code
571
+
572
+ \${instructions}
573
+
574
+ Review the changes and produce the structured XML output above.`,
575
+ };
576
+
577
+ export const reviewOverview: PromptTemplate<'review-overview'> = {
578
+ id: 'review-overview',
579
+ variables: ['files', 'message', 'instructions'],
580
+ template: `You are an expert code reviewer performing an initial assessment of a set of code changes. You are given a file manifest (not full diffs) — use the file paths, change types, and line counts to identify which areas deserve closer inspection.
581
+
582
+ File manifest (JSON array of changed files):
583
+ <~~files~~>
584
+ \${files}
585
+ </~~files~~>
586
+
587
+ Author's description of the changes:
588
+ <~~message~~>
589
+ \${message}
590
+ </~~message~~>
591
+
592
+ Produce a structured assessment in the following XML format. Include ONLY the XML tags described — no other text:
593
+
594
+ <overview>
595
+ A concise 1-3 sentence summary of the scope and nature of these changes based on the file manifest and description.
596
+ </overview>
597
+ <focus-areas>
598
+ <area severity="critical|warning|suggestion" files="comma-separated file paths">
599
+ <label>Short title of the area to inspect (under 60 chars)</label>
600
+ <rationale>Why this area deserves closer review — based on the types of files changed, the volume of changes, and potential risk</rationale>
601
+ </area>
602
+ </focus-areas>
603
+
604
+ Guidelines:
605
+ - Severity reflects potential risk: "critical" = security-sensitive files, auth/crypto/payment paths, database migrations; "warning" = core logic changes, API changes, large modifications; "suggestion" = refactoring, config changes, documentation
606
+ - Rank focus areas from highest to lowest risk
607
+ - Group related files into focus areas by theme
608
+ - Include 2-6 focus areas. If changes are very simple, fewer is fine
609
+ - Do NOT include <findings> — those come in a later pass when full diffs are available
610
+
611
+ \${instructions}
612
+
613
+ Assess the changes and produce the structured XML output above.`,
614
+ };
615
+
616
+ export const reviewDetail: PromptTemplate<'review-detail'> = {
617
+ id: 'review-detail',
618
+ variables: ['diff', 'overview', 'message', 'focusArea', 'instructions'],
619
+ template: `You are an expert code reviewer performing a detailed inspection of specific files that were flagged for closer review. You have the context from an initial overview assessment.
620
+
621
+ Initial overview of the full changeset:
622
+ <~~overview~~>
623
+ \${overview}
624
+ </~~overview~~>
625
+
626
+ Focus area being inspected: \${focusArea}
627
+
628
+ Author's description of the changes:
629
+ <~~message~~>
630
+ \${message}
631
+ </~~message~~>
632
+
633
+ Code changes for the files in this focus area (Git diff format):
634
+ <~~diff~~>
635
+ \${diff}
636
+ </~~diff~~>
637
+
638
+ Produce detailed findings in the following XML format. Include ONLY the XML tags — no other text:
639
+
640
+ <findings>
641
+ <finding severity="critical|warning|suggestion" file="path/to/file.ts" lines="start-end">
642
+ <title>Specific issue title</title>
643
+ <description>Clear explanation of the problem and how to address it</description>
644
+ </finding>
645
+ </findings>
646
+
647
+ Guidelines:
648
+ - Severity: "critical" = bugs, security, data loss; "warning" = logic concerns, error handling, regressions; "suggestion" = improvements, maintainability
649
+ - Reference specific line numbers from the diff
650
+ - Be concrete — explain what the problem is and how to fix it
651
+ - If the code in this area looks correct, return an empty <findings></findings> block
652
+ - Do not flag style issues or things a linter would catch
653
+
654
+ \${instructions}
655
+
656
+ Inspect the diff for this focus area and produce the structured XML findings above.`,
657
+ };
@@ -4,6 +4,8 @@ export function getActionName(action: AIActionType): string {
4
4
  switch (action) {
5
5
  case 'explain-changes':
6
6
  return 'Explain Changes';
7
+ case 'review-changes':
8
+ return 'Review Changes';
7
9
  case 'generate-commitMessage':
8
10
  return 'Generate Commit Message';
9
11
  case 'generate-stashMessage':
@@ -1,3 +1,11 @@
1
+ import type {
2
+ AIReviewDetailResult,
3
+ AIReviewFinding,
4
+ AIReviewFocusArea,
5
+ AIReviewResult,
6
+ AIReviewSeverity,
7
+ } from '../models/results.js';
8
+
1
9
  const summaryTagRegex = /<summary>([\s\S]*?)(?:<\/summary>|$)/;
2
10
  const bodyTagRegex = /<body>([\s\S]*?)(?:<\/body>|$)/;
3
11
  const codeBlockRegex = /```([\s\S]*?)```/;
@@ -48,3 +56,94 @@ export function splitMessageIntoSummaryAndBody(message: string): { readonly summ
48
56
  body: message.substring(index + 1).trim(),
49
57
  };
50
58
  }
59
+
60
+ const overviewTagRegex = /<overview>([\s\S]*?)(?:<\/overview>|$)/;
61
+ const areaTagRegex = /<area\s+([^>]*)>([\s\S]*?)(?:<\/area>|$)/g;
62
+ const findingTagRegex = /<finding\s+([^>]*)>([\s\S]*?)(?:<\/finding>|$)/g;
63
+ const labelTagRegex = /<label>([\s\S]*?)(?:<\/label>|$)/;
64
+ const rationaleTagRegex = /<rationale>([\s\S]*?)(?:<\/rationale>|$)/;
65
+ const titleTagRegex = /<title>([\s\S]*?)(?:<\/title>|$)/;
66
+ const descriptionTagRegex = /<description>([\s\S]*?)(?:<\/description>|$)/;
67
+ const findingsBlockRegex = /<findings>([\s\S]*?)(?:<\/findings>|$)/;
68
+
69
+ function parseAttr(attrs: string, name: string): string | undefined {
70
+ const match = new RegExp(`${name}="([^"]*)"`, 'i').exec(attrs);
71
+ return match?.[1]?.trim() || undefined;
72
+ }
73
+
74
+ function parseSeverity(value: string | undefined): AIReviewSeverity {
75
+ if (value === 'critical' || value === 'warning' || value === 'suggestion') return value;
76
+ return 'suggestion';
77
+ }
78
+
79
+ function parseLineRange(value: string | undefined): { start: number; end: number } | undefined {
80
+ if (!value) return undefined;
81
+ const parts = value.split('-');
82
+ const start = parseInt(parts[0], 10);
83
+ if (isNaN(start)) return undefined;
84
+ const end = parts.length > 1 ? parseInt(parts[1], 10) : start;
85
+ return { start: start, end: isNaN(end) ? start : end };
86
+ }
87
+
88
+ function parseFindings(content: string, idPrefix: string): AIReviewFinding[] {
89
+ const findings: AIReviewFinding[] = [];
90
+ let findingIndex = 0;
91
+
92
+ for (const match of content.matchAll(findingTagRegex)) {
93
+ const attrs = match[1];
94
+ const inner = match[2];
95
+ findingIndex++;
96
+
97
+ findings.push({
98
+ id: `${idPrefix}-f${findingIndex}`,
99
+ severity: parseSeverity(parseAttr(attrs, 'severity')),
100
+ title: inner.match(titleTagRegex)?.[1]?.trim() ?? 'Untitled finding',
101
+ description: inner.match(descriptionTagRegex)?.[1]?.trim() ?? '',
102
+ filePath: parseAttr(attrs, 'file'),
103
+ lineRange: parseLineRange(parseAttr(attrs, 'lines')),
104
+ });
105
+ }
106
+
107
+ return findings;
108
+ }
109
+
110
+ export function parseReviewResult(result: string, mode: 'single-pass' | 'two-pass'): AIReviewResult {
111
+ result = result.trim();
112
+
113
+ const overview = result.match(overviewTagRegex)?.[1]?.trim() ?? '';
114
+
115
+ const focusAreas: AIReviewFocusArea[] = [];
116
+ let areaIndex = 0;
117
+
118
+ for (const match of result.matchAll(areaTagRegex)) {
119
+ const attrs = match[1];
120
+ const inner = match[2];
121
+ areaIndex++;
122
+
123
+ const id = `area-${areaIndex}`;
124
+ const filesAttr = parseAttr(attrs, 'files');
125
+
126
+ const findingsBlock = inner.match(findingsBlockRegex)?.[1];
127
+ const findings = findingsBlock ? parseFindings(findingsBlock, id) : undefined;
128
+
129
+ focusAreas.push({
130
+ id: id,
131
+ label: inner.match(labelTagRegex)?.[1]?.trim() ?? 'Untitled area',
132
+ rationale: inner.match(rationaleTagRegex)?.[1]?.trim() ?? '',
133
+ severity: parseSeverity(parseAttr(attrs, 'severity')),
134
+ files: filesAttr?.split(',').map(f => f.trim()) ?? [],
135
+ findings: findings,
136
+ });
137
+ }
138
+
139
+ return { overview: overview, focusAreas: focusAreas, mode: mode };
140
+ }
141
+
142
+ export function parseReviewDetailResult(result: string, focusAreaId: string): AIReviewDetailResult {
143
+ result = result.trim();
144
+
145
+ const findingsBlock = result.match(findingsBlockRegex)?.[1] ?? result;
146
+ const findings = parseFindings(findingsBlock, focusAreaId);
147
+
148
+ return { findings: findings };
149
+ }
@@ -52,7 +52,9 @@ type DiffTemplateType =
52
52
  | 'generate-create-cloudPatch'
53
53
  | 'generate-create-codeSuggestion'
54
54
  | 'generate-create-pullRequest'
55
- | 'explain-changes';
55
+ | 'explain-changes'
56
+ | 'review-changes'
57
+ | 'review-detail';
56
58
 
57
59
  /** File type base scores (0-100, higher = more important) */
58
60
  const fileTypeScores: Record<string, number> = {
@@ -8,6 +8,7 @@ import { debug } from '../../../../utils/decorators/log.js';
8
8
  import { getScopedLogger } from '../../../../utils/logger.scoped.js';
9
9
  import type { PagedResult, PagingOptions } from '../../../../utils/paging.js';
10
10
  import { emptyPagedResult } from '../../../../utils/paging.js';
11
+ import { getSettledValue } from '../../../../utils/promise.js';
11
12
  import type { CacheController } from '../../../../utils/promiseCache.js';
12
13
  import { toTokenInfo } from '../../api/tokenUtils.js';
13
14
  import { HeadType } from '../../context.js';
@@ -206,6 +207,9 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
206
207
  ): Promise<BranchContributionsOverview | undefined> {
207
208
  const scope = getScopedLogger();
208
209
 
210
+ // No outer cache here — GitHub provider has no stored-target safety net (no Tier 1
211
+ // equivalent), so the result is purely PR-resolution-dependent and a `ref`-keyed cache
212
+ // would serve stale data when the PR is later retargeted.
209
213
  try {
210
214
  let mergeTarget: string | undefined;
211
215
 
@@ -223,11 +227,18 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
223
227
  const mergeBase = await this.provider.refs.getMergeBase(repoPath, ref, mergeTarget);
224
228
  if (mergeBase == null) return undefined;
225
229
 
226
- const result = await this.provider.contributors.getContributors(
227
- repoPath,
228
- createRevisionRange(mergeBase, ref, '..'),
229
- { stats: true },
230
- );
230
+ // Fetch the merge-base commit's committer date in parallel with contributors so consumers
231
+ // that just need the date for the scope window don't pay a separate round-trip.
232
+ // `allSettled` so a transient failure in one side doesn't drop the overview entirely.
233
+ const [contributorsSettled, mergeBaseCommitSettled] = await Promise.allSettled([
234
+ this.provider.contributors.getContributors(repoPath, createRevisionRange(mergeBase, ref, '..'), {
235
+ stats: true,
236
+ }),
237
+ this.provider.commits.getCommit(repoPath, mergeBase),
238
+ ]);
239
+
240
+ const result = getSettledValue(contributorsSettled) ?? { contributors: [] };
241
+ const mergeBaseCommit = getSettledValue(mergeBaseCommitSettled);
231
242
 
232
243
  sortContributors(result.contributors, { orderBy: 'score:desc' });
233
244
 
@@ -265,6 +276,7 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
265
276
  branch: ref,
266
277
  mergeTarget: mergeTarget,
267
278
  mergeBase: mergeBase,
279
+ mergeBaseDate: mergeBaseCommit?.committer?.date ?? mergeBaseCommit?.author?.date,
268
280
 
269
281
  commits: totalCommits,
270
282
  files: totalFiles,
@@ -59,7 +59,7 @@ export class DiffGitSubProvider implements GitDiffSubProvider {
59
59
  repoPath: string,
60
60
  to?: string,
61
61
  _from?: string,
62
- _options?: { uris?: (string | Uri)[] },
62
+ _options?: { uris?: (string | Uri)[]; includeUntracked?: boolean },
63
63
  _cancellation?: AbortSignal,
64
64
  ): Promise<GitDiffShortStat | undefined> {
65
65
  // TODO@eamodio if there is no ref we can't return anything, until we can get at the change store from RemoteHub
@@ -79,7 +79,13 @@ export class DiffGitSubProvider implements GitDiffSubProvider {
79
79
  repoPath: string,
80
80
  ref1OrRange: string | GitRevisionRange,
81
81
  ref2?: string,
82
- _options?: { filters?: GitDiffFilter[]; path?: string; renameLimit?: number; similarityThreshold?: number },
82
+ _options?: {
83
+ filters?: GitDiffFilter[];
84
+ includeUntracked?: boolean;
85
+ path?: string;
86
+ renameLimit?: number;
87
+ similarityThreshold?: number;
88
+ },
83
89
  ): Promise<GitFile[] | undefined> {
84
90
  if (repoPath == null) return undefined;
85
91
 
@@ -265,7 +265,7 @@ function log<T extends (...arg: any[]) => any>(
265
265
  }
266
266
 
267
267
  if (result != null && isPromise(result)) {
268
- result.then(logResult, logError);
268
+ void result.then(logResult, logError).catch(() => {});
269
269
  } else {
270
270
  logResult(result);
271
271
  }
@@ -103,12 +103,14 @@ export function sequentialize<T extends (...args: any[]) => any>(
103
103
  }
104
104
 
105
105
  // Cleanup when done
106
- void promise.finally(() => {
107
- const s = this[prop];
108
- if (s?.promise === promise && s.waiting == null) {
109
- this[prop] = undefined;
110
- }
111
- });
106
+ void promise
107
+ .finally(() => {
108
+ const s = this[prop];
109
+ if (s?.promise === promise && s.waiting == null) {
110
+ this[prop] = undefined;
111
+ }
112
+ })
113
+ .catch(() => {});
112
114
 
113
115
  return promise;
114
116
  };
@@ -5,7 +5,7 @@ export function base64(data: string | Uint8Array): string {
5
5
  }
6
6
 
7
7
  export function fromBase64(s: string): Uint8Array {
8
- return Buffer.from(s, 'base64') as unknown as Uint8Array;
8
+ return Buffer.from(s, 'base64');
9
9
  }
10
10
 
11
11
  /**
@@ -373,8 +373,8 @@ export function runSpawn<T extends string | Buffer>(
373
373
  function getStdio<T>(
374
374
  encoding: BufferEncoding | 'buffer' | string,
375
375
  ): { stdout: T; stderr: T } | Promise<{ stdout: T; stderr: T }> {
376
- const stdout = Buffer.concat(stdoutBuffers as ReadonlyArray<Uint8Array>);
377
- const stderr = Buffer.concat(stderrBuffers as ReadonlyArray<Uint8Array>);
376
+ const stdout = Buffer.concat(stdoutBuffers);
377
+ const stderr = Buffer.concat(stderrBuffers);
378
378
  if (encoding === 'utf8' || encoding === 'binary') {
379
379
  return { stdout: stdout.toString(encoding) as T, stderr: stderr.toString(encoding) as T };
380
380
  }
@@ -176,23 +176,16 @@ export abstract class Formatter<Item = any, Options extends FormatOptions = Form
176
176
  options.tokenOptions = tokenOptions;
177
177
  }
178
178
 
179
- if (this._formatter == null) {
180
- this._formatter = new formatter(item, options);
181
- } else {
182
- this._formatter.reset(item, options);
183
- }
184
-
185
- return interpolateAsync(template, this._formatter);
179
+ // Always allocate a fresh formatter: `interpolateAsync` awaits between tokens, so a
180
+ // concurrent call that reset a shared instance would poison in-flight token resolution.
181
+ return interpolateAsync(template, new formatter(item, options));
186
182
  }
187
183
 
188
184
  static has<TOptions extends FormatOptions>(
189
185
  template: string,
190
186
  ...tokens: (keyof NonNullable<TOptions['tokenOptions']>)[]
191
187
  ): boolean {
192
- const token =
193
- tokens.length === 1
194
- ? (tokens[0] as string)
195
- : (`(${tokens.join('|')})` as keyof NonNullable<TOptions['tokenOptions']> as string);
188
+ const token = tokens.length === 1 ? (tokens[0] as string) : `(${tokens.join('|')})`;
196
189
 
197
190
  let regex = hasTokenRegexMap.get(token);
198
191
  if (regex == null) {
@@ -179,7 +179,7 @@ class FilterIterator<T, U extends T = T> implements IterableIterator<T | U> {
179
179
  }
180
180
 
181
181
  if (this.predicate === undefined ? result.value != null : this.predicate(result.value)) {
182
- return { done: false, value: result.value as T | U };
182
+ return { done: false, value: result.value };
183
183
  }
184
184
  }
185
185
  }