@renfeng/kiro-code-review 1.9.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.
- package/LICENSE +21 -0
- package/README.md +69 -0
- package/dist/api/gitlab-api.d.ts +122 -0
- package/dist/api/gitlab-api.d.ts.map +1 -0
- package/dist/api/gitlab-api.js +334 -0
- package/dist/api/gitlab-api.js.map +1 -0
- package/dist/bin/index.d.ts +11 -0
- package/dist/bin/index.d.ts.map +1 -0
- package/dist/bin/index.js +67 -0
- package/dist/bin/index.js.map +1 -0
- package/dist/config/glab-config.loader.d.ts +14 -0
- package/dist/config/glab-config.loader.d.ts.map +1 -0
- package/dist/config/glab-config.loader.js +87 -0
- package/dist/config/glab-config.loader.js.map +1 -0
- package/dist/errors/error-handler.d.ts +45 -0
- package/dist/errors/error-handler.d.ts.map +1 -0
- package/dist/errors/error-handler.js +134 -0
- package/dist/errors/error-handler.js.map +1 -0
- package/dist/logging/index.d.ts +2 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +2 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/logger.service.d.ts +34 -0
- package/dist/logging/logger.service.d.ts.map +1 -0
- package/dist/logging/logger.service.js +91 -0
- package/dist/logging/logger.service.js.map +1 -0
- package/dist/servers/general-server.d.ts +18 -0
- package/dist/servers/general-server.d.ts.map +1 -0
- package/dist/servers/general-server.js +73 -0
- package/dist/servers/general-server.js.map +1 -0
- package/dist/servers/git-server.d.ts +17 -0
- package/dist/servers/git-server.d.ts.map +1 -0
- package/dist/servers/git-server.js +74 -0
- package/dist/servers/git-server.js.map +1 -0
- package/dist/servers/gitlab-server.d.ts +18 -0
- package/dist/servers/gitlab-server.d.ts.map +1 -0
- package/dist/servers/gitlab-server.js +139 -0
- package/dist/servers/gitlab-server.js.map +1 -0
- package/dist/services/__tests__/batch-retro.service.test.d.ts +9 -0
- package/dist/services/__tests__/batch-retro.service.test.d.ts.map +1 -0
- package/dist/services/__tests__/batch-retro.service.test.js +235 -0
- package/dist/services/__tests__/batch-retro.service.test.js.map +1 -0
- package/dist/services/__tests__/general-save-diffs.test.d.ts +5 -0
- package/dist/services/__tests__/general-save-diffs.test.d.ts.map +1 -0
- package/dist/services/__tests__/general-save-diffs.test.js +99 -0
- package/dist/services/__tests__/general-save-diffs.test.js.map +1 -0
- package/dist/services/__tests__/git-diff-files.test.d.ts +8 -0
- package/dist/services/__tests__/git-diff-files.test.d.ts.map +1 -0
- package/dist/services/__tests__/git-diff-files.test.js +64 -0
- package/dist/services/__tests__/git-diff-files.test.js.map +1 -0
- package/dist/services/__tests__/review-tools.service.test.d.ts +8 -0
- package/dist/services/__tests__/review-tools.service.test.d.ts.map +1 -0
- package/dist/services/__tests__/review-tools.service.test.js +169 -0
- package/dist/services/__tests__/review-tools.service.test.js.map +1 -0
- package/dist/services/__tests__/review-utils.service.test.d.ts +8 -0
- package/dist/services/__tests__/review-utils.service.test.d.ts.map +1 -0
- package/dist/services/__tests__/review-utils.service.test.js +306 -0
- package/dist/services/__tests__/review-utils.service.test.js.map +1 -0
- package/dist/services/anchor-resolver.d.ts +29 -0
- package/dist/services/anchor-resolver.d.ts.map +1 -0
- package/dist/services/anchor-resolver.js +97 -0
- package/dist/services/anchor-resolver.js.map +1 -0
- package/dist/services/general.service.d.ts +42 -0
- package/dist/services/general.service.d.ts.map +1 -0
- package/dist/services/general.service.js +157 -0
- package/dist/services/general.service.js.map +1 -0
- package/dist/services/git-tools.service.d.ts +123 -0
- package/dist/services/git-tools.service.d.ts.map +1 -0
- package/dist/services/git-tools.service.js +231 -0
- package/dist/services/git-tools.service.js.map +1 -0
- package/dist/services/review-tools.service.d.ts +130 -0
- package/dist/services/review-tools.service.d.ts.map +1 -0
- package/dist/services/review-tools.service.js +902 -0
- package/dist/services/review-tools.service.js.map +1 -0
- package/dist/tools/general-tools.d.ts +9 -0
- package/dist/tools/general-tools.d.ts.map +1 -0
- package/dist/tools/general-tools.js +99 -0
- package/dist/tools/general-tools.js.map +1 -0
- package/dist/tools/git-tools.d.ts +8 -0
- package/dist/tools/git-tools.d.ts.map +1 -0
- package/dist/tools/git-tools.js +175 -0
- package/dist/tools/git-tools.js.map +1 -0
- package/dist/tools/gitlab-tools.d.ts +8 -0
- package/dist/tools/gitlab-tools.d.ts.map +1 -0
- package/dist/tools/gitlab-tools.js +348 -0
- package/dist/tools/gitlab-tools.js.map +1 -0
- package/dist/types.d.ts +294 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for ReviewService anchor resolution.
|
|
3
|
+
*
|
|
4
|
+
* These tests mock GitToolsService.runGit to supply synthetic diffs,
|
|
5
|
+
* then verify that resolveAnchors correctly maps anchors to line numbers.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
8
|
+
import { writeFileSync, readFileSync, mkdtempSync, rmSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { tmpdir } from 'os';
|
|
11
|
+
import { GeneralService } from '../general.service.js';
|
|
12
|
+
import { GitToolsService } from '../git-tools.service.js';
|
|
13
|
+
import { Logger, LogLevel } from '../../logging/logger.service.js';
|
|
14
|
+
describe('ReviewService.resolveAnchors', () => {
|
|
15
|
+
let service;
|
|
16
|
+
let gitTools;
|
|
17
|
+
let logger;
|
|
18
|
+
let tmpDir;
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
logger = new Logger('test', LogLevel.ERROR);
|
|
21
|
+
gitTools = new GitToolsService(logger);
|
|
22
|
+
service = new GeneralService(logger, gitTools);
|
|
23
|
+
tmpDir = mkdtempSync(join(tmpdir(), 'resolve-anchors-'));
|
|
24
|
+
});
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
27
|
+
vi.restoreAllMocks();
|
|
28
|
+
});
|
|
29
|
+
function writeFindingsFile(findings) {
|
|
30
|
+
const path = join(tmpDir, 'findings.json');
|
|
31
|
+
writeFileSync(path, JSON.stringify(findings, null, 2));
|
|
32
|
+
return path;
|
|
33
|
+
}
|
|
34
|
+
function readFindingsFile(path) {
|
|
35
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
36
|
+
}
|
|
37
|
+
it('resolves a simple added-line anchor', async () => {
|
|
38
|
+
const diff = [
|
|
39
|
+
'diff --git a/file.ts b/file.ts',
|
|
40
|
+
'index abc1234..def5678 100644',
|
|
41
|
+
'--- a/file.ts',
|
|
42
|
+
'+++ b/file.ts',
|
|
43
|
+
'@@ -1,3 +1,4 @@',
|
|
44
|
+
' line one',
|
|
45
|
+
'+const added = true;',
|
|
46
|
+
' line two',
|
|
47
|
+
' line three',
|
|
48
|
+
].join('\n');
|
|
49
|
+
vi.spyOn(gitTools, 'runGit').mockResolvedValue(diff);
|
|
50
|
+
const findingsPath = writeFindingsFile({
|
|
51
|
+
comments: [{
|
|
52
|
+
file: 'file.ts',
|
|
53
|
+
hunk: null,
|
|
54
|
+
anchor: 'const added = true;',
|
|
55
|
+
blocking: true,
|
|
56
|
+
note: 'test finding',
|
|
57
|
+
}],
|
|
58
|
+
summary: 'test',
|
|
59
|
+
verdict: 'comment_only',
|
|
60
|
+
});
|
|
61
|
+
const result = await service.resolveAnchors({
|
|
62
|
+
repoDir: '/fake/repo',
|
|
63
|
+
mergeBase: 'abc123',
|
|
64
|
+
findingsPath,
|
|
65
|
+
});
|
|
66
|
+
expect(result.resolved).toBe(1);
|
|
67
|
+
expect(result.fallbackToGeneral).toBe(0);
|
|
68
|
+
const findings = readFindingsFile(findingsPath);
|
|
69
|
+
expect(findings.comments[0].changed).toBe('new');
|
|
70
|
+
expect(findings.comments[0].line).toBe(2);
|
|
71
|
+
});
|
|
72
|
+
it('resolves anchor with trailing backslash (line continuation)', async () => {
|
|
73
|
+
// Reproduces the bug from MR !19 on ai-code-review-kirospace:
|
|
74
|
+
// The anchor omits the trailing " \" but the actual diff line has it.
|
|
75
|
+
const diff = [
|
|
76
|
+
'diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml',
|
|
77
|
+
'index 0000000..1111111 100644',
|
|
78
|
+
'--- /dev/null',
|
|
79
|
+
'+++ b/.gitlab-ci.yml',
|
|
80
|
+
'@@ -0,0 +1,10 @@',
|
|
81
|
+
'+ - |',
|
|
82
|
+
'+ echo "Looking for build artifact..."',
|
|
83
|
+
'+ PIPELINES=$(curl -sf --header "JOB-TOKEN: ${CI_JOB_TOKEN}" \\',
|
|
84
|
+
'+ "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/pipelines")',
|
|
85
|
+
'+ BUILD_JOB_ID=""',
|
|
86
|
+
].join('\n');
|
|
87
|
+
vi.spyOn(gitTools, 'runGit').mockResolvedValue(diff);
|
|
88
|
+
const findingsPath = writeFindingsFile({
|
|
89
|
+
comments: [{
|
|
90
|
+
file: '.gitlab-ci.yml',
|
|
91
|
+
hunk: null,
|
|
92
|
+
anchor: 'PIPELINES=$(curl -sf --header "JOB-TOKEN: ${CI_JOB_TOKEN}"',
|
|
93
|
+
blocking: true,
|
|
94
|
+
note: 'Silent curl failure',
|
|
95
|
+
}],
|
|
96
|
+
summary: 'test',
|
|
97
|
+
verdict: 'request_changes',
|
|
98
|
+
});
|
|
99
|
+
const result = await service.resolveAnchors({
|
|
100
|
+
repoDir: '/fake/repo',
|
|
101
|
+
mergeBase: 'abc123',
|
|
102
|
+
findingsPath,
|
|
103
|
+
});
|
|
104
|
+
expect(result.resolved).toBe(1);
|
|
105
|
+
expect(result.fallbackToGeneral).toBe(0);
|
|
106
|
+
const findings = readFindingsFile(findingsPath);
|
|
107
|
+
expect(findings.comments[0].changed).toBe('new');
|
|
108
|
+
expect(findings.comments[0].line).toBe(3);
|
|
109
|
+
});
|
|
110
|
+
it('resolves anchor on a deleted line with OLD: prefix', async () => {
|
|
111
|
+
const diff = [
|
|
112
|
+
'diff --git a/config.ts b/config.ts',
|
|
113
|
+
'index abc1234..def5678 100644',
|
|
114
|
+
'--- a/config.ts',
|
|
115
|
+
'+++ b/config.ts',
|
|
116
|
+
'@@ -5,4 +5,3 @@',
|
|
117
|
+
' keep this',
|
|
118
|
+
'-const removed = "old";',
|
|
119
|
+
' keep that',
|
|
120
|
+
].join('\n');
|
|
121
|
+
vi.spyOn(gitTools, 'runGit').mockResolvedValue(diff);
|
|
122
|
+
const findingsPath = writeFindingsFile({
|
|
123
|
+
comments: [{
|
|
124
|
+
file: 'config.ts',
|
|
125
|
+
hunk: null,
|
|
126
|
+
anchor: 'OLD:const removed = "old";',
|
|
127
|
+
blocking: false,
|
|
128
|
+
note: 'deleted line',
|
|
129
|
+
}],
|
|
130
|
+
summary: 'test',
|
|
131
|
+
verdict: 'comment_only',
|
|
132
|
+
});
|
|
133
|
+
const result = await service.resolveAnchors({
|
|
134
|
+
repoDir: '/fake/repo',
|
|
135
|
+
mergeBase: 'abc123',
|
|
136
|
+
findingsPath,
|
|
137
|
+
});
|
|
138
|
+
expect(result.resolved).toBe(1);
|
|
139
|
+
const findings = readFindingsFile(findingsPath);
|
|
140
|
+
expect(findings.comments[0].changed).toBe('old');
|
|
141
|
+
expect(findings.comments[0].old_line).toBe(6);
|
|
142
|
+
});
|
|
143
|
+
it('falls back to general when file has no diff', async () => {
|
|
144
|
+
vi.spyOn(gitTools, 'runGit').mockRejectedValue(new Error('no diff'));
|
|
145
|
+
const findingsPath = writeFindingsFile({
|
|
146
|
+
comments: [{
|
|
147
|
+
file: 'missing.ts',
|
|
148
|
+
hunk: null,
|
|
149
|
+
anchor: 'some content',
|
|
150
|
+
blocking: true,
|
|
151
|
+
note: 'no diff available',
|
|
152
|
+
}],
|
|
153
|
+
summary: 'test',
|
|
154
|
+
verdict: 'comment_only',
|
|
155
|
+
});
|
|
156
|
+
const result = await service.resolveAnchors({
|
|
157
|
+
repoDir: '/fake/repo',
|
|
158
|
+
mergeBase: 'abc123',
|
|
159
|
+
findingsPath,
|
|
160
|
+
});
|
|
161
|
+
expect(result.fallbackToGeneral).toBe(1);
|
|
162
|
+
const findings = readFindingsFile(findingsPath);
|
|
163
|
+
expect(findings.comments[0].changed).toBe('general');
|
|
164
|
+
});
|
|
165
|
+
it('skips comments already marked as general', async () => {
|
|
166
|
+
vi.spyOn(gitTools, 'runGit');
|
|
167
|
+
const findingsPath = writeFindingsFile({
|
|
168
|
+
comments: [{
|
|
169
|
+
file: 'file.ts',
|
|
170
|
+
hunk: null,
|
|
171
|
+
anchor: 'something',
|
|
172
|
+
changed: 'general',
|
|
173
|
+
blocking: true,
|
|
174
|
+
note: 'already general',
|
|
175
|
+
}],
|
|
176
|
+
summary: 'test',
|
|
177
|
+
verdict: 'comment_only',
|
|
178
|
+
});
|
|
179
|
+
const result = await service.resolveAnchors({
|
|
180
|
+
repoDir: '/fake/repo',
|
|
181
|
+
mergeBase: 'abc123',
|
|
182
|
+
findingsPath,
|
|
183
|
+
});
|
|
184
|
+
expect(result.resolved).toBe(0);
|
|
185
|
+
expect(result.fallbackToGeneral).toBe(0);
|
|
186
|
+
expect(gitTools.runGit).not.toHaveBeenCalled();
|
|
187
|
+
});
|
|
188
|
+
it('resolves anchor using hunk header when provided', async () => {
|
|
189
|
+
const diff = [
|
|
190
|
+
'diff --git a/app.ts b/app.ts',
|
|
191
|
+
'index abc..def 100644',
|
|
192
|
+
'--- a/app.ts',
|
|
193
|
+
'+++ b/app.ts',
|
|
194
|
+
'@@ -1,3 +1,4 @@',
|
|
195
|
+
' first section',
|
|
196
|
+
'+const x = 1;',
|
|
197
|
+
' end first',
|
|
198
|
+
'@@ -10,3 +11,4 @@',
|
|
199
|
+
' second section',
|
|
200
|
+
'+const x = 1;',
|
|
201
|
+
' end second',
|
|
202
|
+
].join('\n');
|
|
203
|
+
vi.spyOn(gitTools, 'runGit').mockResolvedValue(diff);
|
|
204
|
+
const findingsPath = writeFindingsFile({
|
|
205
|
+
comments: [{
|
|
206
|
+
file: 'app.ts',
|
|
207
|
+
hunk: '@@ -10,3 +11,4 @@',
|
|
208
|
+
anchor: 'const x = 1;',
|
|
209
|
+
blocking: true,
|
|
210
|
+
note: 'targets second hunk',
|
|
211
|
+
}],
|
|
212
|
+
summary: 'test',
|
|
213
|
+
verdict: 'comment_only',
|
|
214
|
+
});
|
|
215
|
+
const result = await service.resolveAnchors({
|
|
216
|
+
repoDir: '/fake/repo',
|
|
217
|
+
mergeBase: 'abc123',
|
|
218
|
+
findingsPath,
|
|
219
|
+
});
|
|
220
|
+
expect(result.resolved).toBe(1);
|
|
221
|
+
const findings = readFindingsFile(findingsPath);
|
|
222
|
+
expect(findings.comments[0].line).toBe(12);
|
|
223
|
+
});
|
|
224
|
+
it('falls back to all hunks when anchor is in a different hunk than specified', async () => {
|
|
225
|
+
// Reproduces the bug from MR !25863 on my-project:
|
|
226
|
+
// The LLM associated the anchor with hunk @@ -1,4 +1,7 @@ but the anchor
|
|
227
|
+
// line was actually in hunk @@ -10,5 +13,13 @@. The old code restricted
|
|
228
|
+
// search to the matched hunk and fell back to general when not found.
|
|
229
|
+
const diff = [
|
|
230
|
+
'diff --git a/app.routes.ts b/app.routes.ts',
|
|
231
|
+
'index abc..def 100644',
|
|
232
|
+
'--- a/app.routes.ts',
|
|
233
|
+
'+++ b/app.routes.ts',
|
|
234
|
+
'@@ -1,4 +1,7 @@',
|
|
235
|
+
'+import { inject } from \'@angular/core\';',
|
|
236
|
+
' import { Route } from \'@angular/router\';',
|
|
237
|
+
'+import { SmeTransferFormService } from \'./services/sme-transfer-form.service\';',
|
|
238
|
+
'+import { SmeTransferFormRouteEnum } from \'./enum/sme-transfer-form-page.enum\';',
|
|
239
|
+
' import { ContractSelectionComponent } from \'./contract-selection/contract-selection.component\';',
|
|
240
|
+
'@@ -10,5 +13,13 @@',
|
|
241
|
+
' loadComponent: () =>',
|
|
242
|
+
' import(\'./customer-information-form/customer-information-form.component\').then((m) => m.CustomerInformationFormComponent),',
|
|
243
|
+
'+ canActivate: [() => inject(SmeTransferFormService).canActivate()],',
|
|
244
|
+
'+ },',
|
|
245
|
+
'+ {',
|
|
246
|
+
'+ path: SmeTransferFormRouteEnum.NO_ACCESS,',
|
|
247
|
+
].join('\n');
|
|
248
|
+
vi.spyOn(gitTools, 'runGit').mockResolvedValue(diff);
|
|
249
|
+
const findingsPath = writeFindingsFile({
|
|
250
|
+
comments: [{
|
|
251
|
+
file: 'app.routes.ts',
|
|
252
|
+
hunk: '@@ -1,4 +1,7 @@', // Wrong hunk — anchor is in the second hunk
|
|
253
|
+
anchor: 'canActivate: [() => inject(SmeTransferFormService).canActivate()],',
|
|
254
|
+
blocking: true,
|
|
255
|
+
note: 'guard only protects one route',
|
|
256
|
+
}],
|
|
257
|
+
summary: 'test',
|
|
258
|
+
verdict: 'request_changes',
|
|
259
|
+
});
|
|
260
|
+
const result = await service.resolveAnchors({
|
|
261
|
+
repoDir: '/fake/repo',
|
|
262
|
+
mergeBase: 'abc123',
|
|
263
|
+
findingsPath,
|
|
264
|
+
});
|
|
265
|
+
expect(result.resolved).toBe(1);
|
|
266
|
+
expect(result.fallbackToGeneral).toBe(0);
|
|
267
|
+
const findings = readFindingsFile(findingsPath);
|
|
268
|
+
expect(findings.comments[0].changed).toBe('new');
|
|
269
|
+
expect(findings.comments[0].line).toBe(15);
|
|
270
|
+
});
|
|
271
|
+
it('resolves context line anchor (unchanged line)', async () => {
|
|
272
|
+
const diff = [
|
|
273
|
+
'diff --git a/file.ts b/file.ts',
|
|
274
|
+
'index abc..def 100644',
|
|
275
|
+
'--- a/file.ts',
|
|
276
|
+
'+++ b/file.ts',
|
|
277
|
+
'@@ -5,4 +5,5 @@',
|
|
278
|
+
' const existing = true;',
|
|
279
|
+
'+const added = false;',
|
|
280
|
+
' const another = 42;',
|
|
281
|
+
].join('\n');
|
|
282
|
+
vi.spyOn(gitTools, 'runGit').mockResolvedValue(diff);
|
|
283
|
+
const findingsPath = writeFindingsFile({
|
|
284
|
+
comments: [{
|
|
285
|
+
file: 'file.ts',
|
|
286
|
+
hunk: null,
|
|
287
|
+
anchor: 'const existing = true;',
|
|
288
|
+
blocking: false,
|
|
289
|
+
note: 'context line',
|
|
290
|
+
}],
|
|
291
|
+
summary: 'test',
|
|
292
|
+
verdict: 'comment_only',
|
|
293
|
+
});
|
|
294
|
+
const result = await service.resolveAnchors({
|
|
295
|
+
repoDir: '/fake/repo',
|
|
296
|
+
mergeBase: 'abc123',
|
|
297
|
+
findingsPath,
|
|
298
|
+
});
|
|
299
|
+
expect(result.resolved).toBe(1);
|
|
300
|
+
const findings = readFindingsFile(findingsPath);
|
|
301
|
+
expect(findings.comments[0].changed).toBe('context');
|
|
302
|
+
expect(findings.comments[0].line).toBe(5);
|
|
303
|
+
expect(findings.comments[0].old_line).toBe(5);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
//# sourceMappingURL=review-utils.service.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-utils.service.test.js","sourceRoot":"","sources":["../../../src/services/__tests__/review-utils.service.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anchor Resolver
|
|
3
|
+
*
|
|
4
|
+
* Shared anchor resolution logic extracted from GeneralService.
|
|
5
|
+
* Maps LLM-produced text anchors to diff line numbers.
|
|
6
|
+
* Used by both GeneralService.resolveAnchors (file-based pipeline)
|
|
7
|
+
* and ReviewToolsService.postDraftNotes (inline resolution).
|
|
8
|
+
*/
|
|
9
|
+
import type { AnchorFinding } from '../types.js';
|
|
10
|
+
export interface ParsedHunk {
|
|
11
|
+
header: string;
|
|
12
|
+
oldStart: number;
|
|
13
|
+
newStart: number;
|
|
14
|
+
lines: string[];
|
|
15
|
+
}
|
|
16
|
+
export interface AnchorResult {
|
|
17
|
+
changed: 'new' | 'old' | 'context';
|
|
18
|
+
line?: number | null;
|
|
19
|
+
old_line?: number | null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Resolve an anchor to a line number within a raw diff string.
|
|
23
|
+
* Returns null if the anchor cannot be matched.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveAnchorInDiff(diff: string, comment: AnchorFinding): AnchorResult | null;
|
|
26
|
+
export declare function parseHunks(diff: string): ParsedHunk[];
|
|
27
|
+
export declare function findMatchingHunk(hunks: ParsedHunk[], hunkHeader: string): ParsedHunk | null;
|
|
28
|
+
export declare function findAnchorInHunk(hunk: ParsedHunk, anchorText: string, isOldAnchor: boolean): AnchorResult | null;
|
|
29
|
+
//# sourceMappingURL=anchor-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anchor-resolver.d.ts","sourceRoot":"","sources":["../../src/services/anchor-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,KAAK,GAAG,KAAK,GAAG,SAAS,CAAC;IACnC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GACnC,YAAY,GAAG,IAAI,CAqBrB;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,CAsBrD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAM3F;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,GACzD,YAAY,GAAG,IAAI,CAyBrB"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anchor Resolver
|
|
3
|
+
*
|
|
4
|
+
* Shared anchor resolution logic extracted from GeneralService.
|
|
5
|
+
* Maps LLM-produced text anchors to diff line numbers.
|
|
6
|
+
* Used by both GeneralService.resolveAnchors (file-based pipeline)
|
|
7
|
+
* and ReviewToolsService.postDraftNotes (inline resolution).
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Resolve an anchor to a line number within a raw diff string.
|
|
11
|
+
* Returns null if the anchor cannot be matched.
|
|
12
|
+
*/
|
|
13
|
+
export function resolveAnchorInDiff(diff, comment) {
|
|
14
|
+
const rawAnchor = comment.anchor;
|
|
15
|
+
const isOldAnchor = rawAnchor.startsWith('OLD:');
|
|
16
|
+
const anchorText = (isOldAnchor ? rawAnchor.slice(4) : rawAnchor).trim().replace(/\s*\\$/, '');
|
|
17
|
+
const hunks = parseHunks(diff);
|
|
18
|
+
const targetHunk = comment.hunk ? findMatchingHunk(hunks, comment.hunk) : null;
|
|
19
|
+
// Try the matched hunk first, then fall back to all hunks.
|
|
20
|
+
// The LLM sometimes associates the correct anchor with the wrong hunk header.
|
|
21
|
+
if (targetHunk) {
|
|
22
|
+
const result = findAnchorInHunk(targetHunk, anchorText, isOldAnchor);
|
|
23
|
+
if (result)
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
for (const hunk of hunks) {
|
|
27
|
+
if (hunk === targetHunk)
|
|
28
|
+
continue;
|
|
29
|
+
const result = findAnchorInHunk(hunk, anchorText, isOldAnchor);
|
|
30
|
+
if (result)
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
export function parseHunks(diff) {
|
|
36
|
+
const lines = diff.split('\n');
|
|
37
|
+
const hunks = [];
|
|
38
|
+
let current = null;
|
|
39
|
+
for (const line of lines) {
|
|
40
|
+
const hunkMatch = line.match(/^@@\s+-(\d+)(?:,\d+)?\s+\+(\d+)(?:,\d+)?\s+@@/);
|
|
41
|
+
if (hunkMatch) {
|
|
42
|
+
current = {
|
|
43
|
+
header: line,
|
|
44
|
+
oldStart: parseInt(hunkMatch[1], 10),
|
|
45
|
+
newStart: parseInt(hunkMatch[2], 10),
|
|
46
|
+
lines: [],
|
|
47
|
+
};
|
|
48
|
+
hunks.push(current);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (current && (line.startsWith('+') || line.startsWith('-') || line.startsWith(' '))) {
|
|
52
|
+
current.lines.push(line);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return hunks;
|
|
56
|
+
}
|
|
57
|
+
export function findMatchingHunk(hunks, hunkHeader) {
|
|
58
|
+
const m = hunkHeader.match(/^@@\s+-(\d+)(?:,\d+)?\s+\+(\d+)(?:,\d+)?\s+@@/);
|
|
59
|
+
if (!m)
|
|
60
|
+
return null;
|
|
61
|
+
const targetOld = parseInt(m[1], 10);
|
|
62
|
+
const targetNew = parseInt(m[2], 10);
|
|
63
|
+
return hunks.find(h => h.oldStart === targetOld && h.newStart === targetNew) || null;
|
|
64
|
+
}
|
|
65
|
+
export function findAnchorInHunk(hunk, anchorText, isOldAnchor) {
|
|
66
|
+
const passes = isOldAnchor
|
|
67
|
+
? [{ prefixes: ['-'] }]
|
|
68
|
+
: [{ prefixes: ['+'] }, { prefixes: [' '] }];
|
|
69
|
+
for (const pass of passes) {
|
|
70
|
+
let oldLine = hunk.oldStart;
|
|
71
|
+
let newLine = hunk.newStart;
|
|
72
|
+
for (const line of hunk.lines) {
|
|
73
|
+
const prefix = line[0];
|
|
74
|
+
const content = line.slice(1).trim().replace(/\s*\\$/, '');
|
|
75
|
+
if (pass.prefixes.includes(prefix) && content === anchorText) {
|
|
76
|
+
if (prefix === '+')
|
|
77
|
+
return { changed: 'new', line: newLine };
|
|
78
|
+
if (prefix === '-')
|
|
79
|
+
return { changed: 'old', old_line: oldLine };
|
|
80
|
+
if (prefix === ' ')
|
|
81
|
+
return { changed: 'context', line: newLine, old_line: oldLine };
|
|
82
|
+
}
|
|
83
|
+
if (prefix === '+') {
|
|
84
|
+
newLine++;
|
|
85
|
+
}
|
|
86
|
+
else if (prefix === '-') {
|
|
87
|
+
oldLine++;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
oldLine++;
|
|
91
|
+
newLine++;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=anchor-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anchor-resolver.js","sourceRoot":"","sources":["../../src/services/anchor-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAiBH;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAAY,EAAE,OAAsB;IAEpC,MAAM,SAAS,GAAG,OAAO,CAAC,MAAO,CAAC;IAClC,MAAM,WAAW,GAAG,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IACjD,MAAM,UAAU,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAE/F,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAE/E,2DAA2D;IAC3D,8EAA8E;IAC9E,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,gBAAgB,CAAC,UAAU,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;QACrE,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;IAC5B,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,KAAK,UAAU;YAAE,SAAS;QAClC,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAAI,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;QAC/D,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;IAC5B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,KAAK,GAAiB,EAAE,CAAC;IAC/B,IAAI,OAAO,GAAsB,IAAI,CAAC;IAEtC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAC;QAC9E,IAAI,SAAS,EAAE,CAAC;YACd,OAAO,GAAG;gBACR,MAAM,EAAE,IAAI;gBACZ,QAAQ,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;gBACpC,QAAQ,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;gBACpC,KAAK,EAAE,EAAE;aACV,CAAC;YACF,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACpB,SAAS;QACX,CAAC;QACD,IAAI,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YACtF,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAmB,EAAE,UAAkB;IACtE,MAAM,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAC;IAC5E,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,MAAM,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACrC,MAAM,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACrC,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,IAAI,IAAI,CAAC;AACvF,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,IAAgB,EAAE,UAAkB,EAAE,WAAoB;IAE1D,MAAM,MAAM,GAAkC,WAAW;QACvD,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAE/C,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;QAC1B,IAAI,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC5B,IAAI,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC;QAE5B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YACvB,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YAE3D,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,OAAO,KAAK,UAAU,EAAE,CAAC;gBAC7D,IAAI,MAAM,KAAK,GAAG;oBAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;gBAC7D,IAAI,MAAM,KAAK,GAAG;oBAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;gBACjE,IAAI,MAAM,KAAK,GAAG;oBAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;YACtF,CAAC;YAED,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;gBAAC,OAAO,EAAE,CAAC;YAAC,CAAC;iBAC7B,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;gBAAC,OAAO,EAAE,CAAC;YAAC,CAAC;iBAClC,CAAC;gBAAC,OAAO,EAAE,CAAC;gBAAC,OAAO,EAAE,CAAC;YAAC,CAAC;QAChC,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review Service
|
|
3
|
+
*
|
|
4
|
+
* Platform-neutral review utilities — findings normalization, anchor resolution,
|
|
5
|
+
* and draft staleness checks.
|
|
6
|
+
* Uses git CLI (via GitToolsService) for diff/show operations. No GitLab API.
|
|
7
|
+
*/
|
|
8
|
+
import { Logger } from '../logging/logger.service.js';
|
|
9
|
+
import type { ResolveAnchorsParams, CheckDraftStalenessParams, DraftStalenessResult, WriteFindingsParams, SavePerFileDiffsParams } from '../types.js';
|
|
10
|
+
import { GitToolsService } from './git-tools.service.js';
|
|
11
|
+
export declare class GeneralService {
|
|
12
|
+
private logger;
|
|
13
|
+
private gitTools;
|
|
14
|
+
constructor(logger: Logger, gitTools: GitToolsService);
|
|
15
|
+
private normalizeFindingsNotes;
|
|
16
|
+
writeFindings(params: WriteFindingsParams): Promise<{
|
|
17
|
+
path: string;
|
|
18
|
+
commentCount: number;
|
|
19
|
+
normalized: number;
|
|
20
|
+
}>;
|
|
21
|
+
resolveAnchors(params: ResolveAnchorsParams): Promise<{
|
|
22
|
+
resolved: number;
|
|
23
|
+
fallbackToGeneral: number;
|
|
24
|
+
total: number;
|
|
25
|
+
}>;
|
|
26
|
+
checkDraftStaleness(params: CheckDraftStalenessParams): Promise<{
|
|
27
|
+
results: DraftStalenessResult[];
|
|
28
|
+
staleCount: number;
|
|
29
|
+
freshCount: number;
|
|
30
|
+
}>;
|
|
31
|
+
savePerFileDiffs(params: SavePerFileDiffsParams): Promise<{
|
|
32
|
+
files: string[];
|
|
33
|
+
totalFiles: number;
|
|
34
|
+
errors: Array<{
|
|
35
|
+
file: string;
|
|
36
|
+
error: string;
|
|
37
|
+
}>;
|
|
38
|
+
}>;
|
|
39
|
+
private getFileDiff;
|
|
40
|
+
private getFileLines;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=general.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"general.service.d.ts","sourceRoot":"","sources":["../../src/services/general.service.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAC;AACtD,OAAO,KAAK,EACV,oBAAoB,EACpB,yBAAyB,EAEzB,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,EACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGzD,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAkB;gBAEtB,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe;IAKrD,OAAO,CAAC,sBAAsB;IAaxB,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC;QACxD,IAAI,EAAE,MAAM,CAAC;QACb,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IAWI,cAAc,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC;QAC1D,QAAQ,EAAE,MAAM,CAAC;QACjB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IAwCI,mBAAmB,CAAC,MAAM,EAAE,yBAAyB,GAAG,OAAO,CAAC;QACpE,OAAO,EAAE,oBAAoB,EAAE,CAAC;QAChC,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IAiCI,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC;QAC9D,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,MAAM,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KAChD,CAAC;YAkCY,WAAW;YAaX,YAAY;CAc3B"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review Service
|
|
3
|
+
*
|
|
4
|
+
* Platform-neutral review utilities — findings normalization, anchor resolution,
|
|
5
|
+
* and draft staleness checks.
|
|
6
|
+
* Uses git CLI (via GitToolsService) for diff/show operations. No GitLab API.
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
9
|
+
import { join, dirname } from 'path';
|
|
10
|
+
import { resolveAnchorInDiff } from './anchor-resolver.js';
|
|
11
|
+
export class GeneralService {
|
|
12
|
+
logger;
|
|
13
|
+
gitTools;
|
|
14
|
+
constructor(logger, gitTools) {
|
|
15
|
+
this.logger = logger;
|
|
16
|
+
this.gitTools = gitTools;
|
|
17
|
+
}
|
|
18
|
+
normalizeFindingsNotes(findings) {
|
|
19
|
+
let normalized = 0;
|
|
20
|
+
for (const c of findings.comments) {
|
|
21
|
+
const raw = c;
|
|
22
|
+
if (!c.note && raw['comment']) {
|
|
23
|
+
c.note = raw['comment'];
|
|
24
|
+
normalized++;
|
|
25
|
+
}
|
|
26
|
+
delete raw['comment'];
|
|
27
|
+
}
|
|
28
|
+
return normalized;
|
|
29
|
+
}
|
|
30
|
+
async writeFindings(params) {
|
|
31
|
+
this.logger.info('Writing findings', { findingsPath: params.findingsPath });
|
|
32
|
+
const findings = params.findings;
|
|
33
|
+
const normalized = this.normalizeFindingsNotes(findings);
|
|
34
|
+
writeFileSync(params.findingsPath, JSON.stringify(findings, null, 2));
|
|
35
|
+
this.logger.info('Findings written', {
|
|
36
|
+
path: params.findingsPath, commentCount: findings.comments.length, normalized,
|
|
37
|
+
});
|
|
38
|
+
return { path: params.findingsPath, commentCount: findings.comments.length, normalized };
|
|
39
|
+
}
|
|
40
|
+
async resolveAnchors(params) {
|
|
41
|
+
this.logger.info('Resolving anchors', {
|
|
42
|
+
repoDir: params.repoDir, mergeBase: params.mergeBase, findingsPath: params.findingsPath,
|
|
43
|
+
});
|
|
44
|
+
const raw = readFileSync(params.findingsPath, 'utf-8');
|
|
45
|
+
const findings = JSON.parse(raw);
|
|
46
|
+
this.normalizeFindingsNotes(findings);
|
|
47
|
+
const diffCache = new Map();
|
|
48
|
+
let resolved = 0;
|
|
49
|
+
let fallbackToGeneral = 0;
|
|
50
|
+
for (const comment of findings.comments) {
|
|
51
|
+
if (!comment.file || !comment.anchor || comment.changed === 'general')
|
|
52
|
+
continue;
|
|
53
|
+
const fileDiff = await this.getFileDiff(params.repoDir, params.mergeBase, comment.file, diffCache);
|
|
54
|
+
if (!fileDiff) {
|
|
55
|
+
comment.changed = 'general';
|
|
56
|
+
fallbackToGeneral++;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const result = resolveAnchorInDiff(fileDiff, comment);
|
|
60
|
+
if (result) {
|
|
61
|
+
comment.changed = result.changed;
|
|
62
|
+
comment.line = result.line;
|
|
63
|
+
comment.old_line = result.old_line;
|
|
64
|
+
resolved++;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
comment.changed = 'general';
|
|
68
|
+
fallbackToGeneral++;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
writeFileSync(params.findingsPath, JSON.stringify(findings, null, 2));
|
|
72
|
+
this.logger.info('Anchor resolution complete', { resolved, fallbackToGeneral, total: findings.comments.length });
|
|
73
|
+
return { resolved, fallbackToGeneral, total: findings.comments.length };
|
|
74
|
+
}
|
|
75
|
+
async checkDraftStaleness(params) {
|
|
76
|
+
this.logger.info('Checking draft staleness', { repoDir: params.repoDir, draftCount: params.drafts.length });
|
|
77
|
+
const results = [];
|
|
78
|
+
const fileCache = new Map();
|
|
79
|
+
for (const draft of params.drafts) {
|
|
80
|
+
if (!draft.file || draft.line == null) {
|
|
81
|
+
results.push({ id: draft.id, stale: false, reason: 'general comment (no file/line)' });
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const lines = await this.getFileLines(params.repoDir, draft.file, fileCache);
|
|
85
|
+
if (!lines) {
|
|
86
|
+
results.push({ id: draft.id, stale: true, reason: `file no longer exists: ${draft.file}` });
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const lineIdx = draft.line - 1;
|
|
90
|
+
if (lineIdx < 0 || lineIdx >= lines.length) {
|
|
91
|
+
results.push({ id: draft.id, stale: true, reason: `line ${draft.line} out of range (file has ${lines.length} lines)` });
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
results.push({ id: draft.id, stale: false, reason: 'line exists in current file' });
|
|
95
|
+
}
|
|
96
|
+
const staleCount = results.filter(r => r.stale).length;
|
|
97
|
+
const freshCount = results.filter(r => !r.stale).length;
|
|
98
|
+
this.logger.info('Draft staleness check complete', { staleCount, freshCount });
|
|
99
|
+
return { results, staleCount, freshCount };
|
|
100
|
+
}
|
|
101
|
+
async savePerFileDiffs(params) {
|
|
102
|
+
this.logger.info('Saving per-file diffs', {
|
|
103
|
+
repoDir: params.repoDir, mergeBase: params.mergeBase, outputDir: params.outputDir,
|
|
104
|
+
});
|
|
105
|
+
const { diff: nameOnly } = await this.gitTools.diff({
|
|
106
|
+
repoDir: params.repoDir, base: params.mergeBase, head: 'HEAD', mode: 'name-only',
|
|
107
|
+
});
|
|
108
|
+
const files = nameOnly.trim().split('\n').filter(f => f.length > 0);
|
|
109
|
+
const saved = [];
|
|
110
|
+
const errors = [];
|
|
111
|
+
for (const file of files) {
|
|
112
|
+
try {
|
|
113
|
+
const { diff } = await this.gitTools.diff({
|
|
114
|
+
repoDir: params.repoDir, base: params.mergeBase, head: 'HEAD', mode: 'full', files: [file],
|
|
115
|
+
});
|
|
116
|
+
const outputPath = join(params.outputDir, `${file}.diff`);
|
|
117
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
118
|
+
writeFileSync(outputPath, diff);
|
|
119
|
+
saved.push(file);
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
123
|
+
this.logger.warn('Failed to save diff for file', { file, error: message });
|
|
124
|
+
errors.push({ file, error: message });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
this.logger.info('Per-file diffs saved', { saved: saved.length, errors: errors.length, outputDir: params.outputDir });
|
|
128
|
+
return { files: saved, totalFiles: saved.length, errors };
|
|
129
|
+
}
|
|
130
|
+
// --- Private helpers for git operations ---
|
|
131
|
+
async getFileDiff(repoDir, mergeBase, filePath, cache) {
|
|
132
|
+
if (cache.has(filePath))
|
|
133
|
+
return cache.get(filePath);
|
|
134
|
+
try {
|
|
135
|
+
const result = await this.gitTools.runGit(repoDir, ['diff', mergeBase, 'HEAD', '--', filePath]);
|
|
136
|
+
cache.set(filePath, result);
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async getFileLines(repoDir, filePath, cache) {
|
|
144
|
+
if (cache.has(filePath))
|
|
145
|
+
return cache.get(filePath);
|
|
146
|
+
try {
|
|
147
|
+
const content = await this.gitTools.runGit(repoDir, ['show', `HEAD:${filePath}`]);
|
|
148
|
+
const lines = content.split('\n');
|
|
149
|
+
cache.set(filePath, lines);
|
|
150
|
+
return lines;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=general.service.js.map
|