claude-teammate 0.1.97 → 0.1.99
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/package.json +1 -1
- package/src/dashboard/ui.html +1 -1
- package/src/forge/gitlab.js +194 -12
package/package.json
CHANGED
package/src/dashboard/ui.html
CHANGED
package/src/forge/gitlab.js
CHANGED
|
@@ -335,13 +335,64 @@ export function createGitLabClient(config) {
|
|
|
335
335
|
|
|
336
336
|
async createPullRequestReview(repoUrl, prNumber, body, suggestions) {
|
|
337
337
|
const repo = parseGitLabRepoUrl(repoUrl);
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
338
|
+
const normalizedSuggestions = Array.isArray(suggestions) ? suggestions.filter(Boolean) : [];
|
|
339
|
+
const fallbackSuggestions = [];
|
|
340
|
+
const discussionResults = [];
|
|
341
|
+
|
|
342
|
+
if (normalizedSuggestions.length > 0) {
|
|
343
|
+
const [mergeRequest, versions, changesPayload] = await Promise.all([
|
|
344
|
+
requestGitLabProject(config, repo, `/merge_requests/${prNumber}`),
|
|
345
|
+
requestGitLabProject(config, repo, `/merge_requests/${prNumber}/versions`),
|
|
346
|
+
requestGitLabProject(config, repo, `/merge_requests/${prNumber}/changes`)
|
|
347
|
+
]);
|
|
348
|
+
const diffRefs = selectLatestGitLabDiffRefs(mergeRequest, versions);
|
|
349
|
+
const positionIndex = buildGitLabDiffPositionIndex(changesPayload?.changes || []);
|
|
350
|
+
|
|
351
|
+
for (const suggestion of normalizedSuggestions) {
|
|
352
|
+
const position = diffRefs
|
|
353
|
+
? findGitLabSuggestionPosition(positionIndex, suggestion)
|
|
354
|
+
: null;
|
|
355
|
+
|
|
356
|
+
if (!position) {
|
|
357
|
+
fallbackSuggestions.push(suggestion);
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
try {
|
|
362
|
+
const payload = await requestGitLabProject(config, repo, `/merge_requests/${prNumber}/discussions`, {
|
|
363
|
+
method: "POST",
|
|
364
|
+
body: {
|
|
365
|
+
body: buildGitLabSuggestionDiscussionBody(suggestion),
|
|
366
|
+
position: {
|
|
367
|
+
position_type: "text",
|
|
368
|
+
base_sha: diffRefs.baseSha,
|
|
369
|
+
start_sha: diffRefs.startSha,
|
|
370
|
+
head_sha: diffRefs.headSha,
|
|
371
|
+
...position
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
discussionResults.push(payload);
|
|
376
|
+
} catch {
|
|
377
|
+
fallbackSuggestions.push(suggestion);
|
|
378
|
+
}
|
|
343
379
|
}
|
|
344
|
-
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const reviewBody = buildGitLabReviewBody(body, fallbackSuggestions);
|
|
383
|
+
const overviewNote = reviewBody
|
|
384
|
+
? await requestGitLabProject(config, repo, `/merge_requests/${prNumber}/notes`, {
|
|
385
|
+
method: "POST",
|
|
386
|
+
body: {
|
|
387
|
+
body: reviewBody
|
|
388
|
+
}
|
|
389
|
+
})
|
|
390
|
+
: null;
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
overviewNote,
|
|
394
|
+
discussions: discussionResults
|
|
395
|
+
};
|
|
345
396
|
},
|
|
346
397
|
|
|
347
398
|
async fetchPullRequestDiff(repoUrl, prNumber) {
|
|
@@ -401,17 +452,148 @@ function buildGitLabReviewBody(body, suggestions = []) {
|
|
|
401
452
|
if (Array.isArray(suggestions) && suggestions.length > 0) {
|
|
402
453
|
base.push("Suggestions:");
|
|
403
454
|
for (const suggestion of suggestions) {
|
|
404
|
-
|
|
405
|
-
const note = String(suggestion.body || "").trim();
|
|
406
|
-
const block = suggestion.committable && suggestion.suggestion
|
|
407
|
-
? `\n\n\`\`\`suggestion\n${suggestion.suggestion}\n\`\`\``
|
|
408
|
-
: "";
|
|
409
|
-
base.push(`- ${location || "general"}: ${note}${block}`);
|
|
455
|
+
base.push(buildGitLabFallbackSuggestionEntry(suggestion));
|
|
410
456
|
}
|
|
411
457
|
}
|
|
412
458
|
return base.filter(Boolean).join("\n\n").trim();
|
|
413
459
|
}
|
|
414
460
|
|
|
461
|
+
function buildGitLabFallbackSuggestionEntry(suggestion) {
|
|
462
|
+
const location = [suggestion?.file, suggestion?.line].filter(Boolean).join(":");
|
|
463
|
+
const note = String(suggestion?.body || "").trim();
|
|
464
|
+
const block = canCreateGitLabSuggestionBlock(suggestion)
|
|
465
|
+
? `\n\n\`\`\`\`suggestion:-0+0\n${String(suggestion.suggestion || "").trim()}\n\`\`\`\``
|
|
466
|
+
: "";
|
|
467
|
+
return `- ${location || "general"}: ${note}${block}`;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function buildGitLabSuggestionDiscussionBody(suggestion) {
|
|
471
|
+
const note = String(suggestion?.body || "").trim();
|
|
472
|
+
const block = canCreateGitLabSuggestionBlock(suggestion)
|
|
473
|
+
? `\n\n\`\`\`\`suggestion:-0+0\n${String(suggestion.suggestion || "").trim()}\n\`\`\`\``
|
|
474
|
+
: "";
|
|
475
|
+
return `${note}${block}`.trim();
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function canCreateGitLabSuggestionBlock(suggestion) {
|
|
479
|
+
return Boolean(
|
|
480
|
+
suggestion?.committable &&
|
|
481
|
+
String(suggestion?.suggestion || "").trim() &&
|
|
482
|
+
String(suggestion?.side || "RIGHT").trim().toUpperCase() === "RIGHT"
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function selectLatestGitLabDiffRefs(mergeRequest, versions) {
|
|
487
|
+
const candidates = Array.isArray(versions) ? versions : [];
|
|
488
|
+
const latestVersion = candidates
|
|
489
|
+
.filter((version) => version && (version.created_at || version.id != null))
|
|
490
|
+
.sort((left, right) => {
|
|
491
|
+
const leftDate = left.created_at ? Date.parse(left.created_at) : Number(left.id) || 0;
|
|
492
|
+
const rightDate = right.created_at ? Date.parse(right.created_at) : Number(right.id) || 0;
|
|
493
|
+
return rightDate - leftDate;
|
|
494
|
+
})[0];
|
|
495
|
+
|
|
496
|
+
const baseSha = latestVersion?.base_commit_sha || mergeRequest?.diff_refs?.base_sha;
|
|
497
|
+
const startSha = latestVersion?.start_commit_sha || mergeRequest?.diff_refs?.start_sha || baseSha;
|
|
498
|
+
const headSha = latestVersion?.head_commit_sha || mergeRequest?.diff_refs?.head_sha;
|
|
499
|
+
|
|
500
|
+
if (!baseSha || !startSha || !headSha) {
|
|
501
|
+
return null;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return { baseSha, startSha, headSha };
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function buildGitLabDiffPositionIndex(changes) {
|
|
508
|
+
return (Array.isArray(changes) ? changes : []).map((change) => ({
|
|
509
|
+
oldPath: String(change?.old_path || change?.new_path || ""),
|
|
510
|
+
newPath: String(change?.new_path || change?.old_path || ""),
|
|
511
|
+
positions: parseGitLabDiffPositions(String(change?.diff || ""))
|
|
512
|
+
}));
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function findGitLabSuggestionPosition(positionIndex, suggestion) {
|
|
516
|
+
const file = String(suggestion?.file || "").trim();
|
|
517
|
+
const line = Number(suggestion?.line) || 0;
|
|
518
|
+
const side = String(suggestion?.side || "RIGHT").trim().toUpperCase();
|
|
519
|
+
if (!file || !line) {
|
|
520
|
+
return null;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const preferredChange = positionIndex.find((change) =>
|
|
524
|
+
side === "LEFT" ? change.oldPath === file : change.newPath === file
|
|
525
|
+
) || positionIndex.find((change) => change.oldPath === file || change.newPath === file);
|
|
526
|
+
|
|
527
|
+
if (!preferredChange) {
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const match = preferredChange.positions.find((position) => (
|
|
532
|
+
side === "LEFT" ? position.oldLine === line : position.newLine === line
|
|
533
|
+
));
|
|
534
|
+
|
|
535
|
+
if (!match) {
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
const payload = {
|
|
540
|
+
old_path: preferredChange.oldPath,
|
|
541
|
+
new_path: preferredChange.newPath
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
if (match.oldLine != null) {
|
|
545
|
+
payload.old_line = match.oldLine;
|
|
546
|
+
}
|
|
547
|
+
if (match.newLine != null) {
|
|
548
|
+
payload.new_line = match.newLine;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return payload;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function parseGitLabDiffPositions(diff) {
|
|
555
|
+
const positions = [];
|
|
556
|
+
let oldLine = 0;
|
|
557
|
+
let newLine = 0;
|
|
558
|
+
|
|
559
|
+
for (const rawLine of String(diff || "").split("\n")) {
|
|
560
|
+
const hunkMatch = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/u.exec(rawLine);
|
|
561
|
+
if (hunkMatch) {
|
|
562
|
+
oldLine = Number(hunkMatch[1]);
|
|
563
|
+
newLine = Number(hunkMatch[2]);
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (!rawLine || rawLine.startsWith("diff --git") || rawLine.startsWith("index ")) {
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
if (rawLine.startsWith("\")) {
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
if (rawLine.startsWith("--- ") || rawLine.startsWith("+++ ")) {
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const prefix = rawLine[0];
|
|
578
|
+
if (prefix === "+") {
|
|
579
|
+
positions.push({ oldLine: null, newLine });
|
|
580
|
+
newLine += 1;
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
if (prefix === "-") {
|
|
584
|
+
positions.push({ oldLine, newLine: null });
|
|
585
|
+
oldLine += 1;
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
positions.push({ oldLine, newLine });
|
|
590
|
+
oldLine += 1;
|
|
591
|
+
newLine += 1;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return positions;
|
|
595
|
+
}
|
|
596
|
+
|
|
415
597
|
function buildGitLabDiff(changes) {
|
|
416
598
|
return (Array.isArray(changes) ? changes : []).map((change) => {
|
|
417
599
|
const oldPath = change.old_path || change.new_path || "unknown";
|