claude-teammate 0.1.313 → 0.1.314
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
CHANGED
package/src/repo.js
CHANGED
|
@@ -770,6 +770,31 @@ export async function branchHasChangesAgainstMain(repoPath, baseRef) {
|
|
|
770
770
|
return Number.isFinite(count) && count > 0;
|
|
771
771
|
}
|
|
772
772
|
|
|
773
|
+
/**
|
|
774
|
+
* True when HEAD's own work has already landed on the base branch (e.g. the
|
|
775
|
+
* branch was merged via another MR / a human merged it directly). In that case
|
|
776
|
+
* HEAD is an ancestor of the base tip while still being a distinct commit, so a
|
|
777
|
+
* branch-vs-base diff is empty and re-running Claude can never produce changes.
|
|
778
|
+
* Callers use this to terminate instead of looping on "no changes to commit".
|
|
779
|
+
* A brand-new branch that merely points at the base tip (HEAD === base) returns
|
|
780
|
+
* false — that is an unstarted PR, not a merged one. Best-effort; never throws.
|
|
781
|
+
*/
|
|
782
|
+
export async function branchMergedIntoBase(repoPath, baseRef) {
|
|
783
|
+
const branch = baseRef || (await resolveRemoteDefaultBranch(repoPath));
|
|
784
|
+
try {
|
|
785
|
+
const headSha = (await execGitOutput(repoPath, ["rev-parse", "HEAD"])).trim();
|
|
786
|
+
const baseSha = (await execGitOutput(repoPath, ["rev-parse", `origin/${branch}`])).trim();
|
|
787
|
+
if (!headSha || !baseSha || headSha === baseSha) {
|
|
788
|
+
return false;
|
|
789
|
+
}
|
|
790
|
+
// exit 0 → HEAD is an ancestor of the base tip → already merged.
|
|
791
|
+
await execGit(repoPath, ["merge-base", "--is-ancestor", "HEAD", `origin/${branch}`]);
|
|
792
|
+
return true;
|
|
793
|
+
} catch {
|
|
794
|
+
return false;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
773
798
|
async function resolveRemoteDefaultBranch(repoPath) {
|
|
774
799
|
try {
|
|
775
800
|
const output = await execGitOutput(repoPath, ["symbolic-ref", "refs/remotes/origin/HEAD"]);
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
} from "../claude.js";
|
|
7
7
|
import {
|
|
8
8
|
branchHasChangesAgainstMain,
|
|
9
|
+
branchMergedIntoBase,
|
|
9
10
|
commitAndPushRepoChanges,
|
|
10
11
|
ensureWorktreeForBranch,
|
|
11
12
|
getBranchDiffAgainstBase,
|
|
@@ -331,6 +332,16 @@ export async function processPullRequestImplementation({
|
|
|
331
332
|
pr: detail.number
|
|
332
333
|
});
|
|
333
334
|
result = { result: "implemented", summary: "Implementation already committed on previous run." };
|
|
335
|
+
} else if (!latestComment && (await branchMergedIntoBase(repoPath, detail.baseRef).catch(() => false))) {
|
|
336
|
+
// The branch's work already landed on the base (merged via another MR or
|
|
337
|
+
// by a human). There is nothing left to implement, so skip Claude
|
|
338
|
+
// entirely — running it would only report "no changes" and loop. A human
|
|
339
|
+
// comment overrides this so feedback can still be addressed.
|
|
340
|
+
await logger.info("Head branch already merged into base; skipping Claude (nothing to implement)", {
|
|
341
|
+
repo: repo.url,
|
|
342
|
+
pr: detail.number
|
|
343
|
+
});
|
|
344
|
+
result = { result: "implemented", summary: "Changes already merged into the base branch." };
|
|
334
345
|
} else {
|
|
335
346
|
result = await services.runClaudeImplementation({
|
|
336
347
|
pullRequest: { ...detail, body: nextBody },
|
|
@@ -440,7 +451,53 @@ export async function processPullRequestImplementation({
|
|
|
440
451
|
);
|
|
441
452
|
|
|
442
453
|
if (!commitResult.committed && !commitResult.branchHasImplementation) {
|
|
443
|
-
|
|
454
|
+
// No diff to commit. If the branch's work already landed on the base
|
|
455
|
+
// (merged via another MR / by a human), re-running Claude can never
|
|
456
|
+
// produce changes — treat it as terminally implemented instead of
|
|
457
|
+
// looping. Otherwise stop without preserving STUCK_AT so the bot waits
|
|
458
|
+
// for human guidance rather than machine-gun retrying an unchangeable
|
|
459
|
+
// no-op every retry window.
|
|
460
|
+
const mergedIntoBase = await branchMergedIntoBase(repoPath, detail.baseRef).catch(() => false);
|
|
461
|
+
if (mergedIntoBase) {
|
|
462
|
+
const mergedBody = setPullRequestStatus(nextBody, "IMPLEMENTED");
|
|
463
|
+
await github.updatePullRequest(repo.url, detail.number, { title: detail.title, body: mergedBody });
|
|
464
|
+
await services.clearForgePullRequestBlocked(github, { ...detail, repoUrl: repo.url });
|
|
465
|
+
if (markReadyOnSuccess) {
|
|
466
|
+
await github.markPullRequestReady(repo.url, detail.number);
|
|
467
|
+
await services.transitionLinkedJiraIssueToReview(detail.title, jira, logger, detail.number);
|
|
468
|
+
}
|
|
469
|
+
const mergedComment =
|
|
470
|
+
successCommentBody ||
|
|
471
|
+
formatPullRequestOutcomeComment(
|
|
472
|
+
result.summary,
|
|
473
|
+
"The requested changes are already merged into the base branch; nothing left to implement."
|
|
474
|
+
);
|
|
475
|
+
await postPullRequestComment(github, repo.url, detail.number, mergedComment);
|
|
476
|
+
await postLinkedJiraComment(extractJiraKey(detail.title), mergedComment, jira, logger);
|
|
477
|
+
await logger.info("Branch already merged into base; PR marked IMPLEMENTED", {
|
|
478
|
+
repo: repo.url,
|
|
479
|
+
pr: detail.number
|
|
480
|
+
});
|
|
481
|
+
return buildDraftPrState(
|
|
482
|
+
{ ...detail, body: mergedBody, draft: markReadyOnSuccess ? false : detail.draft },
|
|
483
|
+
"IMPLEMENTED",
|
|
484
|
+
"implemented"
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
const noChangeBody = clearStuckAt(setPullRequestStatus(nextBody, "STUCK"));
|
|
488
|
+
await github.updatePullRequest(repo.url, detail.number, { title: detail.title, body: noChangeBody });
|
|
489
|
+
await services.setForgePullRequestBlocked(github, { ...detail, repoUrl: repo.url });
|
|
490
|
+
const noChangeComment = formatPullRequestOutcomeComment(
|
|
491
|
+
result.summary,
|
|
492
|
+
"Claude reported success but produced no repository changes, so there was nothing to commit. Halting automatic retries until there is new guidance."
|
|
493
|
+
);
|
|
494
|
+
await postPullRequestComment(github, repo.url, detail.number, noChangeComment);
|
|
495
|
+
await postLinkedJiraComment(extractJiraKey(detail.title), noChangeComment, jira, logger);
|
|
496
|
+
await logger.error("Pull request produced no repository changes; halting auto-retry", {
|
|
497
|
+
repo: repo.url,
|
|
498
|
+
pr: detail.number
|
|
499
|
+
});
|
|
500
|
+
return buildDraftPrState({ ...detail, body: noChangeBody }, "STUCK", "stuck");
|
|
444
501
|
}
|
|
445
502
|
|
|
446
503
|
const implementedBody = setPullRequestStatus(nextBody, "IMPLEMENTED");
|