claude-teammate 0.1.85 → 0.1.87
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/commands/worker.js +45 -13
- package/src/forge/github.js +8 -0
- package/src/forge/gitlab.js +25 -1
- package/src/repo.js +45 -3
package/package.json
CHANGED
package/src/commands/worker.js
CHANGED
|
@@ -410,7 +410,7 @@ export async function processReviewPullRequest({
|
|
|
410
410
|
github.createPullRequestReview(repoUrl, prNumber, body, suggestions, event)
|
|
411
411
|
}) {
|
|
412
412
|
await github.addLabelsToPullRequest(pr.repoUrl, pr.number, ["AI-reviewed"]);
|
|
413
|
-
await github
|
|
413
|
+
await postPullRequestComment(github, pr.repoUrl, pr.number, "I am reviewing the PR.");
|
|
414
414
|
|
|
415
415
|
const repoPath = await ensureReviewRepoFn(pr.repoUrl, reviewReposDir);
|
|
416
416
|
const prDetail = await fetchPullRequestFn(pr.repoUrl, pr.number);
|
|
@@ -782,7 +782,7 @@ async function processGitHubIssue({ repo, issue, projectRoot, github, jira, gith
|
|
|
782
782
|
return buildGitHubIssueState(detail, githubIssueMemory, null);
|
|
783
783
|
}
|
|
784
784
|
|
|
785
|
-
const isMemberForIssue = await checkCommentAuthorIsMember(github, repo, latestComment, logger, detail.number);
|
|
785
|
+
const isMemberForIssue = await checkCommentAuthorIsMember(github, repo, latestComment, logger, detail.number, false);
|
|
786
786
|
if (!isMemberForIssue) {
|
|
787
787
|
return buildGitHubIssueState(detail, githubIssueMemory, null);
|
|
788
788
|
}
|
|
@@ -1021,7 +1021,7 @@ async function processTrackedPullRequest({ projectRoot, repo, pullRequest, githu
|
|
|
1021
1021
|
return buildDraftPrState(detail, currentStatus, null);
|
|
1022
1022
|
}
|
|
1023
1023
|
|
|
1024
|
-
const isMemberForPr = await checkCommentAuthorIsMember(github, repo, latestComment, logger, detail.number);
|
|
1024
|
+
const isMemberForPr = await checkCommentAuthorIsMember(github, repo, latestComment, logger, detail.number, true);
|
|
1025
1025
|
if (!isMemberForPr) {
|
|
1026
1026
|
return buildDraftPrState(detail, currentStatus, null);
|
|
1027
1027
|
}
|
|
@@ -1131,7 +1131,7 @@ async function processTrackedPullRequest({ projectRoot, repo, pullRequest, githu
|
|
|
1131
1131
|
});
|
|
1132
1132
|
|
|
1133
1133
|
if (review.decision === "comment" && review.comment_body) {
|
|
1134
|
-
await github
|
|
1134
|
+
await postPullRequestComment(github, repo.url, detail.number, review.comment_body);
|
|
1135
1135
|
if (issueMemoryRecord && issueMemory && githubIssueMemory) {
|
|
1136
1136
|
githubIssueMemory.last_comment_action = "commented";
|
|
1137
1137
|
issueMemory.github_issues = dedupeGitHubIssues(issueMemory.github_issues);
|
|
@@ -1143,7 +1143,7 @@ async function processTrackedPullRequest({ projectRoot, repo, pullRequest, githu
|
|
|
1143
1143
|
}
|
|
1144
1144
|
|
|
1145
1145
|
if (commentReviewRepoAccess.blocker) {
|
|
1146
|
-
await github
|
|
1146
|
+
await postPullRequestComment(github, repo.url, detail.number, commentReviewRepoAccess.blocker);
|
|
1147
1147
|
if (issueMemoryRecord && issueMemory && githubIssueMemory) {
|
|
1148
1148
|
githubIssueMemory.last_comment_action = "blocked";
|
|
1149
1149
|
issueMemory.github_issues = dedupeGitHubIssues(issueMemory.github_issues);
|
|
@@ -1214,7 +1214,7 @@ async function processPullRequestImplementation({
|
|
|
1214
1214
|
title: detail.title,
|
|
1215
1215
|
body: stuckBody
|
|
1216
1216
|
});
|
|
1217
|
-
await github
|
|
1217
|
+
await postPullRequestComment(github, repo.url, detail.number, repoAccess.blocker);
|
|
1218
1218
|
await logger.info("Pull request blocked before implementation", {
|
|
1219
1219
|
repo: repo.url,
|
|
1220
1220
|
pr: detail.number,
|
|
@@ -1229,7 +1229,7 @@ async function processPullRequestImplementation({
|
|
|
1229
1229
|
title: detail.title,
|
|
1230
1230
|
body: nextBody
|
|
1231
1231
|
});
|
|
1232
|
-
await github
|
|
1232
|
+
await postPullRequestComment(github, repo.url, detail.number, buildImplementingGifComment());
|
|
1233
1233
|
await logger.info("Pull request status moved to IMPLEMENTING", {
|
|
1234
1234
|
repo: repo.url,
|
|
1235
1235
|
pr: detail.number,
|
|
@@ -1325,7 +1325,7 @@ async function processPullRequestImplementation({
|
|
|
1325
1325
|
await transitionLinkedJiraIssueToReview(detail.title, jira, logger, detail.number);
|
|
1326
1326
|
}
|
|
1327
1327
|
const successComment = successCommentBody || formatPullRequestOutcomeComment(result.summary, "Implemented the requested changes and pushed them to this PR branch.");
|
|
1328
|
-
await github
|
|
1328
|
+
await postPullRequestComment(github, repo.url, detail.number, successComment);
|
|
1329
1329
|
await postLinkedJiraComment(extractJiraKey(detail.title), successComment, jira, logger);
|
|
1330
1330
|
await logger.info("Pull request implemented", {
|
|
1331
1331
|
repo: repo.url,
|
|
@@ -1353,7 +1353,7 @@ async function processPullRequestImplementation({
|
|
|
1353
1353
|
body: stuckBody
|
|
1354
1354
|
});
|
|
1355
1355
|
const stuckComment = formatPullRequestOutcomeComment(result.summary, "I could not complete the requested changes and the implementation is currently stuck.");
|
|
1356
|
-
await github
|
|
1356
|
+
await postPullRequestComment(github, repo.url, detail.number, stuckComment);
|
|
1357
1357
|
await postLinkedJiraComment(extractJiraKey(detail.title), stuckComment, jira, logger);
|
|
1358
1358
|
await logger.info("Pull request marked STUCK", {
|
|
1359
1359
|
repo: repo.url,
|
|
@@ -1391,7 +1391,7 @@ async function processPullRequestImplementation({
|
|
|
1391
1391
|
body: stuckBody
|
|
1392
1392
|
});
|
|
1393
1393
|
const errorComment = formatPullRequestOutcomeComment(message, "I hit an error while trying to implement the requested changes.");
|
|
1394
|
-
await github
|
|
1394
|
+
await postPullRequestComment(github, repo.url, detail.number, errorComment);
|
|
1395
1395
|
await postLinkedJiraComment(extractJiraKey(detail.title), errorComment, jira, logger);
|
|
1396
1396
|
await logger.error("Pull request implementation failed", {
|
|
1397
1397
|
repo: repo.url,
|
|
@@ -1851,7 +1851,7 @@ export function hasPlusOneReaction(comment) {
|
|
|
1851
1851
|
}
|
|
1852
1852
|
|
|
1853
1853
|
export async function acknowledgePullRequestComment({ github, logger, repoUrl, issueNumber, commentId = "" }) {
|
|
1854
|
-
return
|
|
1854
|
+
return markPullRequestThreadRead({
|
|
1855
1855
|
github,
|
|
1856
1856
|
logger,
|
|
1857
1857
|
repoUrl,
|
|
@@ -2091,6 +2091,34 @@ async function markGitHubThreadRead({ github, logger, repoUrl, issueNumber, comm
|
|
|
2091
2091
|
}
|
|
2092
2092
|
}
|
|
2093
2093
|
|
|
2094
|
+
async function markPullRequestThreadRead({ github, logger, repoUrl, issueNumber, commentId = "" }) {
|
|
2095
|
+
try {
|
|
2096
|
+
if (!commentId) {
|
|
2097
|
+
return false;
|
|
2098
|
+
}
|
|
2099
|
+
const react = typeof github.createPullRequestCommentReaction === "function"
|
|
2100
|
+
? github.createPullRequestCommentReaction.bind(github)
|
|
2101
|
+
: github.createIssueCommentReaction.bind(github);
|
|
2102
|
+
await react(repoUrl, commentId, "eyes", issueNumber);
|
|
2103
|
+
return true;
|
|
2104
|
+
} catch (error) {
|
|
2105
|
+
await logger.error("Failed to add pull request read reaction", {
|
|
2106
|
+
repo: repoUrl,
|
|
2107
|
+
issue: issueNumber,
|
|
2108
|
+
commentId,
|
|
2109
|
+
error
|
|
2110
|
+
});
|
|
2111
|
+
return false;
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
async function postPullRequestComment(github, repoUrl, pullNumber, body) {
|
|
2116
|
+
const postComment = typeof github.postPullRequestComment === "function"
|
|
2117
|
+
? github.postPullRequestComment.bind(github)
|
|
2118
|
+
: github.postIssueComment.bind(github);
|
|
2119
|
+
return postComment(repoUrl, pullNumber, body);
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2094
2122
|
function buildIssueState(issue, epicMemory, issueMemory, claudeDecision, lastError = null) {
|
|
2095
2123
|
const repoUrls = epicMemory.repos.map((repo) => repo.url);
|
|
2096
2124
|
const localRepoPaths = epicMemory.repos.map((repo) => repo.local_path).filter(Boolean);
|
|
@@ -2515,11 +2543,15 @@ async function resolveRepoLocalPath(repo, reposDir, logger, skipLabel) {
|
|
|
2515
2543
|
}
|
|
2516
2544
|
}
|
|
2517
2545
|
|
|
2518
|
-
async function checkCommentAuthorIsMember(github, repo, latestComment, logger, issueNumber = "") {
|
|
2546
|
+
async function checkCommentAuthorIsMember(github, repo, latestComment, logger, issueNumber = "", isPullRequest = false) {
|
|
2519
2547
|
try {
|
|
2520
2548
|
const isMember = await github.isRepoCollaborator(repo.url, latestComment.author.login);
|
|
2521
2549
|
if (!isMember) {
|
|
2522
|
-
|
|
2550
|
+
const react =
|
|
2551
|
+
isPullRequest && typeof github.createPullRequestCommentReaction === "function"
|
|
2552
|
+
? github.createPullRequestCommentReaction.bind(github)
|
|
2553
|
+
: github.createIssueCommentReaction.bind(github);
|
|
2554
|
+
await react(repo.url, latestComment.id, "+1", issueNumber);
|
|
2523
2555
|
return false;
|
|
2524
2556
|
}
|
|
2525
2557
|
return true;
|
package/src/forge/github.js
CHANGED
|
@@ -158,6 +158,10 @@ export function createGitHubClient(config) {
|
|
|
158
158
|
return mapGitHubComment(payload);
|
|
159
159
|
},
|
|
160
160
|
|
|
161
|
+
async postPullRequestComment(repoUrl, pullNumber, body) {
|
|
162
|
+
return this.postIssueComment(repoUrl, pullNumber, body);
|
|
163
|
+
},
|
|
164
|
+
|
|
161
165
|
async createIssueReaction(repoUrl, issueNumber, content = "eyes") {
|
|
162
166
|
const repo = parseGitHubRepoUrl(repoUrl);
|
|
163
167
|
return requestGitHub(
|
|
@@ -184,6 +188,10 @@ export function createGitHubClient(config) {
|
|
|
184
188
|
);
|
|
185
189
|
},
|
|
186
190
|
|
|
191
|
+
async createPullRequestCommentReaction(repoUrl, commentId, content = "eyes", pullNumber = "") {
|
|
192
|
+
return this.createIssueCommentReaction(repoUrl, commentId, content, pullNumber);
|
|
193
|
+
},
|
|
194
|
+
|
|
187
195
|
async isRepoCollaborator(repoUrl, username) {
|
|
188
196
|
const repo = parseGitHubRepoUrl(repoUrl);
|
|
189
197
|
const response = await fetch(
|
package/src/forge/gitlab.js
CHANGED
|
@@ -145,6 +145,15 @@ export function createGitLabClient(config) {
|
|
|
145
145
|
return mapGitLabComment(payload);
|
|
146
146
|
},
|
|
147
147
|
|
|
148
|
+
async postPullRequestComment(repoUrl, pullNumber, body) {
|
|
149
|
+
const repo = parseGitLabRepoUrl(repoUrl);
|
|
150
|
+
const payload = await requestGitLabProject(config, repo, mergeRequestNotePath(pullNumber), {
|
|
151
|
+
method: "POST",
|
|
152
|
+
body: { body }
|
|
153
|
+
});
|
|
154
|
+
return mapGitLabComment(payload);
|
|
155
|
+
},
|
|
156
|
+
|
|
148
157
|
async createIssueReaction(repoUrl, issueNumber, content = "eyes") {
|
|
149
158
|
const repo = parseGitLabRepoUrl(repoUrl);
|
|
150
159
|
return requestGitLabProject(config, repo, `${issueNotePath(issueNumber)}/award_emoji`, {
|
|
@@ -159,7 +168,7 @@ export function createGitLabClient(config) {
|
|
|
159
168
|
const repo = parseGitLabRepoUrl(repoUrl);
|
|
160
169
|
const body = { name: mapReaction(content) };
|
|
161
170
|
const candidates = [
|
|
162
|
-
projectPath(repo,
|
|
171
|
+
projectPath(repo, `${mergeRequestNotePath(issueNumber)}/${commentId}/award_emoji`),
|
|
163
172
|
projectPath(repo, `/issues/${issueNumber}/notes/${commentId}/award_emoji`)
|
|
164
173
|
];
|
|
165
174
|
|
|
@@ -179,6 +188,17 @@ export function createGitLabClient(config) {
|
|
|
179
188
|
throw lastError;
|
|
180
189
|
},
|
|
181
190
|
|
|
191
|
+
async createPullRequestCommentReaction(repoUrl, commentId, content = "eyes", pullNumber = "") {
|
|
192
|
+
const repo = parseGitLabRepoUrl(repoUrl);
|
|
193
|
+
return requestGitLab(config, projectPath(repo, `${mergeRequestNotePath(pullNumber)}/${commentId}/award_emoji`), {
|
|
194
|
+
baseUrl: `${repo.origin}/`,
|
|
195
|
+
method: "POST",
|
|
196
|
+
body: {
|
|
197
|
+
name: mapReaction(content)
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
},
|
|
201
|
+
|
|
182
202
|
async isRepoCollaborator(repoUrl, username) {
|
|
183
203
|
const repo = parseGitLabRepoUrl(repoUrl);
|
|
184
204
|
const users = await requestGitLab(config, "/api/v4/users", {
|
|
@@ -365,6 +385,10 @@ function issueNotePath(issueNumber) {
|
|
|
365
385
|
return `/issues/${issueNumber}/notes`;
|
|
366
386
|
}
|
|
367
387
|
|
|
388
|
+
function mergeRequestNotePath(pullNumber) {
|
|
389
|
+
return `/merge_requests/${pullNumber}/notes`;
|
|
390
|
+
}
|
|
391
|
+
|
|
368
392
|
function requestGitLabProject(config, repo, suffix = "", init = {}) {
|
|
369
393
|
return requestGitLab(config, projectPath(repo, suffix), {
|
|
370
394
|
...init,
|
package/src/repo.js
CHANGED
|
@@ -238,7 +238,7 @@ async function execGit(repoPath, args) {
|
|
|
238
238
|
await execFileAsync("git", args, {
|
|
239
239
|
cwd: repoPath,
|
|
240
240
|
maxBuffer: 10 * 1024 * 1024,
|
|
241
|
-
env:
|
|
241
|
+
env: await resolveRepoGitEnv(repoPath)
|
|
242
242
|
});
|
|
243
243
|
} catch (error) {
|
|
244
244
|
throw formatGitExecError(error);
|
|
@@ -250,7 +250,7 @@ async function execGitOutput(repoPath, args) {
|
|
|
250
250
|
const { stdout } = await execFileAsync("git", args, {
|
|
251
251
|
cwd: repoPath,
|
|
252
252
|
maxBuffer: 10 * 1024 * 1024,
|
|
253
|
-
env:
|
|
253
|
+
env: await resolveRepoGitEnv(repoPath)
|
|
254
254
|
});
|
|
255
255
|
return String(stdout || "");
|
|
256
256
|
} catch (error) {
|
|
@@ -259,11 +259,35 @@ async function execGitOutput(repoPath, args) {
|
|
|
259
259
|
}
|
|
260
260
|
|
|
261
261
|
async function branchHasChangesAgainstMain(repoPath) {
|
|
262
|
-
const
|
|
262
|
+
const defaultBranch = await resolveRemoteDefaultBranch(repoPath);
|
|
263
|
+
const output = await execGitOutput(repoPath, ["rev-list", "--count", `origin/${defaultBranch}..HEAD`]);
|
|
263
264
|
const count = Number.parseInt(output.trim(), 10);
|
|
264
265
|
return Number.isFinite(count) && count > 0;
|
|
265
266
|
}
|
|
266
267
|
|
|
268
|
+
async function resolveRemoteDefaultBranch(repoPath) {
|
|
269
|
+
try {
|
|
270
|
+
const output = await execGitOutput(repoPath, ["symbolic-ref", "refs/remotes/origin/HEAD"]);
|
|
271
|
+
const match = /refs\/remotes\/origin\/(.+)\s*$/u.exec(output);
|
|
272
|
+
if (match?.[1]) {
|
|
273
|
+
return match[1];
|
|
274
|
+
}
|
|
275
|
+
} catch {
|
|
276
|
+
// Fall through to ls-remote when origin/HEAD is unavailable locally.
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
const output = await execGitOutput(repoPath, ["ls-remote", "--symref", "origin", "HEAD"]);
|
|
281
|
+
const match = /^ref:\s+refs\/heads\/([^\s]+)\s+HEAD$/mu.exec(output);
|
|
282
|
+
if (match?.[1]) {
|
|
283
|
+
return match[1];
|
|
284
|
+
}
|
|
285
|
+
} catch {
|
|
286
|
+
// Fall back to main when the remote HEAD cannot be resolved.
|
|
287
|
+
}
|
|
288
|
+
return "main";
|
|
289
|
+
}
|
|
290
|
+
|
|
267
291
|
async function initializeRemoteDefaultBranch(repoPath, baseBranch) {
|
|
268
292
|
await execGit(repoPath, ["checkout", "--orphan", baseBranch]);
|
|
269
293
|
await execGit(repoPath, [
|
|
@@ -298,3 +322,21 @@ function isMissingRemoteBranchError(error) {
|
|
|
298
322
|
const message = error instanceof Error ? error.message : String(error);
|
|
299
323
|
return /couldn't find remote ref /u.test(message);
|
|
300
324
|
}
|
|
325
|
+
|
|
326
|
+
async function resolveRepoGitEnv(repoPath) {
|
|
327
|
+
const remoteUrl = await readGitRemoteUrl(repoPath);
|
|
328
|
+
return buildGitEnvForRepoUrl(remoteUrl);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async function readGitRemoteUrl(repoPath) {
|
|
332
|
+
try {
|
|
333
|
+
const { stdout } = await execFileAsync("git", ["config", "--get", "remote.origin.url"], {
|
|
334
|
+
cwd: repoPath,
|
|
335
|
+
maxBuffer: 1024 * 1024,
|
|
336
|
+
env: buildGitEnvForRepoUrl()
|
|
337
|
+
});
|
|
338
|
+
return String(stdout || "").trim();
|
|
339
|
+
} catch {
|
|
340
|
+
return "";
|
|
341
|
+
}
|
|
342
|
+
}
|