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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-teammate",
3
- "version": "0.1.85",
3
+ "version": "0.1.87",
4
4
  "description": "CLI bootstrapper for Claude Teammate.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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.postIssueComment(pr.repoUrl, pr.number, "I am reviewing the PR.");
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.postIssueComment(repo.url, detail.number, review.comment_body);
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.postIssueComment(repo.url, detail.number, commentReviewRepoAccess.blocker);
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.postIssueComment(repo.url, detail.number, repoAccess.blocker);
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.postIssueComment(repo.url, detail.number, buildImplementingGifComment());
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.postIssueComment(repo.url, detail.number, successComment);
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.postIssueComment(repo.url, detail.number, stuckComment);
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.postIssueComment(repo.url, detail.number, errorComment);
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 markGitHubThreadRead({
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
- await github.createIssueCommentReaction(repo.url, latestComment.id, "+1", issueNumber);
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;
@@ -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(
@@ -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, `/merge_requests/${issueNumber}/notes/${commentId}/award_emoji`),
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: buildGitEnvForRepoUrl()
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: buildGitEnvForRepoUrl()
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 output = await execGitOutput(repoPath, ["rev-list", "--count", "origin/main..HEAD"]);
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
+ }